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