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: }