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 @jdk.internal.MigratedValueClass
144 abstract class ChronoLocalDateImpl<D extends ChronoLocalDate>
145 implements ChronoLocalDate, Temporal, TemporalAdjuster, Serializable {
146
147 /**
148 * Serialization version.
149 */
150 @java.io.Serial
151 private static final long serialVersionUID = 6282433883239719096L;
152
153 /**
154 * Casts the {@code Temporal} to {@code ChronoLocalDate} ensuring it bas the specified chronology.
155 *
156 * @param chrono the chronology to check for, not null
157 * @param temporal a date-time to cast, not null
158 * @return the date-time checked and cast to {@code ChronoLocalDate}, not null
159 * @throws ClassCastException if the date-time cannot be cast to ChronoLocalDate
160 * or the chronology is not equal this Chronology
161 */
162 static <D extends ChronoLocalDate> D ensureValid(Chronology chrono, Temporal temporal) {
163 @SuppressWarnings("unchecked")
164 D other = (D) temporal;
165 if (chrono.equals(other.getChronology()) == false) {
166 throw new ClassCastException("Chronology mismatch, expected: " + chrono.getId() + ", actual: " + other.getChronology().getId());
167 }
168 return other;
169 }
170
171 //-----------------------------------------------------------------------
172 /**
173 * Creates an instance.
174 */
175 ChronoLocalDateImpl() {
176 }
177
178 @Override
179 @SuppressWarnings("unchecked")
180 public D with(TemporalAdjuster adjuster) {
181 return (D) ChronoLocalDate.super.with(adjuster);
182 }
183
184 @Override
185 @SuppressWarnings("unchecked")
186 public D with(TemporalField field, long value) {
187 return (D) ChronoLocalDate.super.with(field, value);
188 }
189
190 //-----------------------------------------------------------------------
191 @Override
192 @SuppressWarnings("unchecked")
193 public D plus(TemporalAmount amount) {
194 return (D) ChronoLocalDate.super.plus(amount);
195 }
196
197 //-----------------------------------------------------------------------
198 @Override
199 @SuppressWarnings("unchecked")
200 public D plus(long amountToAdd, TemporalUnit unit) {
201 if (unit instanceof ChronoUnit chronoUnit) {
202 return switch (chronoUnit) {
203 case DAYS -> plusDays(amountToAdd);
204 case WEEKS -> plusDays(Math.multiplyExact(amountToAdd, 7));
205 case MONTHS -> plusMonths(amountToAdd);
206 case YEARS -> plusYears(amountToAdd);
207 case DECADES -> plusYears(Math.multiplyExact(amountToAdd, 10));
208 case CENTURIES -> plusYears(Math.multiplyExact(amountToAdd, 100));
209 case MILLENNIA -> plusYears(Math.multiplyExact(amountToAdd, 1000));
210 case ERAS -> with(ERA, Math.addExact(getLong(ERA), amountToAdd));
211 default -> throw new UnsupportedTemporalTypeException("Unsupported unit: " + unit);
212 };
213 }
214 return (D) ChronoLocalDate.super.plus(amountToAdd, unit);
215 }
216
217 @Override
218 @SuppressWarnings("unchecked")
219 public D minus(TemporalAmount amount) {
220 return (D) ChronoLocalDate.super.minus(amount);
221 }
222
223 @Override
224 @SuppressWarnings("unchecked")
225 public D minus(long amountToSubtract, TemporalUnit unit) {
226 return (D) ChronoLocalDate.super.minus(amountToSubtract, unit);
227 }
228
229 //-----------------------------------------------------------------------
230 /**
231 * Returns a copy of this date with the specified number of years added.
232 * <p>
233 * This adds the specified period in years to the date.
234 * In some cases, adding years can cause the resulting date to become invalid.
235 * If this occurs, then other fields, typically the day-of-month, will be adjusted to ensure
236 * that the result is valid. Typically this will select the last valid day of the month.
237 * <p>
238 * This instance is immutable and unaffected by this method call.
239 *
240 * @param yearsToAdd the years to add, may be negative
241 * @return a date based on this one with the years added, not null
242 * @throws DateTimeException if the result exceeds the supported date range
243 */
244 abstract D plusYears(long yearsToAdd);
245
246 /**
247 * Returns a copy of this date with the specified number of months added.
248 * <p>
249 * This adds the specified period in months to the date.
250 * In some cases, adding months can cause the resulting date to become invalid.
251 * If this occurs, then other fields, typically the day-of-month, will be adjusted to ensure
252 * that the result is valid. Typically this will select the last valid day of the month.
253 * <p>
254 * This instance is immutable and unaffected by this method call.
255 *
256 * @param monthsToAdd the months to add, may be negative
257 * @return a date based on this one with the months added, not null
258 * @throws DateTimeException if the result exceeds the supported date range
259 */
260 abstract D plusMonths(long monthsToAdd);
261
262 /**
263 * Returns a copy of this date with the specified number of weeks added.
264 * <p>
265 * This adds the specified period in weeks to the date.
266 * In some cases, adding weeks can cause the resulting date to become invalid.
267 * If this occurs, then other fields will be adjusted to ensure that the result is valid.
268 * <p>
269 * The default implementation uses {@link #plusDays(long)} using a 7 day week.
270 * <p>
271 * This instance is immutable and unaffected by this method call.
272 *
273 * @param weeksToAdd the weeks to add, may be negative
274 * @return a date based on this one with the weeks added, not null
275 * @throws DateTimeException if the result exceeds the supported date range
276 */
277 D plusWeeks(long weeksToAdd) {
278 return plusDays(Math.multiplyExact(weeksToAdd, 7));
279 }
280
281 /**
282 * Returns a copy of this date with the specified number of days added.
283 * <p>
284 * This adds the specified period in days to the date.
285 * <p>
286 * This instance is immutable and unaffected by this method call.
287 *
288 * @param daysToAdd the days to add, may be negative
289 * @return a date based on this one with the days added, not null
290 * @throws DateTimeException if the result exceeds the supported date range
291 */
292 abstract D plusDays(long daysToAdd);
293
294 //-----------------------------------------------------------------------
295 /**
296 * Returns a copy of this date with the specified number of years subtracted.
297 * <p>
298 * This subtracts the specified period in years to the date.
299 * In some cases, subtracting years can cause the resulting date to become invalid.
300 * If this occurs, then other fields, typically the day-of-month, will be adjusted to ensure
301 * that the result is valid. Typically this will select the last valid day of the month.
302 * <p>
303 * The default implementation uses {@link #plusYears(long)}.
304 * <p>
305 * This instance is immutable and unaffected by this method call.
306 *
307 * @param yearsToSubtract the years to subtract, may be negative
308 * @return a date based on this one with the years subtracted, not null
309 * @throws DateTimeException if the result exceeds the supported date range
310 */
311 @SuppressWarnings("unchecked")
312 D minusYears(long yearsToSubtract) {
313 return (yearsToSubtract == Long.MIN_VALUE ? ((ChronoLocalDateImpl<D>)plusYears(Long.MAX_VALUE)).plusYears(1) : plusYears(-yearsToSubtract));
314 }
315
316 /**
317 * Returns a copy of this date with the specified number of months subtracted.
318 * <p>
319 * This subtracts the specified period in months to the date.
320 * In some cases, subtracting months can cause the resulting date to become invalid.
321 * If this occurs, then other fields, typically the day-of-month, will be adjusted to ensure
322 * that the result is valid. Typically this will select the last valid day of the month.
323 * <p>
324 * The default implementation uses {@link #plusMonths(long)}.
325 * <p>
326 * This instance is immutable and unaffected by this method call.
327 *
328 * @param monthsToSubtract the months to subtract, may be negative
329 * @return a date based on this one with the months subtracted, not null
330 * @throws DateTimeException if the result exceeds the supported date range
331 */
332 @SuppressWarnings("unchecked")
333 D minusMonths(long monthsToSubtract) {
334 return (monthsToSubtract == Long.MIN_VALUE ? ((ChronoLocalDateImpl<D>)plusMonths(Long.MAX_VALUE)).plusMonths(1) : plusMonths(-monthsToSubtract));
335 }
336
337 /**
338 * Returns a copy of this date with the specified number of weeks subtracted.
339 * <p>
340 * This subtracts the specified period in weeks to the date.
341 * In some cases, subtracting weeks can cause the resulting date to become invalid.
342 * If this occurs, then other fields will be adjusted to ensure that the result is valid.
343 * <p>
344 * The default implementation uses {@link #plusWeeks(long)}.
345 * <p>
346 * This instance is immutable and unaffected by this method call.
347 *
348 * @param weeksToSubtract the weeks to subtract, may be negative
349 * @return a date based on this one with the weeks subtracted, not null
350 * @throws DateTimeException if the result exceeds the supported date range
351 */
352 @SuppressWarnings("unchecked")
353 D minusWeeks(long weeksToSubtract) {
354 return (weeksToSubtract == Long.MIN_VALUE ? ((ChronoLocalDateImpl<D>)plusWeeks(Long.MAX_VALUE)).plusWeeks(1) : plusWeeks(-weeksToSubtract));
355 }
356
357 /**
358 * Returns a copy of this date with the specified number of days subtracted.
359 * <p>
360 * This subtracts the specified period in days to the date.
361 * <p>
362 * The default implementation uses {@link #plusDays(long)}.
363 * <p>
364 * This instance is immutable and unaffected by this method call.
365 *
366 * @param daysToSubtract the days to subtract, may be negative
367 * @return a date based on this one with the days subtracted, not null
368 * @throws DateTimeException if the result exceeds the supported date range
369 */
370 @SuppressWarnings("unchecked")
371 D minusDays(long daysToSubtract) {
372 return (daysToSubtract == Long.MIN_VALUE ? ((ChronoLocalDateImpl<D>)plusDays(Long.MAX_VALUE)).plusDays(1) : plusDays(-daysToSubtract));
373 }
374
375 //-----------------------------------------------------------------------
376 @Override
377 public long until(Temporal endExclusive, TemporalUnit unit) {
378 Objects.requireNonNull(endExclusive, "endExclusive");
379 ChronoLocalDate end = getChronology().date(endExclusive);
380 if (unit instanceof ChronoUnit chronoUnit) {
381 return switch (chronoUnit) {
382 case DAYS -> daysUntil(end);
383 case WEEKS -> daysUntil(end) / 7;
384 case MONTHS -> monthsUntil(end);
385 case YEARS -> monthsUntil(end) / 12;
386 case DECADES -> monthsUntil(end) / 120;
387 case CENTURIES -> monthsUntil(end) / 1200;
388 case MILLENNIA -> monthsUntil(end) / 12000;
389 case ERAS -> end.getLong(ERA) - getLong(ERA);
390 default -> throw new UnsupportedTemporalTypeException("Unsupported unit: " + unit);
391 };
392 }
393 Objects.requireNonNull(unit, "unit");
394 return unit.between(this, end);
395 }
396
397 private long daysUntil(ChronoLocalDate end) {
398 return end.toEpochDay() - toEpochDay(); // no overflow
399 }
400
401 private long monthsUntil(ChronoLocalDate end) {
402 ValueRange range = getChronology().range(MONTH_OF_YEAR);
403 if (range.getMaximum() != 12) {
404 throw new IllegalStateException("ChronoLocalDateImpl only supports Chronologies with 12 months per year");
405 }
406 long packed1 = getLong(PROLEPTIC_MONTH) * 32L + get(DAY_OF_MONTH); // no overflow
407 long packed2 = end.getLong(PROLEPTIC_MONTH) * 32L + end.get(DAY_OF_MONTH); // no overflow
408 return (packed2 - packed1) / 32;
409 }
410
411 @Override
412 public boolean equals(Object obj) {
413 if (this == obj) {
414 return true;
415 }
416 if (obj instanceof ChronoLocalDate) {
417 return compareTo((ChronoLocalDate) obj) == 0;
418 }
419 return false;
420 }
421
422 @Override
423 public int hashCode() {
424 long epDay = toEpochDay();
425 return getChronology().hashCode() ^ Long.hashCode(epDay);
426 }
427
428 @Override
429 public String toString() {
430 // getLong() reduces chances of exceptions in toString()
431 long yoe = getLong(YEAR_OF_ERA);
432 long moy = getLong(MONTH_OF_YEAR);
433 long dom = getLong(DAY_OF_MONTH);
434 StringBuilder buf = new StringBuilder(30);
435 buf.append(getChronology().toString())
436 .append(" ")
437 .append(getEra())
438 .append(" ")
439 .append(yoe)
440 .append(moy < 10 ? "-0" : "-").append(moy)
441 .append(dom < 10 ? "-0" : "-").append(dom);
442 return buf.toString();
443 }
444
445 }