2 *******************************************************************************
\r
3 * Copyright (C) 2007-2010, International Business Machines Corporation and *
\r
4 * others. All Rights Reserved. *
\r
5 *******************************************************************************
\r
7 package com.ibm.icu.util;
\r
9 import java.util.BitSet;
\r
10 import java.util.Date;
\r
11 import java.util.LinkedList;
\r
12 import java.util.List;
\r
14 import com.ibm.icu.impl.Grego;
\r
17 * {@icu} BasicTimeZone extends <code>TimeZone</code> with additional methods to access
\r
18 * time zone transitions and rules. All ICU <code>TimeZone</code> concrete subclasses
\r
19 * extend this class. APIs added to <code>java.util.TimeZone</code> by
\r
20 * <code>BasicTimeZone</code> are annotated with <strong>'<font
\r
21 * style="color:red">[icu]</font>'</strong>.
\r
23 * @see com.ibm.icu.util.TimeZoneRule
\r
24 * @see com.ibm.icu.util.TimeZoneTransition
\r
28 public abstract class BasicTimeZone extends TimeZone {
\r
30 private static final long serialVersionUID = -3204278532246180932L;
\r
32 private static final long MILLIS_PER_YEAR = 365*24*60*60*1000L;
\r
35 * {@icu} Returns the first time zone transition after the base time.
\r
37 * @param base The base time.
\r
38 * @param inclusive Whether the base time is inclusive or not.
\r
40 * @return A <code>Date</code> holding the first time zone transition time
\r
41 * after the given base time, or null if no time zone transitions
\r
42 * are available after the base time.
\r
46 public abstract TimeZoneTransition getNextTransition(long base, boolean inclusive);
\r
49 * {@icu} Returns the last time zone transition before the base time.
\r
51 * @param base The base time.
\r
52 * @param inclusive Whether the base time is inclusive or not.
\r
54 * @return A <code>Date</code> holding the last time zone transition time
\r
55 * before the given base time, or null if no time zone transitions
\r
56 * are available before the base time.
\r
60 public abstract TimeZoneTransition getPreviousTransition(long base, boolean inclusive);
\r
63 * {@icu} Checks if the time zone has equivalent transitions in the time range.
\r
64 * This method returns true when all of transition times, from/to standard
\r
65 * offsets and DST savings used by this time zone match the other in the
\r
68 * @param tz The instance of <code>TimeZone</code>
\r
69 * @param start The start time of the evaluated time range (inclusive)
\r
70 * @param end The end time of the evaluated time range (inclusive)
\r
72 * @return true if the other time zone has the equivalent transitions in the
\r
73 * time range. When tz is not a <code>BasicTimeZone</code>, this method
\r
78 public boolean hasEquivalentTransitions(TimeZone tz, long start, long end) {
\r
79 return hasEquivalentTransitions(tz, start, end, false);
\r
83 * {@icu} Checks if the time zone has equivalent transitions in the time range.
\r
84 * This method returns true when all of transition times, from/to standard
\r
85 * offsets and DST savings used by this time zone match the other in the
\r
88 * @param tz The instance of <code>TimeZone</code>
\r
89 * @param start The start time of the evaluated time range (inclusive)
\r
90 * @param end The end time of the evaluated time range (inclusive)
\r
91 * @param ignoreDstAmount When true, any transitions with only daylight saving amount
\r
92 * changes will be ignored, except either of them is zero. For example, a transition
\r
93 * from rawoffset 3:00/dstsavings 1:00 to rawoffset 2:00/dstsavings 2:00 is excluded
\r
94 * from the comparison, but a transtion from rawoffset 2:00/dstsavings 1:00 to
\r
95 * rawoffset 3:00/dstsavings 0:00 is included.
\r
97 * @return true if the other time zone has the equivalent transitions in the
\r
98 * time range. When tz is not a <code>BasicTimeZone</code>, this method
\r
103 public boolean hasEquivalentTransitions(TimeZone tz, long start, long end,
\r
104 boolean ignoreDstAmount) {
\r
105 if (hasSameRules(tz)) {
\r
108 if (!(tz instanceof BasicTimeZone)) {
\r
112 // Check the offsets at the start time
\r
113 int[] offsets1 = new int[2];
\r
114 int[] offsets2 = new int[2];
\r
116 getOffset(start, false, offsets1);
\r
117 tz.getOffset(start, false, offsets2);
\r
119 if (ignoreDstAmount) {
\r
120 if ((offsets1[0] + offsets1[1] != offsets2[0] + offsets2[1])
\r
121 || (offsets1[1] != 0 && offsets2[1] == 0)
\r
122 || (offsets1[1] == 0 && offsets2[1] != 0)) {
\r
126 if (offsets1[0] != offsets2[0] || offsets1[1] != offsets2[1]) {
\r
131 // Check transitions in the range
\r
134 TimeZoneTransition tr1 = getNextTransition(time, false);
\r
135 TimeZoneTransition tr2 = ((BasicTimeZone)tz).getNextTransition(time, false);
\r
137 if (ignoreDstAmount) {
\r
138 // Skip a transition which only differ the amount of DST savings
\r
140 && (tr1.getFrom().getRawOffset() + tr1.getFrom().getDSTSavings()
\r
141 == tr1.getTo().getRawOffset() + tr1.getTo().getDSTSavings())
\r
142 && (tr1.getFrom().getDSTSavings() != 0 && tr1.getTo().getDSTSavings() != 0)) {
\r
143 tr1 = getNextTransition(tr1.getTime(), false);
\r
146 && (tr2.getFrom().getRawOffset() + tr2.getFrom().getDSTSavings()
\r
147 == tr2.getTo().getRawOffset() + tr2.getTo().getDSTSavings())
\r
148 && (tr2.getFrom().getDSTSavings() != 0 && tr2.getTo().getDSTSavings() != 0)) {
\r
149 tr2 = getNextTransition(tr2.getTime(), false);
\r
153 boolean inRange1 = false;
\r
154 boolean inRange2 = false;
\r
156 if (tr1.getTime() <= end) {
\r
161 if (tr2.getTime() <= end) {
\r
165 if (!inRange1 && !inRange2) {
\r
166 // No more transition in the range
\r
169 if (!inRange1 || !inRange2) {
\r
172 if (tr1.getTime() != tr2.getTime()) {
\r
175 if (ignoreDstAmount) {
\r
176 if (tr1.getTo().getRawOffset() + tr1.getTo().getDSTSavings()
\r
177 != tr2.getTo().getRawOffset() + tr2.getTo().getDSTSavings()
\r
178 || tr1.getTo().getDSTSavings() != 0 && tr2.getTo().getDSTSavings() == 0
\r
179 || tr1.getTo().getDSTSavings() == 0 && tr2.getTo().getDSTSavings() != 0) {
\r
183 if (tr1.getTo().getRawOffset() != tr2.getTo().getRawOffset() ||
\r
184 tr1.getTo().getDSTSavings() != tr2.getTo().getDSTSavings()) {
\r
188 time = tr1.getTime();
\r
194 * {@icu} Returns the array of <code>TimeZoneRule</code> which represents the rule
\r
195 * of this time zone object. The first element in the result array will
\r
196 * be the <code>InitialTimeZoneRule</code> instance for the initial rule.
\r
197 * The rest will be either <code>AnnualTimeZoneRule</code> or
\r
198 * <code>TimeArrayTimeZoneRule</code> instances representing transitions.
\r
200 * @return The array of <code>TimeZoneRule</code> which represents this
\r
205 public abstract TimeZoneRule[] getTimeZoneRules();
\r
208 * {@icu} Returns the array of <code>TimeZoneRule</code> which represents the rule
\r
209 * of this time zone object since the specified start time. The first
\r
210 * element in the result array will be the <code>InitialTimeZoneRule</code>
\r
211 * instance for the initial rule. The rest will be either
\r
212 * <code>AnnualTimeZoneRule</code> or <code>TimeArrayTimeZoneRule</code>
\r
213 * instances representing transitions.
\r
215 * @param start The start time (inclusive).
\r
216 * @return The array of <code>TimeZoneRule</code> which represents this
\r
217 * time zone since the start time.
\r
221 public TimeZoneRule[] getTimeZoneRules(long start) {
\r
222 TimeZoneRule[] all = getTimeZoneRules();
\r
223 TimeZoneTransition tzt = getPreviousTransition(start, true);
\r
225 // No need to filter out rules only applicable to time before the start
\r
229 BitSet isProcessed = new BitSet(all.length);
\r
230 List<TimeZoneRule> filteredRules = new LinkedList<TimeZoneRule>();
\r
232 // Create initial rule
\r
233 TimeZoneRule initial = new InitialTimeZoneRule(tzt.getTo().getName(),
\r
234 tzt.getTo().getRawOffset(), tzt.getTo().getDSTSavings());
\r
235 filteredRules.add(initial);
\r
236 isProcessed.set(0);
\r
238 // Mark rules which does not need to be processed
\r
239 for (int i = 1; i < all.length; i++) {
\r
240 Date d = all[i].getNextStart(start, initial.getRawOffset(),
\r
241 initial.getDSTSavings(), false);
\r
243 isProcessed.set(i);
\r
248 boolean bFinalStd = false, bFinalDst = false;
\r
249 while(!bFinalStd || !bFinalDst) {
\r
250 tzt = getNextTransition(time, false);
\r
254 time = tzt.getTime();
\r
256 TimeZoneRule toRule = tzt.getTo();
\r
258 for (; ruleIdx < all.length; ruleIdx++) {
\r
259 if (all[ruleIdx].equals(toRule)) {
\r
263 if (ruleIdx >= all.length) {
\r
264 throw new IllegalStateException("The rule was not found");
\r
266 if (isProcessed.get(ruleIdx)) {
\r
269 if (toRule instanceof TimeArrayTimeZoneRule) {
\r
270 TimeArrayTimeZoneRule tar = (TimeArrayTimeZoneRule)toRule;
\r
272 // Get the previous raw offset and DST savings before the very first start time
\r
275 tzt = getNextTransition(t, false);
\r
279 if (tzt.getTo().equals(tar)) {
\r
285 // Check if the entire start times to be added
\r
286 Date firstStart = tar.getFirstStart(tzt.getFrom().getRawOffset(),
\r
287 tzt.getFrom().getDSTSavings());
\r
288 if (firstStart.getTime() > start) {
\r
289 // Just add the rule as is
\r
290 filteredRules.add(tar);
\r
292 // Collect transitions after the start time
\r
293 long[] times = tar.getStartTimes();
\r
294 int timeType = tar.getTimeType();
\r
296 for (idx = 0; idx < times.length; idx++) {
\r
298 if (timeType == DateTimeRule.STANDARD_TIME) {
\r
299 t -= tzt.getFrom().getRawOffset();
\r
301 if (timeType == DateTimeRule.WALL_TIME) {
\r
302 t -= tzt.getFrom().getDSTSavings();
\r
308 int asize = times.length - idx;
\r
310 long[] newtimes = new long[asize];
\r
311 System.arraycopy(times, idx, newtimes, 0, asize);
\r
312 TimeArrayTimeZoneRule newtar = new TimeArrayTimeZoneRule(
\r
313 tar.getName(), tar.getRawOffset(), tar.getDSTSavings(),
\r
314 newtimes, tar.getTimeType());
\r
315 filteredRules.add(newtar);
\r
319 } else if (toRule instanceof AnnualTimeZoneRule) {
\r
320 AnnualTimeZoneRule ar = (AnnualTimeZoneRule)toRule;
\r
321 Date firstStart = ar.getFirstStart(tzt.getFrom().getRawOffset(),
\r
322 tzt.getFrom().getDSTSavings());
\r
323 if (firstStart.getTime() == tzt.getTime()) {
\r
324 // Just add the rule as is
\r
325 filteredRules.add(ar);
\r
327 // Calculate the transition year
\r
328 int[] dfields = new int[6];
\r
329 Grego.timeToFields(tzt.getTime(), dfields);
\r
330 // Recreate the rule
\r
331 AnnualTimeZoneRule newar = new AnnualTimeZoneRule(ar.getName(),
\r
332 ar.getRawOffset(), ar.getDSTSavings(),
\r
333 ar.getRule(), dfields[0], ar.getEndYear());
\r
334 filteredRules.add(newar);
\r
336 // Check if this is a final rule
\r
337 if (ar.getEndYear() == AnnualTimeZoneRule.MAX_YEAR) {
\r
338 // After both final standard and dst rule are processed,
\r
339 // exit this while loop.
\r
340 if (ar.getDSTSavings() == 0) {
\r
347 isProcessed.set(ruleIdx);
\r
349 TimeZoneRule[] rules = filteredRules.toArray(new TimeZoneRule[filteredRules.size()]);
\r
354 * {@icu} Returns the array of <code>TimeZoneRule</code> which represents the rule of
\r
355 * this time zone object near the specified date. Some applications are not
\r
356 * capable to handle historic time zone rule changes. Also some applications
\r
357 * can only handle certain type of rule definitions. This method returns
\r
358 * either a single <code>InitialTimeZoneRule</code> if this time zone does not
\r
359 * have any daylight saving time within 1 year from the specified time, or a
\r
360 * pair of <code>AnnualTimeZoneRule</code> whose rule type is
\r
361 * <code>DateTimeRule.DOW</code> for date and <code>DateTimeRule.WALL_TIME</code>
\r
362 * for time with a single <code>InitialTimeZoneRule</code> representing the
\r
363 * initial time, when this time zone observes daylight saving time near the
\r
364 * specified date. Thus, the result may be only valid for dates around the
\r
367 * @param date The date to be used for <code>TimeZoneRule</code> extraction.
\r
368 * @return The array of <code>TimeZoneRule</code>, either a single
\r
369 * <code>InitialTimeZoneRule</code> object, or a pair of <code>AnnualTimeZoneRule</code>
\r
370 * with a single <code>InitialTimeZoneRule</code>. The first element in the
\r
371 * array is always a <code>InitialTimeZoneRule</code>.
\r
375 public TimeZoneRule[] getSimpleTimeZoneRulesNear(long date) {
\r
376 AnnualTimeZoneRule[] annualRules = null;
\r
377 TimeZoneRule initialRule = null;
\r
378 // Get the next transition
\r
379 TimeZoneTransition tr = getNextTransition(date, false);
\r
381 String initialName = tr.getFrom().getName();
\r
382 int initialRaw = tr.getFrom().getRawOffset();
\r
383 int initialDst = tr.getFrom().getDSTSavings();
\r
385 // Check if the next transition is either DST->STD or STD->DST and
\r
386 // within roughly 1 year from the specified date
\r
387 long nextTransitionTime = tr.getTime();
\r
388 if (((tr.getFrom().getDSTSavings() == 0 && tr.getTo().getDSTSavings() != 0)
\r
389 || (tr.getFrom().getDSTSavings() != 0 && tr.getTo().getDSTSavings() == 0))
\r
390 && date + MILLIS_PER_YEAR > nextTransitionTime) {
\r
391 annualRules = new AnnualTimeZoneRule[2];
\r
392 // Get local wall time for the transition time
\r
393 int dtfields[] = Grego.timeToFields(nextTransitionTime
\r
394 + tr.getFrom().getRawOffset() + tr.getFrom().getDSTSavings(), null);
\r
395 int weekInMonth = Grego.getDayOfWeekInMonth(dtfields[0], dtfields[1], dtfields[2]);
\r
397 DateTimeRule dtr = new DateTimeRule(dtfields[1], weekInMonth, dtfields[3],
\r
398 dtfields[5], DateTimeRule.WALL_TIME);
\r
400 AnnualTimeZoneRule secondRule = null;
\r
402 // Note: SimpleTimeZone does not support raw offset change.
\r
403 // So we always use raw offset of the given time for the rule,
\r
404 // even raw offset is changed. This will result that the result
\r
405 // zone to return wrong offset after the transition.
\r
406 // When we encounter such case, we do not inspect next next
\r
407 // transition for another rule.
\r
408 annualRules[0] = new AnnualTimeZoneRule(tr.getTo().getName(),
\r
409 initialRaw, tr.getTo().getDSTSavings(),
\r
410 dtr, dtfields[0], AnnualTimeZoneRule.MAX_YEAR);
\r
412 if (tr.getTo().getRawOffset() == initialRaw) {
\r
414 // Get the next next transition
\r
415 tr = getNextTransition(nextTransitionTime, false);
\r
417 // Check if the next next transition is either DST->STD or STD->DST
\r
418 // and within roughly 1 year from the next transition
\r
419 if (((tr.getFrom().getDSTSavings() == 0 && tr.getTo().getDSTSavings() != 0)
\r
420 || (tr.getFrom().getDSTSavings() != 0
\r
421 && tr.getTo().getDSTSavings() == 0))
\r
422 && nextTransitionTime + MILLIS_PER_YEAR > tr.getTime()) {
\r
423 // Generate another DOW rule
\r
424 dtfields = Grego.timeToFields(tr.getTime()
\r
425 + tr.getFrom().getRawOffset() + tr.getFrom().getDSTSavings(),
\r
427 weekInMonth = Grego.getDayOfWeekInMonth(dtfields[0], dtfields[1],
\r
429 dtr = new DateTimeRule(dtfields[1], weekInMonth, dtfields[3],
\r
430 dtfields[5], DateTimeRule.WALL_TIME);
\r
431 secondRule = new AnnualTimeZoneRule(tr.getTo().getName(),
\r
432 tr.getTo().getRawOffset(), tr.getTo().getDSTSavings(),
\r
433 dtr, dtfields[0] - 1, AnnualTimeZoneRule.MAX_YEAR);
\r
434 // Make sure this rule can be applied to the specified date
\r
435 Date d = secondRule.getPreviousStart(date, tr.getFrom().getRawOffset(),
\r
436 tr.getFrom().getDSTSavings(), true);
\r
437 if (d != null && d.getTime() <= date
\r
438 && initialRaw == tr.getTo().getRawOffset()
\r
439 && initialDst == tr.getTo().getDSTSavings()) {
\r
440 // We can use this rule as the second transition rule
\r
441 annualRules[1] = secondRule;
\r
447 if (annualRules[1] == null) {
\r
448 // Try previous transition
\r
449 tr = getPreviousTransition(date, true);
\r
451 // Check if the previous transition is either DST->STD or STD->DST.
\r
452 // The actual transition time does not matter here.
\r
453 if ((tr.getFrom().getDSTSavings() == 0 && tr.getTo().getDSTSavings() != 0)
\r
454 || (tr.getFrom().getDSTSavings() != 0
\r
455 && tr.getTo().getDSTSavings() == 0)) {
\r
456 // Generate another DOW rule
\r
457 dtfields = Grego.timeToFields(tr.getTime()
\r
458 + tr.getFrom().getRawOffset() + tr.getFrom().getDSTSavings(),
\r
460 weekInMonth = Grego.getDayOfWeekInMonth(dtfields[0], dtfields[1],
\r
462 dtr = new DateTimeRule(dtfields[1], weekInMonth, dtfields[3],
\r
463 dtfields[5], DateTimeRule.WALL_TIME);
\r
465 // second rule raw/dst offsets should match raw/dst offsets
\r
466 // at the given time
\r
467 secondRule = new AnnualTimeZoneRule(
\r
468 tr.getTo().getName(), initialRaw, initialDst, dtr,
\r
469 annualRules[0].getStartYear() - 1, AnnualTimeZoneRule.MAX_YEAR);
\r
471 // Check if this rule start after the first rule after the
\r
473 Date d = secondRule.getNextStart(date, tr.getFrom().getRawOffset(),
\r
474 tr.getFrom().getDSTSavings(), false);
\r
475 if (d.getTime() > nextTransitionTime) {
\r
476 // We can use this rule as the second transition rule
\r
477 annualRules[1] = secondRule;
\r
482 if (annualRules[1] == null) {
\r
483 // Cannot generate a good pair of AnnualTimeZoneRule
\r
484 annualRules = null;
\r
486 // The initial rule should represent the rule before the previous transition
\r
487 initialName = annualRules[0].getName();
\r
488 initialRaw = annualRules[0].getRawOffset();
\r
489 initialDst = annualRules[0].getDSTSavings();
\r
492 initialRule = new InitialTimeZoneRule(initialName, initialRaw, initialDst);
\r
494 // Try the previous one
\r
495 tr = getPreviousTransition(date, true);
\r
497 initialRule = new InitialTimeZoneRule(tr.getTo().getName(),
\r
498 tr.getTo().getRawOffset(), tr.getTo().getDSTSavings());
\r
500 // No transitions in the past. Just use the current offsets
\r
501 int[] offsets = new int[2];
\r
502 getOffset(date, false, offsets);
\r
503 initialRule = new InitialTimeZoneRule(getID(), offsets[0], offsets[1]);
\r
507 TimeZoneRule[] result = null;
\r
508 if (annualRules == null) {
\r
509 result = new TimeZoneRule[1];
\r
510 result[0] = initialRule;
\r
512 result = new TimeZoneRule[3];
\r
513 result[0] = initialRule;
\r
514 result[1] = annualRules[0];
\r
515 result[2] = annualRules[1];
\r
522 * {@icu} The time type option for standard time used by
\r
523 * {@link #getOffsetFromLocal(long, int, int, int[])}
\r
525 * @deprecated This API is ICU internal only.
\r
527 public static final int LOCAL_STD = 0x01;
\r
530 * {@icu} The time type option for daylight saving time used by
\r
531 * {@link #getOffsetFromLocal(long, int, int, int[])}
\r
533 * @deprecated This API is ICU internal only.
\r
535 public static final int LOCAL_DST = 0x03;
\r
538 * {@icu} The option designate former time to be used by
\r
539 * {@link #getOffsetFromLocal(long, int, int, int[])}
\r
541 * @deprecated This API is ICU internal only.
\r
543 public static final int LOCAL_FORMER = 0x04;
\r
546 * {@icu} The option designate latter time to be used by
\r
547 * {@link #getOffsetFromLocal(long, int, int, int[])}
\r
549 * @deprecated This API is ICU internal only.
\r
551 public static final int LOCAL_LATTER = 0x0C;
\r
554 * {@icu} The bit mask for the time type option used by
\r
555 * {@link #getOffsetFromLocal(long, int, int, int[])}
\r
557 * @deprecated This API is ICU internal only.
\r
559 protected static final int STD_DST_MASK = 0x03;
\r
562 * {@icu} The bit mask for the former/latter option used by
\r
563 * {@link #getOffsetFromLocal(long, int, int, int[])}
\r
565 * @deprecated This API is ICU internal only.
\r
567 protected static final int FORMER_LATTER_MASK = 0x0C;
\r
570 * {@icu} Returns time zone offsets from local wall time.
\r
572 * @deprecated This API is ICU internal only.
\r
574 public void getOffsetFromLocal(long date,
\r
575 int nonExistingTimeOpt, int duplicatedTimeOpt, int[] offsets) {
\r
576 throw new IllegalStateException("Not implemented");
\r
580 * Protected no arg constructor.
\r
583 protected BasicTimeZone() {
\r