Home | History | Annotate | Download | only in chrono
      1 /*
      2  * Copyright (c) 2012, 2015, 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 /*
     27  * Copyright (c) 2012, Stephen Colebourne & Michael Nascimento Santos
     28  *
     29  * All rights reserved.
     30  *
     31  * Redistribution and use in source and binary forms, with or without
     32  * modification, are permitted provided that the following conditions are met:
     33  *
     34  *  * Redistributions of source code must retain the above copyright notice,
     35  *    this list of conditions and the following disclaimer.
     36  *
     37  *  * Redistributions in binary form must reproduce the above copyright notice,
     38  *    this list of conditions and the following disclaimer in the documentation
     39  *    and/or other materials provided with the distribution.
     40  *
     41  *  * Neither the name of JSR-310 nor the names of its contributors
     42  *    may be used to endorse or promote products derived from this software
     43  *    without specific prior written permission.
     44  *
     45  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     46  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     47  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     48  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
     49  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     50  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     51  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     52  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
     53  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
     54  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
     55  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     56  */
     57 package java.time.chrono;
     58 
     59 import static java.time.temporal.ChronoField.DAY_OF_MONTH;
     60 import static java.time.temporal.ChronoField.ERA;
     61 import static java.time.temporal.ChronoField.MONTH_OF_YEAR;
     62 import static java.time.temporal.ChronoField.PROLEPTIC_MONTH;
     63 import static java.time.temporal.ChronoField.YEAR_OF_ERA;
     64 
     65 import java.io.Serializable;
     66 import java.time.DateTimeException;
     67 import java.time.temporal.ChronoUnit;
     68 import java.time.temporal.Temporal;
     69 import java.time.temporal.TemporalAdjuster;
     70 import java.time.temporal.TemporalAmount;
     71 import java.time.temporal.TemporalField;
     72 import java.time.temporal.TemporalUnit;
     73 import java.time.temporal.UnsupportedTemporalTypeException;
     74 import java.time.temporal.ValueRange;
     75 import java.util.Objects;
     76 
     77 /**
     78  * A date expressed in terms of a standard year-month-day calendar system.
     79  * <p>
     80  * This class is used by applications seeking to handle dates in non-ISO calendar systems.
     81  * For example, the Japanese, Minguo, Thai Buddhist and others.
     82  * <p>
     83  * {@code ChronoLocalDate} is built on the generic concepts of year, month and day.
     84  * The calendar system, represented by a {@link java.time.chrono.Chronology}, expresses the relationship between
     85  * the fields and this class allows the resulting date to be manipulated.
     86  * <p>
     87  * Note that not all calendar systems are suitable for use with this class.
     88  * For example, the Mayan calendar uses a system that bears no relation to years, months and days.
     89  * <p>
     90  * The API design encourages the use of {@code LocalDate} for the majority of the application.
     91  * This includes code to read and write from a persistent data store, such as a database,
     92  * and to send dates and times across a network. The {@code ChronoLocalDate} instance is then used
     93  * at the user interface level to deal with localized input/output.
     94  *
     95  * <P>Example: </p>
     96  * <pre>
     97  *        System.out.printf("Example()%n");
     98  *        // Enumerate the list of available calendars and print today for each
     99  *        Set&lt;Chronology&gt; chronos = Chronology.getAvailableChronologies();
    100  *        for (Chronology chrono : chronos) {
    101  *            ChronoLocalDate date = chrono.dateNow();
    102  *            System.out.printf("   %20s: %s%n", chrono.getID(), date.toString());
    103  *        }
    104  *
    105  *        // Print the Hijrah date and calendar
    106  *        ChronoLocalDate date = Chronology.of("Hijrah").dateNow();
    107  *        int day = date.get(ChronoField.DAY_OF_MONTH);
    108  *        int dow = date.get(ChronoField.DAY_OF_WEEK);
    109  *        int month = date.get(ChronoField.MONTH_OF_YEAR);
    110  *        int year = date.get(ChronoField.YEAR);
    111  *        System.out.printf("  Today is %s %s %d-%s-%d%n", date.getChronology().getID(),
    112  *                dow, day, month, year);
    113 
    114  *        // Print today's date and the last day of the year
    115  *        ChronoLocalDate now1 = Chronology.of("Hijrah").dateNow();
    116  *        ChronoLocalDate first = now1.with(ChronoField.DAY_OF_MONTH, 1)
    117  *                .with(ChronoField.MONTH_OF_YEAR, 1);
    118  *        ChronoLocalDate last = first.plus(1, ChronoUnit.YEARS)
    119  *                .minus(1, ChronoUnit.DAYS);
    120  *        System.out.printf("  Today is %s: start: %s; end: %s%n", last.getChronology().getID(),
    121  *                first, last);
    122  * </pre>
    123  *
    124  * <h3>Adding Calendars</h3>
    125  * <p> The set of calendars is extensible by defining a subclass of {@link ChronoLocalDate}
    126  * to represent a date instance and an implementation of {@code Chronology}
    127  * to be the factory for the ChronoLocalDate subclass.
    128  * </p>
    129  * <p> To permit the discovery of the additional calendar types the implementation of
    130  * {@code Chronology} must be registered as a Service implementing the {@code Chronology} interface
    131  * in the {@code META-INF/Services} file as per the specification of {@link java.util.ServiceLoader}.
    132  * The subclass must function according to the {@code Chronology} class description and must provide its
    133  * {@link java.time.chrono.Chronology#getId() chronlogy ID} and {@link Chronology#getCalendarType() calendar type}. </p>
    134  *
    135  * @implSpec
    136  * This abstract class must be implemented with care to ensure other classes operate correctly.
    137  * All implementations that can be instantiated must be final, immutable and thread-safe.
    138  * Subclasses should be Serializable wherever possible.
    139  *
    140  * @param <D> the ChronoLocalDate of this date-time
    141  * @since 1.8
    142  */
    143 abstract class ChronoLocalDateImpl<D extends ChronoLocalDate>
    144         implements ChronoLocalDate, Temporal, TemporalAdjuster, Serializable {
    145 
    146     /**
    147      * Serialization version.
    148      */
    149     private static final long serialVersionUID = 6282433883239719096L;
    150 
    151     /**
    152      * Casts the {@code Temporal} to {@code ChronoLocalDate} ensuring it bas the specified chronology.
    153      *
    154      * @param chrono  the chronology to check for, not null
    155      * @param temporal  a date-time to cast, not null
    156      * @return the date-time checked and cast to {@code ChronoLocalDate}, not null
    157      * @throws ClassCastException if the date-time cannot be cast to ChronoLocalDate
    158      *  or the chronology is not equal this Chronology
    159      */
    160     static <D extends ChronoLocalDate> D ensureValid(Chronology chrono, Temporal temporal) {
    161         @SuppressWarnings("unchecked")
    162         D other = (D) temporal;
    163         if (chrono.equals(other.getChronology()) == false) {
    164             throw new ClassCastException("Chronology mismatch, expected: " + chrono.getId() + ", actual: " + other.getChronology().getId());
    165         }
    166         return other;
    167     }
    168 
    169     //-----------------------------------------------------------------------
    170     /**
    171      * Creates an instance.
    172      */
    173     ChronoLocalDateImpl() {
    174     }
    175 
    176     @Override
    177     @SuppressWarnings("unchecked")
    178     public D with(TemporalAdjuster adjuster) {
    179         return (D) ChronoLocalDate.super.with(adjuster);
    180     }
    181 
    182     @Override
    183     @SuppressWarnings("unchecked")
    184     public D with(TemporalField field, long value) {
    185         return (D) ChronoLocalDate.super.with(field, value);
    186     }
    187 
    188     //-----------------------------------------------------------------------
    189     @Override
    190     @SuppressWarnings("unchecked")
    191     public D plus(TemporalAmount amount) {
    192         return (D) ChronoLocalDate.super.plus(amount);
    193     }
    194 
    195     //-----------------------------------------------------------------------
    196     @Override
    197     @SuppressWarnings("unchecked")
    198     public D plus(long amountToAdd, TemporalUnit unit) {
    199         if (unit instanceof ChronoUnit) {
    200             ChronoUnit f = (ChronoUnit) unit;
    201             switch (f) {
    202                 case DAYS: return plusDays(amountToAdd);
    203                 case WEEKS: return plusDays(Math.multiplyExact(amountToAdd, 7));
    204                 case MONTHS: return plusMonths(amountToAdd);
    205                 case YEARS: return plusYears(amountToAdd);
    206                 case DECADES: return plusYears(Math.multiplyExact(amountToAdd, 10));
    207                 case CENTURIES: return plusYears(Math.multiplyExact(amountToAdd, 100));
    208                 case MILLENNIA: return plusYears(Math.multiplyExact(amountToAdd, 1000));
    209                 case ERAS: return with(ERA, Math.addExact(getLong(ERA), amountToAdd));
    210             }
    211             throw new UnsupportedTemporalTypeException("Unsupported unit: " + unit);
    212         }
    213         return (D) ChronoLocalDate.super.plus(amountToAdd, unit);
    214     }
    215 
    216     @Override
    217     @SuppressWarnings("unchecked")
    218     public D minus(TemporalAmount amount) {
    219         return (D) ChronoLocalDate.super.minus(amount);
    220     }
    221 
    222     @Override
    223     @SuppressWarnings("unchecked")
    224     public D minus(long amountToSubtract, TemporalUnit unit) {
    225         return (D) ChronoLocalDate.super.minus(amountToSubtract, unit);
    226     }
    227 
    228     //-----------------------------------------------------------------------
    229     /**
    230      * Returns a copy of this date with the specified number of years added.
    231      * <p>
    232      * This adds the specified period in years to the date.
    233      * In some cases, adding years can cause the resulting date to become invalid.
    234      * If this occurs, then other fields, typically the day-of-month, will be adjusted to ensure
    235      * that the result is valid. Typically this will select the last valid day of the month.
    236      * <p>
    237      * This instance is immutable and unaffected by this method call.
    238      *
    239      * @param yearsToAdd  the years to add, may be negative
    240      * @return a date based on this one with the years added, not null
    241      * @throws DateTimeException if the result exceeds the supported date range
    242      */
    243     abstract D plusYears(long yearsToAdd);
    244 
    245     /**
    246      * Returns a copy of this date with the specified number of months added.
    247      * <p>
    248      * This adds the specified period in months to the date.
    249      * In some cases, adding months can cause the resulting date to become invalid.
    250      * If this occurs, then other fields, typically the day-of-month, will be adjusted to ensure
    251      * that the result is valid. Typically this will select the last valid day of the month.
    252      * <p>
    253      * This instance is immutable and unaffected by this method call.
    254      *
    255      * @param monthsToAdd  the months to add, may be negative
    256      * @return a date based on this one with the months added, not null
    257      * @throws DateTimeException if the result exceeds the supported date range
    258      */
    259     abstract D plusMonths(long monthsToAdd);
    260 
    261     /**
    262      * Returns a copy of this date with the specified number of weeks added.
    263      * <p>
    264      * This adds the specified period in weeks to the date.
    265      * In some cases, adding weeks can cause the resulting date to become invalid.
    266      * If this occurs, then other fields will be adjusted to ensure that the result is valid.
    267      * <p>
    268      * The default implementation uses {@link #plusDays(long)} using a 7 day week.
    269      * <p>
    270      * This instance is immutable and unaffected by this method call.
    271      *
    272      * @param weeksToAdd  the weeks to add, may be negative
    273      * @return a date based on this one with the weeks added, not null
    274      * @throws DateTimeException if the result exceeds the supported date range
    275      */
    276     D plusWeeks(long weeksToAdd) {
    277         return plusDays(Math.multiplyExact(weeksToAdd, 7));
    278     }
    279 
    280     /**
    281      * Returns a copy of this date with the specified number of days added.
    282      * <p>
    283      * This adds the specified period in days to the date.
    284      * <p>
    285      * This instance is immutable and unaffected by this method call.
    286      *
    287      * @param daysToAdd  the days to add, may be negative
    288      * @return a date based on this one with the days added, not null
    289      * @throws DateTimeException if the result exceeds the supported date range
    290      */
    291     abstract D plusDays(long daysToAdd);
    292 
    293     //-----------------------------------------------------------------------
    294     /**
    295      * Returns a copy of this date with the specified number of years subtracted.
    296      * <p>
    297      * This subtracts the specified period in years to the date.
    298      * In some cases, subtracting years can cause the resulting date to become invalid.
    299      * If this occurs, then other fields, typically the day-of-month, will be adjusted to ensure
    300      * that the result is valid. Typically this will select the last valid day of the month.
    301      * <p>
    302      * The default implementation uses {@link #plusYears(long)}.
    303      * <p>
    304      * This instance is immutable and unaffected by this method call.
    305      *
    306      * @param yearsToSubtract  the years to subtract, may be negative
    307      * @return a date based on this one with the years subtracted, not null
    308      * @throws DateTimeException if the result exceeds the supported date range
    309      */
    310     @SuppressWarnings("unchecked")
    311     D minusYears(long yearsToSubtract) {
    312         return (yearsToSubtract == Long.MIN_VALUE ? ((ChronoLocalDateImpl<D>)plusYears(Long.MAX_VALUE)).plusYears(1) : plusYears(-yearsToSubtract));
    313     }
    314 
    315     /**
    316      * Returns a copy of this date with the specified number of months subtracted.
    317      * <p>
    318      * This subtracts the specified period in months to the date.
    319      * In some cases, subtracting months can cause the resulting date to become invalid.
    320      * If this occurs, then other fields, typically the day-of-month, will be adjusted to ensure
    321      * that the result is valid. Typically this will select the last valid day of the month.
    322      * <p>
    323      * The default implementation uses {@link #plusMonths(long)}.
    324      * <p>
    325      * This instance is immutable and unaffected by this method call.
    326      *
    327      * @param monthsToSubtract  the months to subtract, may be negative
    328      * @return a date based on this one with the months subtracted, not null
    329      * @throws DateTimeException if the result exceeds the supported date range
    330      */
    331     @SuppressWarnings("unchecked")
    332     D minusMonths(long monthsToSubtract) {
    333         return (monthsToSubtract == Long.MIN_VALUE ? ((ChronoLocalDateImpl<D>)plusMonths(Long.MAX_VALUE)).plusMonths(1) : plusMonths(-monthsToSubtract));
    334     }
    335 
    336     /**
    337      * Returns a copy of this date with the specified number of weeks subtracted.
    338      * <p>
    339      * This subtracts the specified period in weeks to the date.
    340      * In some cases, subtracting weeks can cause the resulting date to become invalid.
    341      * If this occurs, then other fields will be adjusted to ensure that the result is valid.
    342      * <p>
    343      * The default implementation uses {@link #plusWeeks(long)}.
    344      * <p>
    345      * This instance is immutable and unaffected by this method call.
    346      *
    347      * @param weeksToSubtract  the weeks to subtract, may be negative
    348      * @return a date based on this one with the weeks subtracted, not null
    349      * @throws DateTimeException if the result exceeds the supported date range
    350      */
    351     @SuppressWarnings("unchecked")
    352     D minusWeeks(long weeksToSubtract) {
    353         return (weeksToSubtract == Long.MIN_VALUE ? ((ChronoLocalDateImpl<D>)plusWeeks(Long.MAX_VALUE)).plusWeeks(1) : plusWeeks(-weeksToSubtract));
    354     }
    355 
    356     /**
    357      * Returns a copy of this date with the specified number of days subtracted.
    358      * <p>
    359      * This subtracts the specified period in days to the date.
    360      * <p>
    361      * The default implementation uses {@link #plusDays(long)}.
    362      * <p>
    363      * This instance is immutable and unaffected by this method call.
    364      *
    365      * @param daysToSubtract  the days to subtract, may be negative
    366      * @return a date based on this one with the days subtracted, not null
    367      * @throws DateTimeException if the result exceeds the supported date range
    368      */
    369     @SuppressWarnings("unchecked")
    370     D minusDays(long daysToSubtract) {
    371         return (daysToSubtract == Long.MIN_VALUE ? ((ChronoLocalDateImpl<D>)plusDays(Long.MAX_VALUE)).plusDays(1) : plusDays(-daysToSubtract));
    372     }
    373 
    374     //-----------------------------------------------------------------------
    375     @Override
    376     public long until(Temporal endExclusive, TemporalUnit unit) {
    377         Objects.requireNonNull(endExclusive, "endExclusive");
    378         ChronoLocalDate end = getChronology().date(endExclusive);
    379         if (unit instanceof ChronoUnit) {
    380             switch ((ChronoUnit) unit) {
    381                 case DAYS: return daysUntil(end);
    382                 case WEEKS: return daysUntil(end) / 7;
    383                 case MONTHS: return monthsUntil(end);
    384                 case YEARS: return monthsUntil(end) / 12;
    385                 case DECADES: return monthsUntil(end) / 120;
    386                 case CENTURIES: return monthsUntil(end) / 1200;
    387                 case MILLENNIA: return monthsUntil(end) / 12000;
    388                 case ERAS: return end.getLong(ERA) - getLong(ERA);
    389             }
    390             throw new UnsupportedTemporalTypeException("Unsupported unit: " + unit);
    391         }
    392         Objects.requireNonNull(unit, "unit");
    393         return unit.between(this, end);
    394     }
    395 
    396     private long daysUntil(ChronoLocalDate end) {
    397         return end.toEpochDay() - toEpochDay();  // no overflow
    398     }
    399 
    400     private long monthsUntil(ChronoLocalDate end) {
    401         ValueRange range = getChronology().range(MONTH_OF_YEAR);
    402         if (range.getMaximum() != 12) {
    403             throw new IllegalStateException("ChronoLocalDateImpl only supports Chronologies with 12 months per year");
    404         }
    405         long packed1 = getLong(PROLEPTIC_MONTH) * 32L + get(DAY_OF_MONTH);  // no overflow
    406         long packed2 = end.getLong(PROLEPTIC_MONTH) * 32L + end.get(DAY_OF_MONTH);  // no overflow
    407         return (packed2 - packed1) / 32;
    408     }
    409 
    410     @Override
    411     public boolean equals(Object obj) {
    412         if (this == obj) {
    413             return true;
    414         }
    415         if (obj instanceof ChronoLocalDate) {
    416             return compareTo((ChronoLocalDate) obj) == 0;
    417         }
    418         return false;
    419     }
    420 
    421     @Override
    422     public int hashCode() {
    423         long epDay = toEpochDay();
    424         return getChronology().hashCode() ^ ((int) (epDay ^ (epDay >>> 32)));
    425     }
    426 
    427     @Override
    428     public String toString() {
    429         // getLong() reduces chances of exceptions in toString()
    430         long yoe = getLong(YEAR_OF_ERA);
    431         long moy = getLong(MONTH_OF_YEAR);
    432         long dom = getLong(DAY_OF_MONTH);
    433         StringBuilder buf = new StringBuilder(30);
    434         buf.append(getChronology().toString())
    435                 .append(" ")
    436                 .append(getEra())
    437                 .append(" ")
    438                 .append(yoe)
    439                 .append(moy < 10 ? "-0" : "-").append(moy)
    440                 .append(dom < 10 ? "-0" : "-").append(dom);
    441         return buf.toString();
    442     }
    443 
    444 }
    445