diff -Nru tzdata-2024a/debian/changelog tzdata-2024a/debian/changelog --- tzdata-2024a/debian/changelog 2024-02-21 14:36:33.000000000 +0100 +++ tzdata-2024a/debian/changelog 2024-05-03 19:59:21.000000000 +0200 @@ -1,3 +1,21 @@ +tzdata (2024a-0ubuntu0.20.04.1) focal; urgency=medium + + * Do not replace CET, CST6CDT, EET, EST*, HST, MET, MST*, PST8PDT, WET. + The replacements differed in using daylight saving. (LP: #2055718) + * Allow ziguard.awk to generate timezone symlinks that point to symlinks + to fix (at least) the timezone symlinks Africa/Asmera, + Antarctica/South_Pole, Iceland, Pacific/Ponape, and Pacific/Truk. + * Correct timezone updates on tzdata configuration: + - Fix updating US/Indiana-Starke to America/Indiana/Knox + - Update Mideast/Riyadh8[789] to Asia/Riyadh + - Update America/Fort_Wayne and America/Indianapolis + to America/Indiana/Indianapolis + - Update America/Knox_IN to America/Indiana/Knox + - Update America/Louisville to America/Kentucky/Louisville + * Test convert_timezone for consistency + + -- Benjamin Drung Fri, 03 May 2024 19:59:21 +0200 + tzdata (2024a-0ubuntu0.20.04) focal; urgency=medium * New upstream version (LP: #2052739): diff -Nru tzdata-2024a/debian/control tzdata-2024a/debian/control --- tzdata-2024a/debian/control 2024-02-21 14:13:45.000000000 +0100 +++ tzdata-2024a/debian/control 2024-05-03 19:09:49.000000000 +0200 @@ -2,7 +2,7 @@ Section: localization Priority: required Build-Depends: debhelper-compat (= 12) -Build-Depends-Indep: gawk, po-debconf, python3, python3-tz, symlinks, icu-devtools +Build-Depends-Indep: gawk, po-debconf, python3, python3-debian, python3-tz, symlinks, icu-devtools Rules-Requires-Root: no Maintainer: Ubuntu Developers XSBC-Original-Maintainer: GNU Libc Maintainers diff -Nru tzdata-2024a/debian/patches/Correct-timezone-symlinks-when-using-BACKWARD-backward-PA.patch tzdata-2024a/debian/patches/Correct-timezone-symlinks-when-using-BACKWARD-backward-PA.patch --- tzdata-2024a/debian/patches/Correct-timezone-symlinks-when-using-BACKWARD-backward-PA.patch 1970-01-01 01:00:00.000000000 +0100 +++ tzdata-2024a/debian/patches/Correct-timezone-symlinks-when-using-BACKWARD-backward-PA.patch 2024-05-03 19:59:21.000000000 +0200 @@ -0,0 +1,83 @@ +From: Benjamin Drung +Date: Wed, 3 Apr 2024 15:14:50 +0200 +Subject: Correct timezone symlinks when using BACKWARD=backward + PACKRATDATA=backzone + +When using BACKWARD=backward PACKRATDATA=backzone the symlinks might +point to the incorrect file (e.g. Africa/Asmera points to Africa/Nairobi +instead of Africa/Asmara). + +Move the link to link feature from vanguard to main dataform to produce +correct symlink (e.g. Africa/Asmera -> Africa/Asmara) in this case. Drop +those link from `backward` that would cause symlinks to symlinks. + +See https://bugs.launchpad.net/ubuntu/+source/tzdata/+bug/2062522 + +Forwarded: https://mm.icann.org/pipermail/tz/2024-April/058853.html +--- + backward | 8 ++++---- + ziguard.awk | 4 ++-- + 2 files changed, 6 insertions(+), 6 deletions(-) + +diff --git a/backward b/backward +index 65c711b..16a01e5 100644 +--- a/backward ++++ b/backward +@@ -30,7 +30,7 @@ + # Pre-1993 naming conventions + + # Link TARGET LINK-NAME #= TARGET1 +-Link Australia/Sydney Australia/ACT #= Australia/Canberra ++Link Australia/Sydney Australia/ACT + Link Australia/Lord_Howe Australia/LHI + Link Australia/Sydney Australia/NSW + Link Australia/Darwin Australia/North +@@ -40,7 +40,7 @@ Link Australia/Hobart Australia/Tasmania + Link Australia/Melbourne Australia/Victoria + Link Australia/Perth Australia/West + Link Australia/Broken_Hill Australia/Yancowinna +-Link America/Rio_Branco Brazil/Acre #= America/Porto_Acre ++Link America/Rio_Branco Brazil/Acre + Link America/Noronha Brazil/DeNoronha + Link America/Sao_Paulo Brazil/East + Link America/Manaus Brazil/West +@@ -101,7 +101,7 @@ Link America/Mazatlan Mexico/BajaSur + Link America/Mexico_City Mexico/General + Link Pacific/Auckland NZ + Link Pacific/Chatham NZ-CHAT +-Link America/Denver Navajo #= America/Shiprock ++Link America/Denver Navajo + Link Asia/Shanghai PRC + Link Europe/Warsaw Poland + Link Europe/Lisbon Portugal +@@ -298,7 +298,7 @@ Link Africa/Nairobi Africa/Asmera #= Africa/Asmara + Link America/Nuuk America/Godthab + Link Asia/Ashgabat Asia/Ashkhabad + Link Asia/Kolkata Asia/Calcutta +-Link Asia/Shanghai Asia/Chungking #= Asia/Chongqing ++Link Asia/Shanghai Asia/Chungking + Link Asia/Dhaka Asia/Dacca + # Istanbul is in both continents. + Link Europe/Istanbul Asia/Istanbul +diff --git a/ziguard.awk b/ziguard.awk +index 7a3404f..f6b8d24 100644 +--- a/ziguard.awk ++++ b/ziguard.awk +@@ -340,7 +340,7 @@ function make_linkline(oldline, target, linkname, oldtarget, comment, \ + return "Link\t" target "\t" replsuffix comment + } + +-/^Link/ && $4 == "#=" && DATAFORM == "vanguard" { ++/^Link/ && $4 == "#=" && (DATAFORM != "rearguard") { + $0 = make_linkline($0, $5, $3, $2) + } + +@@ -378,7 +378,7 @@ function cut_link_chains_short( \ + } + + END { +- if (DATAFORM != "vanguard") { ++ if (DATAFORM == "rearguard") { + cut_link_chains_short() + } + for (i = 1; i <= NR; i++) diff -Nru tzdata-2024a/debian/patches/series tzdata-2024a/debian/patches/series --- tzdata-2024a/debian/patches/series 2024-02-21 14:13:45.000000000 +0100 +++ tzdata-2024a/debian/patches/series 2024-05-03 19:58:11.000000000 +0200 @@ -1,2 +1,3 @@ readd-systemv.diff make-systemv.patch +Correct-timezone-symlinks-when-using-BACKWARD-backward-PA.patch diff -Nru tzdata-2024a/debian/rules tzdata-2024a/debian/rules --- tzdata-2024a/debian/rules 2024-02-21 14:13:45.000000000 +0100 +++ tzdata-2024a/debian/rules 2024-05-03 19:09:49.000000000 +0200 @@ -93,6 +93,7 @@ # The upstream tests are related to the sources. Just skip it. override_dh_auto_test: + debian/test_timezone_conversions -z "$(TZGEN)" PYTZ_TZDATADIR="$(TZGEN)" debian/tests/python override_dh_auto_install: diff -Nru tzdata-2024a/debian/tests/control tzdata-2024a/debian/tests/control --- tzdata-2024a/debian/tests/control 2024-02-21 14:13:45.000000000 +0100 +++ tzdata-2024a/debian/tests/control 2024-05-03 19:09:49.000000000 +0200 @@ -5,3 +5,8 @@ Tests: python-icu Depends: python3, python3-icu, tzdata Restrictions: allow-stderr + +Test-command: debian/test_timezone_conversions -d /var/lib/dpkg/info/ +Depends: python3, python3-debian, tzdata +Restrictions: allow-stderr superficial +Features: test-name=test_timezone_conversions diff -Nru tzdata-2024a/debian/tests/python tzdata-2024a/debian/tests/python --- tzdata-2024a/debian/tests/python 2024-02-21 14:36:16.000000000 +0100 +++ tzdata-2024a/debian/tests/python 2024-05-03 19:59:21.000000000 +0200 @@ -60,6 +60,12 @@ return backwards_links +def read_link(link: pathlib.Path) -> pathlib.Path: + """Return the absolute path to which the symbolic link points.""" + destination = link.parent / os.readlink(link) + return pathlib.Path(os.path.normpath(destination)) + + class TestZoneinfo(unittest.TestCase): """Test timezones using pytz module.""" @@ -139,6 +145,28 @@ future = now + datetime.timedelta(days=30 * 6) self._assert_equal_zones_at_date(future, tz_link, tz_target) + def assert_not_symlink_to_symlink(self, timezone_path: pathlib.Path) -> None: + """Assert that the timezone is not a symlink to another symlink.""" + if not timezone_path.is_symlink(): + return + destination = read_link(timezone_path) + if not destination.is_symlink(): + return + self.fail( + f"Symlink to symlink found: {timezone_path} -> {destination}" + f" -> {read_link(destination)}" + ) + + def test_no_symlinks_to_symlinks(self) -> None: + """Check that no timezone is a symlink to another symlink.""" + tzpath = os.environ.get("PYTZ_TZDATADIR", "/usr/share/zoneinfo") + for timezone in sorted(available_timezones()): + if timezone == "localtime": + continue + with self.subTest(timezone): + timezone_path = pathlib.Path(tzpath) / timezone + self.assert_not_symlink_to_symlink(timezone_path) + @unittest.skip("Needs https://launchpad.net/bugs/207604") def test_timezones(self) -> None: """Test all zones to load, have a name, and have a reasonable offset.""" @@ -177,6 +205,7 @@ date = timezone.localize(datetime.datetime(2024, 3, 2)) self.assertEqual(self._hours(date.utcoffset()), 5) + def main() -> None: """Run unit tests in verbose mode.""" argv = sys.argv.copy() diff -Nru tzdata-2024a/debian/test_timezone_conversions tzdata-2024a/debian/test_timezone_conversions --- tzdata-2024a/debian/test_timezone_conversions 1970-01-01 01:00:00.000000000 +0100 +++ tzdata-2024a/debian/test_timezone_conversions 2024-05-03 19:09:49.000000000 +0200 @@ -0,0 +1,269 @@ +#!/usr/bin/python3 + +# Author: Benjamin Drung + +"""Check convert_timezone from tzdata.config for consistency.""" + +import argparse +import functools +import logging +import os +import pathlib +import re +import subprocess +import sys +import typing + +import debian.deb822 + +LOG_FORMAT = "%(levelname)s: %(message)s" +# Special timezones that should not be selectable in debconf +SPECIAL = {"Factory", "localtime", "posixrules"} +# Not selectable timezones that are not mentioned in the backward file. +# See also https://launchpad.net/bugs/2030684 +EXCLUDE_UNSELECTABLE = { + "CET", + "CST6CDT", + "EET", + "EST", + "EST5EDT", + "GMT", + "HST", + "MET", + "MST", + "MST7MDT", + "PST8PDT", + "UTC", + "WET", +} + + +class ConvertTimezone: + """Wrap convert_timezone from tzdata.config.""" + + def __init__(self, tzdata_config: pathlib.Path) -> None: + self.tzdata_config = tzdata_config + content = tzdata_config.read_text(encoding="utf-8") + match = re.search(r"convert_timezone\(\).*\n}", content, flags=re.DOTALL) + assert match, f"convert_timezone function not found in {tzdata_config}" + self.convert_timezone = match.group(0) + + @functools.lru_cache(maxsize=8192) + def __call__(self, timezone: str) -> str: + shell_script = f"{self.convert_timezone}\nconvert_timezone '{timezone}'\n" + shell = subprocess.run( + ["/bin/sh", "-c", shell_script], + capture_output=True, + check=True, + encoding="utf-8", + ) + return shell.stdout.strip() + + def filter_converted_timezones(self, timezones): + """Return dict of timezones that will be converted by convert_timezone.""" + converted = {} + for timezone in timezones: + conversion = self(timezone) + if conversion != timezone: + converted[timezone] = conversion + return converted + + def filter_unconverted_timezones(self, timezones): + """Return set of timezones that will not be converted by convert_timezone.""" + return timezones - set(self.filter_converted_timezones(timezones)) + + def get_targets(self): + """Return set of conversion targets.""" + targets = set(re.findall('echo "([^"$]+)"', self.convert_timezone)) + logging.getLogger(__name__).info( + "Available conversion targets in %s: %i", self.tzdata_config, len(targets) + ) + return targets + + +def _is_tzif_file(fpath): + with open(fpath, "rb") as tz_file: + return tz_file.read(4) == b"TZif" + + +def get_available_timezones(directory: typing.Optional[pathlib.Path]): + """Return a set of available timezones in the directory. + + If directory is not set, use the sytem's default. + """ + logger = logging.getLogger(__name__) + + available = set() + tz_root = str(directory or "/usr/share/zoneinfo") + for root, dirnames, files in os.walk(tz_root): + if root == tz_root: + # right/ and posix/ are special directories and shouldn't be + # included in the output of available zones + if "right" in dirnames: + dirnames.remove("right") + if "posix" in dirnames: + dirnames.remove("posix") + + for file in files: + fpath = os.path.join(root, file) + key = os.path.relpath(fpath, start=tz_root) + if _is_tzif_file(fpath): + available.add(key) + + if not available: + logger.error("Found no timezones in %s.", tz_root) + sys.exit(1) + logger.info("Available timezones in %s: %i", directory or "system", len(available)) + return available + + +def get_debconf_choices(template_filename: pathlib.Path): + """Extract the timezone choices from the debconf template.""" + logger = logging.getLogger(__name__) + debconf_choices = set() + with template_filename.open(encoding="utf-8") as template_file: + for paragraph in debian.deb822.Deb822.iter_paragraphs(template_file): + area_match = re.match("tzdata/Zones/(.*)", paragraph["Template"]) + if not area_match: + continue + area = area_match.group(1) + choices = paragraph.get("Choices", paragraph.get("__Choices", "")) + debconf_choices.update([f"{area}/{c}" for c in choices.split(", ")]) + if not debconf_choices: + logger.error("Found no selectable timezones in %s.", template_filename) + sys.exit(1) + logger.info( + "Selectable timezones in %s: %i", template_filename, len(debconf_choices) + ) + return debconf_choices + + +def _check_symlink(tzpath: pathlib.Path, source: str, target: str) -> bool: + """Check if the given timezone source symlinks to the given target.""" + timezone = tzpath / source + if timezone.exists(): + expected_target = pathlib.Path(tzpath) / target + return timezone.resolve() == expected_target.resolve() + return True + + +def check_symlinks(tzpath: typing.Optional[pathlib.Path], timezones): + """Check timezone replacements are identical to the symlinks on disk.""" + if tzpath is None: + tzpath = pathlib.Path("/usr/share/zoneinfo") + mismatch = set() + for source, target in timezones.items(): + if source.startswith("posix/") or source.startswith("right/"): + continue + if not _check_symlink(tzpath, source, target): + mismatch.add(source) + return mismatch + + +def existing_dir_path(string: str) -> pathlib.Path: + """Convert string to existing dir path or raise ArgumentTypeError.""" + path = pathlib.Path(string) + if not path.is_dir(): + raise argparse.ArgumentTypeError(f"Directory {string} does not exist") + return path + + +def parse_args() -> argparse.Namespace: + """Parse command line arguments and return namespace.""" + parser = argparse.ArgumentParser() + parser.add_argument( + "-z", + "--zoneinfo-directory", + type=existing_dir_path, + help="Directory containing the generated zoneinfo files (default: system)", + ) + parser.add_argument( + "-d", + "--debian-directory", + default=pathlib.Path("debian"), + type=existing_dir_path, + help="Path to debian directory containing tzdata.config" + " and tzdata.templates (default: %(default)s)", + ) + parser.add_argument( + "--all-selectable", + action="store_true", + help="Require all available timezones to be selectable in debconf", + ) + return parser.parse_args() + + +def main() -> int: + """Check convert_timezone from tzdata.config for consistency.""" + args = parse_args() + logging.basicConfig(format=LOG_FORMAT, level=logging.INFO) + logger = logging.getLogger(__name__) + + selectable = get_debconf_choices(args.debian_directory / "tzdata.templates") + available = get_available_timezones(args.zoneinfo_directory) + convert_timezone = ConvertTimezone(args.debian_directory / "tzdata.config") + conversion_targets = convert_timezone.get_targets() + failures = 0 + + converted = set(convert_timezone.filter_converted_timezones(selectable)) + if converted: + logger.warning( + "Following %i timezones can be selected, but will be converted:\n%s", + len(converted), + "\n".join(sorted(converted)), + ) + + unselectable = available - selectable - SPECIAL - EXCLUDE_UNSELECTABLE + if args.all_selectable and unselectable: + logger.error( + "Following %i timezones cannot be selected, but are available:\n%s", + len(unselectable), + "\n".join(sorted(unselectable)), + ) + failures += 1 + + missing = convert_timezone.filter_unconverted_timezones(unselectable) + if missing: + logger.error( + "Following %i timezones cannot be selected, but are not converted:\n%s", + len(missing), + "\n".join(sorted(missing)), + ) + failures += 1 + + targets = conversion_targets - available + if targets: + logger.error( + "Following %i timezones are conversion targets, but are not available:\n%s", + len(targets), + "\n".join(sorted(targets)), + ) + failures += 1 + + targets = conversion_targets - selectable + if targets: + logger.error( + "Following %i timezones are conversion targets," + " but are not selectable:\n%s", + len(targets), + "\n".join(sorted(targets)), + ) + failures += 1 + + mismatch = check_symlinks( + args.zoneinfo_directory, convert_timezone.filter_converted_timezones(available) + ) + if mismatch: + logger.error( + "Following %i timezones are converted," + " but they do not match their symlink targets:\n%s", + len(mismatch), + "\n".join(sorted(mismatch)), + ) + failures += 1 + + return failures + + +if __name__ == "__main__": + sys.exit(main()) diff -Nru tzdata-2024a/debian/tzdata.config tzdata-2024a/debian/tzdata.config --- tzdata-2024a/debian/tzdata.config 2024-02-21 14:13:45.000000000 +0100 +++ tzdata-2024a/debian/tzdata.config 2024-05-03 19:09:49.000000000 +0200 @@ -23,9 +23,18 @@ ("America/Cordoba"|"America/Rosario") echo "America/Argentina/Cordoba" ;; + "America/Fort_Wayne" | "America/Indianapolis") + echo "America/Indiana/Indianapolis" + ;; ("America/Jujuy") echo "America/Argentina/Jujuy" ;; + "America/Knox_IN") + echo "America/Indiana/Knox" + ;; + "America/Louisville") + echo "America/Kentucky/Louisville" + ;; ("America/Mendoza") echo "America/Argentina/Mendoza" ;; @@ -131,36 +140,21 @@ "Canada/Yukon") echo "America/Whitehorse" ;; - "CET") - echo "Europe/Paris" - ;; "Chile/Continental") echo "America/Santiago" ;; "Chile/EasterIsland") echo "Pacific/Easter" ;; - "CST6CDT") - echo "SystemV/CST6CDT" - ;; "Cuba") echo "America/Havana" ;; - "EET") - echo "Europe/Helsinki" - ;; "Egypt") echo "Africa/Cairo" ;; "Eire") echo "Europe/Dublin" ;; - "EST") - echo "SystemV/EST5" - ;; - "EST5EDT") - echo "SystemV/EST5EDT" - ;; "Europe/Kiev") echo "Europe/Kyiv" ;; @@ -194,9 +188,6 @@ "Hongkong") echo "Asia/Hong_Kong" ;; - "HST") - echo "Pacific/Honolulu" - ;; "Iceland") echo "Atlantic/Reykjavik" ;; @@ -218,9 +209,6 @@ "Libya") echo "Africa/Tripoli" ;; - "MET") - echo "Europe/Paris" - ;; "Mexico/BajaNorte") echo "America/Tijuana" ;; @@ -231,19 +219,13 @@ echo "America/Mexico_City" ;; "Mideast/Riyadh87") - echo "Asia/Riyadh87" + echo "Asia/Riyadh" ;; "Mideast/Riyadh88") - echo "Asia/Riyadh88" + echo "Asia/Riyadh" ;; "Mideast/Riyadh89") - echo "Asia/Riyadh89" - ;; - "MST") - echo "SystemV/MST7" - ;; - "MST7MDT") - echo "SystemV/MST7MDT" + echo "Asia/Riyadh" ;; "Navajo") echo "America/Denver" @@ -263,9 +245,6 @@ "PRC") echo "Asia/Shanghai" ;; - "PST8PDT") - echo "SystemV/PST8PDT" - ;; "ROC") echo "Asia/Taipei" ;; @@ -297,7 +276,7 @@ echo "America/Chicago" ;; "US/East-Indiana") - echo "America/Indianapolis" + echo "America/Indiana/Indianapolis" ;; "US/Eastern") echo "America/New_York" @@ -306,7 +285,7 @@ echo "Pacific/Honolulu" ;; "US/Indiana-Starke") - echo "America/Indianapolis" + echo "America/Indiana/Knox" ;; "US/Michigan") echo "America/Detroit" @@ -323,9 +302,6 @@ "UTC") echo "Etc/UTC" ;; - "WET") - echo "Europe/Lisbon" - ;; "W-SU") echo "Europe/Moscow" ;;