1// © 2016 and later: Unicode, Inc. and others. 2// License & terms of use: http://www.unicode.org/copyright.html#License 3/* 4 ******************************************************************************* 5 * Copyright (C) 2007-2010, International Business Machines Corporation and * 6 * others. All Rights Reserved. * 7 ******************************************************************************* 8 */ 9package com.ibm.icu.util; 10import java.util.Date; 11 12import com.ibm.icu.impl.Grego; 13 14 15/** 16 * <code>AnnualTimeZoneRule</code> is a class used for representing a time zone 17 * rule which takes effect annually. Years used in this class are 18 * all Gregorian calendar years. 19 * 20 * @stable ICU 3.8 21 */ 22public class AnnualTimeZoneRule extends TimeZoneRule { 23 24 private static final long serialVersionUID = -8870666707791230688L; 25 26 /** 27 * The constant representing the maximum year used for designating a rule is permanent. 28 * @stable ICU 3.8 29 */ 30 public static final int MAX_YEAR = Integer.MAX_VALUE; 31 32 private final DateTimeRule dateTimeRule; 33 private final int startYear; 34 private final int endYear; 35 36 /** 37 * Constructs a <code>AnnualTimeZoneRule</code> with the name, the GMT offset of its 38 * standard time, the amount of daylight saving offset adjustment, 39 * the annual start time rule and the start/until years. 40 * 41 * @param name The time zone name. 42 * @param rawOffset The GMT offset of its standard time in milliseconds. 43 * @param dstSavings The amount of daylight saving offset adjustment in 44 * milliseconds. If this ia a rule for standard time, 45 * the value of this argument is 0. 46 * @param dateTimeRule The start date/time rule repeated annually. 47 * @param startYear The first year when this rule takes effect. 48 * @param endYear The last year when this rule takes effect. If this 49 * rule is effective forever in future, specify MAX_YEAR. 50 * 51 * @stable ICU 3.8 52 */ 53 public AnnualTimeZoneRule(String name, int rawOffset, int dstSavings, 54 DateTimeRule dateTimeRule, int startYear, int endYear) { 55 super(name, rawOffset, dstSavings); 56 this.dateTimeRule = dateTimeRule; 57 this.startYear = startYear; 58 this.endYear = endYear; 59 } 60 61 /** 62 * Gets the start date/time rule associated used by this rule. 63 * 64 * @return An <code>AnnualDateTimeRule</code> which represents the start date/time 65 * rule used by this time zone rule. 66 * 67 * @stable ICU 3.8 68 */ 69 public DateTimeRule getRule() { 70 return dateTimeRule; 71 } 72 73 /** 74 * Gets the first year when this rule takes effect. 75 * 76 * @return The start year of this rule. The year is in Gregorian calendar 77 * with 0 == 1 BCE, -1 == 2 BCE, etc. 78 * 79 * @stable ICU 3.8 80 */ 81 public int getStartYear() { 82 return startYear; 83 } 84 85 /** 86 * Gets the end year when this rule takes effect. 87 * 88 * @return The end year of this rule (inclusive). The year is in Gregorian calendar 89 * with 0 == 1 BCE, -1 == 2 BCE, etc. 90 * 91 * @stable ICU 3.8 92 */ 93 public int getEndYear() { 94 return endYear; 95 } 96 97 /** 98 * Gets the time when this rule takes effect in the given year. 99 * 100 * @param year The Gregorian year, with 0 == 1 BCE, -1 == 2 BCE, etc. 101 * @param prevRawOffset The standard time offset from UTC before this rule 102 * takes effect in milliseconds. 103 * @param prevDSTSavings The amount of daylight saving offset from the 104 * standard time. 105 * 106 * @return The time when this rule takes effect in the year, or 107 * null if this rule is not applicable in the year. 108 * 109 * @stable ICU 3.8 110 */ 111 public Date getStartInYear(int year, int prevRawOffset, int prevDSTSavings) { 112 if (year < startYear || year > endYear) { 113 return null; 114 } 115 116 long ruleDay; 117 int type = dateTimeRule.getDateRuleType(); 118 119 if (type == DateTimeRule.DOM) { 120 ruleDay = Grego.fieldsToDay(year, dateTimeRule.getRuleMonth(), dateTimeRule.getRuleDayOfMonth()); 121 } else { 122 boolean after = true; 123 if (type == DateTimeRule.DOW) { 124 int weeks = dateTimeRule.getRuleWeekInMonth(); 125 if (weeks > 0) { 126 ruleDay = Grego.fieldsToDay(year, dateTimeRule.getRuleMonth(), 1); 127 ruleDay += 7 * (weeks - 1); 128 } else { 129 after = false; 130 ruleDay = Grego.fieldsToDay(year, dateTimeRule.getRuleMonth(), 131 Grego.monthLength(year, dateTimeRule.getRuleMonth())); 132 ruleDay += 7 * (weeks + 1); 133 } 134 } else { 135 int month = dateTimeRule.getRuleMonth(); 136 int dom = dateTimeRule.getRuleDayOfMonth(); 137 if (type == DateTimeRule.DOW_LEQ_DOM) { 138 after = false; 139 // Handle Feb <=29 140 if (month == Calendar.FEBRUARY && dom == 29 && !Grego.isLeapYear(year)) { 141 dom--; 142 } 143 } 144 ruleDay = Grego.fieldsToDay(year, month, dom); 145 } 146 147 int dow = Grego.dayOfWeek(ruleDay); 148 int delta = dateTimeRule.getRuleDayOfWeek() - dow; 149 if (after) { 150 delta = delta < 0 ? delta + 7 : delta; 151 } else { 152 delta = delta > 0 ? delta - 7 : delta; 153 } 154 ruleDay += delta; 155 } 156 157 long ruleTime = ruleDay * Grego.MILLIS_PER_DAY + dateTimeRule.getRuleMillisInDay(); 158 if (dateTimeRule.getTimeRuleType() != DateTimeRule.UTC_TIME) { 159 ruleTime -= prevRawOffset; 160 } 161 if (dateTimeRule.getTimeRuleType() == DateTimeRule.WALL_TIME) { 162 ruleTime -= prevDSTSavings; 163 } 164 return new Date(ruleTime); 165 } 166 167 /** 168 * {@inheritDoc} 169 * @stable ICU 3.8 170 */ 171 @Override 172 public Date getFirstStart(int prevRawOffset, int prevDSTSavings) { 173 return getStartInYear(startYear, prevRawOffset, prevDSTSavings); 174 } 175 176 /** 177 * {@inheritDoc} 178 * @stable ICU 3.8 179 */ 180 @Override 181 public Date getFinalStart(int prevRawOffset, int prevDSTSavings) { 182 if (endYear == MAX_YEAR) { 183 return null; 184 } 185 return getStartInYear(endYear, prevRawOffset, prevDSTSavings); 186 } 187 188 /** 189 * {@inheritDoc} 190 * @stable ICU 3.8 191 */ 192 @Override 193 public Date getNextStart(long base, int prevRawOffset, int prevDSTSavings, boolean inclusive) { 194 int[] fields = Grego.timeToFields(base, null); 195 int year = fields[0]; 196 if (year < startYear) { 197 return getFirstStart(prevRawOffset, prevDSTSavings); 198 } 199 Date d = getStartInYear(year, prevRawOffset, prevDSTSavings); 200 if (d != null && (d.getTime() < base || (!inclusive && (d.getTime() == base)))) { 201 d = getStartInYear(year + 1, prevRawOffset, prevDSTSavings); 202 } 203 return d; 204 } 205 206 /** 207 * {@inheritDoc} 208 * @stable ICU 3.8 209 */ 210 @Override 211 public Date getPreviousStart(long base, int prevRawOffset, int prevDSTSavings, boolean inclusive) { 212 int[] fields = Grego.timeToFields(base, null); 213 int year = fields[0]; 214 if (year > endYear) { 215 return getFinalStart(prevRawOffset, prevDSTSavings); 216 } 217 Date d = getStartInYear(year, prevRawOffset, prevDSTSavings); 218 if (d != null && (d.getTime() > base || (!inclusive && (d.getTime() == base)))) { 219 d = getStartInYear(year - 1, prevRawOffset, prevDSTSavings); 220 } 221 return d; 222 } 223 224 /** 225 * {@inheritDoc} 226 * @stable ICU 3.8 227 */ 228 @Override 229 public boolean isEquivalentTo(TimeZoneRule other) { 230 if (!(other instanceof AnnualTimeZoneRule)) { 231 return false; 232 } 233 AnnualTimeZoneRule otherRule = (AnnualTimeZoneRule)other; 234 if (startYear == otherRule.startYear 235 && endYear == otherRule.endYear 236 && dateTimeRule.equals(otherRule.dateTimeRule)) { 237 return super.isEquivalentTo(other); 238 } 239 return false; 240 } 241 242 /** 243 * {@inheritDoc}<br><br> 244 * Note: This method in <code>AnnualTimeZoneRule</code> always returns true. 245 * @stable ICU 3.8 246 */ 247 @Override 248 public boolean isTransitionRule() { 249 return true; 250 } 251 252 /** 253 * Returns a <code>String</code> representation of this <code>AnnualTimeZoneRule</code> object. 254 * This method is used for debugging purpose only. The string representation can be changed 255 * in future version of ICU without any notice. 256 * 257 * @stable ICU 3.8 258 */ 259 @Override 260 public String toString() { 261 StringBuilder buf = new StringBuilder(); 262 buf.append(super.toString()); 263 buf.append(", rule={" + dateTimeRule + "}"); 264 buf.append(", startYear=" + startYear); 265 buf.append(", endYear="); 266 if (endYear == MAX_YEAR) { 267 buf.append("max"); 268 } else { 269 buf.append(endYear); 270 } 271 return buf.toString(); 272 } 273} 274