2 *******************************************************************************
3 * Copyright (C) 2007-2011, 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.
36 * <p>Example code:{@.jcite com.ibm.icu.samples.util.timezone.BasicTimeZoneExample:---getNextTransitionExample}
38 * @param base The base time.
39 * @param inclusive Whether the base time is inclusive or not.
41 * @return A <code>Date</code> holding the first time zone transition time
42 * after the given base time, or null if no time zone transitions
43 * are available after the base time.
47 public abstract TimeZoneTransition getNextTransition(long base, boolean inclusive);
50 * {@icu} Returns the last time zone transition before the base time.
51 * <p>Example code:{@.jcite com.ibm.icu.samples.util.timezone.BasicTimeZoneExample:---getPreviousTransitionExample}
53 * @param base The base time.
54 * @param inclusive Whether the base time is inclusive or not.
56 * @return A <code>Date</code> holding the last time zone transition time
57 * before the given base time, or null if no time zone transitions
58 * are available before the base time.
62 public abstract TimeZoneTransition getPreviousTransition(long base, boolean inclusive);
65 * {@icu} Checks if the time zone has equivalent transitions in the time range.
66 * This method returns true when all of transition times, from/to standard
67 * offsets and DST savings used by this time zone match the other in the
69 * <p>Example code:{@.jcite com.ibm.icu.samples.util.timezone.BasicTimeZoneExample:---hasEquivalentTransitionsExample}
71 * @param tz The instance of <code>TimeZone</code>
72 * @param start The start time of the evaluated time range (inclusive)
73 * @param end The end time of the evaluated time range (inclusive)
75 * @return true if the other time zone has the equivalent transitions in the
76 * time range. When tz is not a <code>BasicTimeZone</code>, this method
81 public boolean hasEquivalentTransitions(TimeZone tz, long start, long end) {
82 return hasEquivalentTransitions(tz, start, end, false);
86 * {@icu} Checks if the time zone has equivalent transitions in the time range.
87 * This method returns true when all of transition times, from/to standard
88 * offsets and DST savings used by this time zone match the other in the
91 * @param tz The instance of <code>TimeZone</code>
92 * @param start The start time of the evaluated time range (inclusive)
93 * @param end The end time of the evaluated time range (inclusive)
94 * @param ignoreDstAmount When true, any transitions with only daylight saving amount
95 * changes will be ignored, except either of them is zero. For example, a transition
96 * from rawoffset 3:00/dstsavings 1:00 to rawoffset 2:00/dstsavings 2:00 is excluded
97 * from the comparison, but a transtion from rawoffset 2:00/dstsavings 1:00 to
98 * rawoffset 3:00/dstsavings 0:00 is included.
100 * @return true if the other time zone has the equivalent transitions in the
101 * time range. When tz is not a <code>BasicTimeZone</code>, this method
106 public boolean hasEquivalentTransitions(TimeZone tz, long start, long end,
107 boolean ignoreDstAmount) {
112 if (!(tz instanceof BasicTimeZone)) {
116 // Check the offsets at the start time
117 int[] offsets1 = new int[2];
118 int[] offsets2 = new int[2];
120 getOffset(start, false, offsets1);
121 tz.getOffset(start, false, offsets2);
123 if (ignoreDstAmount) {
124 if ((offsets1[0] + offsets1[1] != offsets2[0] + offsets2[1])
125 || (offsets1[1] != 0 && offsets2[1] == 0)
126 || (offsets1[1] == 0 && offsets2[1] != 0)) {
130 if (offsets1[0] != offsets2[0] || offsets1[1] != offsets2[1]) {
135 // Check transitions in the range
138 TimeZoneTransition tr1 = getNextTransition(time, false);
139 TimeZoneTransition tr2 = ((BasicTimeZone)tz).getNextTransition(time, false);
141 if (ignoreDstAmount) {
142 // Skip a transition which only differ the amount of DST savings
145 && tr1.getTime() <= end
146 && (tr1.getFrom().getRawOffset() + tr1.getFrom().getDSTSavings()
147 == tr1.getTo().getRawOffset() + tr1.getTo().getDSTSavings())
148 && (tr1.getFrom().getDSTSavings() != 0 && tr1.getTo().getDSTSavings() != 0)) {
149 tr1 = getNextTransition(tr1.getTime(), false);
156 && tr2.getTime() <= end
157 && (tr2.getFrom().getRawOffset() + tr2.getFrom().getDSTSavings()
158 == tr2.getTo().getRawOffset() + tr2.getTo().getDSTSavings())
159 && (tr2.getFrom().getDSTSavings() != 0 && tr2.getTo().getDSTSavings() != 0)) {
160 tr2 = ((BasicTimeZone)tz).getNextTransition(tr2.getTime(), false);
167 boolean inRange1 = false;
168 boolean inRange2 = false;
170 if (tr1.getTime() <= end) {
175 if (tr2.getTime() <= end) {
179 if (!inRange1 && !inRange2) {
180 // No more transition in the range
183 if (!inRange1 || !inRange2) {
186 if (tr1.getTime() != tr2.getTime()) {
189 if (ignoreDstAmount) {
190 if (tr1.getTo().getRawOffset() + tr1.getTo().getDSTSavings()
191 != tr2.getTo().getRawOffset() + tr2.getTo().getDSTSavings()
192 || tr1.getTo().getDSTSavings() != 0 && tr2.getTo().getDSTSavings() == 0
193 || tr1.getTo().getDSTSavings() == 0 && tr2.getTo().getDSTSavings() != 0) {
197 if (tr1.getTo().getRawOffset() != tr2.getTo().getRawOffset() ||
198 tr1.getTo().getDSTSavings() != tr2.getTo().getDSTSavings()) {
202 time = tr1.getTime();
208 * {@icu} Returns the array of <code>TimeZoneRule</code> which represents the rule
209 * of this time zone object. The first element in the result array will
210 * be the <code>InitialTimeZoneRule</code> instance for the initial rule.
211 * The rest will be either <code>AnnualTimeZoneRule</code> or
212 * <code>TimeArrayTimeZoneRule</code> instances representing transitions.
214 * @return The array of <code>TimeZoneRule</code> which represents this
219 public abstract TimeZoneRule[] getTimeZoneRules();
222 * {@icu} Returns the array of <code>TimeZoneRule</code> which represents the rule
223 * of this time zone object since the specified start time. The first
224 * element in the result array will be the <code>InitialTimeZoneRule</code>
225 * instance for the initial rule. The rest will be either
226 * <code>AnnualTimeZoneRule</code> or <code>TimeArrayTimeZoneRule</code>
227 * instances representing transitions.
228 * <p>Example code:{@.jcite com.ibm.icu.samples.util.timezone.BasicTimeZoneExample:---getTimeZoneRulesExample}
230 * @param start The start time (inclusive).
231 * @return The array of <code>TimeZoneRule</code> which represents this
232 * time zone since the start time.
236 public TimeZoneRule[] getTimeZoneRules(long start) {
237 TimeZoneRule[] all = getTimeZoneRules();
238 TimeZoneTransition tzt = getPreviousTransition(start, true);
240 // No need to filter out rules only applicable to time before the start
244 BitSet isProcessed = new BitSet(all.length);
245 List<TimeZoneRule> filteredRules = new LinkedList<TimeZoneRule>();
247 // Create initial rule
248 TimeZoneRule initial = new InitialTimeZoneRule(tzt.getTo().getName(),
249 tzt.getTo().getRawOffset(), tzt.getTo().getDSTSavings());
250 filteredRules.add(initial);
253 // Mark rules which does not need to be processed
254 for (int i = 1; i < all.length; i++) {
255 Date d = all[i].getNextStart(start, initial.getRawOffset(),
256 initial.getDSTSavings(), false);
263 boolean bFinalStd = false, bFinalDst = false;
264 while(!bFinalStd || !bFinalDst) {
265 tzt = getNextTransition(time, false);
269 time = tzt.getTime();
271 TimeZoneRule toRule = tzt.getTo();
273 for (; ruleIdx < all.length; ruleIdx++) {
274 if (all[ruleIdx].equals(toRule)) {
278 if (ruleIdx >= all.length) {
279 throw new IllegalStateException("The rule was not found");
281 if (isProcessed.get(ruleIdx)) {
284 if (toRule instanceof TimeArrayTimeZoneRule) {
285 TimeArrayTimeZoneRule tar = (TimeArrayTimeZoneRule)toRule;
287 // Get the previous raw offset and DST savings before the very first start time
290 tzt = getNextTransition(t, false);
294 if (tzt.getTo().equals(tar)) {
300 // Check if the entire start times to be added
301 Date firstStart = tar.getFirstStart(tzt.getFrom().getRawOffset(),
302 tzt.getFrom().getDSTSavings());
303 if (firstStart.getTime() > start) {
304 // Just add the rule as is
305 filteredRules.add(tar);
307 // Collect transitions after the start time
308 long[] times = tar.getStartTimes();
309 int timeType = tar.getTimeType();
311 for (idx = 0; idx < times.length; idx++) {
313 if (timeType == DateTimeRule.STANDARD_TIME) {
314 t -= tzt.getFrom().getRawOffset();
316 if (timeType == DateTimeRule.WALL_TIME) {
317 t -= tzt.getFrom().getDSTSavings();
323 int asize = times.length - idx;
325 long[] newtimes = new long[asize];
326 System.arraycopy(times, idx, newtimes, 0, asize);
327 TimeArrayTimeZoneRule newtar = new TimeArrayTimeZoneRule(
328 tar.getName(), tar.getRawOffset(), tar.getDSTSavings(),
329 newtimes, tar.getTimeType());
330 filteredRules.add(newtar);
334 } else if (toRule instanceof AnnualTimeZoneRule) {
335 AnnualTimeZoneRule ar = (AnnualTimeZoneRule)toRule;
336 Date firstStart = ar.getFirstStart(tzt.getFrom().getRawOffset(),
337 tzt.getFrom().getDSTSavings());
338 if (firstStart.getTime() == tzt.getTime()) {
339 // Just add the rule as is
340 filteredRules.add(ar);
342 // Calculate the transition year
343 int[] dfields = new int[6];
344 Grego.timeToFields(tzt.getTime(), dfields);
346 AnnualTimeZoneRule newar = new AnnualTimeZoneRule(ar.getName(),
347 ar.getRawOffset(), ar.getDSTSavings(),
348 ar.getRule(), dfields[0], ar.getEndYear());
349 filteredRules.add(newar);
351 // Check if this is a final rule
352 if (ar.getEndYear() == AnnualTimeZoneRule.MAX_YEAR) {
353 // After both final standard and dst rule are processed,
354 // exit this while loop.
355 if (ar.getDSTSavings() == 0) {
362 isProcessed.set(ruleIdx);
364 TimeZoneRule[] rules = filteredRules.toArray(new TimeZoneRule[filteredRules.size()]);
369 * {@icu} Returns the array of <code>TimeZoneRule</code> which represents the rule of
370 * this time zone object near the specified date. Some applications are not
371 * capable to handle historic time zone rule changes. Also some applications
372 * can only handle certain type of rule definitions. This method returns
373 * either a single <code>InitialTimeZoneRule</code> if this time zone does not
374 * have any daylight saving time within 1 year from the specified time, or a
375 * pair of <code>AnnualTimeZoneRule</code> whose rule type is
376 * <code>DateTimeRule.DOW</code> for date and <code>DateTimeRule.WALL_TIME</code>
377 * for time with a single <code>InitialTimeZoneRule</code> representing the
378 * initial time, when this time zone observes daylight saving time near the
379 * specified date. Thus, the result may be only valid for dates around the
382 * @param date The date to be used for <code>TimeZoneRule</code> extraction.
383 * @return The array of <code>TimeZoneRule</code>, either a single
384 * <code>InitialTimeZoneRule</code> object, or a pair of <code>AnnualTimeZoneRule</code>
385 * with a single <code>InitialTimeZoneRule</code>. The first element in the
386 * array is always a <code>InitialTimeZoneRule</code>.
390 public TimeZoneRule[] getSimpleTimeZoneRulesNear(long date) {
391 AnnualTimeZoneRule[] annualRules = null;
392 TimeZoneRule initialRule = null;
393 // Get the next transition
394 TimeZoneTransition tr = getNextTransition(date, false);
396 String initialName = tr.getFrom().getName();
397 int initialRaw = tr.getFrom().getRawOffset();
398 int initialDst = tr.getFrom().getDSTSavings();
400 // Check if the next transition is either DST->STD or STD->DST and
401 // within roughly 1 year from the specified date
402 long nextTransitionTime = tr.getTime();
403 if (((tr.getFrom().getDSTSavings() == 0 && tr.getTo().getDSTSavings() != 0)
404 || (tr.getFrom().getDSTSavings() != 0 && tr.getTo().getDSTSavings() == 0))
405 && date + MILLIS_PER_YEAR > nextTransitionTime) {
406 annualRules = new AnnualTimeZoneRule[2];
407 // Get local wall time for the transition time
408 int dtfields[] = Grego.timeToFields(nextTransitionTime
409 + tr.getFrom().getRawOffset() + tr.getFrom().getDSTSavings(), null);
410 int weekInMonth = Grego.getDayOfWeekInMonth(dtfields[0], dtfields[1], dtfields[2]);
412 DateTimeRule dtr = new DateTimeRule(dtfields[1], weekInMonth, dtfields[3],
413 dtfields[5], DateTimeRule.WALL_TIME);
415 AnnualTimeZoneRule secondRule = null;
417 // Note: SimpleTimeZone does not support raw offset change.
418 // So we always use raw offset of the given time for the rule,
419 // even raw offset is changed. This will result that the result
420 // zone to return wrong offset after the transition.
421 // When we encounter such case, we do not inspect next next
422 // transition for another rule.
423 annualRules[0] = new AnnualTimeZoneRule(tr.getTo().getName(),
424 initialRaw, tr.getTo().getDSTSavings(),
425 dtr, dtfields[0], AnnualTimeZoneRule.MAX_YEAR);
427 if (tr.getTo().getRawOffset() == initialRaw) {
429 // Get the next next transition
430 tr = getNextTransition(nextTransitionTime, false);
432 // Check if the next next transition is either DST->STD or STD->DST
433 // and within roughly 1 year from the next transition
434 if (((tr.getFrom().getDSTSavings() == 0 && tr.getTo().getDSTSavings() != 0)
435 || (tr.getFrom().getDSTSavings() != 0
436 && tr.getTo().getDSTSavings() == 0))
437 && nextTransitionTime + MILLIS_PER_YEAR > tr.getTime()) {
438 // Generate another DOW rule
439 dtfields = Grego.timeToFields(tr.getTime()
440 + tr.getFrom().getRawOffset() + tr.getFrom().getDSTSavings(),
442 weekInMonth = Grego.getDayOfWeekInMonth(dtfields[0], dtfields[1],
444 dtr = new DateTimeRule(dtfields[1], weekInMonth, dtfields[3],
445 dtfields[5], DateTimeRule.WALL_TIME);
446 secondRule = new AnnualTimeZoneRule(tr.getTo().getName(),
447 tr.getTo().getRawOffset(), tr.getTo().getDSTSavings(),
448 dtr, dtfields[0] - 1, AnnualTimeZoneRule.MAX_YEAR);
449 // Make sure this rule can be applied to the specified date
450 Date d = secondRule.getPreviousStart(date, tr.getFrom().getRawOffset(),
451 tr.getFrom().getDSTSavings(), true);
452 if (d != null && d.getTime() <= date
453 && initialRaw == tr.getTo().getRawOffset()
454 && initialDst == tr.getTo().getDSTSavings()) {
455 // We can use this rule as the second transition rule
456 annualRules[1] = secondRule;
462 if (annualRules[1] == null) {
463 // Try previous transition
464 tr = getPreviousTransition(date, true);
466 // Check if the previous transition is either DST->STD or STD->DST.
467 // The actual transition time does not matter here.
468 if ((tr.getFrom().getDSTSavings() == 0 && tr.getTo().getDSTSavings() != 0)
469 || (tr.getFrom().getDSTSavings() != 0
470 && tr.getTo().getDSTSavings() == 0)) {
471 // Generate another DOW rule
472 dtfields = Grego.timeToFields(tr.getTime()
473 + tr.getFrom().getRawOffset() + tr.getFrom().getDSTSavings(),
475 weekInMonth = Grego.getDayOfWeekInMonth(dtfields[0], dtfields[1],
477 dtr = new DateTimeRule(dtfields[1], weekInMonth, dtfields[3],
478 dtfields[5], DateTimeRule.WALL_TIME);
480 // second rule raw/dst offsets should match raw/dst offsets
482 secondRule = new AnnualTimeZoneRule(
483 tr.getTo().getName(), initialRaw, initialDst, dtr,
484 annualRules[0].getStartYear() - 1, AnnualTimeZoneRule.MAX_YEAR);
486 // Check if this rule start after the first rule after the
488 Date d = secondRule.getNextStart(date, tr.getFrom().getRawOffset(),
489 tr.getFrom().getDSTSavings(), false);
490 if (d.getTime() > nextTransitionTime) {
491 // We can use this rule as the second transition rule
492 annualRules[1] = secondRule;
497 if (annualRules[1] == null) {
498 // Cannot generate a good pair of AnnualTimeZoneRule
501 // The initial rule should represent the rule before the previous transition
502 initialName = annualRules[0].getName();
503 initialRaw = annualRules[0].getRawOffset();
504 initialDst = annualRules[0].getDSTSavings();
507 initialRule = new InitialTimeZoneRule(initialName, initialRaw, initialDst);
509 // Try the previous one
510 tr = getPreviousTransition(date, true);
512 initialRule = new InitialTimeZoneRule(tr.getTo().getName(),
513 tr.getTo().getRawOffset(), tr.getTo().getDSTSavings());
515 // No transitions in the past. Just use the current offsets
516 int[] offsets = new int[2];
517 getOffset(date, false, offsets);
518 initialRule = new InitialTimeZoneRule(getID(), offsets[0], offsets[1]);
522 TimeZoneRule[] result = null;
523 if (annualRules == null) {
524 result = new TimeZoneRule[1];
525 result[0] = initialRule;
527 result = new TimeZoneRule[3];
528 result[0] = initialRule;
529 result[1] = annualRules[0];
530 result[2] = annualRules[1];
537 * {@icu} The time type option for standard time used by
538 * {@link #getOffsetFromLocal(long, int, int, int[])}
540 * @deprecated This API is ICU internal only.
542 public static final int LOCAL_STD = 0x01;
545 * {@icu} The time type option for daylight saving time used by
546 * {@link #getOffsetFromLocal(long, int, int, int[])}
548 * @deprecated This API is ICU internal only.
550 public static final int LOCAL_DST = 0x03;
553 * {@icu} The option designate former time to be used by
554 * {@link #getOffsetFromLocal(long, int, int, int[])}
556 * @deprecated This API is ICU internal only.
558 public static final int LOCAL_FORMER = 0x04;
561 * {@icu} The option designate latter time to be used by
562 * {@link #getOffsetFromLocal(long, int, int, int[])}
564 * @deprecated This API is ICU internal only.
566 public static final int LOCAL_LATTER = 0x0C;
569 * {@icu} The bit mask for the time type option used by
570 * {@link #getOffsetFromLocal(long, int, int, int[])}
572 * @deprecated This API is ICU internal only.
574 protected static final int STD_DST_MASK = 0x03;
577 * {@icu} The bit mask for the former/latter option used by
578 * {@link #getOffsetFromLocal(long, int, int, int[])}
580 * @deprecated This API is ICU internal only.
582 protected static final int FORMER_LATTER_MASK = 0x0C;
585 * {@icu} Returns time zone offsets from local wall time.
587 * @deprecated This API is ICU internal only.
589 public void getOffsetFromLocal(long date,
590 int nonExistingTimeOpt, int duplicatedTimeOpt, int[] offsets) {
591 throw new IllegalStateException("Not implemented");
595 * Protected no arg constructor.
598 protected BasicTimeZone() {
602 * Constructing a BasicTimeZone with the given time zone ID.
603 * @param ID the time zone ID.
605 * @deprecated This API is ICU internal only.
607 protected BasicTimeZone(String ID) {