Source for gnu.java.util.ZoneInfo

   1: /* gnu.java.util.ZoneInfo
   2:    Copyright (C) 2007 Free Software Foundation, Inc.
   3: 
   4: This file is part of GNU Classpath.
   5: 
   6: GNU Classpath is free software; you can redistribute it and/or modify
   7: it under the terms of the GNU General Public License as published by
   8: the Free Software Foundation; either version 2, or (at your option)
   9: any later version.
  10: 
  11: GNU Classpath is distributed in the hope that it will be useful, but
  12: WITHOUT ANY WARRANTY; without even the implied warranty of
  13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  14: General Public License for more details.
  15: 
  16: You should have received a copy of the GNU General Public License
  17: along with GNU Classpath; see the file COPYING.  If not, write to the
  18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  19: 02110-1301 USA.
  20: 
  21: Linking this library statically or dynamically with other modules is
  22: making a combined work based on this library.  Thus, the terms and
  23: conditions of the GNU General Public License cover the whole
  24: combination.
  25: 
  26: As a special exception, the copyright holders of this library give you
  27: permission to link this library with independent modules to produce an
  28: executable, regardless of the license terms of these independent
  29: modules, and to copy and distribute the resulting executable under
  30: terms of your choice, provided that you also meet, for each linked
  31: independent module, the terms and conditions of the license of that
  32: module.  An independent module is a module which is not derived from
  33: or based on this library.  If you modify this library, you may extend
  34: this exception to your version of the library, but you are not
  35: obligated to do so.  If you do not wish to do so, delete this
  36: exception statement from your version. */
  37: 
  38: 
  39: package gnu.java.util;
  40: 
  41: import java.io.BufferedInputStream;
  42: import java.io.DataInputStream;
  43: import java.io.EOFException;
  44: import java.io.FileInputStream;
  45: import java.io.InputStream;
  46: import java.io.IOException;
  47: import java.util.Calendar;
  48: import java.util.Date;
  49: import java.util.GregorianCalendar;
  50: import java.util.SimpleTimeZone;
  51: import java.util.TimeZone;
  52: 
  53: /**
  54:  * This class represents more advanced variant of java.util.SimpleTimeZone.
  55:  * It can handle zic(8) compiled transition dates plus uses a SimpleTimeZone
  56:  * for years beyond last precomputed transition.  Before first precomputed
  57:  * transition it assumes no daylight saving was in effect.
  58:  * Timezones that never used daylight saving time should use just
  59:  * SimpleTimeZone instead of this class.
  60:  *
  61:  * This object is tightly bound to the Gregorian calendar.  It assumes
  62:  * a regular seven days week, and the month lengths are that of the
  63:  * Gregorian Calendar.
  64:  *
  65:  * @see Calendar
  66:  * @see GregorianCalendar
  67:  * @see SimpleTimeZone
  68:  * @author Jakub Jelinek
  69:  */
  70: public class ZoneInfo extends TimeZone
  71: {
  72:   private static final int SECS_SHIFT = 22;
  73:   private static final long OFFSET_MASK = (1 << 21) - 1;
  74:   private static final int OFFSET_SHIFT = 64 - 21;
  75:   private static final long IS_DST = 1 << 21;
  76: 
  77:   /**
  78:    * The raw time zone offset in milliseconds to GMT, ignoring
  79:    * daylight savings.
  80:    * @serial
  81:    */
  82:   private int rawOffset;
  83: 
  84:   /**
  85:    * Cached DST savings for the last transition rule.
  86:    */
  87:   private int dstSavings;
  88: 
  89:   /**
  90:    * Cached flag whether last transition rule uses DST saving.
  91:    */
  92:   private boolean useDaylight;
  93: 
  94:   /**
  95:    * Array of encoded transitions.
  96:    * Transition time in UTC seconds since epoch is in the most
  97:    * significant 64 - SECS_SHIFT bits, then one bit flag
  98:    * whether DST is active and the least significant bits
  99:    * containing offset relative to rawOffset.  Both the DST
 100:    * flag and relative offset apply to time before the transition
 101:    * and after or equal to previous transition if any.
 102:    * The array must be sorted.
 103:    */
 104:   private long[] transitions;
 105: 
 106:   /**
 107:    * SimpleTimeZone rule which applies on or after the latest
 108:    * transition.  If the DST changes are not expresible as a
 109:    * SimpleTimeZone rule, then the rule should just contain
 110:    * the standard time and no DST time.
 111:    */
 112:   private SimpleTimeZone lastRule;
 113: 
 114:   /**
 115:    * Cached GMT SimpleTimeZone object for internal use in
 116:    * getOffset method.
 117:    */
 118:   private static SimpleTimeZone gmtZone = null;
 119: 
 120:   static final long serialVersionUID = -3740626706860383657L;
 121: 
 122:   /**
 123:    * Create a <code>ZoneInfo</code> with the given time offset
 124:    * from GMT and with daylight savings.
 125:    *
 126:    * @param rawOffset The time offset from GMT in milliseconds.
 127:    * @param id  The identifier of this time zone.
 128:    * @param transitions  Array of transition times in UTC seconds since
 129:    * Epoch in topmost 42 bits, below that 1 boolean bit whether the time
 130:    * before that transition used daylight saving and in bottommost 21
 131:    * bits relative daylight saving offset against rawOffset in seconds
 132:    * that applies before this transition.
 133:    * @param endRule SimpleTimeZone class describing the daylight saving
 134:    * rules after the last transition.
 135:    */
 136:   public ZoneInfo(int rawOffset, String id, long[] transitions,
 137:                   SimpleTimeZone lastRule)
 138:   {
 139:     if (transitions == null || transitions.length < 1)
 140:       throw new IllegalArgumentException("transitions must not be null");
 141:     if (lastRule == null)
 142:       throw new IllegalArgumentException("lastRule must not be null");
 143:     this.rawOffset = rawOffset;
 144:     this.transitions = transitions;
 145:     this.lastRule = lastRule;
 146:     setID(id);
 147:     computeDSTSavings();
 148:   }
 149: 
 150:   /**
 151:    * Gets the time zone offset, for current date, modified in case of
 152:    * daylight savings.  This is the offset to add to UTC to get the local
 153:    * time.
 154:    *
 155:    * The day must be a positive number and dayOfWeek must be a positive value
 156:    * from Calendar.  dayOfWeek is redundant, but must match the other values
 157:    * or an inaccurate result may be returned.
 158:    *
 159:    * @param era the era of the given date
 160:    * @param year the year of the given date
 161:    * @param month the month of the given date, 0 for January.
 162:    * @param day the day of month
 163:    * @param dayOfWeek the day of week; this must match the other fields.
 164:    * @param millis the millis in the day (in local standard time)
 165:    * @return the time zone offset in milliseconds.
 166:    * @throws IllegalArgumentException if arguments are incorrect.
 167:    */
 168:   public int getOffset(int era, int year, int month, int day, int dayOfWeek,
 169:                        int millis)
 170:   {
 171:     if (gmtZone == null)
 172:       gmtZone = new SimpleTimeZone(0, "GMT");
 173: 
 174:     if (dayOfWeek < Calendar.SUNDAY || dayOfWeek > Calendar.SATURDAY)
 175:       throw new IllegalArgumentException("dayOfWeek out of range");
 176:     if (month < Calendar.JANUARY || month > Calendar.DECEMBER)
 177:       throw new IllegalArgumentException("month out of range:" + month);
 178: 
 179:     if (era != GregorianCalendar.AD)
 180:       return (int) (((transitions[0] << OFFSET_SHIFT) >> OFFSET_SHIFT) * 1000);
 181: 
 182:     GregorianCalendar cal = new GregorianCalendar((TimeZone) gmtZone);
 183:     cal.set(year, month, day, 0, 0, 0);
 184:     if (cal.get(Calendar.DAY_OF_MONTH) != day)
 185:       throw new IllegalArgumentException("day out of range");
 186: 
 187:     return getOffset(cal.getTimeInMillis() - rawOffset + millis);
 188:   }
 189: 
 190:   private long findTransition(long secs)
 191:   {
 192:     if (secs < (transitions[0] >> SECS_SHIFT))
 193:       return transitions[0];
 194: 
 195:     if (secs >= (transitions[transitions.length-1] >> SECS_SHIFT))
 196:       return Long.MAX_VALUE;
 197: 
 198:     long val = (secs + 1) << SECS_SHIFT;
 199:     int lo = 1;
 200:     int hi = transitions.length;
 201:     int mid = 1;
 202:     while (lo < hi)
 203:       {
 204:         mid = (lo + hi) / 2;
 205:         // secs < (transitions[mid-1] >> SECS_SHIFT)
 206:         if (val <= transitions[mid-1])
 207:           hi = mid;
 208:         // secs >= (transitions[mid] >> SECS_SHIFT)
 209:         else if (val > transitions[mid])
 210:           lo = mid + 1;
 211:         else
 212:           break;
 213:       }
 214:     return transitions[mid];
 215:   }
 216: 
 217:   /**
 218:    * Get the time zone offset for the specified date, modified in case of
 219:    * daylight savings.  This is the offset to add to UTC to get the local
 220:    * time.
 221:    * @param date the date represented in millisecends
 222:    * since January 1, 1970 00:00:00 GMT.
 223:    */
 224:   public int getOffset(long date)
 225:   {
 226:     long d = (date >= 0 ? date / 1000 : (date + 1) / 1000 - 1);
 227:     long transition = findTransition(d);
 228: 
 229:     // For times on or after last transition use lastRule.
 230:     if (transition == Long.MAX_VALUE)
 231:       return lastRule.getOffset(date);
 232: 
 233:     return (int) (((transition << OFFSET_SHIFT) >> OFFSET_SHIFT) * 1000);
 234:   }
 235: 
 236:   /**
 237:    * Returns the time zone offset to GMT in milliseconds, ignoring
 238:    * day light savings.
 239:    * @return the time zone offset.
 240:    */
 241:   public int getRawOffset()
 242:   {
 243:     return rawOffset;
 244:   }
 245: 
 246:   /**
 247:    * Sets the standard time zone offset to GMT.
 248:    * @param rawOffset The time offset from GMT in milliseconds.
 249:    */
 250:   public void setRawOffset(int rawOffset)
 251:   {
 252:     this.rawOffset = rawOffset;
 253:     lastRule.setRawOffset(rawOffset);
 254:   }
 255: 
 256:   private void computeDSTSavings()
 257:   {
 258:     if (lastRule.useDaylightTime())
 259:       {
 260:         dstSavings = lastRule.getDSTSavings();
 261:         useDaylight = true;
 262:       }
 263:     else
 264:       {
 265:         dstSavings = 0;
 266:         useDaylight = false;
 267:         // lastRule might say no DST is in effect simply because
 268:         // the DST rules are too complex for SimpleTimeZone, say
 269:         // for Asia/Jerusalem.
 270:         // Look at the last DST offset if it is newer than current time.
 271:         long currentSecs = System.currentTimeMillis() / 1000;
 272:         int i;
 273:         for (i = transitions.length - 1;
 274:              i >= 0 && currentSecs < (transitions[i] >> SECS_SHIFT);
 275:              i--)
 276:           if ((transitions[i] & IS_DST) != 0)
 277:             {
 278:               dstSavings = (int) (((transitions[i] << OFFSET_SHIFT)
 279:                                    >> OFFSET_SHIFT) * 1000)
 280:                            - rawOffset;
 281:               useDaylight = true;
 282:               break;
 283:             }
 284:       }
 285:   }
 286: 
 287:   /**
 288:    * Gets the daylight savings offset.  This is a positive offset in
 289:    * milliseconds with respect to standard time.  Typically this
 290:    * is one hour, but for some time zones this may be half an our.
 291:    * @return the daylight savings offset in milliseconds.
 292:    */
 293:   public int getDSTSavings()
 294:   {
 295:     return dstSavings;
 296:   }
 297: 
 298:   /**
 299:    * Returns if this time zone uses daylight savings time.
 300:    * @return true, if we use daylight savings time, false otherwise.
 301:    */
 302:   public boolean useDaylightTime()
 303:   {
 304:     return useDaylight;
 305:   }
 306: 
 307:   /**
 308:    * Determines if the given date is in daylight savings time.
 309:    * @return true, if it is in daylight savings time, false otherwise.
 310:    */
 311:   public boolean inDaylightTime(Date date)
 312:   {
 313:     long d = date.getTime();
 314:     d = (d >= 0 ? d / 1000 : (d + 1) / 1000 - 1);
 315:     long transition = findTransition(d);
 316: 
 317:     // For times on or after last transition use lastRule.
 318:     if (transition == Long.MAX_VALUE)
 319:       return lastRule.inDaylightTime(date);
 320: 
 321:     return (transition & IS_DST) != 0;
 322:   }
 323: 
 324:   /**
 325:    * Generates the hashCode for the SimpleDateFormat object.  It is
 326:    * the rawOffset, possibly, if useDaylightSavings is true, xored
 327:    * with startYear, startMonth, startDayOfWeekInMonth, ..., endTime.
 328:    */
 329:   public synchronized int hashCode()
 330:   {
 331:     int hash = lastRule.hashCode();
 332:     // FIXME - hash transitions?
 333:     return hash;
 334:   }
 335: 
 336:   public synchronized boolean equals(Object o)
 337:   {
 338:     if (! hasSameRules((TimeZone) o))
 339:       return false;
 340: 
 341:     ZoneInfo zone = (ZoneInfo) o;
 342:     return getID().equals(zone.getID());
 343:   }
 344: 
 345:   /**
 346:    * Test if the other time zone uses the same rule and only
 347:    * possibly differs in ID.  This implementation for this particular
 348:    * class will return true if the other object is a ZoneInfo,
 349:    * the raw offsets and useDaylight are identical and if useDaylight
 350:    * is true, also the start and end datas are identical.
 351:    * @return true if this zone uses the same rule.
 352:    */
 353:   public boolean hasSameRules(TimeZone o)
 354:   {
 355:     if (this == o)
 356:       return true;
 357:     if (! (o instanceof ZoneInfo))
 358:       return false;
 359:     ZoneInfo zone = (ZoneInfo) o;
 360:     if (zone.hashCode() != hashCode() || rawOffset != zone.rawOffset)
 361:       return false;
 362:     if (! lastRule.equals(zone.lastRule))
 363:       return false;
 364:     // FIXME - compare transitions?
 365:     return true;
 366:   }
 367: 
 368:   /**
 369:    * Returns a string representation of this ZoneInfo object.
 370:    * @return a string representation of this ZoneInfo object.
 371:    */
 372:   public String toString()
 373:   {
 374:     return getClass().getName() + "[" + "id=" + getID() + ",offset="
 375:            + rawOffset + ",transitions=" + transitions.length
 376:            + ",useDaylight=" + useDaylight
 377:            + (useDaylight ? (",dstSavings=" + dstSavings) : "")
 378:            + ",lastRule=" + lastRule.toString() + "]";
 379:   }
 380: 
 381:   /**
 382:    * Reads zic(8) compiled timezone data file from file
 383:    * and returns a TimeZone class describing it, either
 384:    * SimpleTimeZone or ZoneInfo depending on whether
 385:    * it can be described by SimpleTimeZone rule or not.
 386:    */
 387:   public static TimeZone readTZFile(String id, String file)
 388:   {
 389:     DataInputStream dis = null;
 390:     try
 391:       {
 392:         FileInputStream fis = new FileInputStream(file);
 393:         BufferedInputStream bis = new BufferedInputStream(fis);
 394:         dis = new DataInputStream(bis);
 395: 
 396:         // Make sure we are reading a tzfile.
 397:         byte[] tzif = new byte[5];
 398:         dis.readFully(tzif);
 399:         int tzif2 = 4;
 400:         if (tzif[0] == 'T' && tzif[1] == 'Z'
 401:             && tzif[2] == 'i' && tzif[3] == 'f')
 402:           {
 403:             if (tzif[4] >= '2')
 404:               tzif2 = 8;
 405:             // Reserved bytes
 406:             skipFully(dis, 16 - 1);
 407:           }
 408:         else
 409:           // Darwin has tzdata files that don't start with the TZif marker
 410:           skipFully(dis, 16 - 5);
 411: 
 412:         int ttisgmtcnt = dis.readInt();
 413:         int ttisstdcnt = dis.readInt();
 414:         int leapcnt = dis.readInt();
 415:         int timecnt = dis.readInt();
 416:         int typecnt = dis.readInt();
 417:         int charcnt = dis.readInt();
 418:         if (tzif2 == 8)
 419:           {
 420:             skipFully(dis, timecnt * (4 + 1) + typecnt * (4 + 1 + 1) + charcnt
 421:                            + leapcnt * (4 + 4) + ttisgmtcnt + ttisstdcnt);
 422: 
 423:             dis.readFully(tzif);
 424:             if (tzif[0] != 'T' || tzif[1] != 'Z' || tzif[2] != 'i'
 425:                 || tzif[3] != 'f' || tzif[4] < '2')
 426:               return null;
 427: 
 428:             // Reserved bytes
 429:             skipFully(dis, 16 - 1);
 430:             ttisgmtcnt = dis.readInt();
 431:             ttisstdcnt = dis.readInt();
 432:             leapcnt = dis.readInt();
 433:             timecnt = dis.readInt();
 434:             typecnt = dis.readInt();
 435:             charcnt = dis.readInt();
 436:           }
 437: 
 438:         // Sanity checks
 439:         if (typecnt <= 0 || timecnt < 0 || charcnt < 0
 440:             || leapcnt < 0 || ttisgmtcnt < 0 || ttisstdcnt < 0
 441:             || ttisgmtcnt > typecnt || ttisstdcnt > typecnt)
 442:           return null;
 443: 
 444:         // Transition times
 445:         long[] times = new long[timecnt];
 446:         for (int i = 0; i < timecnt; i++)
 447:           if (tzif2 == 8)
 448:             times[i] = dis.readLong();
 449:           else
 450:             times[i] = (long) dis.readInt();
 451: 
 452:         // Transition types
 453:         int[] types = new int[timecnt];
 454:         for (int i = 0; i < timecnt; i++)
 455:           {
 456:             types[i] = dis.readByte();
 457:             if (types[i] < 0)
 458:               types[i] += 256;
 459:             if (types[i] >= typecnt)
 460:               return null;
 461:           }
 462: 
 463:         // Types
 464:         int[] offsets = new int[typecnt];
 465:         int[] typeflags = new int[typecnt];
 466:         for (int i = 0; i < typecnt; i++)
 467:           {
 468:             offsets[i] = dis.readInt();
 469:             if (offsets[i] >= IS_DST / 2 || offsets[i] <= -IS_DST / 2)
 470:               return null;
 471:             int dst = dis.readByte();
 472:             int abbrind = dis.readByte();
 473:             if (abbrind < 0)
 474:               abbrind += 256;
 475:             if (abbrind >= charcnt)
 476:               return null;
 477:             typeflags[i] = (dst != 0 ? (1 << 8) : 0) + abbrind;
 478:           }
 479: 
 480:         // Abbrev names
 481:         byte[] names = new byte[charcnt];
 482:         dis.readFully(names);
 483: 
 484:         // Leap transitions, for now ignore
 485:         skipFully(dis, leapcnt * (tzif2 + 4) + ttisstdcnt + ttisgmtcnt);
 486: 
 487:         // tzIf2 format has optional POSIX TZ env string
 488:         String tzstr = null;
 489:         if (tzif2 == 8 && dis.readByte() == '\n')
 490:           {
 491:             tzstr = dis.readLine();
 492:             if (tzstr.length() <= 0)
 493:               tzstr = null;
 494:           }
 495: 
 496:         // Get std/dst_offset and dst/non-dst time zone names.
 497:         int std_ind = -1;
 498:         int dst_ind = -1;
 499:         if (timecnt == 0)
 500:           std_ind = 0;
 501:         else
 502:           for (int i = timecnt - 1; i >= 0; i--)
 503:             {
 504:               if (std_ind == -1 && (typeflags[types[i]] & (1 << 8)) == 0)
 505:                 std_ind = types[i];
 506:               else if (dst_ind == -1 && (typeflags[types[i]] & (1 << 8)) != 0)
 507:                 dst_ind = types[i];
 508:               if (dst_ind != -1 && std_ind != -1)
 509:                 break;
 510:             }
 511: 
 512:         if (std_ind == -1)
 513:           return null;
 514: 
 515:         int j = typeflags[std_ind] & 255;
 516:         while (j < charcnt && names[j] != 0)
 517:           j++;
 518:         String std_zonename = new String(names, typeflags[std_ind] & 255,
 519:                                          j - (typeflags[std_ind] & 255),
 520:                                          "ASCII");
 521: 
 522:         String dst_zonename = "";
 523:         if (dst_ind != -1)
 524:           {
 525:             j = typeflags[dst_ind] & 255;
 526:             while (j < charcnt && names[j] != 0)
 527:               j++;
 528:             dst_zonename = new String(names, typeflags[dst_ind] & 255,
 529:                                       j - (typeflags[dst_ind] & 255), "ASCII");
 530:           }
 531: 
 532:         // Only use gmt offset when necessary.
 533:         // Also special case GMT+/- timezones.
 534:         String std_offset_string = "";
 535:         String dst_offset_string = "";
 536:         if (tzstr == null
 537:             && (dst_ind != -1
 538:                 || (offsets[std_ind] != 0
 539:                     && !std_zonename.startsWith("GMT+")
 540:                     && !std_zonename.startsWith("GMT-"))))
 541:           {
 542:             std_offset_string = Integer.toString(-offsets[std_ind] / 3600);
 543:             int seconds = -offsets[std_ind] % 3600;
 544:             if (seconds != 0)
 545:               {
 546:                 if (seconds < 0)
 547:                   seconds *= -1;
 548:                 if (seconds < 600)
 549:                   std_offset_string += ":0" + Integer.toString(seconds / 60);
 550:                 else
 551:                   std_offset_string += ":" + Integer.toString(seconds / 60);
 552:                 seconds = seconds % 60;
 553:                 if (seconds >= 10)
 554:                   std_offset_string += ":" + Integer.toString(seconds);
 555:                 else if (seconds > 0)
 556:                   std_offset_string += ":0" + Integer.toString(seconds);
 557:               }
 558: 
 559:             if (dst_ind != -1 && offsets[dst_ind] != offsets[std_ind] + 3600)
 560:               {
 561:                 dst_offset_string = Integer.toString(-offsets[dst_ind] / 3600);
 562:                 seconds = -offsets[dst_ind] % 3600;
 563:                 if (seconds != 0)
 564:                   {
 565:                     if (seconds < 0)
 566:                       seconds *= -1;
 567:                     if (seconds < 600)
 568:                       dst_offset_string
 569:                         += ":0" + Integer.toString(seconds / 60);
 570:                     else
 571:                       dst_offset_string
 572:                         += ":" + Integer.toString(seconds / 60);
 573:                     seconds = seconds % 60;
 574:                     if (seconds >= 10)
 575:                       dst_offset_string += ":" + Integer.toString(seconds);
 576:                     else if (seconds > 0)
 577:                       dst_offset_string += ":0" + Integer.toString(seconds);
 578:                   }
 579:               }
 580:           }
 581: 
 582:         /*
 583:          * If no tzIf2 POSIX TZ string is available and the timezone
 584:          * uses DST, try to guess the last rule by trying to make
 585:          * sense from transitions at 5 years in the future and onwards.
 586:          * tzdata actually uses only 3 forms of rules:
 587:          * fixed date within a month, e.g. change on April, 5th
 588:          * 1st weekday on or after Nth: change on Sun>=15 in April
 589:          * last weekday in a month: change on lastSun in April
 590:          */
 591:         String[] change_spec = { null, null };
 592:         if (tzstr == null && dst_ind != -1 && timecnt > 10)
 593:           {
 594:             long nowPlus5y = System.currentTimeMillis() / 1000
 595:                              + 5 * 365 * 86400;
 596:             int first;
 597: 
 598:             for (first = timecnt - 1; first >= 0; first--)
 599:               if (times[first] < nowPlus5y
 600:                   || (types[first] != std_ind && types[first] != dst_ind)
 601:                   || types[first] != types[timecnt - 2 + ((first ^ timecnt) & 1)])
 602:                 break;
 603:             first++;
 604: 
 605:             if (timecnt - first >= 10 && types[timecnt - 1] != types[timecnt - 2])
 606:               {
 607:                 GregorianCalendar cal
 608:                   = new GregorianCalendar(new SimpleTimeZone(0, "GMT"));
 609: 
 610:                 int[] values = new int[2 * 11];
 611:                 int i;
 612:                 for (i = timecnt - 1; i >= first; i--)
 613:                   {
 614:                     int base = (i % 2) * 11;
 615:                     int offset = offsets[types[i > first ? i - 1 : i + 1]];
 616:                     cal.setTimeInMillis((times[i] + offset) * 1000);
 617:                     if (i >= timecnt - 2)
 618:                       {
 619:                         values[base + 0] = cal.get(Calendar.YEAR);
 620:                         values[base + 1] = cal.get(Calendar.MONTH);
 621:                         values[base + 2] = cal.get(Calendar.DAY_OF_MONTH);
 622:                         values[base + 3]
 623:                           = cal.getActualMaximum(Calendar.DAY_OF_MONTH);
 624:                         values[base + 4] = cal.get(Calendar.DAY_OF_WEEK);
 625:                         values[base + 5] = cal.get(Calendar.HOUR_OF_DAY);
 626:                         values[base + 6] = cal.get(Calendar.MINUTE);
 627:                         values[base + 7] = cal.get(Calendar.SECOND);
 628:                         values[base + 8] = values[base + 2]; // Range start
 629:                         values[base + 9] = values[base + 2]; // Range end
 630:                         values[base + 10] = 0; // Determined type
 631:                       }
 632:                     else
 633:                       {
 634:                         int year = cal.get(Calendar.YEAR);
 635:                         int month = cal.get(Calendar.MONTH);
 636:                         int day_of_month = cal.get(Calendar.DAY_OF_MONTH);
 637:                         int month_days
 638:                           = cal.getActualMaximum(Calendar.DAY_OF_MONTH);
 639:                         int day_of_week = cal.get(Calendar.DAY_OF_WEEK);
 640:                         int hour = cal.get(Calendar.HOUR_OF_DAY);
 641:                         int minute = cal.get(Calendar.MINUTE);
 642:                         int second = cal.get(Calendar.SECOND);
 643:                         if (year != values[base + 0] - 1
 644:                             || month != values[base + 1]
 645:                             || hour != values[base + 5]
 646:                             || minute != values[base + 6]
 647:                             || second != values[base + 7])
 648:                           break;
 649:                         if (day_of_week == values[base + 4])
 650:                           {
 651:                             // Either a Sun>=8 or lastSun rule.
 652:                             if (day_of_month < values[base + 8])
 653:                               values[base + 8] = day_of_month;
 654:                             if (day_of_month > values[base + 9])
 655:                               values[base + 9] = day_of_month;
 656:                             if (values[base + 10] < 0)
 657:                               break;
 658:                             if (values[base + 10] == 0)
 659:                               {
 660:                                 values[base + 10] = 1;
 661:                                 // If day of month > 28, this is
 662:                                 // certainly lastSun rule.
 663:                                 if (values[base + 2] > 28)
 664:                                   values[base + 2] = 3;
 665:                                 // If day of month isn't in the last
 666:                                 // week, it can't be lastSun rule.
 667:                                 else if (values[base + 2]
 668:                                          <= values[base + 3] - 7)
 669:                                   values[base + 3] = 2;
 670:                               }
 671:                             if (values[base + 10] == 1)
 672:                               {
 673:                                 // If day of month is > 28, this is
 674:                                 // certainly lastSun rule.
 675:                                 if (day_of_month > 28)
 676:                                   values[base + 10] = 3;
 677:                                 // If day of month isn't in the last
 678:                                 // week, it can't be lastSun rule.
 679:                                 else if (day_of_month <= month_days - 7)
 680:                                   values[base + 10] = 2;
 681:                               }
 682:                             else if ((values[base + 10] == 2
 683:                                       && day_of_month > 28)
 684:                                      || (values[base + 10] == 3
 685:                                          && day_of_month <= month_days - 7))
 686:                               break;
 687:                           }
 688:                         else
 689:                           {
 690:                             // Must be fixed day in month rule.
 691:                             if (day_of_month != values[base + 2]
 692:                                 || values[base + 10] > 0)
 693:                               break;
 694:                             values[base + 4] = day_of_week;
 695:                             values[base + 10] = -1;
 696:                           }
 697:                         values[base + 0] -= 1;
 698:                       }
 699:                   }
 700: 
 701:                 if (i < first)
 702:                   {
 703:                     for (i = 0; i < 2; i++)
 704:                       {
 705:                         int base = 11 * i;
 706:                         if (values[base + 10] == 0)
 707:                           continue;
 708:                         if (values[base + 10] == -1)
 709:                           {
 710:                             int[] dayCount
 711:                               = { 0, 31, 59, 90, 120, 151,
 712:                                   181, 212, 243, 273, 304, 334 };
 713:                             int d = dayCount[values[base + 1]
 714:                                              - Calendar.JANUARY];
 715:                             d += values[base + 2];
 716:                             change_spec[i] = ",J" + Integer.toString(d);
 717:                           }
 718:                         else if (values[base + 10] == 2)
 719:                           {
 720:                             // If we haven't seen all days of the week,
 721:                             // we can't be sure what the rule really is.
 722:                             if (values[base + 8] + 6 != values[base + 9])
 723:                               continue;
 724: 
 725:                             int d;
 726:                             d = values[base + 1] - Calendar.JANUARY + 1;
 727:                             // E.g. Sun >= 5 is not representable in POSIX
 728:                             // TZ env string, use ",Am.n.d" extension
 729:                             // where m is month 1 .. 12, n is the date on
 730:                             // or after which it happens and d is day
 731:                             // of the week, 0 .. 6.  So Sun >= 5 in April
 732:                             // is ",A4.5.0".
 733:                             if ((values[base + 8] % 7) == 1)
 734:                               {
 735:                                 change_spec[i] = ",M" + Integer.toString(d);
 736:                                 d = (values[base + 8] + 6) / 7;
 737:                               }
 738:                             else
 739:                               {
 740:                                 change_spec[i] = ",A" + Integer.toString(d);
 741:                                 d = values[base + 8];
 742:                               }
 743:                             change_spec[i] += "." + Integer.toString(d);
 744:                             d = values[base + 4] - Calendar.SUNDAY;
 745:                             change_spec[i] += "." + Integer.toString(d);
 746:                           }
 747:                         else
 748:                           {
 749:                             // If we don't know whether this is lastSun or
 750:                             // Sun >= 22 rule.  That can be either because
 751:                             // there was insufficient number of
 752:                             // transitions, or February, where it is quite
 753:                             // probable we haven't seen any 29th dates.
 754:                             // For February, assume lastSun rule, otherwise
 755:                             // punt.
 756:                             if (values[base + 10] == 1
 757:                                 && values[base + 1] != Calendar.FEBRUARY)
 758:                               continue;
 759: 
 760:                             int d;
 761:                             d = values[base + 1] - Calendar.JANUARY + 1;
 762:                             change_spec[i] = ",M" + Integer.toString(d);
 763:                             d = values[base + 4] - Calendar.SUNDAY;
 764:                             change_spec[i] += ".5." + Integer.toString(d);
 765:                           }
 766: 
 767:                         // Don't add time specification if time is
 768:                         // 02:00:00.
 769:                         if (values[base + 5] != 2
 770:                             || values[base + 6] != 0
 771:                             || values[base + 7] != 0)
 772:                           {
 773:                             int d = values[base + 5];
 774:                             change_spec[i] += "/" + Integer.toString(d);
 775:                             if (values[base + 6] != 0 || values[base + 7] != 0)
 776:                               {
 777:                                 d = values[base + 6];
 778:                                 if (d < 10)
 779:                                   change_spec[i]
 780:                                     += ":0" + Integer.toString(d);
 781:                                 else
 782:                                   change_spec[i] += ":" + Integer.toString(d);
 783:                                 d = values[base + 7];
 784:                                 if (d >= 10)
 785:                                    change_spec[i]
 786:                                      += ":" + Integer.toString(d);
 787:                                 else if (d > 0)
 788:                                   change_spec[i]
 789:                                     += ":0" + Integer.toString(d);
 790:                               }
 791:                           }
 792:                       }
 793:                     if (types[(timecnt - 1) & -2] == std_ind)
 794:                       {
 795:                         String tmp = change_spec[0];
 796:                         change_spec[0] = change_spec[1];
 797:                         change_spec[1] = tmp;
 798:                       }
 799:                   }
 800:               }
 801:           }
 802: 
 803:         if (tzstr == null)
 804:           {
 805:             tzstr = std_zonename + std_offset_string;
 806:             if (change_spec[0] != null && change_spec[1] != null)
 807:               tzstr += dst_zonename + dst_offset_string
 808:                        + change_spec[0] + change_spec[1];
 809:           }
 810: 
 811:         if (timecnt == 0)
 812:           return new SimpleTimeZone(offsets[std_ind] * 1000,
 813:                                     id != null ? id : tzstr);
 814: 
 815:         SimpleTimeZone endRule = createLastRule(tzstr);
 816:         if (endRule == null)
 817:           return null;
 818: 
 819:         /* Finally adjust the times array into the form the constructor
 820:          * expects.  times[0] is special, the offset and DST flag there
 821:          * are for all times before that transition.  Use the first non-DST
 822:          * type.  For all other transitions, the data file has the type
 823:          * (<offset, isdst, zonename>) for the time interval starting
 824:          */
 825:         for (int i = 0; i < typecnt; i++)
 826:           if ((typeflags[i] & (1 << 8)) == 0)
 827:             {
 828:               times[0] = (times[0] << SECS_SHIFT) | (offsets[i] & OFFSET_MASK);
 829:               break;
 830:             }
 831: 
 832:         for (int i = 1; i < timecnt; i++)
 833:           times[i] = (times[i] << SECS_SHIFT)
 834:                      | (offsets[types[i - 1]] & OFFSET_MASK)
 835:                      | ((typeflags[types[i - 1]] & (1 << 8)) != 0 ? IS_DST : 0);
 836: 
 837:         return new ZoneInfo(offsets[std_ind] * 1000, id != null ? id : tzstr,
 838:                             times, endRule);
 839:       }
 840:     catch (IOException ioe)
 841:       {
 842:         // Parse error, not a proper tzfile.
 843:         return null;
 844:       }
 845:     finally
 846:       {
 847:         try
 848:           {
 849:             if (dis != null)
 850:               dis.close();
 851:           }
 852:         catch(IOException ioe)
 853:           {
 854:             // Error while close, nothing we can do.
 855:           }
 856:       }
 857:   }
 858: 
 859:   /**
 860:    * Skips the requested number of bytes in the given InputStream.
 861:    * Throws EOFException if not enough bytes could be skipped.
 862:    * Negative numbers of bytes to skip are ignored.
 863:    */
 864:   private static void skipFully(InputStream is, long l) throws IOException
 865:   {
 866:     while (l > 0)
 867:       {
 868:         long k = is.skip(l);
 869:         if (k <= 0)
 870:           throw new EOFException();
 871:         l -= k;
 872:       }
 873:   }
 874: 
 875:   /**
 876:    * Create a SimpleTimeZone from a POSIX TZ environment string,
 877:    * see http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap08.html
 878:    * for details.
 879:    * It supports also an extension, where Am.n.d rule (m 1 .. 12, n 1 .. 25, d
 880:    * 0 .. 6) describes day of week d on or after nth day of month m.
 881:    * Say A4.5.0 is Sun>=5 in April.
 882:    */
 883:   private static SimpleTimeZone createLastRule(String tzstr)
 884:   {
 885:     String stdName = null;
 886:     int stdOffs;
 887:     int dstOffs;
 888:     try
 889:       {
 890:         int idLength = tzstr.length();
 891: 
 892:         int index = 0;
 893:         int prevIndex;
 894:         char c;
 895: 
 896:         // get std
 897:         do
 898:           c = tzstr.charAt(index);
 899:         while (c != '+' && c != '-' && c != ',' && c != ':'
 900:                && ! Character.isDigit(c) && c != '\0' && ++index < idLength);
 901: 
 902:         if (index >= idLength)
 903:           return new SimpleTimeZone(0, tzstr);
 904: 
 905:         stdName = tzstr.substring(0, index);
 906:         prevIndex = index;
 907: 
 908:         // get the std offset
 909:         do
 910:           c = tzstr.charAt(index++);
 911:         while ((c == '-' || c == '+' || c == ':' || Character.isDigit(c))
 912:                && index < idLength);
 913:         if (index < idLength)
 914:           index--;
 915: 
 916:         { // convert the dst string to a millis number
 917:             String offset = tzstr.substring(prevIndex, index);
 918:             prevIndex = index;
 919: 
 920:             if (offset.charAt(0) == '+' || offset.charAt(0) == '-')
 921:               stdOffs = parseTime(offset.substring(1));
 922:             else
 923:               stdOffs = parseTime(offset);
 924: 
 925:             if (offset.charAt(0) == '-')
 926:               stdOffs = -stdOffs;
 927: 
 928:             // TZ timezone offsets are positive when WEST of the meridian.
 929:             stdOffs = -stdOffs;
 930:         }
 931: 
 932:         // Done yet? (Format: std offset)
 933:         if (index >= idLength)
 934:           return new SimpleTimeZone(stdOffs, stdName);
 935: 
 936:         // get dst
 937:         do
 938:           c = tzstr.charAt(index);
 939:         while (c != '+' && c != '-' && c != ',' && c != ':'
 940:                && ! Character.isDigit(c) && c != '\0' && ++index < idLength);
 941: 
 942:         // Done yet? (Format: std offset dst)
 943:         if (index >= idLength)
 944:           return new SimpleTimeZone(stdOffs, stdName);
 945: 
 946:         // get the dst offset
 947:         prevIndex = index;
 948:         do
 949:           c = tzstr.charAt(index++);
 950:         while ((c == '-' || c == '+' || c == ':' || Character.isDigit(c))
 951:                && index < idLength);
 952:         if (index < idLength)
 953:           index--;
 954: 
 955:         if (index == prevIndex && (c == ',' || c == ';'))
 956:           {
 957:             // Missing dst offset defaults to one hour ahead of standard
 958:             // time.
 959:             dstOffs = stdOffs + 60 * 60 * 1000;
 960:           }
 961:         else
 962:           { // convert the dst string to a millis number
 963:             String offset = tzstr.substring(prevIndex, index);
 964:             prevIndex = index;
 965: 
 966:             if (offset.charAt(0) == '+' || offset.charAt(0) == '-')
 967:               dstOffs = parseTime(offset.substring(1));
 968:             else
 969:               dstOffs = parseTime(offset);
 970: 
 971:             if (offset.charAt(0) == '-')
 972:               dstOffs = -dstOffs;
 973: 
 974:             // TZ timezone offsets are positive when WEST of the meridian.
 975:             dstOffs = -dstOffs;
 976:           }
 977: 
 978:         // Done yet? (Format: std offset dst offset)
 979:         if (index >= idLength)
 980:           return new SimpleTimeZone(stdOffs, stdName);
 981: 
 982:         // get the DST rule
 983:         if (tzstr.charAt(index) == ','
 984:             || tzstr.charAt(index) == ';')
 985:           {
 986:             index++;
 987:             int offs = index;
 988:             while (tzstr.charAt(index) != ','
 989:                    && tzstr.charAt(index) != ';')
 990:               index++;
 991:             String startTime = tzstr.substring(offs, index);
 992:             index++;
 993:             String endTime = tzstr.substring(index);
 994: 
 995:             index = startTime.indexOf('/');
 996:             int startMillis;
 997:             int endMillis;
 998:             String startDate;
 999:             String endDate;
1000:             if (index != -1)
1001:               {
1002:                 startDate = startTime.substring(0, index);
1003:                 startMillis = parseTime(startTime.substring(index + 1));
1004:               }
1005:             else
1006:               {
1007:                 startDate = startTime;
1008:                 // if time isn't given, default to 2:00:00 AM.
1009:                 startMillis = 2 * 60 * 60 * 1000;
1010:               }
1011:             index = endTime.indexOf('/');
1012:             if (index != -1)
1013:               {
1014:                 endDate = endTime.substring(0, index);
1015:                 endMillis = parseTime(endTime.substring(index + 1));
1016:               }
1017:             else
1018:               {
1019:                 endDate = endTime;
1020:                 // if time isn't given, default to 2:00:00 AM.
1021:                 endMillis = 2 * 60 * 60 * 1000;
1022:               }
1023: 
1024:             int[] start = getDateParams(startDate);
1025:             int[] end = getDateParams(endDate);
1026:             return new SimpleTimeZone(stdOffs, tzstr, start[0], start[1],
1027:                                       start[2], startMillis, end[0], end[1],
1028:                                       end[2], endMillis, (dstOffs - stdOffs));
1029:           }
1030:       }
1031: 
1032:     catch (IndexOutOfBoundsException _)
1033:       {
1034:       }
1035:     catch (NumberFormatException _)
1036:       {
1037:       }
1038: 
1039:     return null;
1040:   }
1041: 
1042:   /**
1043:    * Parses and returns the params for a POSIX TZ date field,
1044:    * in the format int[]{ month, day, dayOfWeek }, following the
1045:    * SimpleTimeZone constructor rules.
1046:    */
1047:   private static int[] getDateParams(String date)
1048:   {
1049:     int[] dayCount = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 };
1050:     int month;
1051:     int type = 0;
1052: 
1053:     if (date.charAt(0) == 'M' || date.charAt(0) == 'm')
1054:       type = 1;
1055:     else if (date.charAt(0) == 'A' || date.charAt(0) == 'a')
1056:       type = 2;
1057: 
1058:     if (type > 0)
1059:       {
1060:         int day;
1061: 
1062:         // Month, week of month, day of week
1063:         // "Mm.w.d".  d is between 0 (Sunday) and 6.  Week w is
1064:         // between 1 and 5; Week 1 is the first week in which day d
1065:         // occurs and Week 5 specifies the last d day in the month.
1066:         // Month m is between 1 and 12.
1067: 
1068:         // Month, day of month, day of week
1069:         // ZoneInfo extension, not in POSIX
1070:         // "Am.n.d".  d is between 0 (Sunday) and 6.  Day of month n is
1071:         // between 1 and 25.  Month m is between 1 and 12.
1072: 
1073:         month = Integer.parseInt(date.substring(1, date.indexOf('.')));
1074:         int week = Integer.parseInt(date.substring(date.indexOf('.') + 1,
1075:                                                    date.lastIndexOf('.')));
1076:         int dayOfWeek = Integer.parseInt(date.substring(date.lastIndexOf('.')
1077:                                                         + 1));
1078:         dayOfWeek++; // Java day of week is one-based, Sunday is first day.
1079: 
1080:         if (type == 2)
1081:           {
1082:             day = week;
1083:             dayOfWeek = -dayOfWeek;
1084:           }
1085:         else if (week == 5)
1086:           day = -1; // last day of month is -1 in java, 5 in TZ
1087:         else
1088:           {
1089:             // First day of week starting on or after.  For example,
1090:             // to specify the second Sunday of April, set month to
1091:             // APRIL, day-of-month to 8, and day-of-week to -SUNDAY.
1092:             day = (week - 1) * 7 + 1;
1093:             dayOfWeek = -dayOfWeek;
1094:           }
1095: 
1096:         month--; // Java month is zero-based.
1097:         return new int[] { month, day, dayOfWeek };
1098:       }
1099: 
1100:     // julian day, either zero-based 0<=n<=365 (incl feb 29)
1101:     // or one-based 1<=n<=365 (no feb 29)
1102:     int julianDay; // Julian day
1103: 
1104:     if (date.charAt(0) != 'J' || date.charAt(0) != 'j')
1105:       {
1106:         julianDay = Integer.parseInt(date.substring(1));
1107:         julianDay++; // make 1-based
1108:         // Adjust day count to include feb 29.
1109:         dayCount = new int[]
1110:                    {
1111:                      0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335
1112:                    };
1113:       }
1114:     else
1115:       // 1-based julian day
1116:       julianDay = Integer.parseInt(date);
1117: 
1118:     int i = 11;
1119:     while (i > 0)
1120:       if (dayCount[i] < julianDay)
1121:         break;
1122:       else
1123:         i--;
1124:     julianDay -= dayCount[i];
1125:     month = i;
1126:     return new int[] { month, julianDay, 0 };
1127:   }
1128: 
1129:   /**
1130:    * Parses a time field hh[:mm[:ss]], returning the result
1131:    * in milliseconds. No leading sign.
1132:    */
1133:   private static int parseTime(String time)
1134:   {
1135:     int millis = 0;
1136:     int i = 0;
1137: 
1138:     while (i < time.length())
1139:       if (time.charAt(i) == ':')
1140:         break;
1141:       else
1142:         i++;
1143:     millis = 60 * 60 * 1000 * Integer.parseInt(time.substring(0, i));
1144:     if (i >= time.length())
1145:       return millis;
1146: 
1147:     int iprev = ++i;
1148:     while (i < time.length())
1149:       if (time.charAt(i) == ':')
1150:         break;
1151:       else
1152:         i++;
1153:     millis += 60 * 1000 * Integer.parseInt(time.substring(iprev, i));
1154:     if (i >= time.length())
1155:       return millis;
1156: 
1157:     millis += 1000 * Integer.parseInt(time.substring(++i));
1158:     return millis;
1159:   }
1160: }