2 *******************************************************************************
3 * Copyright (C) 2007-2010, International Business Machines Corporation and *
4 * others. All Rights Reserved. *
5 *******************************************************************************
7 package com.ibm.icu.util;
9 import java.util.BitSet;
10 import java.util.Date;
11 import java.util.LinkedList;
12 import java.util.List;
14 import com.ibm.icu.impl.Grego;
17 * {@icu} BasicTimeZone extends <code>TimeZone</code> with additional methods to access
18 * time zone transitions and rules. All ICU <code>TimeZone</code> concrete subclasses
19 * extend this class. APIs added to <code>java.util.TimeZone</code> by
20 * <code>BasicTimeZone</code> are annotated with <strong>'<font
21 * style="color:red">[icu]</font>'</strong>.
23 * @see com.ibm.icu.util.TimeZoneRule
24 * @see com.ibm.icu.util.TimeZoneTransition
28 public abstract class BasicTimeZone extends TimeZone {
30 private static final long serialVersionUID = -3204278532246180932L;
32 private static final long MILLIS_PER_YEAR = 365*24*60*60*1000L;
35 * {@icu} Returns the first time zone transition after the base time.
37 * @param base The base time.
38 * @param inclusive Whether the base time is inclusive or not.
40 * @return A <code>Date</code> holding the first time zone transition time
41 * after the given base time, or null if no time zone transitions
42 * are available after the base time.
46 public abstract TimeZoneTransition getNextTransition(long base, boolean inclusive);
49 * {@icu} Returns the last time zone transition before the base time.
51 * @param base The base time.
52 * @param inclusive Whether the base time is inclusive or not.
54 * @return A <code>Date</code> holding the last time zone transition time
55 * before the given base time, or null if no time zone transitions
56 * are available before the base time.
60 public abstract TimeZoneTransition getPreviousTransition(long base, boolean inclusive);
63 * {@icu} Checks if the time zone has equivalent transitions in the time range.
64 * This method returns true when all of transition times, from/to standard
65 * offsets and DST savings used by this time zone match the other in the
68 * @param tz The instance of <code>TimeZone</code>
69 * @param start The start time of the evaluated time range (inclusive)
70 * @param end The end time of the evaluated time range (inclusive)
72 * @return true if the other time zone has the equivalent transitions in the
73 * time range. When tz is not a <code>BasicTimeZone</code>, this method
78 public boolean hasEquivalentTransitions(TimeZone tz, long start, long end) {
79 return hasEquivalentTransitions(tz, start, end, false);
83 * {@icu} Checks if the time zone has equivalent transitions in the time range.
84 * This method returns true when all of transition times, from/to standard
85 * offsets and DST savings used by this time zone match the other in the
88 * @param tz The instance of <code>TimeZone</code>
89 * @param start The start time of the evaluated time range (inclusive)
90 * @param end The end time of the evaluated time range (inclusive)
91 * @param ignoreDstAmount When true, any transitions with only daylight saving amount
92 * changes will be ignored, except either of them is zero. For example, a transition
93 * from rawoffset 3:00/dstsavings 1:00 to rawoffset 2:00/dstsavings 2:00 is excluded
94 * from the comparison, but a transtion from rawoffset 2:00/dstsavings 1:00 to
95 * rawoffset 3:00/dstsavings 0:00 is included.
97 * @return true if the other time zone has the equivalent transitions in the
98 * time range. When tz is not a <code>BasicTimeZone</code>, this method
103 public boolean hasEquivalentTransitions(TimeZone tz, long start, long end,
104 boolean ignoreDstAmount) {
105 if (hasSameRules(tz)) {
108 if (!(tz instanceof BasicTimeZone)) {
112 // Check the offsets at the start time
113 int[] offsets1 = new int[2];
114 int[] offsets2 = new int[2];
116 getOffset(start, false, offsets1);
117 tz.getOffset(start, false, offsets2);
119 if (ignoreDstAmount) {
120 if ((offsets1[0] + offsets1[1] != offsets2[0] + offsets2[1])
121 || (offsets1[1] != 0 && offsets2[1] == 0)
122 || (offsets1[1] == 0 && offsets2[1] != 0)) {
126 if (offsets1[0] != offsets2[0] || offsets1[1] != offsets2[1]) {
131 // Check transitions in the range
134 TimeZoneTransition tr1 = getNextTransition(time, false);
135 TimeZoneTransition tr2 = ((BasicTimeZone)tz).getNextTransition(time, false);
137 if (ignoreDstAmount) {
138 // Skip a transition which only differ the amount of DST savings
141 && tr1.getTime() <= end
142 && (tr1.getFrom().getRawOffset() + tr1.getFrom().getDSTSavings()
143 == tr1.getTo().getRawOffset() + tr1.getTo().getDSTSavings())
144 && (tr1.getFrom().getDSTSavings() != 0 && tr1.getTo().getDSTSavings() != 0)) {
145 tr1 = getNextTransition(tr1.getTime(), false);
152 && tr2.getTime() <= end
153 && (tr2.getFrom().getRawOffset() + tr2.getFrom().getDSTSavings()
154 == tr2.getTo().getRawOffset() + tr2.getTo().getDSTSavings())
155 && (tr2.getFrom().getDSTSavings() != 0 && tr2.getTo().getDSTSavings() != 0)) {
156 tr2 = ((BasicTimeZone)tz).getNextTransition(tr2.getTime(), false);
163 boolean inRange1 = false;
164 boolean inRange2 = false;
166 if (tr1.getTime() <= end) {
171 if (tr2.getTime() <= end) {
175 if (!inRange1 && !inRange2) {
176 // No more transition in the range
179 if (!inRange1 || !inRange2) {
182 if (tr1.getTime() != tr2.getTime()) {
185 if (ignoreDstAmount) {
186 if (tr1.getTo().getRawOffset() + tr1.getTo().getDSTSavings()
187 != tr2.getTo().getRawOffset() + tr2.getTo().getDSTSavings()
188 || tr1.getTo().getDSTSavings() != 0 && tr2.getTo().getDSTSavings() == 0
189 || tr1.getTo().getDSTSavings() == 0 && tr2.getTo().getDSTSavings() != 0) {
193 if (tr1.getTo().getRawOffset() != tr2.getTo().getRawOffset() ||
194 tr1.getTo().getDSTSavings() != tr2.getTo().getDSTSavings()) {
198 time = tr1.getTime();
204 * {@icu} Returns the array of <code>TimeZoneRule</code> which represents the rule
205 * of this time zone object. The first element in the result array will
206 * be the <code>InitialTimeZoneRule</code> instance for the initial rule.
207 * The rest will be either <code>AnnualTimeZoneRule</code> or
208 * <code>TimeArrayTimeZoneRule</code> instances representing transitions.
210 * @return The array of <code>TimeZoneRule</code> which represents this
215 public abstract TimeZoneRule[] getTimeZoneRules();
218 * {@icu} Returns the array of <code>TimeZoneRule</code> which represents the rule
219 * of this time zone object since the specified start time. The first
220 * element in the result array will be the <code>InitialTimeZoneRule</code>
221 * instance for the initial rule. The rest will be either
222 * <code>AnnualTimeZoneRule</code> or <code>TimeArrayTimeZoneRule</code>
223 * instances representing transitions.
225 * @param start The start time (inclusive).
226 * @return The array of <code>TimeZoneRule</code> which represents this
227 * time zone since the start time.
231 public TimeZoneRule[] getTimeZoneRules(long start) {
232 TimeZoneRule[] all = getTimeZoneRules();
233 TimeZoneTransition tzt = getPreviousTransition(start, true);
235 // No need to filter out rules only applicable to time before the start
239 BitSet isProcessed = new BitSet(all.length);
240 List<TimeZoneRule> filteredRules = new LinkedList<TimeZoneRule>();
242 // Create initial rule
243 TimeZoneRule initial = new InitialTimeZoneRule(tzt.getTo().getName(),
244 tzt.getTo().getRawOffset(), tzt.getTo().getDSTSavings());
245 filteredRules.add(initial);
248 // Mark rules which does not need to be processed
249 for (int i = 1; i < all.length; i++) {
250 Date d = all[i].getNextStart(start, initial.getRawOffset(),
251 initial.getDSTSavings(), false);
258 boolean bFinalStd = false, bFinalDst = false;
259 while(!bFinalStd || !bFinalDst) {
260 tzt = getNextTransition(time, false);
264 time = tzt.getTime();
266 TimeZoneRule toRule = tzt.getTo();
268 for (; ruleIdx < all.length; ruleIdx++) {
269 if (all[ruleIdx].equals(toRule)) {
273 if (ruleIdx >= all.length) {
274 throw new IllegalStateException("The rule was not found");
276 if (isProcessed.get(ruleIdx)) {
279 if (toRule instanceof TimeArrayTimeZoneRule) {
280 TimeArrayTimeZoneRule tar = (TimeArrayTimeZoneRule)toRule;
282 // Get the previous raw offset and DST savings before the very first start time
285 tzt = getNextTransition(t, false);
289 if (tzt.getTo().equals(tar)) {
295 // Check if the entire start times to be added
296 Date firstStart = tar.getFirstStart(tzt.getFrom().getRawOffset(),
297 tzt.getFrom().getDSTSavings());
298 if (firstStart.getTime() > start) {
299 // Just add the rule as is
300 filteredRules.add(tar);
302 // Collect transitions after the start time
303 long[] times = tar.getStartTimes();
304 int timeType = tar.getTimeType();
306 for (idx = 0; idx < times.length; idx++) {
308 if (timeType == DateTimeRule.STANDARD_TIME) {
309 t -= tzt.getFrom().getRawOffset();
311 if (timeType == DateTimeRule.WALL_TIME) {
312 t -= tzt.getFrom().getDSTSavings();
318 int asize = times.length - idx;
320 long[] newtimes = new long[asize];
321 System.arraycopy(times, idx, newtimes, 0, asize);
322 TimeArrayTimeZoneRule newtar = new TimeArrayTimeZoneRule(
323 tar.getName(), tar.getRawOffset(), tar.getDSTSavings(),
324 newtimes, tar.getTimeType());
325 filteredRules.add(newtar);
329 } else if (toRule instanceof AnnualTimeZoneRule) {
330 AnnualTimeZoneRule ar = (AnnualTimeZoneRule)toRule;
331 Date firstStart = ar.getFirstStart(tzt.getFrom().getRawOffset(),
332 tzt.getFrom().getDSTSavings());
333 if (firstStart.getTime() == tzt.getTime()) {
334 // Just add the rule as is
335 filteredRules.add(ar);
337 // Calculate the transition year
338 int[] dfields = new int[6];
339 Grego.timeToFields(tzt.getTime(), dfields);
341 AnnualTimeZoneRule newar = new AnnualTimeZoneRule(ar.getName(),
342 ar.getRawOffset(), ar.getDSTSavings(),
343 ar.getRule(), dfields[0], ar.getEndYear());
344 filteredRules.add(newar);
346 // Check if this is a final rule
347 if (ar.getEndYear() == AnnualTimeZoneRule.MAX_YEAR) {
348 // After both final standard and dst rule are processed,
349 // exit this while loop.
350 if (ar.getDSTSavings() == 0) {
357 isProcessed.set(ruleIdx);
359 TimeZoneRule[] rules = filteredRules.toArray(new TimeZoneRule[filteredRules.size()]);
364 * {@icu} Returns the array of <code>TimeZoneRule</code> which represents the rule of
365 * this time zone object near the specified date. Some applications are not
366 * capable to handle historic time zone rule changes. Also some applications
367 * can only handle certain type of rule definitions. This method returns
368 * either a single <code>InitialTimeZoneRule</code> if this time zone does not
369 * have any daylight saving time within 1 year from the specified time, or a
370 * pair of <code>AnnualTimeZoneRule</code> whose rule type is
371 * <code>DateTimeRule.DOW</code> for date and <code>DateTimeRule.WALL_TIME</code>
372 * for time with a single <code>InitialTimeZoneRule</code> representing the
373 * initial time, when this time zone observes daylight saving time near the
374 * specified date. Thus, the result may be only valid for dates around the
377 * @param date The date to be used for <code>TimeZoneRule</code> extraction.
378 * @return The array of <code>TimeZoneRule</code>, either a single
379 * <code>InitialTimeZoneRule</code> object, or a pair of <code>AnnualTimeZoneRule</code>
380 * with a single <code>InitialTimeZoneRule</code>. The first element in the
381 * array is always a <code>InitialTimeZoneRule</code>.
385 public TimeZoneRule[] getSimpleTimeZoneRulesNear(long date) {
386 AnnualTimeZoneRule[] annualRules = null;
387 TimeZoneRule initialRule = null;
388 // Get the next transition
389 TimeZoneTransition tr = getNextTransition(date, false);
391 String initialName = tr.getFrom().getName();
392 int initialRaw = tr.getFrom().getRawOffset();
393 int initialDst = tr.getFrom().getDSTSavings();
395 // Check if the next transition is either DST->STD or STD->DST and
396 // within roughly 1 year from the specified date
397 long nextTransitionTime = tr.getTime();
398 if (((tr.getFrom().getDSTSavings() == 0 && tr.getTo().getDSTSavings() != 0)
399 || (tr.getFrom().getDSTSavings() != 0 && tr.getTo().getDSTSavings() == 0))
400 && date + MILLIS_PER_YEAR > nextTransitionTime) {
401 annualRules = new AnnualTimeZoneRule[2];
402 // Get local wall time for the transition time
403 int dtfields[] = Grego.timeToFields(nextTransitionTime
404 + tr.getFrom().getRawOffset() + tr.getFrom().getDSTSavings(), null);
405 int weekInMonth = Grego.getDayOfWeekInMonth(dtfields[0], dtfields[1], dtfields[2]);
407 DateTimeRule dtr = new DateTimeRule(dtfields[1], weekInMonth, dtfields[3],
408 dtfields[5], DateTimeRule.WALL_TIME);
410 AnnualTimeZoneRule secondRule = null;
412 // Note: SimpleTimeZone does not support raw offset change.
413 // So we always use raw offset of the given time for the rule,
414 // even raw offset is changed. This will result that the result
415 // zone to return wrong offset after the transition.
416 // When we encounter such case, we do not inspect next next
417 // transition for another rule.
418 annualRules[0] = new AnnualTimeZoneRule(tr.getTo().getName(),
419 initialRaw, tr.getTo().getDSTSavings(),
420 dtr, dtfields[0], AnnualTimeZoneRule.MAX_YEAR);
422 if (tr.getTo().getRawOffset() == initialRaw) {
424 // Get the next next transition
425 tr = getNextTransition(nextTransitionTime, false);
427 // Check if the next next transition is either DST->STD or STD->DST
428 // and within roughly 1 year from the next transition
429 if (((tr.getFrom().getDSTSavings() == 0 && tr.getTo().getDSTSavings() != 0)
430 || (tr.getFrom().getDSTSavings() != 0
431 && tr.getTo().getDSTSavings() == 0))
432 && nextTransitionTime + MILLIS_PER_YEAR > tr.getTime()) {
433 // Generate another DOW rule
434 dtfields = Grego.timeToFields(tr.getTime()
435 + tr.getFrom().getRawOffset() + tr.getFrom().getDSTSavings(),
437 weekInMonth = Grego.getDayOfWeekInMonth(dtfields[0], dtfields[1],
439 dtr = new DateTimeRule(dtfields[1], weekInMonth, dtfields[3],
440 dtfields[5], DateTimeRule.WALL_TIME);
441 secondRule = new AnnualTimeZoneRule(tr.getTo().getName(),
442 tr.getTo().getRawOffset(), tr.getTo().getDSTSavings(),
443 dtr, dtfields[0] - 1, AnnualTimeZoneRule.MAX_YEAR);
444 // Make sure this rule can be applied to the specified date
445 Date d = secondRule.getPreviousStart(date, tr.getFrom().getRawOffset(),
446 tr.getFrom().getDSTSavings(), true);
447 if (d != null && d.getTime() <= date
448 && initialRaw == tr.getTo().getRawOffset()
449 && initialDst == tr.getTo().getDSTSavings()) {
450 // We can use this rule as the second transition rule
451 annualRules[1] = secondRule;
457 if (annualRules[1] == null) {
458 // Try previous transition
459 tr = getPreviousTransition(date, true);
461 // Check if the previous transition is either DST->STD or STD->DST.
462 // The actual transition time does not matter here.
463 if ((tr.getFrom().getDSTSavings() == 0 && tr.getTo().getDSTSavings() != 0)
464 || (tr.getFrom().getDSTSavings() != 0
465 && tr.getTo().getDSTSavings() == 0)) {
466 // Generate another DOW rule
467 dtfields = Grego.timeToFields(tr.getTime()
468 + tr.getFrom().getRawOffset() + tr.getFrom().getDSTSavings(),
470 weekInMonth = Grego.getDayOfWeekInMonth(dtfields[0], dtfields[1],
472 dtr = new DateTimeRule(dtfields[1], weekInMonth, dtfields[3],
473 dtfields[5], DateTimeRule.WALL_TIME);
475 // second rule raw/dst offsets should match raw/dst offsets
477 secondRule = new AnnualTimeZoneRule(
478 tr.getTo().getName(), initialRaw, initialDst, dtr,
479 annualRules[0].getStartYear() - 1, AnnualTimeZoneRule.MAX_YEAR);
481 // Check if this rule start after the first rule after the
483 Date d = secondRule.getNextStart(date, tr.getFrom().getRawOffset(),
484 tr.getFrom().getDSTSavings(), false);
485 if (d.getTime() > nextTransitionTime) {
486 // We can use this rule as the second transition rule
487 annualRules[1] = secondRule;
492 if (annualRules[1] == null) {
493 // Cannot generate a good pair of AnnualTimeZoneRule
496 // The initial rule should represent the rule before the previous transition
497 initialName = annualRules[0].getName();
498 initialRaw = annualRules[0].getRawOffset();
499 initialDst = annualRules[0].getDSTSavings();
502 initialRule = new InitialTimeZoneRule(initialName, initialRaw, initialDst);
504 // Try the previous one
505 tr = getPreviousTransition(date, true);
507 initialRule = new InitialTimeZoneRule(tr.getTo().getName(),
508 tr.getTo().getRawOffset(), tr.getTo().getDSTSavings());
510 // No transitions in the past. Just use the current offsets
511 int[] offsets = new int[2];
512 getOffset(date, false, offsets);
513 initialRule = new InitialTimeZoneRule(getID(), offsets[0], offsets[1]);
517 TimeZoneRule[] result = null;
518 if (annualRules == null) {
519 result = new TimeZoneRule[1];
520 result[0] = initialRule;
522 result = new TimeZoneRule[3];
523 result[0] = initialRule;
524 result[1] = annualRules[0];
525 result[2] = annualRules[1];
532 * {@icu} The time type option for standard time used by
533 * {@link #getOffsetFromLocal(long, int, int, int[])}
535 * @deprecated This API is ICU internal only.
537 public static final int LOCAL_STD = 0x01;
540 * {@icu} The time type option for daylight saving time used by
541 * {@link #getOffsetFromLocal(long, int, int, int[])}
543 * @deprecated This API is ICU internal only.
545 public static final int LOCAL_DST = 0x03;
548 * {@icu} The option designate former time to be used by
549 * {@link #getOffsetFromLocal(long, int, int, int[])}
551 * @deprecated This API is ICU internal only.
553 public static final int LOCAL_FORMER = 0x04;
556 * {@icu} The option designate latter time to be used by
557 * {@link #getOffsetFromLocal(long, int, int, int[])}
559 * @deprecated This API is ICU internal only.
561 public static final int LOCAL_LATTER = 0x0C;
564 * {@icu} The bit mask for the time type option used by
565 * {@link #getOffsetFromLocal(long, int, int, int[])}
567 * @deprecated This API is ICU internal only.
569 protected static final int STD_DST_MASK = 0x03;
572 * {@icu} The bit mask for the former/latter option used by
573 * {@link #getOffsetFromLocal(long, int, int, int[])}
575 * @deprecated This API is ICU internal only.
577 protected static final int FORMER_LATTER_MASK = 0x0C;
580 * {@icu} Returns time zone offsets from local wall time.
582 * @deprecated This API is ICU internal only.
584 public void getOffsetFromLocal(long date,
585 int nonExistingTimeOpt, int duplicatedTimeOpt, int[] offsets) {
586 throw new IllegalStateException("Not implemented");
590 * Protected no arg constructor.
593 protected BasicTimeZone() {