Skip to content

Commit

Permalink
Fix TZDB compiler %z parsing (#787)
Browse files Browse the repository at this point in the history
* Needs to take account of standard millis
* Update separate code path to allow %z to flow through
  • Loading branch information
jodastephen authored Sep 15, 2024
1 parent 0a264f1 commit 54b3d1c
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 30 deletions.
50 changes: 32 additions & 18 deletions src/main/java/org/joda/time/tz/ZoneInfoCompiler.java
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down Expand Up @@ -501,7 +501,7 @@ public Map<String, DateTimeZone> 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);
Expand Down Expand Up @@ -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) {
Expand All @@ -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);
}
Expand Down Expand Up @@ -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);
}
}
}
Expand Down
31 changes: 19 additions & 12 deletions src/test/java/org/joda/time/tz/TestCompiler.java
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}

//-----------------------------------------------------------------------
Expand Down Expand Up @@ -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()));
}

}

0 comments on commit 54b3d1c

Please sign in to comment.