From 54b3d1c6e306c46ed3f6eca80a7c00e0736dde11 Mon Sep 17 00:00:00 2001 From: Stephen Colebourne Date: Sun, 15 Sep 2024 22:50:15 +0100 Subject: [PATCH] Fix TZDB compiler %z parsing (#787) * Needs to take account of standard millis * Update separate code path to allow %z to flow through --- .../org/joda/time/tz/ZoneInfoCompiler.java | 50 ++++++++++++------- .../java/org/joda/time/tz/TestCompiler.java | 31 +++++++----- 2 files changed, 51 insertions(+), 30 deletions(-) diff --git a/src/main/java/org/joda/time/tz/ZoneInfoCompiler.java b/src/main/java/org/joda/time/tz/ZoneInfoCompiler.java index 7b0ac98a3..49c793c2e 100644 --- a/src/main/java/org/joda/time/tz/ZoneInfoCompiler.java +++ b/src/main/java/org/joda/time/tz/ZoneInfoCompiler.java @@ -366,7 +366,7 @@ static boolean test(String id, DateTimeZone tz) { return false; } - if (nextKey == null || (nextKey.length() < 3 && !"??".equals(nextKey))) { + if (nextKey == null || (nextKey.length() < 3 && !"??".equals(nextKey) && !"%z".equals(nextKey))) { System.out.println("*s* Error in " + tz.getID() + " " + new DateTime(millis, ISOChronology.getInstanceUTC()) @@ -501,7 +501,7 @@ public Map compile(File outputDir, File[] sources) throws } } - // store "back" links as aliases (where name is permanently mapped + // store "back" links as aliases (where name is permanently mapped) for (int pass = 0; pass < 2; pass++) { for (int i = 0; i < iBackLinks.size(); i += 2) { String id = iBackLinks.get(i); @@ -840,17 +840,23 @@ static class Rule { * Adds a recurring savings rule to the builder. * * @param builder the builder + * @param standardMillis the standard millis, pre-adjusted to the negativeSave value * @param negativeSave the negative save value * @param nameFormat the name format */ - public void addRecurring(DateTimeZoneBuilder builder, int negativeSave, String nameFormat) { + public void addRecurring(DateTimeZoneBuilder builder, int standardMillis, int negativeSave, String nameFormat) { int saveMillis = iSaveMillis + -negativeSave; - String nameKey = formatName(nameFormat, saveMillis, iLetterS); + String nameKey = formatName(nameFormat, standardMillis, saveMillis, iLetterS); iDateTimeOfYear.addRecurring(builder, nameKey, saveMillis, iFromYear, iToYear); } // ScopedForTesting - static String formatName(String nameFormat, int saveMillis, String letterS) { + static String formatName(String nameFormat, int standardMillis, int saveMillis, String letterS) { + // this method is called while adding rules to the builder + // the input parameters give the context as to whether the input is standard or 'summer' time + // saveMillis == 0 in 'winter' time, and != 0 in 'summer' time + // (negative save millis have been applied before this method is called) + // SPEC: Alternatively, a slash (/) separates standard and daylight abbreviations. int index = nameFormat.indexOf('/'); if (index > 0) { @@ -876,22 +882,30 @@ static String formatName(String nameFormat, int saveMillis, String letterS) { // offset in the form ±hh, ±hhmm, or ±hhmmss, using the shortest form that does not lose information, // where hh, mm, and ss are the hours, minutes, and seconds east (+) or west (-) of UT. if (nameFormat.equals("%z")) { - String sign = saveMillis < 0 ? "-" : "+"; - int saveSecs = Math.abs(saveMillis) / 1000; - int hours = saveSecs / 3600; - int mins = ((saveSecs / 60) % 60); - int secs = (saveSecs % 60); - if (secs == 0) { - if (mins == 0) { - return sign + twoDigitString(hours); - } - return sign + twoDigitString(hours) + twoDigitString(mins); + if (saveMillis == 0) { + return formatOffset(standardMillis).intern(); + } else { + return formatOffset(standardMillis + saveMillis).intern(); } - return sign + twoDigitString(hours) + twoDigitString(mins) + twoDigitString(secs); } return nameFormat; } + private static String formatOffset(int millis) { + String sign = millis < 0 ? "-" : "+"; + int saveSecs = Math.abs(millis) / 1000; + int hours = saveSecs / 3600; + int mins = ((saveSecs / 60) % 60); + int secs = (saveSecs % 60); + if (secs == 0) { + if (mins == 0) { + return sign + twoDigitString(hours); + } + return sign + twoDigitString(hours) + twoDigitString(mins); + } + return sign + twoDigitString(hours) + twoDigitString(mins) + twoDigitString(secs); + } + private static String twoDigitString(int value) { return Integer.toString(value + 100).substring(1); } @@ -960,13 +974,13 @@ public void addRecurring(DateTimeZoneBuilder builder, int standardMillis, String // add a fake rule that predates all other rules to ensure standard=summer (see Namibia) if (negativeSave < 0) { Rule rule = new Rule(iRules.get(0)); - rule.addRecurring(builder, negativeSave, nameFormat); + rule.addRecurring(builder, standardMillis, negativeSave, nameFormat); } // add each rule, passing through the negative save to alter the actual iSaveMillis value that is used for (int i = 0; i < iRules.size(); i++) { Rule rule = iRules.get(i); - rule.addRecurring(builder, negativeSave, nameFormat); + rule.addRecurring(builder, standardMillis, negativeSave, nameFormat); } } } diff --git a/src/test/java/org/joda/time/tz/TestCompiler.java b/src/test/java/org/joda/time/tz/TestCompiler.java index 2a8d93861..519bd4c2c 100644 --- a/src/test/java/org/joda/time/tz/TestCompiler.java +++ b/src/test/java/org/joda/time/tz/TestCompiler.java @@ -217,18 +217,19 @@ private void deleteOnExit(File tempFile) { //----------------------------------------------------------------------- public void test_formatName() { - assertEquals("PST", ZoneInfoCompiler.Rule.formatName("PST/PDT", 0, null)); - assertEquals("PDT", ZoneInfoCompiler.Rule.formatName("PST/PDT", 7200000, null)); - assertEquals("PST", ZoneInfoCompiler.Rule.formatName("P%sT", 7200000, "S")); - assertEquals("PDT", ZoneInfoCompiler.Rule.formatName("P%sT", 7200000, "D")); - assertEquals("PT", ZoneInfoCompiler.Rule.formatName("P%sT", 7200000, null)); - assertEquals("+00", ZoneInfoCompiler.Rule.formatName("%z", 0, null)); - assertEquals("+02", ZoneInfoCompiler.Rule.formatName("%z", 7200000, null)); - assertEquals("+020030", ZoneInfoCompiler.Rule.formatName("%z", 7230000, null)); - assertEquals("+0201", ZoneInfoCompiler.Rule.formatName("%z", 7260000, null)); - assertEquals("+020101", ZoneInfoCompiler.Rule.formatName("%z", 7261000, null)); - assertEquals("-02", ZoneInfoCompiler.Rule.formatName("%z", -7200000, null)); - assertEquals("-020030", ZoneInfoCompiler.Rule.formatName("%z", -7230000, null)); + assertEquals("PST", ZoneInfoCompiler.Rule.formatName("PST/PDT", 14400000, 0, null)); + assertEquals("PDT", ZoneInfoCompiler.Rule.formatName("PST/PDT", 14400000, 7200000, null)); + assertEquals("PST", ZoneInfoCompiler.Rule.formatName("P%sT", 14400000, 7200000, "S")); + assertEquals("PDT", ZoneInfoCompiler.Rule.formatName("P%sT", 14400000, 7200000, "D")); + assertEquals("PT", ZoneInfoCompiler.Rule.formatName("P%sT", 14400000, 7200000, null)); + assertEquals("+00", ZoneInfoCompiler.Rule.formatName("%z", 0, 0, null)); + assertEquals("+04", ZoneInfoCompiler.Rule.formatName("%z", 14400000, 0, null)); + assertEquals("+06", ZoneInfoCompiler.Rule.formatName("%z", 14400000, 7200000, null)); + assertEquals("+060030", ZoneInfoCompiler.Rule.formatName("%z", 14400000, 7230000, null)); + assertEquals("+0601", ZoneInfoCompiler.Rule.formatName("%z", 14400000, 7260000, null)); + assertEquals("+060101", ZoneInfoCompiler.Rule.formatName("%z", 14400000, 7261000, null)); + assertEquals("+02", ZoneInfoCompiler.Rule.formatName("%z", 14400000, -7200000, null)); + assertEquals("+020030", ZoneInfoCompiler.Rule.formatName("%z", 14400000, -7170000, null)); } //----------------------------------------------------------------------- @@ -299,4 +300,10 @@ public void test_Tokyo_1949() { assertEquals(expected.getMillis(), next); } + public void test_Azores() { + DateTimeZone zone = DateTimeZone.forID("Atlantic/Azores"); + assertEquals("-01", zone.getNameKey(new DateTime(2000, 1, 1, 0, 0, zone).getMillis())); + assertEquals("+00", zone.getNameKey(new DateTime(2000, 7, 1, 0, 0, zone).getMillis())); + } + }