001: /*
002: *******************************************************************************
003: * Copyright (C) 2005-2006, International Business Machines Corporation and *
004: * others. All Rights Reserved. *
005: *******************************************************************************
006: */
007: package com.ibm.icu.impl;
008:
009: import java.util.Date;
010:
011: import com.ibm.icu.util.Calendar;
012: import com.ibm.icu.util.GregorianCalendar;
013: import com.ibm.icu.util.SimpleTimeZone;
014: import com.ibm.icu.util.TimeZone;
015:
016: /**
017: * A time zone based on the Olson database. Olson time zones change
018: * behavior over time. The raw offset, rules, presence or absence of
019: * daylight savings time, and even the daylight savings amount can all
020: * vary.
021: *
022: * This class uses a resource bundle named "zoneinfo". Zoneinfo is a
023: * table containing different kinds of resources. In several places,
024: * zones are referred to using integers. A zone's integer is a number
025: * from 0..n-1, where n is the number of zones, with the zones sorted
026: * in lexicographic order.
027: *
028: * 1. Zones. These have keys corresponding to the Olson IDs, e.g.,
029: * "Asia/Shanghai". Each resource describes the behavior of the given
030: * zone. Zones come in several formats, which are differentiated
031: * based on length.
032: *
033: * a. Alias (int, length 1). An alias zone is an int resource. The
034: * integer is the zone number of the target zone. The key of this
035: * resource is an alternate name for the target zone. Aliases
036: * represent Olson links and ICU compatibility IDs.
037: *
038: * b. Simple zone (array, length 3). The three subelements are:
039: *
040: * i. An intvector of transitions. These are given in epoch
041: * seconds. This may be an empty invector (length 0). If the
042: * transtions list is empty, then the zone's behavior is fixed and
043: * given by the offset list, which will contain exactly one pair.
044: * Otherwise each transtion indicates a time after which (inclusive)
045: * the associated offset pair is in effect.
046: *
047: * ii. An intvector of offsets. These are in pairs of raw offset /
048: * DST offset, in units of seconds. There will be at least one pair
049: * (length >= 2 && length % 2 == 0).
050: *
051: * iii. A binary resource. This is of the same length as the
052: * transitions vector, so length may be zero. Each unsigned byte
053: * corresponds to one transition, and has a value of 0..n-1, where n
054: * is the number of pairs in the offset vector. This forms a map
055: * between transitions and offset pairs.
056: *
057: * c. Simple zone with aliases (array, length 4). This is like a
058: * simple zone, but also contains a fourth element:
059: *
060: * iv. An intvector of aliases. This list includes this zone
061: * itself, and lists all aliases of this zone.
062: *
063: * d. Complex zone (array, length 5). This is like a simple zone,
064: * but contains two more elements:
065: *
066: * iv. A string, giving the name of a rule. This is the "final
067: * rule", which governs the zone's behavior beginning in the "final
068: * year". The rule ID is given without leading underscore, e.g.,
069: * "EU".
070: *
071: * v. An intvector of length 2, containing the raw offset for the
072: * final rule (in seconds), and the final year. The final rule
073: * takes effect for years >= the final year.
074: *
075: * e. Complex zone with aliases (array, length 6). This is like a
076: * complex zone, but also contains a sixth element:
077: *
078: * vi. An intvector of aliases. This list includes this zone
079: * itself, and lists all aliases of this zone.
080: *
081: * 2. Rules. These have keys corresponding to the Olson rule IDs,
082: * with an underscore prepended, e.g., "_EU". Each resource describes
083: * the behavior of the given rule using an intvector, containing the
084: * onset list, the cessation list, and the DST savings. The onset and
085: * cessation lists consist of the month, dowim, dow, time, and time
086: * mode. The end result is that the 11 integers describing the rule
087: * can be passed directly into the SimpleTimeZone 13-argument
088: * constructor (the other two arguments will be the raw offset, taken
089: * from the complex zone element 5, and the ID string, which is not
090: * used), with the times and the DST savings multiplied by 1000 to
091: * scale from seconds to milliseconds.
092: *
093: * 3. Countries. These have keys corresponding to the 2-letter ISO
094: * country codes, with a percent sign prepended, e.g., "%US". Each
095: * resource is an intvector listing the zones associated with the
096: * given country. The special entry "%" corresponds to "no country",
097: * that is, the category of zones assigned to no country in the Olson
098: * DB.
099: *
100: * 4. Metadata. Metadata is stored under the key "_". It is an
101: * intvector of length three containing the number of zones resources,
102: * rule resources, and country resources. For the purposes of this
103: * count, the metadata entry itself is considered a rule resource,
104: * since its key begins with an underscore.
105: */
106: public class OlsonTimeZone extends TimeZone {
107:
108: // Generated by serialver from JDK 1.4.1_01
109: static final long serialVersionUID = -6281977362477515376L;
110:
111: private static final boolean ASSERT = false;
112:
113: /* (non-Javadoc)
114: * @see com.ibm.icu.util.TimeZone#getOffset(int, int, int, int, int, int)
115: */
116: public int getOffset(int era, int year, int month, int day,
117: int dayOfWeek, int milliseconds) {
118: if (month < Calendar.JANUARY || month > Calendar.DECEMBER) {
119: throw new IllegalArgumentException(
120: "Month is not in the legal range: " + month);
121: } else {
122: return getOffset(era, year, month, day, dayOfWeek,
123: milliseconds, MONTH_LENGTH[month
124: + (isLeapYear(year) ? 12 : 0)]);
125: }
126: }
127:
128: /**
129: * TimeZone API.
130: */
131: public int getOffset(int era, int year, int month, int dom,
132: int dow, int millis, int monthLength) {
133:
134: if ((era != GregorianCalendar.AD && era != GregorianCalendar.BC)
135: || month < Calendar.JANUARY
136: || month > Calendar.DECEMBER
137: || dom < 1
138: || dom > monthLength
139: || dow < Calendar.SUNDAY
140: || dow > Calendar.SATURDAY
141: || millis < 0
142: || millis >= MILLIS_PER_DAY
143: || monthLength < 28
144: || monthLength > 31) {
145: throw new IllegalArgumentException();
146: }
147:
148: if (era == GregorianCalendar.BC) {
149: year = -year;
150: }
151:
152: if (year > finalYear) { // [sic] >, not >=; see above
153: if (ASSERT)
154: Assert.assrt("(finalZone != null)", finalZone != null);
155: return finalZone.getOffset(era, year, month, dom, dow,
156: millis, monthLength);
157: }
158:
159: // Compute local epoch seconds from input fields
160: double time = fieldsToDay(year, month, dom) * SECONDS_PER_DAY
161: + Math.floor(millis / (double) MILLIS_PER_SECOND);
162:
163: int[] offsets = new int[2];
164: getHistoricalOffset(time, true, offsets);
165: return offsets[0] + offsets[1];
166: }
167:
168: /* (non-Javadoc)
169: * @see com.ibm.icu.util.TimeZone#setRawOffset(int)
170: */
171: public void setRawOffset(int offsetMillis) {
172: finalZone.setRawOffset(offsetMillis);
173: }
174:
175: public Object clone() {
176: OlsonTimeZone other = (OlsonTimeZone) super .clone();
177: if (finalZone != null) {
178: finalZone.setID(getID());
179: other.finalZone = (SimpleTimeZone) finalZone.clone();
180: }
181: other.transitionTimes = (int[]) transitionTimes.clone();
182: other.typeData = (byte[]) typeData.clone();
183: other.typeOffsets = (int[]) typeOffsets.clone();
184: return other;
185: }
186:
187: /**
188: * TimeZone API.
189: */
190: public void getOffset(long date, boolean local, int[] offsets) {
191: int rawoff, dstoff;
192: // The check against finalMillis will suffice most of the time, except
193: // for the case in which finalMillis == DBL_MAX, date == DBL_MAX,
194: // and finalZone == 0. For this case we add "&& finalZone != 0".
195: if (date >= finalMillis && finalZone != null) {
196: double[] doub = floorDivide(date, (double) MILLIS_PER_DAY);
197: double millis = doub[1];
198: double days = doub[0];
199: int[] temp = dayToFields(days);
200: int year = temp[0], month = temp[1], dom = temp[2], dow = temp[3];
201: rawoff = finalZone.getRawOffset();
202:
203: if (!local) {
204: // Adjust from GMT to local
205: date += rawoff;
206: doub = floorDivide(date, (double) MILLIS_PER_DAY);
207: double days2 = doub[0];
208: millis = doub[1];
209: if (days2 != days) {
210: temp = dayToFields(days2);
211: year = temp[0];
212: month = temp[1];
213: dom = temp[2];
214: dow = temp[3];
215: }
216: }
217:
218: dstoff = finalZone.getOffset(GregorianCalendar.AD, year,
219: month, dom, dow, (int) millis)
220: - rawoff;
221: offsets[0] = rawoff;
222: offsets[1] = dstoff;
223: return;
224: }
225:
226: double secs = Math.floor(date / MILLIS_PER_SECOND);
227: getHistoricalOffset(secs, local, offsets);
228: return;
229: }
230:
231: double[] floorDivide(double dividend, double divisor) {
232: double remainder;
233: double[] ret = new double[2];
234: // Only designed to work for positive divisors
235: if (ASSERT)
236: Assert.assrt("divisor > 0", divisor > 0);
237: double quotient = Math.floor(dividend / divisor);
238: remainder = dividend - (quotient * divisor);
239: // N.B. For certain large dividends, on certain platforms, there
240: // is a bug such that the quotient is off by one. If you doubt
241: // this to be true, set a breakpoint below and run cintltst.
242: if (remainder < 0 || remainder >= divisor) {
243: // E.g. 6.7317038241449352e+022 / 86400000.0 is wrong on my
244: // machine (too high by one). 4.1792057231752762e+024 /
245: // 86400000.0 is wrong the other way (too low).
246: double q = quotient;
247: quotient += (remainder < 0) ? -1 : +1;
248: if (q == quotient) {
249: // For quotients > ~2^53, we won't be able to add or
250: // subtract one, since the LSB of the mantissa will be >
251: // 2^0; that is, the exponent (base 2) will be larger than
252: // the length, in bits, of the mantissa. In that case, we
253: // can't give a correct answer, so we set the remainder to
254: // zero. This has the desired effect of making extreme
255: // values give back an approximate answer rather than
256: // crashing. For example, UDate values above a ~10^25
257: // might all have a time of midnight.
258: remainder = 0;
259: } else {
260: remainder = dividend - (quotient * divisor);
261: }
262: }
263: if (ASSERT)
264: Assert.assrt("0 <= remainder && remainder < divisor",
265: 0 <= remainder && remainder < divisor);
266: ret[0] = quotient;
267: ret[1] = remainder;
268: return ret;
269: }
270:
271: /* (non-Javadoc)
272: * @see com.ibm.icu.util.TimeZone#getRawOffset()
273: */
274: public int getRawOffset() {
275: int[] ret = new int[2];
276: getOffset(System.currentTimeMillis(), false, ret);
277: return ret[0];
278: }
279:
280: /* (non-Javadoc)
281: * @see com.ibm.icu.util.TimeZone#useDaylightTime()
282: */
283: public boolean useDaylightTime() {
284: // If DST was observed in 1942 (for example) but has never been
285: // observed from 1943 to the present, most clients will expect
286: // this method to return FALSE. This method determines whether
287: // DST is in use in the current year (at any point in the year)
288: // and returns TRUE if so.
289:
290: double[] dt = floorDivide(System.currentTimeMillis(),
291: (double) MILLIS_PER_DAY); // epoch days
292: int days = (int) dt[0];
293: int[] it = dayToFields(days);
294:
295: int year = it[0]; /*, month=it[1], dom=it[2], dow=it[3]*/
296: if (year > finalYear) { // [sic] >, not >=; see above
297: if (ASSERT)
298: Assert
299: .assrt(
300: "finalZone != null && finalZone.useDaylightTime()",
301: finalZone != null
302: && finalZone.useDaylightTime());
303: return true;
304: }
305:
306: // Find start of this year, and start of next year
307: int start = (int) fieldsToDay(year, 0, 1) * SECONDS_PER_DAY;
308: int limit = (int) fieldsToDay(year + 1, 0, 1) * SECONDS_PER_DAY;
309:
310: // Return TRUE if DST is observed at any time during the current
311: // year.
312: for (int i = 0; i < transitionCount; ++i) {
313: if (transitionTimes[i] >= limit) {
314: break;
315: }
316: if (transitionTimes[i] >= start
317: && dstOffset(typeData[i]) != 0) {
318: return true;
319: }
320: }
321: return false;
322: }
323:
324: /**
325: * TimeZone API
326: * Returns the amount of time to be added to local standard time
327: * to get local wall clock time.
328: */
329: public int getDSTSavings() {
330: if (finalZone != null) {
331: return finalZone.getDSTSavings();
332: }
333: return super .getDSTSavings();
334:
335: }
336:
337: /* (non-Javadoc)
338: * @see com.ibm.icu.util.TimeZone#inDaylightTime(java.util.Date)
339: */
340: public boolean inDaylightTime(Date date) {
341: int[] temp = new int[2];
342: getOffset(date.getTime(), false, temp);
343: return temp[1] != 0;
344: }
345:
346: /**
347: * Construct a GMT+0 zone with no transitions. This is done when a
348: * constructor fails so the resultant object is well-behaved.
349: */
350: private void constructEmpty() {
351: transitionCount = 0;
352: typeCount = 1;
353: transitionTimes = typeOffsets = new int[] { 0, 0 };
354: typeData = new byte[2];
355:
356: }
357:
358: /**
359: * Construct from a resource bundle
360: * @param top the top-level zoneinfo resource bundle. This is used
361: * to lookup the rule that `res' may refer to, if there is one.
362: * @param res the resource bundle of the zone to be constructed
363: */
364: public OlsonTimeZone(ICUResourceBundle top, ICUResourceBundle res) {
365: construct(top, res);
366: }
367:
368: private void construct(ICUResourceBundle top, ICUResourceBundle res) {
369:
370: if ((top == null || res == null)) {
371: throw new IllegalArgumentException();
372: }
373: if (DEBUG)
374: System.out.println("OlsonTimeZone(" + res.getKey() + ")");
375:
376: // TODO -- clean up -- Doesn't work if res points to an alias
377: // // TODO remove nonconst casts below when ures_* API is fixed
378: // setID(ures_getKey((UResourceBundle*) res)); // cast away const
379:
380: // Size 1 is an alias TO another zone (int)
381: // HOWEVER, the caller should dereference this and never pass it in to us
382: // Size 3 is a purely historical zone (no final rules)
383: // Size 4 is like size 3, but with an alias list at the end
384: // Size 5 is a hybrid zone, with historical and final elements
385: // Size 6 is like size 5, but with an alias list at the end
386: int size = res.getSize();
387: if (size < 3 || size > 6) {
388: // ec = U_INVALID_FORMAT_ERROR;
389: throw new IllegalArgumentException("Invalid Format");
390: }
391:
392: // Transitions list may be empty
393: ICUResourceBundle r = res.get(0);
394: transitionTimes = r.getIntVector();
395:
396: if ((transitionTimes.length < 0 || transitionTimes.length > 0x7FFF)) {
397: throw new IllegalArgumentException("Invalid Format");
398: }
399: transitionCount = (int) transitionTimes.length;
400:
401: // Type offsets list must be of even size, with size >= 2
402: r = res.get(1);
403: typeOffsets = r.getIntVector();
404: if ((typeOffsets.length < 2 || typeOffsets.length > 0x7FFE || ((typeOffsets.length & 1) != 0))) {
405: throw new IllegalArgumentException("Invalid Format");
406: }
407: typeCount = (int) typeOffsets.length >> 1;
408:
409: // Type data must be of the same size as the transitions list
410: r = res.get(2);
411: typeData = r.getBinary().array();
412: if (typeData.length != transitionCount) {
413: throw new IllegalArgumentException("Invalid Format");
414: }
415:
416: // Process final rule and data, if any
417: if (size >= 5) {
418: String ruleid = res.getString(3);
419: r = res.get(4);
420: int[] data = r.getIntVector();
421:
422: if (data != null && data.length == 2) {
423: int rawOffset = data[0] * MILLIS_PER_SECOND;
424: // Subtract one from the actual final year; we
425: // actually store final year - 1, and compare
426: // using > rather than >=. This allows us to use
427: // INT32_MAX as an exclusive upper limit for all
428: // years, including INT32_MAX.
429: if (ASSERT)
430: Assert.assrt("data[1] > Integer.MIN_VALUE",
431: data[1] > Integer.MIN_VALUE);
432: finalYear = data[1] - 1;
433: // Also compute the millis for Jan 1, 0:00 GMT of the
434: // finalYear. This reduces runtime computations.
435: finalMillis = fieldsToDay(data[1], 0, 1)
436: * TimeZone.MILLIS_PER_DAY;
437: //U_DEBUG_TZ_MSG(("zone%s|%s: {%d,%d}, finalYear%d, finalMillis%.1lf\n",
438: // zKey,rKey, data[0], data[1], finalYear, finalMillis));
439: r = loadRule(top, ruleid);
440:
441: // 3, 1, -1, 7200, 0, 9, -31, -1, 7200, 0, 3600
442: data = r.getIntVector();
443: if (data.length == 11) {
444: //U_DEBUG_TZ_MSG(("zone%s, rule%s: {%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d}", zKey, ures_getKey(r),
445: // data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7], data[8], data[9], data[10]));
446: finalZone = new SimpleTimeZone(rawOffset, "",
447: data[0], data[1], data[2], data[3]
448: * MILLIS_PER_SECOND, data[4],
449: data[5], data[6], data[7], data[8]
450: * MILLIS_PER_SECOND, data[9],
451: data[10] * MILLIS_PER_SECOND);
452: } else {
453: throw new IllegalArgumentException("Invalid Format");
454: }
455: } else {
456: throw new IllegalArgumentException("Invalid Format");
457: }
458: }
459: }
460:
461: public OlsonTimeZone() {
462: /*
463: *
464: finalYear = Integer.MAX_VALUE;
465: finalMillis = Double.MAX_VALUE;
466: finalZone = null;
467: */
468: constructEmpty();
469: }
470:
471: public OlsonTimeZone(String id) {
472: ICUResourceBundle top = (ICUResourceBundle) ICUResourceBundle
473: .getBundleInstance(ICUResourceBundle.ICU_BASE_NAME,
474: "zoneinfo",
475: ICUResourceBundle.ICU_DATA_CLASS_LOADER);
476: ICUResourceBundle res = ZoneMeta.openOlsonResource(id);
477: construct(top, res);
478: if (finalZone != null) {
479: finalZone.setID(id);
480: }
481: super .setID(id);
482: }
483:
484: public void setID(String id) {
485: if (finalZone != null) {
486: finalZone.setID(id);
487: }
488: super .setID(id);
489: }
490:
491: private static final int UNSIGNED_BYTE_MASK = 0xFF;
492:
493: private int getInt(byte val) {
494: return (int) (UNSIGNED_BYTE_MASK & val);
495: }
496:
497: private void getHistoricalOffset(double time, boolean local,
498: int[] offsets) {
499: if (transitionCount != 0) {
500: // Linear search from the end is the fastest approach, since
501: // most lookups will happen at/near the end.
502: int i = 0;
503: for (i = transitionCount - 1; i > 0; --i) {
504: int transition = transitionTimes[i];
505: if (local) {
506: int zoneOffsetPrev = zoneOffset(getInt(typeData[i - 1]));
507: int zoneOffsetCurr = zoneOffset(getInt(typeData[i]));
508: if (zoneOffsetPrev < zoneOffsetCurr) {
509: transition += zoneOffsetPrev;
510: } else {
511: transition += zoneOffsetCurr;
512: }
513: }
514: if (time >= transition) {
515: break;
516: }
517: }
518:
519: if (ASSERT)
520: Assert.assrt("i>=0 && i<transitionCount", i >= 0
521: && i < transitionCount);
522:
523: // Check invariants for GMT times; if these pass for GMT times
524: // the local logic should be working too.
525: if (ASSERT) {
526: Assert
527: .assrt(
528: "local || time < transitionTimes[0] || time >= transitionTimes[i]",
529: local || time < transitionTimes[0]
530: || time >= transitionTimes[i]);
531: Assert
532: .assrt(
533: "local || i == transitionCount-1 || time < transitionTimes[i+1]",
534: local
535: || i == transitionCount - 1
536: || time < transitionTimes[i + 1]);
537: }
538: if (i == 0) {
539: // Check if the given time is before the very first transition
540: int firstTransition = transitionTimes[0];
541: int initialRawOffset = rawOffset(getInt(typeData[0]));
542: if (local) {
543: firstTransition += initialRawOffset;
544: }
545: if (time >= firstTransition) {
546: // The given time is between the first and the second transition
547: offsets[0] = initialRawOffset * MILLIS_PER_SECOND;
548: offsets[1] = dstOffset(getInt(typeData[0]))
549: * MILLIS_PER_SECOND;
550: } else {
551: // The given time is before the first transition
552: offsets[0] = initialRawOffset * MILLIS_PER_SECOND;
553: offsets[1] = 0;
554: }
555: } else {
556: int index = getInt(typeData[i]);
557: offsets[0] = rawOffset(index) * MILLIS_PER_SECOND;
558: offsets[1] = dstOffset(index) * MILLIS_PER_SECOND;
559: }
560: } else {
561: // No transitions, single pair of offsets only
562: offsets[0] = rawOffset(0) * MILLIS_PER_SECOND;
563: offsets[1] = dstOffset(0) * MILLIS_PER_SECOND;
564: }
565: }
566:
567: private int zoneOffset(int index) {
568: index = index << 1;
569: return typeOffsets[index] + typeOffsets[index + 1];
570: }
571:
572: private int rawOffset(int index) {
573: return typeOffsets[(int) (index << 1)];
574: }
575:
576: private int dstOffset(int index) {
577: return typeOffsets[(int) ((index << 1) + 1)];
578: }
579:
580: // temp
581: public String toString() {
582: StringBuffer buf = new StringBuffer();
583: buf.append(super .toString());
584: buf.append('[');
585: buf.append("transitionCount=" + transitionCount);
586: buf.append(",typeCount=" + typeCount);
587: buf.append(",transitionTimes=");
588: if (transitionTimes != null) {
589: buf.append('[');
590: for (int i = 0; i < transitionTimes.length; ++i) {
591: if (i > 0) {
592: buf.append(',');
593: }
594: buf.append(Integer.toString(transitionTimes[i]));
595: }
596: buf.append(']');
597: } else {
598: buf.append("null");
599: }
600: buf.append(",typeOffsets=");
601: if (typeOffsets != null) {
602: buf.append('[');
603: for (int i = 0; i < typeOffsets.length; ++i) {
604: if (i > 0) {
605: buf.append(',');
606: }
607: buf.append(Integer.toString(typeOffsets[i]));
608: }
609: buf.append(']');
610: } else {
611: buf.append("null");
612: }
613: buf.append(",finalYear=" + finalYear);
614: buf.append(",finalMillis=" + finalMillis);
615: buf.append(",finalZone=" + finalZone);
616: buf.append(']');
617:
618: return buf.toString();
619: }
620:
621: /**
622: * Number of transitions, 0..~370
623: */
624: private int transitionCount;
625:
626: /**
627: * Number of types, 1..255
628: */
629: private int typeCount;
630:
631: /**
632: * Time of each transition in seconds from 1970 epoch.
633: * Length is transitionCount int32_t's.
634: */
635: private int[] transitionTimes; // alias into res; do not delete
636:
637: /**
638: * Offset from GMT in seconds for each type.
639: * Length is typeCount int32_t's.
640: */
641: private int[] typeOffsets; // alias into res; do not delete
642:
643: /**
644: * Type description data, consisting of transitionCount uint8_t
645: * type indices (from 0..typeCount-1).
646: * Length is transitionCount int8_t's.
647: */
648: private byte[] typeData; // alias into res; do not delete
649:
650: /**
651: * The last year for which the transitions data are to be used
652: * rather than the finalZone. If there is no finalZone, then this
653: * is set to INT32_MAX. NOTE: This corresponds to the year _before_
654: * the one indicated by finalMillis.
655: */
656: private int finalYear = Integer.MAX_VALUE;
657:
658: /**
659: * The millis for the start of the first year for which finalZone
660: * is to be used, or DBL_MAX if finalZone is 0. NOTE: This is
661: * 0:00 GMT Jan 1, <finalYear + 1> (not <finalMillis>).
662: */
663: private double finalMillis = Double.MAX_VALUE;
664:
665: /**
666: * A SimpleTimeZone that governs the behavior for years > finalYear.
667: * If and only if finalYear == INT32_MAX then finalZone == 0.
668: */
669: private SimpleTimeZone finalZone = null; // owned, may be NULL
670:
671: private static final boolean DEBUG = ICUDebug.enabled("olson");
672: private static final int[] DAYS_BEFORE = new int[] { 0, 31, 59, 90,
673: 120, 151, 181, 212, 243, 273, 304, 334, 0, 31, 60, 91, 121,
674: 152, 182, 213, 244, 274, 305, 335 };
675:
676: private static final int[] MONTH_LENGTH = new int[] { 31, 28, 31,
677: 30, 31, 30, 31, 31, 30, 31, 30, 31, 31, 29, 31, 30, 31, 30,
678: 31, 31, 30, 31, 30, 31 };
679: private static final int JULIAN_1_CE = 1721426; // January 1, 1 CE Gregorian
680: private static final int JULIAN_1970_CE = 2440588; // January 1, 1970 CE Gregorian
681: private static final int MILLIS_PER_SECOND = 1000;
682: private static final int SECONDS_PER_DAY = 24 * 60 * 60;
683:
684: private static final double fieldsToDay(int year, int month, int dom) {
685: int y = year - 1;
686: double julian = 365 * y + myFloorDivide(y, 4)
687: + (JULIAN_1_CE - 3) + // Julian cal
688: myFloorDivide(y, 400) - myFloorDivide(y, 100) + 2 + // => Gregorian cal
689: DAYS_BEFORE[month + (isLeapYear(year) ? 12 : 0)] + dom; // => month/dom
690:
691: return julian - JULIAN_1970_CE; // JD => epoch day
692: }
693:
694: private static final boolean isLeapYear(int year) {
695: // year&0x3 == year%4
696: return ((year & 0x3) == 0)
697: && ((year % 100 != 0) || (year % 400 == 0));
698: }
699:
700: private static ICUResourceBundle loadRule(ICUResourceBundle top,
701: String ruleid) {
702: ICUResourceBundle r = top.get("Rules");
703: r = r.get(ruleid);
704: return r;
705: }
706:
707: /**
708: * Divide two long integers, returning the floor of the quotient.
709: * <p>
710: * Unlike the built-in division, this is mathematically well-behaved.
711: * E.g., <code>-1/4</code> => 0
712: * but <code>floorDivide(-1,4)</code> => -1.
713: * @param numerator the numerator
714: * @param denominator a divisor which must be > 0
715: * @return the floor of the quotient.
716: * @stable ICU 2.0
717: */
718: private static final long myFloorDivide(long numerator,
719: long denominator) {
720: // We do this computation in order to handle
721: // a numerator of Long.MIN_VALUE correctly
722: return (numerator >= 0) ? numerator / denominator
723: : ((numerator + 1) / denominator) - 1;
724: }
725:
726: int[] dayToFields(double day) {
727: int year, month, dom, dow;
728: double doy;
729: int[] ret = new int[5];
730:
731: // Convert from 1970 CE epoch to 1 CE epoch (Gregorian calendar)
732: day += JULIAN_1970_CE - JULIAN_1_CE;
733:
734: // Convert from the day number to the multiple radix
735: // representation. We use 400-year, 100-year, and 4-year cycles.
736: // For example, the 4-year cycle has 4 years + 1 leap day; giving
737: // 1461 == 365*4 + 1 days.
738: double[] temp = floorDivide(day, 146097); // 400-year cycle length
739: double n400 = temp[0];
740: doy = temp[1];
741: temp = floorDivide(doy, 36524); // 100-year cycle length
742: double n100 = temp[0];
743: doy = temp[1];
744: temp = floorDivide(doy, 1461); // 4-year cycle length
745: double n4 = temp[0];
746: doy = temp[1];
747: temp = floorDivide(doy, 365);
748: double n1 = temp[0];
749: doy = temp[1];
750: year = (int) (400 * n400 + 100 * n100 + 4 * n4 + n1);
751: if (n100 == 4 || n1 == 4) {
752: doy = 365; // Dec 31 at end of 4- or 400-year cycle
753: } else {
754: ++year;
755: }
756:
757: boolean isLeap = isLeapYear(year);
758:
759: // Gregorian day zero is a Monday.
760: dow = (int) ((day + 1) % 7);
761: dow += (dow < 0) ? (Calendar.SUNDAY + 7) : Calendar.SUNDAY;
762:
763: // Common Julian/Gregorian calculation
764: int correction = 0;
765: int march1 = isLeap ? 60 : 59; // zero-based DOY for March 1
766: if (doy >= march1) {
767: correction = isLeap ? 1 : 2;
768: }
769: month = (int) ((12 * (doy + correction) + 6) / 367); // zero-based month
770: dom = (int) (doy - DAYS_BEFORE[month + (isLeap ? 12 : 0)] + 1); // one-based DOM
771: doy++; // one-based doy
772: ret[0] = year;
773: ret[1] = month;
774: ret[2] = dom;
775: ret[3] = dow;
776: ret[4] = (int) doy;
777: return ret;
778: }
779:
780: public boolean equals(Object obj) {
781: if (!super .equals(obj))
782: return false; // super does class check
783:
784: OlsonTimeZone z = (OlsonTimeZone) obj;
785:
786: return (Utility.arrayEquals(typeData, z.typeData) ||
787: // If the pointers are not equal, the zones may still
788: // be equal if their rules and transitions are equal
789: (finalYear == z.finalYear &&
790: // Don't compare finalMillis; if finalYear is ==, so is finalMillis
791: ((finalZone == null && z.finalZone == null) || (finalZone != null
792: && z.finalZone != null && finalZone.equals(z.finalZone))
793: && transitionCount == z.transitionCount
794: && typeCount == z.typeCount
795: && Utility.arrayEquals(transitionTimes,
796: z.transitionTimes)
797: && Utility.arrayEquals(typeOffsets, z.typeOffsets)
798: && Utility.arrayEquals(typeData, z.typeData))));
799:
800: }
801:
802: public int hashCode() {
803: int ret = (int) (finalYear ^ (finalYear >>> 4)
804: + transitionCount ^ (transitionCount >>> 6) + typeCount ^ (typeCount >>> 8)
805: + Double.doubleToLongBits(finalMillis)
806: + (finalZone == null ? 0 : finalZone.hashCode())
807: + super .hashCode());
808: for (int i = 0; i < transitionTimes.length; i++) {
809: ret += transitionTimes[i] ^ (transitionTimes[i] >>> 8);
810: }
811: for (int i = 0; i < typeOffsets.length; i++) {
812: ret += typeOffsets[i] ^ (typeOffsets[i] >>> 8);
813: }
814: for (int i = 0; i < typeData.length; i++) {
815: ret += typeData[i] & UNSIGNED_BYTE_MASK;
816: }
817: return ret;
818: }
819: /*
820: private void readObject(ObjectInputStream s) throws IOException {
821: s.defaultReadObject();
822: // customized deserialization code
823:
824: // followed by code to update the object, if necessary
825: }
826: */
827: }
|