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