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 }