Skip to content

Commit

Permalink
Revive zones destroyed by the IANA tzdb maintainer
Browse files Browse the repository at this point in the history
Links in the main tzdb data files are now treated as zones
with the exception of a few special cases
Links in the 'backward' file are still treated as aliases
  • Loading branch information
jodastephen committed Feb 12, 2015
1 parent eb1b998 commit 4222b61
Show file tree
Hide file tree
Showing 2 changed files with 176 additions and 37 deletions.
91 changes: 91 additions & 0 deletions RELEASE-NOTES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,97 @@ Serialization compatible - Yes

Data compatible - Yes
- DateTimeZone data updated to version 2015a
- In the past year, the IANA time-zone database maintainer has changed unique time-zone data for many parts
of the world into shared "links". For example, in IANA 'Asia/Kuwait' now links to 'Asia/Riyadh'.
(I personally disagree with the IANA time-zone database maintainer's actions but my objections were ignored).
This was a problem for Joda-Time as we interpreted the concept of links (many years ago) as only being used
for backwards compatibility. Links are exposed in Joda-Time, because if you request a time-zone for a linked
identifier you get the target of the link back. As such, in recent versions of Joda-Time, it was not possible
to obtain a DateTimeZone with an appropriate identifier for many parts of the world, such as 'Asia/Kuwait'.
With this release, most links in the main IANA files are now restored to be real zones.
As a side effect of this, some identifiers that used to act as links are now real zones.
In summary, in the last Joda-Time release, requesting 'Asia/Kuwait' would have given back 'Asia/Riyadh',
but it now returns 'Asia/Kuwait' (where 'Asia/Kuwait' and 'Asia/Riyadh' have the same time-zone rules).
In most cases, this won't have any impact on your application.
Zones affected (now real zones rather than incorrect links):
* Africa/Bamako
* Africa/Banjul
* Africa/Conakry
* Africa/Dakar
* Africa/Freetown
* Africa/Lome
* Africa/Nouakchott
* Africa/Ouagadougou
* Africa/Sao_Tome
* Atlantic/St_Helena
* Africa/Addis_Ababa
* Africa/Asmara
* Africa/Dar_es_Salaam
* Africa/Djibouti
* Africa/Kampala
* Africa/Mogadishu
* Indian/Antananarivo
* Indian/Comoro
* Indian/Mayotte
* Africa/Blantyre
* Africa/Bujumbura
* Africa/Gaborone
* Africa/Harare
* Africa/Kigali
* Africa/Lubumbashi
* Africa/Lusaka
* Africa/Bangui
* Africa/Brazzaville
* Africa/Douala
* Africa/Kinshasa
* Africa/Libreville
* Africa/Luanda
* Africa/Malabo
* Africa/Niamey
* Africa/Porto-Novo
* Africa/Maseru
* Africa/Mbabane
* Africa/Juba
* Europe/Nicosia
* Asia/Bahrain
* Asia/Aden
* Asia/Kuwait
* Asia/Phnom_Penh
* Asia/Vientiane
* Asia/Muscat
* Antarctica/McMurdo
* Europe/Jersey
* Europe/Guernsey
* Europe/Isle_of_Man
* Europe/Mariehamn
* Europe/Busingen
* Europe/Vatican
* Europe/San_Marino
* Europe/Vaduz
* Arctic/Longyearbyen
* Europe/Ljubljana
* Europe/Podgorica
* Europe/Sarajevo
* Europe/Skopje
* Europe/Zagreb
* Europe/Bratislava
* Asia/Istanbul
* Pacific/Johnston
* America/Aruba
* America/Lower_Princes
* America/Kralendijk
* America/Anguilla
* America/Dominica
* America/Grenada
* America/Guadeloupe
* America/Marigot
* America/Montserrat
* America/St_Barthelemy
* America/St_Kitts
* America/St_Lucia
* America/St_Thomas
* America/St_Vincent
* America/Tortola

Semantic compatible - Yes

Expand Down
122 changes: 85 additions & 37 deletions src/main/java/org/joda/time/tz/ZoneInfoCompiler.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.StringTokenizer;
import java.util.TreeMap;
import java.util.Map.Entry;

import org.joda.time.Chronology;
import org.joda.time.DateTime;
Expand Down Expand Up @@ -350,12 +350,16 @@ static boolean test(String id, DateTimeZone tz) {
private List<Zone> iZones;

// List String pairs to link.
private List<String> iLinks;
private List<String> iGoodLinks;

// List String pairs to link.
private List<String> iBackLinks;

public ZoneInfoCompiler() {
iRuleSets = new HashMap<String, RuleSet>();
iZones = new ArrayList<Zone>();
iLinks = new ArrayList<String>();
iGoodLinks = new ArrayList<String>();
iBackLinks = new ArrayList<String>();
}

/**
Expand All @@ -368,7 +372,7 @@ public Map<String, DateTimeZone> compile(File outputDir, File[] sources) throws
if (sources != null) {
for (int i=0; i<sources.length; i++) {
BufferedReader in = new BufferedReader(new FileReader(sources[i]));
parseDataFile(in);
parseDataFile(in, "backward".equals(sources[i].getName()));
in.close();
}
}
Expand All @@ -385,60 +389,68 @@ public Map<String, DateTimeZone> compile(File outputDir, File[] sources) throws
}

Map<String, DateTimeZone> map = new TreeMap<String, DateTimeZone>();
Map<String, Zone> sourceMap = new TreeMap<String, Zone>();

System.out.println("Writing zoneinfo files");
for (int i=0; i<iZones.size(); i++) {
// write out the standard entries
for (int i = 0; i < iZones.size(); i++) {
Zone zone = iZones.get(i);
DateTimeZoneBuilder builder = new DateTimeZoneBuilder();
zone.addToBuilder(builder, iRuleSets);
final DateTimeZone original = builder.toDateTimeZone(zone.iName, true);
DateTimeZone tz = original;
DateTimeZone tz = builder.toDateTimeZone(zone.iName, true);
if (test(tz.getID(), tz)) {
map.put(tz.getID(), tz);
sourceMap.put(tz.getID(), zone);
if (outputDir != null) {
if (ZoneInfoCompiler.verbose()) {
System.out.println("Writing " + tz.getID());
}
File file = new File(outputDir, tz.getID());
if (!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
OutputStream out = new FileOutputStream(file);
try {
builder.writeTo(zone.iName, out);
} finally {
out.close();
}

// Test if it can be read back.
InputStream in = new FileInputStream(file);
DateTimeZone tz2 = DateTimeZoneBuilder.readFrom(in, tz.getID());
in.close();
writeZone(outputDir, builder, tz);
}
}
}

if (!original.equals(tz2)) {
System.out.println("*e* Error in " + tz.getID() +
": Didn't read properly from file");
// revive zones from "good" links
for (int i = 0; i < iGoodLinks.size(); i += 2) {
String baseId = iGoodLinks.get(i);
String alias = iGoodLinks.get(i + 1);
Zone sourceZone = sourceMap.get(baseId);
if (sourceZone == null) {
System.out.println("Cannot find source zone '" + baseId + "' to link alias '" + alias + "' to");
} else {
DateTimeZoneBuilder builder = new DateTimeZoneBuilder();
sourceZone.addToBuilder(builder, iRuleSets);
DateTimeZone revived = builder.toDateTimeZone(alias, true);
if (test(revived.getID(), revived)) {
map.put(revived.getID(), revived);
if (outputDir != null) {
writeZone(outputDir, builder, revived);
}
}
map.put(revived.getID(), revived);
if (ZoneInfoCompiler.verbose()) {
System.out.println("Good link: " + alias + " -> " + baseId + " revived");
}
}
}

for (int pass=0; pass<2; pass++) {
for (int i=0; i<iLinks.size(); i += 2) {
String id = iLinks.get(i);
String alias = iLinks.get(i + 1);
// 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);
String alias = iBackLinks.get(i + 1);
DateTimeZone tz = map.get(id);
if (tz == null) {
if (pass > 0) {
System.out.println("Cannot find time zone '" + id +
"' to link alias '" + alias + "' to");
System.out.println("Cannot find time zone '" + id + "' to link alias '" + alias + "' to");
}
} else {
map.put(alias, tz);
if (ZoneInfoCompiler.verbose()) {
System.out.println("Back link: " + alias + " -> " + tz.getID());
}
}
}
}

// write map that unites the time-zone data, pointing aliases and real zones at files
if (outputDir != null) {
System.out.println("Writing ZoneInfoMap");
File file = new File(outputDir, "ZoneInfoMap");
Expand All @@ -461,7 +473,33 @@ public Map<String, DateTimeZone> compile(File outputDir, File[] sources) throws
return map;
}

public void parseDataFile(BufferedReader in) throws IOException {
private void writeZone(File outputDir, DateTimeZoneBuilder builder, DateTimeZone tz) throws IOException {
if (ZoneInfoCompiler.verbose()) {
System.out.println("Writing " + tz.getID());
}
File file = new File(outputDir, tz.getID());
if (!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
OutputStream out = new FileOutputStream(file);
try {
builder.writeTo(tz.getID(), out);
} finally {
out.close();
}

// Test if it can be read back.
InputStream in = new FileInputStream(file);
DateTimeZone tz2 = DateTimeZoneBuilder.readFrom(in, tz.getID());
in.close();

if (!tz.equals(tz2)) {
System.out.println("*e* Error in " + tz.getID() +
": Didn't read properly from file");
}
}

public void parseDataFile(BufferedReader in, boolean backward) throws IOException {
Zone zone = null;
String line;
while ((line = in.readLine()) != null) {
Expand Down Expand Up @@ -506,8 +544,18 @@ public void parseDataFile(BufferedReader in) throws IOException {
} else if (token.equalsIgnoreCase("Zone")) {
zone = new Zone(st);
} else if (token.equalsIgnoreCase("Link")) {
iLinks.add(st.nextToken());
iLinks.add(st.nextToken());
String real = st.nextToken();
String alias = st.nextToken();
// links in "backward" are deprecated names
// links in other files should be kept
// special case a few to try to repair terrible damage to tzdb
if (backward || alias.equals("US/Pacific-New") || alias.startsWith("Etc/") || alias.equals("GMT")) {
iBackLinks.add(real);
iBackLinks.add(alias);
} else {
iGoodLinks.add(real);
iGoodLinks.add(alias);
}
} else {
System.out.println("Unknown line: " + line);
}
Expand Down

0 comments on commit 4222b61

Please sign in to comment.