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 import jdk.internal.util.DecimalDigits;
78
79 /**
80 * A date expressed in terms of a standard year-month-day calendar system.
81 * <p>
82 * This class is used by applications seeking to handle dates in non-ISO calendar systems.
83 * For example, the Japanese, Minguo, Thai Buddhist and others.
84 * <p>
85 * {@code ChronoLocalDate} is built on the generic concepts of year, month and day.
86 * The calendar system, represented by a {@link java.time.chrono.Chronology}, expresses the relationship between
87 * the fields and this class allows the resulting date to be manipulated.
88 * <p>
89 * Note that not all calendar systems are suitable for use with this class.
90 * For example, the Mayan calendar uses a system that bears no relation to years, months and days.
91 * <p>
92 * The API design encourages the use of {@code LocalDate} for the majority of the application.
93 * This includes code to read and write from a persistent data store, such as a database,
94 * and to send dates and times across a network. The {@code ChronoLocalDate} instance is then used
95 * at the user interface level to deal with localized input/output.
96 *
97 * <P>Example: </p>
98 * <pre>
99 * System.out.printf("Example()%n");
100 * // Enumerate the list of available calendars and print today for each
101 * Set<Chronology> chronos = Chronology.getAvailableChronologies();
102 * for (Chronology chrono : chronos) {
103 * ChronoLocalDate date = chrono.dateNow();
104 * System.out.printf(" %20s: %s%n", chrono.getID(), date.toString());
105 * }
106 *
107 * // Print the Hijrah date and calendar
108 * ChronoLocalDate date = Chronology.of("Hijrah").dateNow();
109 * int day = date.get(ChronoField.DAY_OF_MONTH);
110 * int dow = date.get(ChronoField.DAY_OF_WEEK);
111 * int month = date.get(ChronoField.MONTH_OF_YEAR);
112 * int year = date.get(ChronoField.YEAR);
113 * System.out.printf(" Today is %s %s %d-%s-%d%n", date.getChronology().getID(),
114 * dow, day, month, year);
115 *
116 * // Print today's date and the last day of the year
117 * ChronoLocalDate now1 = Chronology.of("Hijrah").dateNow();
118 * ChronoLocalDate first = now1.with(ChronoField.DAY_OF_MONTH, 1)
119 * .with(ChronoField.MONTH_OF_YEAR, 1);
120 * ChronoLocalDate last = first.plus(1, ChronoUnit.YEARS)
121 * .minus(1, ChronoUnit.DAYS);
122 * System.out.printf(" Today is %s: start: %s; end: %s%n", last.getChronology().getID(),
123 * first, last);
124 * </pre>
125 *
126 * <h2>Adding Calendars</h2>
127 * <p> The set of calendars is extensible by defining a subclass of {@link ChronoLocalDate}
128 * to represent a date instance and an implementation of {@code Chronology}
129 * to be the factory for the ChronoLocalDate subclass.
130 * </p>
131 * <p> To permit the discovery of the additional calendar types the implementation of
132 * {@code Chronology} must be registered as a Service implementing the {@code Chronology} interface
133 * in the {@code META-INF/Services} file as per the specification of {@link java.util.ServiceLoader}.
134 * The subclass must function according to the {@code Chronology} class description and must provide its
135 * {@link java.time.chrono.Chronology#getId() chronlogy ID} and {@link Chronology#getCalendarType() calendar type}. </p>
136 *
137 * @implSpec
138 * This abstract class must be implemented with care to ensure other classes operate correctly.
139 * All implementations that can be instantiated must be final, immutable and thread-safe.
140 * Subclasses should be Serializable wherever possible.
141 *
142 * @param <D> the ChronoLocalDate of this date-time
143 * @since 1.8
144 */
145 @jdk.internal.MigratedValueClass
146 abstract class ChronoLocalDateImpl<D extends ChronoLocalDate>
147 implements ChronoLocalDate, Temporal, TemporalAdjuster, Serializable {
148
149 /**
150 * Serialization version.
151 */
152 @java.io.Serial
153 private static final long serialVersionUID = 6282433883239719096L;
154
155 /**
156 * Casts the {@code Temporal} to {@code ChronoLocalDate} ensuring it bas the specified chronology.
157 *
158 * @param chrono the chronology to check for, not null
159 * @param temporal a date-time to cast, not null
160 * @return the date-time checked and cast to {@code ChronoLocalDate}, not null
161 * @throws ClassCastException if the date-time cannot be cast to ChronoLocalDate
162 * or the chronology is not equal this Chronology
163 */
164 static <D extends ChronoLocalDate> D ensureValid(Chronology chrono, Temporal temporal) {
165 @SuppressWarnings("unchecked")
166 D other = (D) temporal;
167 if (chrono.equals(other.getChronology()) == false) {
168 throw new ClassCastException("Chronology mismatch, expected: " + chrono.getId() + ", actual: " + other.getChronology().getId());
169 }
170 return other;
171 }
172
173 //-----------------------------------------------------------------------
174 /**
175 * Creates an instance.
176 */
177 ChronoLocalDateImpl() {
178 }
179
180 @Override
181 @SuppressWarnings("unchecked")
182 public D with(TemporalAdjuster adjuster) {
183 return (D) ChronoLocalDate.super.with(adjuster);
184 }
185
186 @Override
187 @SuppressWarnings("unchecked")
188 public D with(TemporalField field, long value) {
189 return (D) ChronoLocalDate.super.with(field, value);
190 }
191
192 //-----------------------------------------------------------------------
193 @Override
194 @SuppressWarnings("unchecked")
195 public D plus(TemporalAmount amount) {
196 return (D) ChronoLocalDate.super.plus(amount);
197 }
198
199 //-----------------------------------------------------------------------
200 @Override
201 @SuppressWarnings("unchecked")
202 public D plus(long amountToAdd, TemporalUnit unit) {
203 if (unit instanceof ChronoUnit chronoUnit) {
204 return switch (chronoUnit) {
205 case DAYS -> plusDays(amountToAdd);
206 case WEEKS -> plusDays(Math.multiplyExact(amountToAdd, 7));
207 case MONTHS -> plusMonths(amountToAdd);
208 case YEARS -> plusYears(amountToAdd);
209 case DECADES -> plusYears(Math.multiplyExact(amountToAdd, 10));
210 case CENTURIES -> plusYears(Math.multiplyExact(amountToAdd, 100));
211 case MILLENNIA -> plusYears(Math.multiplyExact(amountToAdd, 1000));
212 case ERAS -> with(ERA, Math.addExact(getLong(ERA), amountToAdd));
213 default -> throw new UnsupportedTemporalTypeException("Unsupported unit: " + unit);
214 };
215 }
216 return (D) ChronoLocalDate.super.plus(amountToAdd, unit);
217 }
218
219 @Override
220 @SuppressWarnings("unchecked")
221 public D minus(TemporalAmount amount) {
222 return (D) ChronoLocalDate.super.minus(amount);
223 }
224
225 @Override
226 @SuppressWarnings("unchecked")
227 public D minus(long amountToSubtract, TemporalUnit unit) {
228 return (D) ChronoLocalDate.super.minus(amountToSubtract, unit);
229 }
230
231 //-----------------------------------------------------------------------
232 /**
233 * Returns a copy of this date with the specified number of years added.
234 * <p>
235 * This adds the specified period in years to the date.
236 * In some cases, adding years can cause the resulting date to become invalid.
237 * If this occurs, then other fields, typically the day-of-month, will be adjusted to ensure
238 * that the result is valid. Typically this will select the last valid day of the month.
239 * <p>
240 * This instance is immutable and unaffected by this method call.
241 *
242 * @param yearsToAdd the years to add, may be negative
243 * @return a date based on this one with the years added, not null
244 * @throws DateTimeException if the result exceeds the supported date range
245 */
246 abstract D plusYears(long yearsToAdd);
247
248 /**
249 * Returns a copy of this date with the specified number of months added.
250 * <p>
251 * This adds the specified period in months to the date.
252 * In some cases, adding months can cause the resulting date to become invalid.
253 * If this occurs, then other fields, typically the day-of-month, will be adjusted to ensure
254 * that the result is valid. Typically this will select the last valid day of the month.
255 * <p>
256 * This instance is immutable and unaffected by this method call.
257 *
258 * @param monthsToAdd the months to add, may be negative
259 * @return a date based on this one with the months added, not null
260 * @throws DateTimeException if the result exceeds the supported date range
261 */
262 abstract D plusMonths(long monthsToAdd);
263
264 /**
265 * Returns a copy of this date with the specified number of weeks added.
266 * <p>
267 * This adds the specified period in weeks to the date.
268 * In some cases, adding weeks can cause the resulting date to become invalid.
269 * If this occurs, then other fields will be adjusted to ensure that the result is valid.
270 * <p>
271 * The default implementation uses {@link #plusDays(long)} using a 7 day week.
272 * <p>
273 * This instance is immutable and unaffected by this method call.
274 *
275 * @param weeksToAdd the weeks to add, may be negative
276 * @return a date based on this one with the weeks added, not null
277 * @throws DateTimeException if the result exceeds the supported date range
278 */
279 D plusWeeks(long weeksToAdd) {
280 return plusDays(Math.multiplyExact(weeksToAdd, 7));
281 }
282
283 /**
284 * Returns a copy of this date with the specified number of days added.
285 * <p>
286 * This adds the specified period in days to the date.
287 * <p>
288 * This instance is immutable and unaffected by this method call.
289 *
290 * @param daysToAdd the days to add, may be negative
291 * @return a date based on this one with the days added, not null
292 * @throws DateTimeException if the result exceeds the supported date range
293 */
294 abstract D plusDays(long daysToAdd);
295
296 //-----------------------------------------------------------------------
297 /**
298 * Returns a copy of this date with the specified number of years subtracted.
299 * <p>
300 * This subtracts the specified period in years to the date.
301 * In some cases, subtracting years can cause the resulting date to become invalid.
302 * If this occurs, then other fields, typically the day-of-month, will be adjusted to ensure
303 * that the result is valid. Typically this will select the last valid day of the month.
304 * <p>
305 * The default implementation uses {@link #plusYears(long)}.
306 * <p>
307 * This instance is immutable and unaffected by this method call.
308 *
309 * @param yearsToSubtract the years to subtract, may be negative
310 * @return a date based on this one with the years subtracted, not null
311 * @throws DateTimeException if the result exceeds the supported date range
312 */
313 @SuppressWarnings("unchecked")
314 D minusYears(long yearsToSubtract) {
315 return (yearsToSubtract == Long.MIN_VALUE ? ((ChronoLocalDateImpl<D>)plusYears(Long.MAX_VALUE)).plusYears(1) : plusYears(-yearsToSubtract));
316 }
317
318 /**
319 * Returns a copy of this date with the specified number of months subtracted.
320 * <p>
321 * This subtracts the specified period in months to the date.
322 * In some cases, subtracting months can cause the resulting date to become invalid.
323 * If this occurs, then other fields, typically the day-of-month, will be adjusted to ensure
324 * that the result is valid. Typically this will select the last valid day of the month.
325 * <p>
326 * The default implementation uses {@link #plusMonths(long)}.
327 * <p>
328 * This instance is immutable and unaffected by this method call.
329 *
330 * @param monthsToSubtract the months to subtract, may be negative
331 * @return a date based on this one with the months subtracted, not null
332 * @throws DateTimeException if the result exceeds the supported date range
333 */
334 @SuppressWarnings("unchecked")
335 D minusMonths(long monthsToSubtract) {
336 return (monthsToSubtract == Long.MIN_VALUE ? ((ChronoLocalDateImpl<D>)plusMonths(Long.MAX_VALUE)).plusMonths(1) : plusMonths(-monthsToSubtract));
337 }
338
339 /**
340 * Returns a copy of this date with the specified number of weeks subtracted.
341 * <p>
342 * This subtracts the specified period in weeks to the date.
343 * In some cases, subtracting weeks can cause the resulting date to become invalid.
344 * If this occurs, then other fields will be adjusted to ensure that the result is valid.
345 * <p>
346 * The default implementation uses {@link #plusWeeks(long)}.
347 * <p>
348 * This instance is immutable and unaffected by this method call.
349 *
350 * @param weeksToSubtract the weeks to subtract, may be negative
351 * @return a date based on this one with the weeks subtracted, not null
352 * @throws DateTimeException if the result exceeds the supported date range
353 */
354 @SuppressWarnings("unchecked")
355 D minusWeeks(long weeksToSubtract) {
356 return (weeksToSubtract == Long.MIN_VALUE ? ((ChronoLocalDateImpl<D>)plusWeeks(Long.MAX_VALUE)).plusWeeks(1) : plusWeeks(-weeksToSubtract));
357 }
358
359 /**
360 * Returns a copy of this date with the specified number of days subtracted.
361 * <p>
362 * This subtracts the specified period in days to the date.
363 * <p>
364 * The default implementation uses {@link #plusDays(long)}.
365 * <p>
366 * This instance is immutable and unaffected by this method call.
367 *
368 * @param daysToSubtract the days to subtract, may be negative
369 * @return a date based on this one with the days subtracted, not null
370 * @throws DateTimeException if the result exceeds the supported date range
371 */
372 @SuppressWarnings("unchecked")
373 D minusDays(long daysToSubtract) {
374 return (daysToSubtract == Long.MIN_VALUE ? ((ChronoLocalDateImpl<D>)plusDays(Long.MAX_VALUE)).plusDays(1) : plusDays(-daysToSubtract));
375 }
376
377 //-----------------------------------------------------------------------
378 @Override
379 public long until(Temporal endExclusive, TemporalUnit unit) {
380 Objects.requireNonNull(endExclusive, "endExclusive");
381 ChronoLocalDate end = getChronology().date(endExclusive);
382 if (unit instanceof ChronoUnit chronoUnit) {
383 return switch (chronoUnit) {
384 case DAYS -> daysUntil(end);
385 case WEEKS -> daysUntil(end) / 7;
386 case MONTHS -> monthsUntil(end);
387 case YEARS -> monthsUntil(end) / 12;
388 case DECADES -> monthsUntil(end) / 120;
389 case CENTURIES -> monthsUntil(end) / 1200;
390 case MILLENNIA -> monthsUntil(end) / 12000;
391 case ERAS -> end.getLong(ERA) - getLong(ERA);
392 default -> throw new UnsupportedTemporalTypeException("Unsupported unit: " + unit);
393 };
394 }
395 Objects.requireNonNull(unit, "unit");
396 return unit.between(this, end);
397 }
398
399 private long daysUntil(ChronoLocalDate end) {
400 return end.toEpochDay() - toEpochDay(); // no overflow
401 }
402
403 private long monthsUntil(ChronoLocalDate end) {
404 ValueRange range = getChronology().range(MONTH_OF_YEAR);
405 if (range.getMaximum() != 12) {
406 throw new IllegalStateException("ChronoLocalDateImpl only supports Chronologies with 12 months per year");
407 }
408 long packed1 = getLong(PROLEPTIC_MONTH) * 32L + get(DAY_OF_MONTH); // no overflow
409 long packed2 = end.getLong(PROLEPTIC_MONTH) * 32L + end.get(DAY_OF_MONTH); // no overflow
410 return (packed2 - packed1) / 32;
411 }
412
413 @Override
414 public boolean equals(Object obj) {
415 if (this == obj) {
416 return true;
417 }
418 if (obj instanceof ChronoLocalDate) {
419 return compareTo((ChronoLocalDate) obj) == 0;
420 }
421 return false;
422 }
423
424 @Override
425 public int hashCode() {
426 long epDay = toEpochDay();
427 return getChronology().hashCode() ^ Long.hashCode(epDay);
428 }
429
430 @Override
431 public String toString() {
432 // Using get() instead of getLong() for performance reasons,
433 // as the values of YEAR_OF_ERA, MONTH_OF_YEAR, and DAY_OF_MONTH
434 // are guaranteed to be within the int range for all chronologies.
435 int yoe = get(YEAR_OF_ERA);
436 int moy = get(MONTH_OF_YEAR);
437 int dom = get(DAY_OF_MONTH);
438 StringBuilder buf = new StringBuilder(30);
439 buf.append(getChronology().toString())
440 .append(" ")
441 .append(getEra())
442 .append(" ")
443 .append(yoe)
444 .append('-');
445 DecimalDigits.appendPair(buf, moy);
446 buf.append('-');
447 DecimalDigits.appendPair(buf, dom);
448 return buf.toString();
449 }
450
451 }