1 16 package org.joda.time.tz; 17 18 import java.io.BufferedReader ; 19 import java.io.DataOutputStream ; 20 import java.io.File ; 21 import java.io.FileInputStream ; 22 import java.io.FileOutputStream ; 23 import java.io.FileReader ; 24 import java.io.IOException ; 25 import java.io.InputStream ; 26 import java.io.OutputStream ; 27 import java.util.ArrayList ; 28 import java.util.HashMap ; 29 import java.util.Iterator ; 30 import java.util.List ; 31 import java.util.Locale ; 32 import java.util.Map ; 33 import java.util.StringTokenizer ; 34 import java.util.TreeMap ; 35 36 import org.joda.time.Chronology; 37 import org.joda.time.DateTime; 38 import org.joda.time.DateTimeField; 39 import org.joda.time.DateTimeZone; 40 import org.joda.time.MutableDateTime; 41 import org.joda.time.chrono.ISOChronology; 42 import org.joda.time.chrono.LenientChronology; 43 import org.joda.time.format.DateTimeFormatter; 44 import org.joda.time.format.ISODateTimeFormat; 45 46 62 public class ZoneInfoCompiler { 63 static DateTimeOfYear cStartOfYear; 64 65 static Chronology cLenientISO; 66 67 77 public static void main(String [] args) throws Exception { 78 if (args.length == 0) { 79 printUsage(); 80 return; 81 } 82 83 File inputDir = null; 84 File outputDir = null; 85 86 int i; 87 for (i=0; i<args.length; i++) { 88 try { 89 if ("-src".equals(args[i])) { 90 inputDir = new File (args[++i]); 91 } else if ("-dst".equals(args[i])) { 92 outputDir = new File (args[++i]); 93 } else if ("-?".equals(args[i])) { 94 printUsage(); 95 return; 96 } else { 97 break; 98 } 99 } catch (IndexOutOfBoundsException e) { 100 printUsage(); 101 return; 102 } 103 } 104 105 if (i >= args.length) { 106 printUsage(); 107 return; 108 } 109 110 File [] sources = new File [args.length - i]; 111 for (int j=0; i<args.length; i++,j++) { 112 sources[j] = inputDir == null ? new File (args[i]) : new File (inputDir, args[i]); 113 } 114 115 ZoneInfoCompiler zic = new ZoneInfoCompiler(); 116 zic.compile(outputDir, sources); 117 } 118 119 private static void printUsage() { 120 System.out.println("Usage: java org.joda.time.tz.ZoneInfoCompiler <options> <source files>"); 121 System.out.println("where possible options include:"); 122 System.out.println(" -src <directory> Specify where to read source files"); 123 System.out.println(" -dst <directory> Specify where to write generated files"); 124 } 125 126 static DateTimeOfYear getStartOfYear() { 127 if (cStartOfYear == null) { 128 cStartOfYear = new DateTimeOfYear(); 129 } 130 return cStartOfYear; 131 } 132 133 static Chronology getLenientISOChronology() { 134 if (cLenientISO == null) { 135 cLenientISO = LenientChronology.getInstance(ISOChronology.getInstanceUTC()); 136 } 137 return cLenientISO; 138 } 139 140 143 static void writeZoneInfoMap(DataOutputStream dout, Map zimap) throws IOException { 144 Map idToIndex = new HashMap (zimap.size()); 146 TreeMap indexToId = new TreeMap (); 147 148 Iterator it = zimap.entrySet().iterator(); 149 short count = 0; 150 while (it.hasNext()) { 151 Map.Entry entry = (Map.Entry )it.next(); 152 String id = (String )entry.getKey(); 153 if (!idToIndex.containsKey(id)) { 154 Short index = new Short (count); 155 idToIndex.put(id, index); 156 indexToId.put(index, id); 157 if (++count == 0) { 158 throw new InternalError ("Too many time zone ids"); 159 } 160 } 161 id = ((DateTimeZone)entry.getValue()).getID(); 162 if (!idToIndex.containsKey(id)) { 163 Short index = new Short (count); 164 idToIndex.put(id, index); 165 indexToId.put(index, id); 166 if (++count == 0) { 167 throw new InternalError ("Too many time zone ids"); 168 } 169 } 170 } 171 172 dout.writeShort(indexToId.size()); 174 it = indexToId.values().iterator(); 175 while (it.hasNext()) { 176 dout.writeUTF((String )it.next()); 177 } 178 179 dout.writeShort(zimap.size()); 181 it = zimap.entrySet().iterator(); 182 while (it.hasNext()) { 183 Map.Entry entry = (Map.Entry )it.next(); 184 String id = (String )entry.getKey(); 185 dout.writeShort(((Short )idToIndex.get(id)).shortValue()); 186 id = ((DateTimeZone)entry.getValue()).getID(); 187 dout.writeShort(((Short )idToIndex.get(id)).shortValue()); 188 } 189 } 190 191 static int parseYear(String str, int def) { 192 str = str.toLowerCase(); 193 if (str.equals("minimum") || str.equals("min")) { 194 return Integer.MIN_VALUE; 195 } else if (str.equals("maximum") || str.equals("max")) { 196 return Integer.MAX_VALUE; 197 } else if (str.equals("only")) { 198 return def; 199 } 200 return Integer.parseInt(str); 201 } 202 203 static int parseMonth(String str) { 204 DateTimeField field = ISOChronology.getInstanceUTC().monthOfYear(); 205 return field.get(field.set(0, str, Locale.ENGLISH)); 206 } 207 208 static int parseDayOfWeek(String str) { 209 DateTimeField field = ISOChronology.getInstanceUTC().dayOfWeek(); 210 return field.get(field.set(0, str, Locale.ENGLISH)); 211 } 212 213 static String parseOptional(String str) { 214 return (str.equals("-")) ? null : str; 215 } 216 217 static int parseTime(String str) { 218 DateTimeFormatter p = ISODateTimeFormat.hourMinuteSecondFraction(); 219 MutableDateTime mdt = new MutableDateTime(0, getLenientISOChronology()); 220 int pos = 0; 221 if (str.startsWith("-")) { 222 pos = 1; 223 } 224 int newPos = p.parseInto(mdt, str, pos); 225 if (newPos == ~pos) { 226 throw new IllegalArgumentException (str); 227 } 228 int millis = (int)mdt.getMillis(); 229 if (pos == 1) { 230 millis = -millis; 231 } 232 return millis; 233 } 234 235 static char parseZoneChar(char c) { 236 switch (c) { 237 case 's': case 'S': 238 return 's'; 240 case 'u': case 'U': case 'g': case 'G': case 'z': case 'Z': 241 return 'u'; 243 case 'w': case 'W': default: 244 return 'w'; 246 } 247 } 248 249 252 static boolean test(String id, DateTimeZone tz) { 253 if (!id.equals(tz.getID())) { 254 return true; 255 } 256 257 259 long millis = ISOChronology.getInstanceUTC().year().set(0, 1850); 260 long end = ISOChronology.getInstanceUTC().year().set(0, 2050); 261 262 int offset = tz.getOffset(millis); 263 String key = tz.getNameKey(millis); 264 265 List transitions = new ArrayList (); 266 267 while (true) { 268 long next = tz.nextTransition(millis); 269 if (next == millis || next > end) { 270 break; 271 } 272 273 millis = next; 274 275 int nextOffset = tz.getOffset(millis); 276 String nextKey = tz.getNameKey(millis); 277 278 if (offset == nextOffset 279 && key.equals(nextKey)) { 280 System.out.println("*d* Error in " + tz.getID() + " " 281 + new DateTime(millis, 282 ISOChronology.getInstanceUTC())); 283 return false; 284 } 285 286 if (nextKey == null || (nextKey.length() < 3 && !"??".equals(nextKey))) { 287 System.out.println("*s* Error in " + tz.getID() + " " 288 + new DateTime(millis, 289 ISOChronology.getInstanceUTC()) 290 + ", nameKey=" + nextKey); 291 return false; 292 } 293 294 transitions.add(new Long (millis)); 295 296 offset = nextOffset; 297 key = nextKey; 298 } 299 300 302 millis = ISOChronology.getInstanceUTC().year().set(0, 2050); 303 end = ISOChronology.getInstanceUTC().year().set(0, 1850); 304 305 for (int i=transitions.size(); --i>= 0; ) { 306 long prev = tz.previousTransition(millis); 307 if (prev == millis || prev < end) { 308 break; 309 } 310 311 millis = prev; 312 313 long trans = ((Long )transitions.get(i)).longValue(); 314 315 if (trans - 1 != millis) { 316 System.out.println("*r* Error in " + tz.getID() + " " 317 + new DateTime(millis, 318 ISOChronology.getInstanceUTC()) + " != " 319 + new DateTime(trans - 1, 320 ISOChronology.getInstanceUTC())); 321 322 return false; 323 } 324 } 325 326 return true; 327 } 328 329 private Map iRuleSets; 331 332 private List iZones; 334 335 private List iLinks; 337 338 public ZoneInfoCompiler() { 339 iRuleSets = new HashMap (); 340 iZones = new ArrayList (); 341 iLinks = new ArrayList (); 342 } 343 344 350 public Map compile(File outputDir, File [] sources) throws IOException { 351 if (sources != null) { 352 for (int i=0; i<sources.length; i++) { 353 BufferedReader in = new BufferedReader (new FileReader (sources[i])); 354 parseDataFile(in); 355 in.close(); 356 } 357 } 358 359 if (outputDir != null) { 360 if (!outputDir.exists()) { 361 throw new IOException ("Destination directory doesn't exist: " + outputDir); 362 } 363 if (!outputDir.isDirectory()) { 364 throw new IOException ("Destination is not a directory: " + outputDir); 365 } 366 } 367 368 Map map = new TreeMap (); 369 370 for (int i=0; i<iZones.size(); i++) { 371 Zone zone = (Zone)iZones.get(i); 372 DateTimeZoneBuilder builder = new DateTimeZoneBuilder(); 373 zone.addToBuilder(builder, iRuleSets); 374 final DateTimeZone original = builder.toDateTimeZone(zone.iName); 375 DateTimeZone tz = original; 376 if (test(tz.getID(), tz)) { 377 map.put(tz.getID(), tz); 378 if (outputDir != null) { 379 System.out.println("Writing " + tz.getID()); 380 File file = new File (outputDir, tz.getID()); 381 if (!file.getParentFile().exists()) { 382 file.getParentFile().mkdirs(); 383 } 384 OutputStream out = new FileOutputStream (file); 385 builder.writeTo(out); 386 out.close(); 387 388 InputStream in = new FileInputStream (file); 390 DateTimeZone tz2 = DateTimeZoneBuilder.readFrom(in, tz.getID()); 391 in.close(); 392 393 if (!original.equals(tz2)) { 394 System.out.println("*e* Error in " + tz.getID() + 395 ": Didn't read properly from file"); 396 } 397 } 398 } 399 } 400 401 for (int pass=0; pass<2; pass++) { 402 for (int i=0; i<iLinks.size(); i += 2) { 403 String id = (String )iLinks.get(i); 404 String alias = (String )iLinks.get(i + 1); 405 DateTimeZone tz = (DateTimeZone)map.get(id); 406 if (tz == null) { 407 if (pass > 0) { 408 System.out.println("Cannot find time zone '" + id + 409 "' to link alias '" + alias + "' to"); 410 } 411 } else { 412 map.put(alias, tz); 413 } 414 } 415 } 416 417 if (outputDir != null) { 418 System.out.println("Writing ZoneInfoMap"); 419 File file = new File (outputDir, "ZoneInfoMap"); 420 if (!file.getParentFile().exists()) { 421 file.getParentFile().mkdirs(); 422 } 423 424 OutputStream out = new FileOutputStream (file); 425 DataOutputStream dout = new DataOutputStream (out); 426 Map zimap = new TreeMap (String.CASE_INSENSITIVE_ORDER); 428 zimap.putAll(map); 429 writeZoneInfoMap(dout, zimap); 430 dout.close(); 431 } 432 433 return map; 434 } 435 436 public void parseDataFile(BufferedReader in) throws IOException { 437 Zone zone = null; 438 String line; 439 while ((line = in.readLine()) != null) { 440 String trimmed = line.trim(); 441 if (trimmed.length() == 0 || trimmed.charAt(0) == '#') { 442 continue; 443 } 444 445 int index = line.indexOf('#'); 446 if (index >= 0) { 447 line = line.substring(0, index); 448 } 449 450 452 StringTokenizer st = new StringTokenizer (line, " \t"); 453 454 if (Character.isWhitespace(line.charAt(0)) && st.hasMoreTokens()) { 455 if (zone != null) { 456 zone.chain(st); 458 } 459 continue; 460 } else { 461 if (zone != null) { 462 iZones.add(zone); 463 } 464 zone = null; 465 } 466 467 if (st.hasMoreTokens()) { 468 String token = st.nextToken(); 469 if (token.equalsIgnoreCase("Rule")) { 470 Rule r = new Rule(st); 471 RuleSet rs = (RuleSet)iRuleSets.get(r.iName); 472 if (rs == null) { 473 rs = new RuleSet(r); 474 iRuleSets.put(r.iName, rs); 475 } else { 476 rs.addRule(r); 477 } 478 } else if (token.equalsIgnoreCase("Zone")) { 479 zone = new Zone(st); 480 } else if (token.equalsIgnoreCase("Link")) { 481 iLinks.add(st.nextToken()); 482 iLinks.add(st.nextToken()); 483 } else { 484 System.out.println("Unknown line: " + line); 485 } 486 } 487 } 488 489 if (zone != null) { 490 iZones.add(zone); 491 } 492 } 493 494 private static class DateTimeOfYear { 495 public final int iMonthOfYear; 496 public final int iDayOfMonth; 497 public final int iDayOfWeek; 498 public final boolean iAdvanceDayOfWeek; 499 public final int iMillisOfDay; 500 public final char iZoneChar; 501 502 DateTimeOfYear() { 503 iMonthOfYear = 1; 504 iDayOfMonth = 1; 505 iDayOfWeek = 0; 506 iAdvanceDayOfWeek = false; 507 iMillisOfDay = 0; 508 iZoneChar = 'w'; 509 } 510 511 DateTimeOfYear(StringTokenizer st) { 512 int month = 1; 513 int day = 1; 514 int dayOfWeek = 0; 515 int millis = 0; 516 boolean advance = false; 517 char zoneChar = 'w'; 518 519 if (st.hasMoreTokens()) { 520 month = parseMonth(st.nextToken()); 521 522 if (st.hasMoreTokens()) { 523 String str = st.nextToken(); 524 if (str.startsWith("last")) { 525 day = -1; 526 dayOfWeek = parseDayOfWeek(str.substring(4)); 527 advance = false; 528 } else { 529 try { 530 day = Integer.parseInt(str); 531 dayOfWeek = 0; 532 advance = false; 533 } catch (NumberFormatException e) { 534 int index = str.indexOf(">="); 535 if (index > 0) { 536 day = Integer.parseInt(str.substring(index + 2)); 537 dayOfWeek = parseDayOfWeek(str.substring(0, index)); 538 advance = true; 539 } else { 540 index = str.indexOf("<="); 541 if (index > 0) { 542 day = Integer.parseInt(str.substring(index + 2)); 543 dayOfWeek = parseDayOfWeek(str.substring(0, index)); 544 advance = false; 545 } else { 546 throw new IllegalArgumentException (str); 547 } 548 } 549 } 550 } 551 552 if (st.hasMoreTokens()) { 553 str = st.nextToken(); 554 zoneChar = parseZoneChar(str.charAt(str.length() - 1)); 555 millis = parseTime(str); 556 } 557 } 558 } 559 560 iMonthOfYear = month; 561 iDayOfMonth = day; 562 iDayOfWeek = dayOfWeek; 563 iAdvanceDayOfWeek = advance; 564 iMillisOfDay = millis; 565 iZoneChar = zoneChar; 566 } 567 568 571 public void addRecurring(DateTimeZoneBuilder builder, String nameKey, 572 int saveMillis, int fromYear, int toYear) 573 { 574 builder.addRecurringSavings(nameKey, saveMillis, 575 fromYear, toYear, 576 iZoneChar, 577 iMonthOfYear, 578 iDayOfMonth, 579 iDayOfWeek, 580 iAdvanceDayOfWeek, 581 iMillisOfDay); 582 } 583 584 587 public void addCutover(DateTimeZoneBuilder builder, int year) { 588 builder.addCutover(year, 589 iZoneChar, 590 iMonthOfYear, 591 iDayOfMonth, 592 iDayOfWeek, 593 iAdvanceDayOfWeek, 594 iMillisOfDay); 595 } 596 597 public String toString() { 598 return 599 "MonthOfYear: " + iMonthOfYear + "\n" + 600 "DayOfMonth: " + iDayOfMonth + "\n" + 601 "DayOfWeek: " + iDayOfWeek + "\n" + 602 "AdvanceDayOfWeek: " + iAdvanceDayOfWeek + "\n" + 603 "MillisOfDay: " + iMillisOfDay + "\n" + 604 "ZoneChar: " + iZoneChar + "\n"; 605 } 606 } 607 608 private static class Rule { 609 public final String iName; 610 public final int iFromYear; 611 public final int iToYear; 612 public final String iType; 613 public final DateTimeOfYear iDateTimeOfYear; 614 public final int iSaveMillis; 615 public final String iLetterS; 616 617 Rule(StringTokenizer st) { 618 iName = st.nextToken().intern(); 619 iFromYear = parseYear(st.nextToken(), 0); 620 iToYear = parseYear(st.nextToken(), iFromYear); 621 if (iToYear < iFromYear) { 622 throw new IllegalArgumentException (); 623 } 624 iType = parseOptional(st.nextToken()); 625 iDateTimeOfYear = new DateTimeOfYear(st); 626 iSaveMillis = parseTime(st.nextToken()); 627 iLetterS = parseOptional(st.nextToken()); 628 } 629 630 633 public void addRecurring(DateTimeZoneBuilder builder, String nameFormat) { 634 String nameKey = formatName(nameFormat); 635 iDateTimeOfYear.addRecurring 636 (builder, nameKey, iSaveMillis, iFromYear, iToYear); 637 } 638 639 private String formatName(String nameFormat) { 640 int index = nameFormat.indexOf('/'); 641 if (index > 0) { 642 if (iSaveMillis == 0) { 643 return nameFormat.substring(0, index).intern(); 645 } else { 646 return nameFormat.substring(index + 1).intern(); 647 } 648 } 649 index = nameFormat.indexOf("%s"); 650 if (index < 0) { 651 return nameFormat; 652 } 653 String left = nameFormat.substring(0, index); 654 String right = nameFormat.substring(index + 2); 655 String name; 656 if (iLetterS == null) { 657 name = left.concat(right); 658 } else { 659 name = left + iLetterS + right; 660 } 661 return name.intern(); 662 } 663 664 public String toString() { 665 return 666 "[Rule]\n" + 667 "Name: " + iName + "\n" + 668 "FromYear: " + iFromYear + "\n" + 669 "ToYear: " + iToYear + "\n" + 670 "Type: " + iType + "\n" + 671 iDateTimeOfYear + 672 "SaveMillis: " + iSaveMillis + "\n" + 673 "LetterS: " + iLetterS + "\n"; 674 } 675 } 676 677 private static class RuleSet { 678 private List iRules; 679 680 RuleSet(Rule rule) { 681 iRules = new ArrayList (); 682 iRules.add(rule); 683 } 684 685 void addRule(Rule rule) { 686 if (!(rule.iName.equals(((Rule)iRules.get(0)).iName))) { 687 throw new IllegalArgumentException ("Rule name mismatch"); 688 } 689 iRules.add(rule); 690 } 691 692 695 public void addRecurring(DateTimeZoneBuilder builder, String nameFormat) { 696 for (int i=0; i<iRules.size(); i++) { 697 Rule rule = (Rule)iRules.get(i); 698 rule.addRecurring(builder, nameFormat); 699 } 700 } 701 } 702 703 private static class Zone { 704 public final String iName; 705 public final int iOffsetMillis; 706 public final String iRules; 707 public final String iFormat; 708 public final int iUntilYear; 709 public final DateTimeOfYear iUntilDateTimeOfYear; 710 711 private Zone iNext; 712 713 Zone(StringTokenizer st) { 714 this(st.nextToken(), st); 715 } 716 717 private Zone(String name, StringTokenizer st) { 718 iName = name.intern(); 719 iOffsetMillis = parseTime(st.nextToken()); 720 iRules = parseOptional(st.nextToken()); 721 iFormat = st.nextToken().intern(); 722 723 int year = Integer.MAX_VALUE; 724 DateTimeOfYear dtOfYear = getStartOfYear(); 725 726 if (st.hasMoreTokens()) { 727 year = Integer.parseInt(st.nextToken()); 728 if (st.hasMoreTokens()) { 729 dtOfYear = new DateTimeOfYear(st); 730 } 731 } 732 733 iUntilYear = year; 734 iUntilDateTimeOfYear = dtOfYear; 735 } 736 737 void chain(StringTokenizer st) { 738 if (iNext != null) { 739 iNext.chain(st); 740 } else { 741 iNext = new Zone(iName, st); 742 } 743 } 744 745 752 753 756 public void addToBuilder(DateTimeZoneBuilder builder, Map ruleSets) { 757 addToBuilder(this, builder, ruleSets); 758 } 759 760 private static void addToBuilder(Zone zone, 761 DateTimeZoneBuilder builder, 762 Map ruleSets) 763 { 764 for (; zone != null; zone = zone.iNext) { 765 builder.setStandardOffset(zone.iOffsetMillis); 766 767 if (zone.iRules == null) { 768 builder.setFixedSavings(zone.iFormat, 0); 769 } else { 770 try { 771 int saveMillis = parseTime(zone.iRules); 773 builder.setFixedSavings(zone.iFormat, saveMillis); 774 } 775 catch (Exception e) { 776 RuleSet rs = (RuleSet)ruleSets.get(zone.iRules); 777 if (rs == null) { 778 throw new IllegalArgumentException 779 ("Rules not found: " + zone.iRules); 780 } 781 rs.addRecurring(builder, zone.iFormat); 782 } 783 } 784 785 if (zone.iUntilYear == Integer.MAX_VALUE) { 786 break; 787 } 788 789 zone.iUntilDateTimeOfYear.addCutover(builder, zone.iUntilYear); 790 } 791 } 792 793 public String toString() { 794 String str = 795 "[Zone]\n" + 796 "Name: " + iName + "\n" + 797 "OffsetMillis: " + iOffsetMillis + "\n" + 798 "Rules: " + iRules + "\n" + 799 "Format: " + iFormat + "\n" + 800 "UntilYear: " + iUntilYear + "\n" + 801 iUntilDateTimeOfYear; 802 803 if (iNext == null) { 804 return str; 805 } 806 807 return str + "...\n" + iNext.toString(); 808 } 809 } 810 } 811 812 | Popular Tags |