/* | |
*********************************************************************** | |
* Copyright (C) 2007, International Business Machines * | |
* Corporation and others. All Rights Reserved. * | |
*********************************************************************** | |
* | |
*/ | |
package com.ibm.icu.dev.tool.timezone; | |
import java.io.BufferedWriter; | |
import java.io.File; | |
import java.io.FileOutputStream; | |
import java.io.IOException; | |
import java.io.OutputStreamWriter; | |
import java.io.PrintWriter; | |
import java.io.Writer; | |
import java.lang.reflect.Method; | |
import java.util.ArrayList; | |
import java.util.Date; | |
import java.util.Iterator; | |
import java.util.List; | |
import java.util.TreeSet; | |
import com.ibm.icu.impl.JDKTimeZone; | |
import com.ibm.icu.text.DecimalFormat; | |
import com.ibm.icu.text.DecimalFormatSymbols; | |
import com.ibm.icu.text.SimpleDateFormat; | |
import com.ibm.icu.util.GregorianCalendar; | |
import com.ibm.icu.util.SimpleTimeZone; | |
import com.ibm.icu.util.ULocale; | |
/** | |
* TimeZone transition dump tool. | |
*/ | |
public class ICUZDump { | |
private static final String DEFAULT_LINE_SEP; | |
static { | |
String sep = System.getProperty("line.separator"); | |
DEFAULT_LINE_SEP = (sep == null) ? "\n" : sep; | |
} | |
private TimeZoneImpl tz = null; | |
private int loyear = 1900; | |
private int hiyear = 2050; | |
private long tick = 1000; | |
private DumpFormatter formatter; | |
private String linesep = DEFAULT_LINE_SEP; | |
public ICUZDump() { | |
} | |
public void setLowYear(int loyear) { | |
this.loyear = loyear; | |
} | |
public void setHighYear(int hiyear) { | |
this.hiyear = hiyear; | |
} | |
public void setTick(int tick) { | |
if (tick <= 0) { | |
throw new IllegalArgumentException("tick must be positive"); | |
} | |
this.tick = tick; | |
} | |
public void setTimeZone(Object tzimpl) { | |
this.tz = new TimeZoneImpl(tzimpl); | |
} | |
public void setDumpFormatter(DumpFormatter formatter) { | |
this.formatter = formatter; | |
} | |
public void setLineSeparator(String sep) { | |
this.linesep = sep; | |
} | |
public void dump(Writer w) throws IOException { | |
if (tz == null) { | |
throw new IllegalStateException("timezone is not initialized"); | |
} | |
if (formatter == null) { | |
formatter = new DumpFormatter(); | |
} | |
final long SEARCH_INCREMENT = 12 * 60 * 60 * 1000; // half day | |
long cutovers[] = getCutOverTimes(); | |
long t = cutovers[0]; | |
int offset = tz.getOffset(t); | |
boolean inDst = tz.inDaylightTime(t); | |
while (t < cutovers[1]) { | |
long newt = t + SEARCH_INCREMENT; | |
int newOffset = tz.getOffset(newt); | |
boolean newInDst = tz.inDaylightTime(newt); | |
if (offset != newOffset || inDst != newInDst) { | |
// find the boundary | |
long lot = t; | |
long hit = newt; | |
while (true) { | |
long diff = hit - lot; | |
if (diff <= tick) { | |
break; | |
} | |
long medt = lot + ((diff / 2) / tick) * tick; | |
int medOffset = tz.getOffset(medt); | |
boolean medInDst = tz.inDaylightTime(medt); | |
if (medOffset != offset || medInDst != inDst) { | |
hit = medt; | |
} else { | |
lot = medt; | |
} | |
} | |
w.write(formatter.format(lot, offset, tz.inDaylightTime(lot))); | |
w.write(" > "); | |
w.write(formatter.format(hit, newOffset, tz.inDaylightTime(hit))); | |
w.write(linesep); | |
offset = newOffset; | |
inDst = newInDst; | |
} | |
t = newt; | |
} | |
} | |
private long[] getCutOverTimes() { | |
long[] cutovers = new long[2]; | |
GregorianCalendar cal = tz.getCalendar(); | |
cal.clear(); | |
cal.set(loyear, 0, 1, 0, 0, 0); | |
cutovers[0] = cal.getTimeInMillis(); | |
cal.clear(); | |
cal.set(hiyear, 0, 1, 0, 0, 0); | |
cutovers[1] = cal.getTimeInMillis(); | |
return cutovers; | |
} | |
private class TimeZoneImpl { | |
private Object tzobj; | |
public TimeZoneImpl(Object tzobj) { | |
this.tzobj = tzobj; | |
} | |
public int getOffset(long time) { | |
try { | |
Method method = tzobj.getClass().getMethod("getOffset", new Class[] {long.class}); | |
Object result = method.invoke(tzobj, new Object[] {new Long(time)}); | |
return ((Integer)result).intValue(); | |
} catch (Exception e) { | |
e.printStackTrace(); | |
} | |
return 0; | |
} | |
public boolean inDaylightTime(long time) { | |
try { | |
Method method = tzobj.getClass().getMethod("inDaylightTime", new Class[] {Date.class}); | |
Object result = method.invoke(tzobj, new Object[] {new Date(time)}); | |
return ((Boolean)result).booleanValue(); | |
} catch (Exception e) { | |
e.printStackTrace(); | |
} | |
return false; | |
} | |
public GregorianCalendar getCalendar() { | |
GregorianCalendar cal = new GregorianCalendar(); | |
com.ibm.icu.util.TimeZone icutz = null; | |
if (tzobj instanceof com.ibm.icu.util.TimeZone) { | |
icutz = (com.ibm.icu.util.TimeZone)tzobj; | |
} else if (tzobj instanceof java.util.TimeZone) { | |
icutz = new JDKTimeZone((java.util.TimeZone)tzobj); | |
} else { | |
throw new IllegalStateException("Unsupported TimeZone implementation"); | |
} | |
cal.setTimeZone(icutz); | |
return cal; | |
} | |
} | |
public class DumpFormatter { | |
private SimpleTimeZone stz = new SimpleTimeZone(0, ""); | |
private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd EEE HH:mm:ss", ULocale.US); | |
private DecimalFormat decf; | |
public DumpFormatter() { | |
DecimalFormatSymbols decfs = new DecimalFormatSymbols(ULocale.US); | |
decf = new DecimalFormat("00", decfs); | |
} | |
public String format(long time, int offset, boolean isDst) { | |
StringBuffer buf = new StringBuffer(); | |
stz.setRawOffset(offset); | |
sdf.setTimeZone(stz); | |
buf.append(sdf.format(new Date(time))); | |
if (offset < 0) { | |
buf.append("-"); | |
offset = -offset; | |
} else { | |
buf.append("+"); | |
} | |
int hour, min, sec; | |
offset /= 1000; | |
sec = offset % 60; | |
offset = (offset - sec) / 60; | |
min = offset % 60; | |
hour = offset / 60; | |
buf.append(decf.format(hour)); | |
buf.append(decf.format(min)); | |
buf.append(decf.format(sec)); | |
buf.append("[DST="); | |
buf.append(isDst ? "1" : "0"); | |
buf.append("]"); | |
return buf.toString(); | |
} | |
} | |
/* | |
* Usage: | |
* | |
* java -cp icu4j.jar com.ibm.icu.dev.tool.timezone [-j] [-a] [-c[<low_year>,]<high_year>] [-d<dir>] [-l<sep>] [<zone_name> [<zone_name>]] | |
* | |
* Options: | |
* -j : Use JDK TimeZone. By default, ICU TimeZone is used. | |
* -a : Dump all available zones. | |
* -c[<low_year>,]<high_year> | |
* : When specified, dump transitions starting <low_year> (inclusive) up to | |
* <high_year> (exclusive). The default values are 1902(low) and 2038(high). | |
* -d<dir> : When specified, write transitions in a file under the directory for each zone. | |
* -l<sep> : New line code type CR/LF/CRLF. | |
*/ | |
public static void main(String[] args) { | |
boolean jdk = false; | |
int low = 1902; | |
int high = 2038; | |
List idlist = new ArrayList(); | |
boolean all = false; | |
String dir = null; | |
String newLineMode = null; | |
for (int i = 0; i < args.length; i++) { | |
if (args[i].equals("-j")) { | |
jdk = true; | |
} else if (args[i].startsWith("-c")) { | |
String val = args[i].substring(2); | |
String[] years = val.split(","); | |
if (years.length == 1) { | |
high = Integer.parseInt(years[0]); | |
} else if (years.length == 2) { | |
low = Integer.parseInt(years[0]); | |
high = Integer.parseInt(years[1]); | |
} | |
} else if (args[i].equals("-a")) { | |
all = true; | |
} else if (args[i].startsWith("-d")) { | |
dir = args[i].substring(2); | |
} else if (args[i].startsWith("-l")) { | |
newLineMode = args[i].substring(2); | |
} else if (!args[i].startsWith("-")){ | |
idlist.add(args[i].trim()); | |
} | |
} | |
String lineSep = System.getProperty("line.separator"); | |
if (newLineMode != null && newLineMode.length() > 0) { | |
if (newLineMode.equalsIgnoreCase("CR")) { | |
lineSep = "\r"; | |
} else if (newLineMode.equalsIgnoreCase("LF")) { | |
lineSep = "\n"; | |
} else if (newLineMode.equalsIgnoreCase("CRLF")) { | |
lineSep = "\r\n"; | |
} | |
} | |
String[] tzids = null; | |
if (all) { | |
if (jdk) { | |
tzids = java.util.TimeZone.getAvailableIDs(); | |
} else { | |
tzids = com.ibm.icu.util.TimeZone.getAvailableIDs(); | |
} | |
// sort tzids | |
TreeSet set = new TreeSet(); | |
for (int i = 0; i < tzids.length; i++) { | |
set.add(tzids[i]); | |
} | |
Iterator it = set.iterator(); | |
int i = 0; | |
while (it.hasNext()) { | |
tzids[i++] = (String)it.next(); | |
} | |
} else { | |
int len = idlist.size(); | |
if (len == 0) { | |
tzids = new String[1]; | |
tzids[0] = java.util.TimeZone.getDefault().getID(); | |
} else { | |
tzids = new String[idlist.size()]; | |
idlist.toArray(tzids); | |
} | |
} | |
File dirfile = null; | |
if (dir == null || dir.length() == 0) { | |
PrintWriter pw = new PrintWriter(System.out); | |
try { | |
for (int i = 0; i < tzids.length; i++) { | |
if (i != 0) { | |
pw.println(); | |
} | |
pw.write("ZONE: "); | |
pw.write(tzids[i]); | |
pw.println(); | |
dumpZone(pw, lineSep, tzids[i], low, high, jdk); | |
} | |
pw.flush(); | |
} catch (IOException ioe) { | |
ioe.printStackTrace(); | |
} | |
} else { | |
dirfile = new File(dir); | |
dirfile.mkdirs(); | |
try { | |
for (int i = 0; i < tzids.length; i++) { | |
FileOutputStream fos = new FileOutputStream(new File(dirfile, tzids[i].replace('/', '-'))); | |
Writer w = new BufferedWriter(new OutputStreamWriter(fos)); | |
dumpZone(w, lineSep, tzids[i], low, high, jdk); | |
w.close(); | |
} | |
} catch (IOException ioe) { | |
ioe.printStackTrace(); | |
} | |
} | |
} | |
private static void dumpZone(Writer w, String lineSep, String tzid, int low, int high, boolean isJdk) throws IOException { | |
ICUZDump dumper = new ICUZDump(); | |
Object tzimpl; | |
if (isJdk) { | |
tzimpl = java.util.TimeZone.getTimeZone(tzid); | |
} else { | |
tzimpl = com.ibm.icu.util.TimeZone.getTimeZone(tzid); | |
} | |
dumper.setTimeZone(tzimpl); | |
dumper.setLowYear(low); | |
dumper.setHighYear(high); | |
dumper.setLineSeparator(lineSep); | |
try { | |
dumper.dump(w); | |
} catch (IOException ioe) { | |
ioe.printStackTrace(); | |
} | |
} | |
} |