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 (Era era : eras) { 68 if (era.getName().equals(eraName)) { 69 return era; 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 // BEGIN Android-changed: Android doesn't have sun.util.calendar.ZoneInfo. 127 // if (zi instanceof ZoneInfo) { 128 // zoneOffset = ((ZoneInfo)zi).getOffsets(millis, offsets); 129 if (zi instanceof libcore.util.ZoneInfo) { 130 zoneOffset = ((libcore.util.ZoneInfo) zi).getOffsetsByUtcTime(millis, offsets); 131 // END Android-changed: Android doesn't have sun.util.calendar.ZoneInfo. 132 } else { 133 zoneOffset = zi.getOffset(millis); 134 offsets[0] = zi.getRawOffset(); 135 offsets[1] = zoneOffset - offsets[0]; 136 } 137 138 // We need to calculate the given millis and time zone 139 // offset separately for java.util.GregorianCalendar 140 // compatibility. (i.e., millis + zoneOffset could cause 141 // overflow or underflow, which must be avoided.) Usually 142 // days should be 0 and ms is in the range of -13:00 to 143 // +14:00. However, we need to deal with extreme cases. 144 days = zoneOffset / DAY_IN_MILLIS; 145 ms = zoneOffset % DAY_IN_MILLIS; 146 saving = offsets[1]; 147 } 148 date.setZoneOffset(zoneOffset); 149 date.setDaylightSaving(saving); 150 151 days += millis / DAY_IN_MILLIS; 152 ms += (int) (millis % DAY_IN_MILLIS); 153 if (ms >= DAY_IN_MILLIS) { 154 // at most ms is (DAY_IN_MILLIS - 1) * 2. 155 ms -= DAY_IN_MILLIS; 156 ++days; 157 } else { 158 // at most ms is (1 - DAY_IN_MILLIS) * 2. Adding one 159 // DAY_IN_MILLIS results in still negative. 160 while (ms < 0) { 161 ms += DAY_IN_MILLIS; 162 --days; 163 } 164 } 165 166 // convert to fixed date (offset from Jan. 1, 1 (Gregorian)) 167 days += EPOCH_OFFSET; 168 169 // calculate date fields from the fixed date 170 getCalendarDateFromFixedDate(date, days); 171 172 // calculate time fields from the time of day 173 setTimeOfDay(date, ms); 174 date.setLeapYear(isLeapYear(date)); 175 date.setNormalized(true); 176 return date; 177 } 178 getTime(CalendarDate date)179 public long getTime(CalendarDate date) { 180 long gd = getFixedDate(date); 181 long ms = (gd - EPOCH_OFFSET) * DAY_IN_MILLIS + getTimeOfDay(date); 182 int zoneOffset = 0; 183 TimeZone zi = date.getZone(); 184 if (zi != null) { 185 if (date.isNormalized()) { 186 return ms - date.getZoneOffset(); 187 } 188 // adjust time zone and daylight saving 189 int[] offsets = new int[2]; 190 if (date.isStandardTime()) { 191 // 1) 2:30am during starting-DST transition is 192 // intrepreted as 2:30am ST 193 // 2) 5:00pm during DST is still interpreted as 5:00pm ST 194 // 3) 1:30am during ending-DST transition is interpreted 195 // as 1:30am ST (after transition) 196 // Android-changed: Android doesn't have sun.util.calendar.ZoneInfo. 197 // if (zi instanceof ZoneInfo) { 198 // ((ZoneInfo)zi).getOffsetsByStandard(ms, offsets); 199 // zoneOffset = offsets[0]; 200 // } else { 201 zoneOffset = zi.getOffset(ms - zi.getRawOffset()); 202 // } 203 } else { 204 // 1) 2:30am during starting-DST transition is 205 // intrepreted as 3:30am DT 206 // 2) 5:00pm during DST is intrepreted as 5:00pm DT 207 // 3) 1:30am during ending-DST transition is interpreted 208 // as 1:30am DT/0:30am ST (before transition) 209 // Android-changed: Android doesn't have sun.util.calendar.ZoneInfo. 210 // if (zi instanceof ZoneInfo) { 211 // zoneOffset = ((ZoneInfo)zi).getOffsetsByWall(ms, offsets); 212 // } else { 213 zoneOffset = zi.getOffset(ms - zi.getRawOffset()); 214 // } 215 } 216 } 217 ms -= zoneOffset; 218 getCalendarDate(ms, date); 219 return ms; 220 } 221 getTimeOfDay(CalendarDate date)222 protected long getTimeOfDay(CalendarDate date) { 223 long fraction = date.getTimeOfDay(); 224 if (fraction != CalendarDate.TIME_UNDEFINED) { 225 return fraction; 226 } 227 fraction = getTimeOfDayValue(date); 228 date.setTimeOfDay(fraction); 229 return fraction; 230 } 231 getTimeOfDayValue(CalendarDate date)232 public long getTimeOfDayValue(CalendarDate date) { 233 long fraction = date.getHours(); 234 fraction *= 60; 235 fraction += date.getMinutes(); 236 fraction *= 60; 237 fraction += date.getSeconds(); 238 fraction *= 1000; 239 fraction += date.getMillis(); 240 return fraction; 241 } 242 setTimeOfDay(CalendarDate cdate, int fraction)243 public CalendarDate setTimeOfDay(CalendarDate cdate, int fraction) { 244 if (fraction < 0) { 245 throw new IllegalArgumentException(); 246 } 247 boolean normalizedState = cdate.isNormalized(); 248 int time = fraction; 249 int hours = time / HOUR_IN_MILLIS; 250 time %= HOUR_IN_MILLIS; 251 int minutes = time / MINUTE_IN_MILLIS; 252 time %= MINUTE_IN_MILLIS; 253 int seconds = time / SECOND_IN_MILLIS; 254 time %= SECOND_IN_MILLIS; 255 cdate.setHours(hours); 256 cdate.setMinutes(minutes); 257 cdate.setSeconds(seconds); 258 cdate.setMillis(time); 259 cdate.setTimeOfDay(fraction); 260 if (hours < 24 && normalizedState) { 261 // If this time of day setting doesn't affect the date, 262 // then restore the normalized state. 263 cdate.setNormalized(normalizedState); 264 } 265 return cdate; 266 } 267 268 /** 269 * Returns 7 in this default implementation. 270 * 271 * @return 7 272 */ getWeekLength()273 public int getWeekLength() { 274 return 7; 275 } 276 isLeapYear(CalendarDate date)277 protected abstract boolean isLeapYear(CalendarDate date); 278 getNthDayOfWeek(int nth, int dayOfWeek, CalendarDate date)279 public CalendarDate getNthDayOfWeek(int nth, int dayOfWeek, CalendarDate date) { 280 CalendarDate ndate = (CalendarDate) date.clone(); 281 normalize(ndate); 282 long fd = getFixedDate(ndate); 283 long nfd; 284 if (nth > 0) { 285 nfd = 7 * nth + getDayOfWeekDateBefore(fd, dayOfWeek); 286 } else { 287 nfd = 7 * nth + getDayOfWeekDateAfter(fd, dayOfWeek); 288 } 289 getCalendarDateFromFixedDate(ndate, nfd); 290 return ndate; 291 } 292 293 /** 294 * Returns a date of the given day of week before the given fixed 295 * date. 296 * 297 * @param fixedDate the fixed date 298 * @param dayOfWeek the day of week 299 * @return the calculated date 300 */ getDayOfWeekDateBefore(long fixedDate, int dayOfWeek)301 static long getDayOfWeekDateBefore(long fixedDate, int dayOfWeek) { 302 return getDayOfWeekDateOnOrBefore(fixedDate - 1, dayOfWeek); 303 } 304 305 /** 306 * Returns a date of the given day of week that is closest to and 307 * after the given fixed date. 308 * 309 * @param fixedDate the fixed date 310 * @param dayOfWeek the day of week 311 * @return the calculated date 312 */ getDayOfWeekDateAfter(long fixedDate, int dayOfWeek)313 static long getDayOfWeekDateAfter(long fixedDate, int dayOfWeek) { 314 return getDayOfWeekDateOnOrBefore(fixedDate + 7, dayOfWeek); 315 } 316 317 /** 318 * Returns a date of the given day of week on or before the given fixed 319 * date. 320 * 321 * @param fixedDate the fixed date 322 * @param dayOfWeek the day of week 323 * @return the calculated date 324 */ 325 // public for java.util.GregorianCalendar getDayOfWeekDateOnOrBefore(long fixedDate, int dayOfWeek)326 public static long getDayOfWeekDateOnOrBefore(long fixedDate, int dayOfWeek) { 327 long fd = fixedDate - (dayOfWeek - 1); 328 if (fd >= 0) { 329 return fixedDate - (fd % 7); 330 } 331 return fixedDate - CalendarUtils.mod(fd, 7); 332 } 333 334 /** 335 * Returns the fixed date calculated with the specified calendar 336 * date. If the specified date is not normalized, its date fields 337 * are normalized. 338 * 339 * @param date a <code>CalendarDate</code> with which the fixed 340 * date is calculated 341 * @return the calculated fixed date 342 * @see AbstractCalendar.html#fixed_date 343 */ getFixedDate(CalendarDate date)344 protected abstract long getFixedDate(CalendarDate date); 345 346 /** 347 * Calculates calendar fields from the specified fixed date. This 348 * method stores the calculated calendar field values in the specified 349 * <code>CalendarDate</code>. 350 * 351 * @param date a <code>CalendarDate</code> to stored the 352 * calculated calendar fields. 353 * @param fixedDate a fixed date to calculate calendar fields 354 * @see AbstractCalendar.html#fixed_date 355 */ getCalendarDateFromFixedDate(CalendarDate date, long fixedDate)356 protected abstract void getCalendarDateFromFixedDate(CalendarDate date, 357 long fixedDate); 358 validateTime(CalendarDate date)359 public boolean validateTime(CalendarDate date) { 360 int t = date.getHours(); 361 if (t < 0 || t >= 24) { 362 return false; 363 } 364 t = date.getMinutes(); 365 if (t < 0 || t >= 60) { 366 return false; 367 } 368 t = date.getSeconds(); 369 // TODO: Leap second support. 370 if (t < 0 || t >= 60) { 371 return false; 372 } 373 t = date.getMillis(); 374 if (t < 0 || t >= 1000) { 375 return false; 376 } 377 return true; 378 } 379 380 normalizeTime(CalendarDate date)381 int normalizeTime(CalendarDate date) { 382 long fraction = getTimeOfDay(date); 383 long days = 0; 384 385 if (fraction >= DAY_IN_MILLIS) { 386 days = fraction / DAY_IN_MILLIS; 387 fraction %= DAY_IN_MILLIS; 388 } else if (fraction < 0) { 389 days = CalendarUtils.floorDivide(fraction, DAY_IN_MILLIS); 390 if (days != 0) { 391 fraction -= DAY_IN_MILLIS * days; // mod(fraction, DAY_IN_MILLIS) 392 } 393 } 394 if (days != 0) { 395 date.setTimeOfDay(fraction); 396 } 397 date.setMillis((int)(fraction % 1000)); 398 fraction /= 1000; 399 date.setSeconds((int)(fraction % 60)); 400 fraction /= 60; 401 date.setMinutes((int)(fraction % 60)); 402 date.setHours((int)(fraction / 60)); 403 return (int)days; 404 } 405 } 406