1 /*
2 * Copyright (c) 2012, 2025, 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<Chronology> 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 * <h2>Adding Calendars</h2>
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 @java.io.Serial
150 private static final long serialVersionUID = 6282433883239719096L;
151
152 /**
153 * Casts the {@code Temporal} to {@code ChronoLocalDate} ensuring it bas the specified chronology.
154 *
155 * @param chrono the chronology to check for, not null
156 * @param temporal a date-time to cast, not null
157 * @return the date-time checked and cast to {@code ChronoLocalDate}, not null
158 * @throws ClassCastException if the date-time cannot be cast to ChronoLocalDate
159 * or the chronology is not equal this Chronology
160 */
161 static <D extends ChronoLocalDate> D ensureValid(Chronology chrono, Temporal temporal) {
162 @SuppressWarnings("unchecked")
163 D other = (D) temporal;
164 if (chrono.equals(other.getChronology()) == false) {
165 throw new ClassCastException("Chronology mismatch, expected: " + chrono.getId() + ", actual: " + other.getChronology().getId());
166 }
167 return other;
168 }
169
170 //-----------------------------------------------------------------------
171 /**
172 * Creates an instance.
173 */
174 ChronoLocalDateImpl() {
175 }
176
177 @Override
178 @SuppressWarnings("unchecked")
179 public D with(TemporalAdjuster adjuster) {
180 return (D) ChronoLocalDate.super.with(adjuster);
181 }
182
183 @Override
184 @SuppressWarnings("unchecked")
185 public D with(TemporalField field, long value) {
186 return (D) ChronoLocalDate.super.with(field, value);
187 }
188
189 //-----------------------------------------------------------------------
190 @Override
191 @SuppressWarnings("unchecked")
192 public D plus(TemporalAmount amount) {
193 return (D) ChronoLocalDate.super.plus(amount);
194 }
195
196 //-----------------------------------------------------------------------
197 @Override
198 @SuppressWarnings("unchecked")
199 public D plus(long amountToAdd, TemporalUnit unit) {
200 if (unit instanceof ChronoUnit chronoUnit) {
201 return switch (chronoUnit) {
202 case DAYS -> plusDays(amountToAdd);
203 case WEEKS -> plusDays(Math.multiplyExact(amountToAdd, 7));
204 case MONTHS -> plusMonths(amountToAdd);
205 case YEARS -> plusYears(amountToAdd);
206 case DECADES -> plusYears(Math.multiplyExact(amountToAdd, 10));
207 case CENTURIES -> plusYears(Math.multiplyExact(amountToAdd, 100));
208 case MILLENNIA -> plusYears(Math.multiplyExact(amountToAdd, 1000));
209 case ERAS -> with(ERA, Math.addExact(getLong(ERA), amountToAdd));
210 default -> throw new UnsupportedTemporalTypeException("Unsupported unit: " + unit);
211 };
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 chronoUnit) {
380 return switch (chronoUnit) {
381 case DAYS -> daysUntil(end);
382 case WEEKS -> daysUntil(end) / 7;
383 case MONTHS -> monthsUntil(end);
384 case YEARS -> monthsUntil(end) / 12;
385 case DECADES -> monthsUntil(end) / 120;
386 case CENTURIES -> monthsUntil(end) / 1200;
387 case MILLENNIA -> monthsUntil(end) / 12000;
388 case ERAS -> end.getLong(ERA) - getLong(ERA);
389 default -> throw new UnsupportedTemporalTypeException("Unsupported unit: " + unit);
390 };
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() ^ Long.hashCode(epDay);
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 }