1 /* 2 * Copyright (c) 2003, 2004, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package sun.util.calendar; 27 28 import java.util.Locale; 29 import java.util.TimeZone; 30 31 /** 32 * The <code>AbstractCalendar</code> class provides a framework for 33 * implementing a concrete calendar system. 34 * 35 * <p><a name="fixed_date"></a><B>Fixed Date</B><br> 36 * 37 * For implementing a concrete calendar system, each calendar must 38 * have the common date numbering, starting from midnight the onset of 39 * Monday, January 1, 1 (Gregorian). It is called a <I>fixed date</I> 40 * in this class. January 1, 1 (Gregorian) is fixed date 1. (See 41 * Nachum Dershowitz and Edward M. Reingold, <I>CALENDRICAL 42 * CALCULATION The Millennium Edition</I>, Section 1.2 for details.) 43 * 44 * @author Masayoshi Okutsu 45 * @since 1.5 46 */ 47 48 public abstract class AbstractCalendar extends CalendarSystem { 49 50 // The constants assume no leap seconds support. 51 static final int SECOND_IN_MILLIS = 1000; 52 static final int MINUTE_IN_MILLIS = SECOND_IN_MILLIS * 60; 53 static final int HOUR_IN_MILLIS = MINUTE_IN_MILLIS * 60; 54 static final int DAY_IN_MILLIS = HOUR_IN_MILLIS * 24; 55 56 // The number of days between January 1, 1 and January 1, 1970 (Gregorian) 57 static final int EPOCH_OFFSET = 719163; 58 59 private Era[] eras; 60 61 protected AbstractCalendar() { 62 } 63 64 public Era getEra(String eraName) { 65 if (eras != null) { 66 for (Era era : eras) { 67 if (era.getName().equals(eraName)) { 68 return era; 69 } 70 } 71 } 72 return null; 73 } 74 75 public Era[] getEras() { 76 Era[] e = null; 77 if (eras != null) { 78 e = new Era[eras.length]; 79 System.arraycopy(eras, 0, e, 0, eras.length); 80 } 81 return e; 82 } 83 84 public void setEra(CalendarDate date, String eraName) { 85 if (eras == null) { 86 return; // should report an error??? 87 } 88 for (int i = 0; i < eras.length; i++) { 89 Era e = eras[i]; 90 if (e != null && e.getName().equals(eraName)) { 91 date.setEra(e); 92 return; 93 } 94 } 95 throw new IllegalArgumentException("unknown era name: " + eraName); 96 } 97 98 protected void setEras(Era[] eras) { 99 this.eras = eras; 100 } 101 102 public CalendarDate getCalendarDate() { 103 return getCalendarDate(System.currentTimeMillis(), newCalendarDate()); 104 } 105 106 public CalendarDate getCalendarDate(long millis) { 107 return getCalendarDate(millis, newCalendarDate()); 108 } 109 110 public CalendarDate getCalendarDate(long millis, TimeZone zone) { 111 CalendarDate date = newCalendarDate(zone); 112 return getCalendarDate(millis, date); 113 } 114 115 public CalendarDate getCalendarDate(long millis, CalendarDate date) { 116 int ms = 0; // time of day 117 int zoneOffset = 0; 118 int saving = 0; 119 long days = 0; // fixed date 120 121 // adjust to local time if `date' has time zone. 122 TimeZone zi = date.getZone(); 123 if (zi != null) { 124 int[] offsets = new int[2]; 125 if (zi instanceof ZoneInfo) { 126 zoneOffset = ((ZoneInfo)zi).getOffsets(millis, offsets); 127 } else { 128 zoneOffset = zi.getOffset(millis); 129 offsets[0] = zi.getRawOffset(); 130 offsets[1] = zoneOffset - offsets[0]; 131 } 132 133 // We need to calculate the given millis and time zone 134 // offset separately for java.util.GregorianCalendar 135 // compatibility. (i.e., millis + zoneOffset could cause 136 // overflow or underflow, which must be avoided.) Usually 137 // days should be 0 and ms is in the range of -13:00 to 138 // +14:00. However, we need to deal with extreme cases. 139 days = zoneOffset / DAY_IN_MILLIS; 140 ms = zoneOffset % DAY_IN_MILLIS; 141 saving = offsets[1]; 142 } 143 date.setZoneOffset(zoneOffset); 144 date.setDaylightSaving(saving); 145 146 days += millis / DAY_IN_MILLIS; 147 ms += (int) (millis % DAY_IN_MILLIS); 148 if (ms >= DAY_IN_MILLIS) { 149 // at most ms is (DAY_IN_MILLIS - 1) * 2. 150 ms -= DAY_IN_MILLIS; 151 ++days; 152 } else { 153 // at most ms is (1 - DAY_IN_MILLIS) * 2. Adding one 154 // DAY_IN_MILLIS results in still negative. 155 while (ms < 0) { 156 ms += DAY_IN_MILLIS; 157 --days; 158 } 159 } 160 161 // convert to fixed date (offset from Jan. 1, 1 (Gregorian)) 162 days += EPOCH_OFFSET; 163 164 // calculate date fields from the fixed date 165 getCalendarDateFromFixedDate(date, days); 166 167 // calculate time fields from the time of day 168 setTimeOfDay(date, ms); 169 date.setLeapYear(isLeapYear(date)); 170 date.setNormalized(true); 171 return date; 172 } 173 174 public long getTime(CalendarDate date) { 175 long gd = getFixedDate(date); 176 long ms = (gd - EPOCH_OFFSET) * DAY_IN_MILLIS + getTimeOfDay(date); 177 int zoneOffset = 0; 178 TimeZone zi = date.getZone(); 179 if (zi != null) { 180 if (date.isNormalized()) { 181 return ms - date.getZoneOffset(); 182 } 183 // adjust time zone and daylight saving 184 int[] offsets = new int[2]; 185 if (date.isStandardTime()) { 186 // 1) 2:30am during starting-DST transition is 187 // intrepreted as 2:30am ST 188 // 2) 5:00pm during DST is still interpreted as 5:00pm ST 189 // 3) 1:30am during ending-DST transition is interpreted 190 // as 1:30am ST (after transition) 191 if (zi instanceof ZoneInfo) { 192 ((ZoneInfo)zi).getOffsetsByStandard(ms, offsets); 193 zoneOffset = offsets[0]; 194 } else { 195 zoneOffset = zi.getOffset(ms - zi.getRawOffset()); 196 } 197 } else { 198 // 1) 2:30am during starting-DST transition is 199 // intrepreted as 3:30am DT 200 // 2) 5:00pm during DST is intrepreted as 5:00pm DT 201 // 3) 1:30am during ending-DST transition is interpreted 202 // as 1:30am DT/0:30am ST (before transition) 203 if (zi instanceof ZoneInfo) { 204 zoneOffset = ((ZoneInfo)zi).getOffsetsByWall(ms, offsets); 205 } else { 206 zoneOffset = zi.getOffset(ms - zi.getRawOffset()); 207 } 208 } 209 } 210 ms -= zoneOffset; 211 getCalendarDate(ms, date); 212 return ms; 213 } 214 215 protected long getTimeOfDay(CalendarDate date) { 216 long fraction = date.getTimeOfDay(); 217 if (fraction != CalendarDate.TIME_UNDEFINED) { 218 return fraction; 219 } 220 fraction = getTimeOfDayValue(date); 221 date.setTimeOfDay(fraction); 222 return fraction; 223 } 224 225 public long getTimeOfDayValue(CalendarDate date) { 226 long fraction = date.getHours(); 227 fraction *= 60; 228 fraction += date.getMinutes(); 229 fraction *= 60; 230 fraction += date.getSeconds(); 231 fraction *= 1000; 232 fraction += date.getMillis(); 233 return fraction; 234 } 235 236 public CalendarDate setTimeOfDay(CalendarDate cdate, int fraction) { 237 if (fraction < 0) { 238 throw new IllegalArgumentException(); 239 } 240 boolean normalizedState = cdate.isNormalized(); 241 int time = fraction; 242 int hours = time / HOUR_IN_MILLIS; 243 time %= HOUR_IN_MILLIS; 244 int minutes = time / MINUTE_IN_MILLIS; 245 time %= MINUTE_IN_MILLIS; 246 int seconds = time / SECOND_IN_MILLIS; 247 time %= SECOND_IN_MILLIS; 248 cdate.setHours(hours); 249 cdate.setMinutes(minutes); 250 cdate.setSeconds(seconds); 251 cdate.setMillis(time); 252 cdate.setTimeOfDay(fraction); 253 if (hours < 24 && normalizedState) { 254 // If this time of day setting doesn't affect the date, 255 // then restore the normalized state. 256 cdate.setNormalized(normalizedState); 257 } 258 return cdate; 259 } 260 261 /** 262 * Returns 7 in this default implementation. 263 * 264 * @return 7 265 */ 266 public int getWeekLength() { 267 return 7; 268 } 269 270 protected abstract boolean isLeapYear(CalendarDate date); 271 272 public CalendarDate getNthDayOfWeek(int nth, int dayOfWeek, CalendarDate date) { 273 CalendarDate ndate = (CalendarDate) date.clone(); 274 normalize(ndate); 275 long fd = getFixedDate(ndate); 276 long nfd; 277 if (nth > 0) { 278 nfd = 7 * nth + getDayOfWeekDateBefore(fd, dayOfWeek); 279 } else { 280 nfd = 7 * nth + getDayOfWeekDateAfter(fd, dayOfWeek); 281 } 282 getCalendarDateFromFixedDate(ndate, nfd); 283 return ndate; 284 } 285 286 /** 287 * Returns a date of the given day of week before the given fixed 288 * date. 289 * 290 * @param fixedDate the fixed date 291 * @param dayOfWeek the day of week 292 * @return the calculated date 293 */ 294 static long getDayOfWeekDateBefore(long fixedDate, int dayOfWeek) { 295 return getDayOfWeekDateOnOrBefore(fixedDate - 1, dayOfWeek); 296 } 297 298 /** 299 * Returns a date of the given day of week that is closest to and 300 * after the given fixed date. 301 * 302 * @param fixedDate the fixed date 303 * @param dayOfWeek the day of week 304 * @return the calculated date 305 */ 306 static long getDayOfWeekDateAfter(long fixedDate, int dayOfWeek) { 307 return getDayOfWeekDateOnOrBefore(fixedDate + 7, dayOfWeek); 308 } 309 310 /** 311 * Returns a date of the given day of week on or before the given fixed 312 * date. 313 * 314 * @param fixedDate the fixed date 315 * @param dayOfWeek the day of week 316 * @return the calculated date 317 */ 318 // public for java.util.GregorianCalendar 319 public static long getDayOfWeekDateOnOrBefore(long fixedDate, int dayOfWeek) { 320 long fd = fixedDate - (dayOfWeek - 1); 321 if (fd >= 0) { 322 return fixedDate - (fd % 7); 323 } 324 return fixedDate - CalendarUtils.mod(fd, 7); 325 } 326 327 /** 328 * Returns the fixed date calculated with the specified calendar 329 * date. If the specified date is not normalized, its date fields 330 * are normalized. 331 * 332 * @param date a <code>CalendarDate</code> with which the fixed 333 * date is calculated 334 * @return the calculated fixed date 335 * @see AbstractCalendar.html#fixed_date 336 */ 337 protected abstract long getFixedDate(CalendarDate date); 338 339 /** 340 * Calculates calendar fields from the specified fixed date. This 341 * method stores the calculated calendar field values in the specified 342 * <code>CalendarDate</code>. 343 * 344 * @param date a <code>CalendarDate</code> to stored the 345 * calculated calendar fields. 346 * @param fixedDate a fixed date to calculate calendar fields 347 * @see AbstractCalendar.html#fixed_date 348 */ 349 protected abstract void getCalendarDateFromFixedDate(CalendarDate date, 350 long fixedDate); 351 352 public boolean validateTime(CalendarDate date) { 353 int t = date.getHours(); 354 if (t < 0 || t >= 24) { 355 return false; 356 } 357 t = date.getMinutes(); 358 if (t < 0 || t >= 60) { 359 return false; 360 } 361 t = date.getSeconds(); 362 // TODO: Leap second support. 363 if (t < 0 || t >= 60) { 364 return false; 365 } 366 t = date.getMillis(); 367 if (t < 0 || t >= 1000) { 368 return false; 369 } 370 return true; 371 } 372 373 374 int normalizeTime(CalendarDate date) { 375 long fraction = getTimeOfDay(date); 376 long days = 0; 377 378 if (fraction >= DAY_IN_MILLIS) { 379 days = fraction / DAY_IN_MILLIS; 380 fraction %= DAY_IN_MILLIS; 381 } else if (fraction < 0) { 382 days = CalendarUtils.floorDivide(fraction, DAY_IN_MILLIS); 383 if (days != 0) { 384 fraction -= DAY_IN_MILLIS * days; // mod(fraction, DAY_IN_MILLIS) 385 } 386 } 387 if (days != 0) { 388 date.setTimeOfDay(fraction); 389 } 390 date.setMillis((int)(fraction % 1000)); 391 fraction /= 1000; 392 date.setSeconds((int)(fraction % 60)); 393 fraction /= 60; 394 date.setMinutes((int)(fraction % 60)); 395 date.setHours((int)(fraction / 60)); 396 return (int)days; 397 } 398 }