Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/mobile
Path: blob/master/src/java.base/share/classes/java/time/Period.java
41152 views
1
/*
2
* Copyright (c) 2012, 2020, 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
* This file is available under and governed by the GNU General Public
28
* License version 2 only, as published by the Free Software Foundation.
29
* However, the following notice accompanied the original version of this
30
* file:
31
*
32
* Copyright (c) 2008-2012, Stephen Colebourne & Michael Nascimento Santos
33
*
34
* All rights reserved.
35
*
36
* Redistribution and use in source and binary forms, with or without
37
* modification, are permitted provided that the following conditions are met:
38
*
39
* * Redistributions of source code must retain the above copyright notice,
40
* this list of conditions and the following disclaimer.
41
*
42
* * Redistributions in binary form must reproduce the above copyright notice,
43
* this list of conditions and the following disclaimer in the documentation
44
* and/or other materials provided with the distribution.
45
*
46
* * Neither the name of JSR-310 nor the names of its contributors
47
* may be used to endorse or promote products derived from this software
48
* without specific prior written permission.
49
*
50
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
51
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
52
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
53
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
54
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
55
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
56
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
57
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
58
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
59
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
60
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
61
*/
62
package java.time;
63
64
import static java.time.temporal.ChronoUnit.DAYS;
65
import static java.time.temporal.ChronoUnit.MONTHS;
66
import static java.time.temporal.ChronoUnit.YEARS;
67
68
import java.io.DataInput;
69
import java.io.DataOutput;
70
import java.io.IOException;
71
import java.io.InvalidObjectException;
72
import java.io.ObjectInputStream;
73
import java.io.Serializable;
74
import java.time.chrono.ChronoLocalDate;
75
import java.time.chrono.ChronoPeriod;
76
import java.time.chrono.Chronology;
77
import java.time.chrono.IsoChronology;
78
import java.time.format.DateTimeParseException;
79
import java.time.temporal.ChronoUnit;
80
import java.time.temporal.Temporal;
81
import java.time.temporal.TemporalAccessor;
82
import java.time.temporal.TemporalAmount;
83
import java.time.temporal.TemporalQueries;
84
import java.time.temporal.TemporalUnit;
85
import java.time.temporal.UnsupportedTemporalTypeException;
86
import java.util.List;
87
import java.util.Objects;
88
import java.util.regex.Matcher;
89
import java.util.regex.Pattern;
90
91
/**
92
* A date-based amount of time in the ISO-8601 calendar system,
93
* such as '2 years, 3 months and 4 days'.
94
* <p>
95
* This class models a quantity or amount of time in terms of years, months and days.
96
* See {@link Duration} for the time-based equivalent to this class.
97
* <p>
98
* Durations and periods differ in their treatment of daylight savings time
99
* when added to {@link ZonedDateTime}. A {@code Duration} will add an exact
100
* number of seconds, thus a duration of one day is always exactly 24 hours.
101
* By contrast, a {@code Period} will add a conceptual day, trying to maintain
102
* the local time.
103
* <p>
104
* For example, consider adding a period of one day and a duration of one day to
105
* 18:00 on the evening before a daylight savings gap. The {@code Period} will add
106
* the conceptual day and result in a {@code ZonedDateTime} at 18:00 the following day.
107
* By contrast, the {@code Duration} will add exactly 24 hours, resulting in a
108
* {@code ZonedDateTime} at 19:00 the following day (assuming a one hour DST gap).
109
* <p>
110
* The supported units of a period are {@link ChronoUnit#YEARS YEARS},
111
* {@link ChronoUnit#MONTHS MONTHS} and {@link ChronoUnit#DAYS DAYS}.
112
* All three fields are always present, but may be set to zero.
113
* <p>
114
* The ISO-8601 calendar system is the modern civil calendar system used today
115
* in most of the world. It is equivalent to the proleptic Gregorian calendar
116
* system, in which today's rules for leap years are applied for all time.
117
* <p>
118
* The period is modeled as a directed amount of time, meaning that individual parts of the
119
* period may be negative.
120
* <p>
121
* This is a <a href="{@docRoot}/java.base/java/lang/doc-files/ValueBased.html">value-based</a>
122
* class; programmers should treat instances that are
123
* {@linkplain #equals(Object) equal} as interchangeable and should not
124
* use instances for synchronization, or unpredictable behavior may
125
* occur. For example, in a future release, synchronization may fail.
126
* The {@code equals} method should be used for comparisons.
127
*
128
* @implSpec
129
* This class is immutable and thread-safe.
130
*
131
* @since 1.8
132
*/
133
@jdk.internal.ValueBased
134
public final class Period
135
implements ChronoPeriod, Serializable {
136
137
/**
138
* A constant for a period of zero.
139
*/
140
public static final Period ZERO = new Period(0, 0, 0);
141
/**
142
* Serialization version.
143
*/
144
@java.io.Serial
145
private static final long serialVersionUID = -3587258372562876L;
146
/**
147
* The pattern for parsing.
148
*/
149
private static final Pattern PATTERN =
150
Pattern.compile("([-+]?)P(?:([-+]?[0-9]+)Y)?(?:([-+]?[0-9]+)M)?(?:([-+]?[0-9]+)W)?(?:([-+]?[0-9]+)D)?", Pattern.CASE_INSENSITIVE);
151
152
/**
153
* The set of supported units.
154
*/
155
private static final List<TemporalUnit> SUPPORTED_UNITS = List.of(YEARS, MONTHS, DAYS);
156
157
/**
158
* The number of years.
159
*/
160
private final int years;
161
/**
162
* The number of months.
163
*/
164
private final int months;
165
/**
166
* The number of days.
167
*/
168
private final int days;
169
170
//-----------------------------------------------------------------------
171
/**
172
* Obtains a {@code Period} representing a number of years.
173
* <p>
174
* The resulting period will have the specified years.
175
* The months and days units will be zero.
176
*
177
* @param years the number of years, positive or negative
178
* @return the period of years, not null
179
*/
180
public static Period ofYears(int years) {
181
return create(years, 0, 0);
182
}
183
184
/**
185
* Obtains a {@code Period} representing a number of months.
186
* <p>
187
* The resulting period will have the specified months.
188
* The years and days units will be zero.
189
*
190
* @param months the number of months, positive or negative
191
* @return the period of months, not null
192
*/
193
public static Period ofMonths(int months) {
194
return create(0, months, 0);
195
}
196
197
/**
198
* Obtains a {@code Period} representing a number of weeks.
199
* <p>
200
* The resulting period will be day-based, with the amount of days
201
* equal to the number of weeks multiplied by 7.
202
* The years and months units will be zero.
203
*
204
* @param weeks the number of weeks, positive or negative
205
* @return the period, with the input weeks converted to days, not null
206
*/
207
public static Period ofWeeks(int weeks) {
208
return create(0, 0, Math.multiplyExact(weeks, 7));
209
}
210
211
/**
212
* Obtains a {@code Period} representing a number of days.
213
* <p>
214
* The resulting period will have the specified days.
215
* The years and months units will be zero.
216
*
217
* @param days the number of days, positive or negative
218
* @return the period of days, not null
219
*/
220
public static Period ofDays(int days) {
221
return create(0, 0, days);
222
}
223
224
//-----------------------------------------------------------------------
225
/**
226
* Obtains a {@code Period} representing a number of years, months and days.
227
* <p>
228
* This creates an instance based on years, months and days.
229
*
230
* @param years the amount of years, may be negative
231
* @param months the amount of months, may be negative
232
* @param days the amount of days, may be negative
233
* @return the period of years, months and days, not null
234
*/
235
public static Period of(int years, int months, int days) {
236
return create(years, months, days);
237
}
238
239
//-----------------------------------------------------------------------
240
/**
241
* Obtains an instance of {@code Period} from a temporal amount.
242
* <p>
243
* This obtains a period based on the specified amount.
244
* A {@code TemporalAmount} represents an amount of time, which may be
245
* date-based or time-based, which this factory extracts to a {@code Period}.
246
* <p>
247
* The conversion loops around the set of units from the amount and uses
248
* the {@link ChronoUnit#YEARS YEARS}, {@link ChronoUnit#MONTHS MONTHS}
249
* and {@link ChronoUnit#DAYS DAYS} units to create a period.
250
* If any other units are found then an exception is thrown.
251
* <p>
252
* If the amount is a {@code ChronoPeriod} then it must use the ISO chronology.
253
*
254
* @param amount the temporal amount to convert, not null
255
* @return the equivalent period, not null
256
* @throws DateTimeException if unable to convert to a {@code Period}
257
* @throws ArithmeticException if the amount of years, months or days exceeds an int
258
*/
259
public static Period from(TemporalAmount amount) {
260
if (amount instanceof Period) {
261
return (Period) amount;
262
}
263
if (amount instanceof ChronoPeriod) {
264
if (IsoChronology.INSTANCE.equals(((ChronoPeriod) amount).getChronology()) == false) {
265
throw new DateTimeException("Period requires ISO chronology: " + amount);
266
}
267
}
268
Objects.requireNonNull(amount, "amount");
269
int years = 0;
270
int months = 0;
271
int days = 0;
272
for (TemporalUnit unit : amount.getUnits()) {
273
long unitAmount = amount.get(unit);
274
if (unit == ChronoUnit.YEARS) {
275
years = Math.toIntExact(unitAmount);
276
} else if (unit == ChronoUnit.MONTHS) {
277
months = Math.toIntExact(unitAmount);
278
} else if (unit == ChronoUnit.DAYS) {
279
days = Math.toIntExact(unitAmount);
280
} else {
281
throw new DateTimeException("Unit must be Years, Months or Days, but was " + unit);
282
}
283
}
284
return create(years, months, days);
285
}
286
287
//-----------------------------------------------------------------------
288
/**
289
* Obtains a {@code Period} from a text string such as {@code PnYnMnD}.
290
* <p>
291
* This will parse the string produced by {@code toString()} which is
292
* based on the ISO-8601 period formats {@code PnYnMnD} and {@code PnW}.
293
* <p>
294
* The string starts with an optional sign, denoted by the ASCII negative
295
* or positive symbol. If negative, the whole period is negated.
296
* The ASCII letter "P" is next in upper or lower case.
297
* There are then four sections, each consisting of a number and a suffix.
298
* At least one of the four sections must be present.
299
* The sections have suffixes in ASCII of "Y", "M", "W" and "D" for
300
* years, months, weeks and days, accepted in upper or lower case.
301
* The suffixes must occur in order.
302
* The number part of each section must consist of ASCII digits.
303
* The number may be prefixed by the ASCII negative or positive symbol.
304
* The number must parse to an {@code int}.
305
* <p>
306
* The leading plus/minus sign, and negative values for other units are
307
* not part of the ISO-8601 standard. In addition, ISO-8601 does not
308
* permit mixing between the {@code PnYnMnD} and {@code PnW} formats.
309
* Any week-based input is multiplied by 7 and treated as a number of days.
310
* <p>
311
* For example, the following are valid inputs:
312
* <pre>
313
* "P2Y" -- Period.ofYears(2)
314
* "P3M" -- Period.ofMonths(3)
315
* "P4W" -- Period.ofWeeks(4)
316
* "P5D" -- Period.ofDays(5)
317
* "P1Y2M3D" -- Period.of(1, 2, 3)
318
* "P1Y2M3W4D" -- Period.of(1, 2, 25)
319
* "P-1Y2M" -- Period.of(-1, 2, 0)
320
* "-P1Y2M" -- Period.of(-1, -2, 0)
321
* </pre>
322
*
323
* @param text the text to parse, not null
324
* @return the parsed period, not null
325
* @throws DateTimeParseException if the text cannot be parsed to a period
326
*/
327
public static Period parse(CharSequence text) {
328
Objects.requireNonNull(text, "text");
329
Matcher matcher = PATTERN.matcher(text);
330
if (matcher.matches()) {
331
int negate = (charMatch(text, matcher.start(1), matcher.end(1), '-') ? -1 : 1);
332
int yearStart = matcher.start(2), yearEnd = matcher.end(2);
333
int monthStart = matcher.start(3), monthEnd = matcher.end(3);
334
int weekStart = matcher.start(4), weekEnd = matcher.end(4);
335
int dayStart = matcher.start(5), dayEnd = matcher.end(5);
336
if (yearStart >= 0 || monthStart >= 0 || weekStart >= 0 || dayStart >= 0) {
337
try {
338
int years = parseNumber(text, yearStart, yearEnd, negate);
339
int months = parseNumber(text, monthStart, monthEnd, negate);
340
int weeks = parseNumber(text, weekStart, weekEnd, negate);
341
int days = parseNumber(text, dayStart, dayEnd, negate);
342
days = Math.addExact(days, Math.multiplyExact(weeks, 7));
343
return create(years, months, days);
344
} catch (NumberFormatException ex) {
345
throw new DateTimeParseException("Text cannot be parsed to a Period", text, 0, ex);
346
}
347
}
348
}
349
throw new DateTimeParseException("Text cannot be parsed to a Period", text, 0);
350
}
351
352
private static boolean charMatch(CharSequence text, int start, int end, char c) {
353
return (start >= 0 && end == start + 1 && text.charAt(start) == c);
354
}
355
356
private static int parseNumber(CharSequence text, int start, int end, int negate) {
357
if (start < 0 || end < 0) {
358
return 0;
359
}
360
int val = Integer.parseInt(text, start, end, 10);
361
try {
362
return Math.multiplyExact(val, negate);
363
} catch (ArithmeticException ex) {
364
throw new DateTimeParseException("Text cannot be parsed to a Period", text, 0, ex);
365
}
366
}
367
368
//-----------------------------------------------------------------------
369
/**
370
* Obtains a {@code Period} consisting of the number of years, months,
371
* and days between two dates.
372
* <p>
373
* The start date is included, but the end date is not.
374
* The period is calculated by removing complete months, then calculating
375
* the remaining number of days, adjusting to ensure that both have the same sign.
376
* The number of months is then split into years and months based on a 12 month year.
377
* A month is considered if the end day-of-month is greater than or equal to the start day-of-month.
378
* For example, from {@code 2010-01-15} to {@code 2011-03-18} is one year, two months and three days.
379
* <p>
380
* The result of this method can be a negative period if the end is before the start.
381
* The negative sign will be the same in each of year, month and day.
382
*
383
* @param startDateInclusive the start date, inclusive, not null
384
* @param endDateExclusive the end date, exclusive, not null
385
* @return the period between this date and the end date, not null
386
* @see ChronoLocalDate#until(ChronoLocalDate)
387
*/
388
public static Period between(LocalDate startDateInclusive, LocalDate endDateExclusive) {
389
return startDateInclusive.until(endDateExclusive);
390
}
391
392
//-----------------------------------------------------------------------
393
/**
394
* Creates an instance.
395
*
396
* @param years the amount
397
* @param months the amount
398
* @param days the amount
399
*/
400
private static Period create(int years, int months, int days) {
401
if ((years | months | days) == 0) {
402
return ZERO;
403
}
404
return new Period(years, months, days);
405
}
406
407
/**
408
* Constructor.
409
*
410
* @param years the amount
411
* @param months the amount
412
* @param days the amount
413
*/
414
private Period(int years, int months, int days) {
415
this.years = years;
416
this.months = months;
417
this.days = days;
418
}
419
420
//-----------------------------------------------------------------------
421
/**
422
* Gets the value of the requested unit.
423
* <p>
424
* This returns a value for each of the three supported units,
425
* {@link ChronoUnit#YEARS YEARS}, {@link ChronoUnit#MONTHS MONTHS} and
426
* {@link ChronoUnit#DAYS DAYS}.
427
* All other units throw an exception.
428
*
429
* @param unit the {@code TemporalUnit} for which to return the value
430
* @return the long value of the unit
431
* @throws DateTimeException if the unit is not supported
432
* @throws UnsupportedTemporalTypeException if the unit is not supported
433
*/
434
@Override
435
public long get(TemporalUnit unit) {
436
if (unit == ChronoUnit.YEARS) {
437
return getYears();
438
} else if (unit == ChronoUnit.MONTHS) {
439
return getMonths();
440
} else if (unit == ChronoUnit.DAYS) {
441
return getDays();
442
} else {
443
throw new UnsupportedTemporalTypeException("Unsupported unit: " + unit);
444
}
445
}
446
447
/**
448
* Gets the set of units supported by this period.
449
* <p>
450
* The supported units are {@link ChronoUnit#YEARS YEARS},
451
* {@link ChronoUnit#MONTHS MONTHS} and {@link ChronoUnit#DAYS DAYS}.
452
* They are returned in the order years, months, days.
453
* <p>
454
* This set can be used in conjunction with {@link #get(TemporalUnit)}
455
* to access the entire state of the period.
456
*
457
* @return a list containing the years, months and days units, not null
458
*/
459
@Override
460
public List<TemporalUnit> getUnits() {
461
return SUPPORTED_UNITS;
462
}
463
464
/**
465
* Gets the chronology of this period, which is the ISO calendar system.
466
* <p>
467
* The {@code Chronology} represents the calendar system in use.
468
* The ISO-8601 calendar system is the modern civil calendar system used today
469
* in most of the world. It is equivalent to the proleptic Gregorian calendar
470
* system, in which today's rules for leap years are applied for all time.
471
*
472
* @return the ISO chronology, not null
473
*/
474
@Override
475
public IsoChronology getChronology() {
476
return IsoChronology.INSTANCE;
477
}
478
479
//-----------------------------------------------------------------------
480
/**
481
* Checks if all three units of this period are zero.
482
* <p>
483
* A zero period has the value zero for the years, months and days units.
484
*
485
* @return true if this period is zero-length
486
*/
487
public boolean isZero() {
488
return (this == ZERO);
489
}
490
491
/**
492
* Checks if any of the three units of this period are negative.
493
* <p>
494
* This checks whether the years, months or days units are less than zero.
495
*
496
* @return true if any unit of this period is negative
497
*/
498
public boolean isNegative() {
499
return years < 0 || months < 0 || days < 0;
500
}
501
502
//-----------------------------------------------------------------------
503
/**
504
* Gets the amount of years of this period.
505
* <p>
506
* This returns the years unit.
507
* <p>
508
* The months unit is not automatically normalized with the years unit.
509
* This means that a period of "15 months" is different to a period
510
* of "1 year and 3 months".
511
*
512
* @return the amount of years of this period, may be negative
513
*/
514
public int getYears() {
515
return years;
516
}
517
518
/**
519
* Gets the amount of months of this period.
520
* <p>
521
* This returns the months unit.
522
* <p>
523
* The months unit is not automatically normalized with the years unit.
524
* This means that a period of "15 months" is different to a period
525
* of "1 year and 3 months".
526
*
527
* @return the amount of months of this period, may be negative
528
*/
529
public int getMonths() {
530
return months;
531
}
532
533
/**
534
* Gets the amount of days of this period.
535
* <p>
536
* This returns the days unit.
537
*
538
* @return the amount of days of this period, may be negative
539
*/
540
public int getDays() {
541
return days;
542
}
543
544
//-----------------------------------------------------------------------
545
/**
546
* Returns a copy of this period with the specified amount of years.
547
* <p>
548
* This sets the amount of the years unit in a copy of this period.
549
* The months and days units are unaffected.
550
* <p>
551
* The months unit is not automatically normalized with the years unit.
552
* This means that a period of "15 months" is different to a period
553
* of "1 year and 3 months".
554
* <p>
555
* This instance is immutable and unaffected by this method call.
556
*
557
* @param years the years to represent, may be negative
558
* @return a {@code Period} based on this period with the requested years, not null
559
*/
560
public Period withYears(int years) {
561
if (years == this.years) {
562
return this;
563
}
564
return create(years, months, days);
565
}
566
567
/**
568
* Returns a copy of this period with the specified amount of months.
569
* <p>
570
* This sets the amount of the months unit in a copy of this period.
571
* The years and days units are unaffected.
572
* <p>
573
* The months unit is not automatically normalized with the years unit.
574
* This means that a period of "15 months" is different to a period
575
* of "1 year and 3 months".
576
* <p>
577
* This instance is immutable and unaffected by this method call.
578
*
579
* @param months the months to represent, may be negative
580
* @return a {@code Period} based on this period with the requested months, not null
581
*/
582
public Period withMonths(int months) {
583
if (months == this.months) {
584
return this;
585
}
586
return create(years, months, days);
587
}
588
589
/**
590
* Returns a copy of this period with the specified amount of days.
591
* <p>
592
* This sets the amount of the days unit in a copy of this period.
593
* The years and months units are unaffected.
594
* <p>
595
* This instance is immutable and unaffected by this method call.
596
*
597
* @param days the days to represent, may be negative
598
* @return a {@code Period} based on this period with the requested days, not null
599
*/
600
public Period withDays(int days) {
601
if (days == this.days) {
602
return this;
603
}
604
return create(years, months, days);
605
}
606
607
//-----------------------------------------------------------------------
608
/**
609
* Returns a copy of this period with the specified period added.
610
* <p>
611
* This operates separately on the years, months and days.
612
* No normalization is performed.
613
* <p>
614
* For example, "1 year, 6 months and 3 days" plus "2 years, 2 months and 2 days"
615
* returns "3 years, 8 months and 5 days".
616
* <p>
617
* The specified amount is typically an instance of {@code Period}.
618
* Other types are interpreted using {@link Period#from(TemporalAmount)}.
619
* <p>
620
* This instance is immutable and unaffected by this method call.
621
*
622
* @param amountToAdd the amount to add, not null
623
* @return a {@code Period} based on this period with the requested period added, not null
624
* @throws DateTimeException if the specified amount has a non-ISO chronology or
625
* contains an invalid unit
626
* @throws ArithmeticException if numeric overflow occurs
627
*/
628
public Period plus(TemporalAmount amountToAdd) {
629
Period isoAmount = Period.from(amountToAdd);
630
return create(
631
Math.addExact(years, isoAmount.years),
632
Math.addExact(months, isoAmount.months),
633
Math.addExact(days, isoAmount.days));
634
}
635
636
/**
637
* Returns a copy of this period with the specified years added.
638
* <p>
639
* This adds the amount to the years unit in a copy of this period.
640
* The months and days units are unaffected.
641
* For example, "1 year, 6 months and 3 days" plus 2 years returns "3 years, 6 months and 3 days".
642
* <p>
643
* This instance is immutable and unaffected by this method call.
644
*
645
* @param yearsToAdd the years to add, positive or negative
646
* @return a {@code Period} based on this period with the specified years added, not null
647
* @throws ArithmeticException if numeric overflow occurs
648
*/
649
public Period plusYears(long yearsToAdd) {
650
if (yearsToAdd == 0) {
651
return this;
652
}
653
return create(Math.toIntExact(Math.addExact(years, yearsToAdd)), months, days);
654
}
655
656
/**
657
* Returns a copy of this period with the specified months added.
658
* <p>
659
* This adds the amount to the months unit in a copy of this period.
660
* The years and days units are unaffected.
661
* For example, "1 year, 6 months and 3 days" plus 2 months returns "1 year, 8 months and 3 days".
662
* <p>
663
* This instance is immutable and unaffected by this method call.
664
*
665
* @param monthsToAdd the months to add, positive or negative
666
* @return a {@code Period} based on this period with the specified months added, not null
667
* @throws ArithmeticException if numeric overflow occurs
668
*/
669
public Period plusMonths(long monthsToAdd) {
670
if (monthsToAdd == 0) {
671
return this;
672
}
673
return create(years, Math.toIntExact(Math.addExact(months, monthsToAdd)), days);
674
}
675
676
/**
677
* Returns a copy of this period with the specified days added.
678
* <p>
679
* This adds the amount to the days unit in a copy of this period.
680
* The years and months units are unaffected.
681
* For example, "1 year, 6 months and 3 days" plus 2 days returns "1 year, 6 months and 5 days".
682
* <p>
683
* This instance is immutable and unaffected by this method call.
684
*
685
* @param daysToAdd the days to add, positive or negative
686
* @return a {@code Period} based on this period with the specified days added, not null
687
* @throws ArithmeticException if numeric overflow occurs
688
*/
689
public Period plusDays(long daysToAdd) {
690
if (daysToAdd == 0) {
691
return this;
692
}
693
return create(years, months, Math.toIntExact(Math.addExact(days, daysToAdd)));
694
}
695
696
//-----------------------------------------------------------------------
697
/**
698
* Returns a copy of this period with the specified period subtracted.
699
* <p>
700
* This operates separately on the years, months and days.
701
* No normalization is performed.
702
* <p>
703
* For example, "1 year, 6 months and 3 days" minus "2 years, 2 months and 2 days"
704
* returns "-1 years, 4 months and 1 day".
705
* <p>
706
* The specified amount is typically an instance of {@code Period}.
707
* Other types are interpreted using {@link Period#from(TemporalAmount)}.
708
* <p>
709
* This instance is immutable and unaffected by this method call.
710
*
711
* @param amountToSubtract the amount to subtract, not null
712
* @return a {@code Period} based on this period with the requested period subtracted, not null
713
* @throws DateTimeException if the specified amount has a non-ISO chronology or
714
* contains an invalid unit
715
* @throws ArithmeticException if numeric overflow occurs
716
*/
717
public Period minus(TemporalAmount amountToSubtract) {
718
Period isoAmount = Period.from(amountToSubtract);
719
return create(
720
Math.subtractExact(years, isoAmount.years),
721
Math.subtractExact(months, isoAmount.months),
722
Math.subtractExact(days, isoAmount.days));
723
}
724
725
/**
726
* Returns a copy of this period with the specified years subtracted.
727
* <p>
728
* This subtracts the amount from the years unit in a copy of this period.
729
* The months and days units are unaffected.
730
* For example, "1 year, 6 months and 3 days" minus 2 years returns "-1 years, 6 months and 3 days".
731
* <p>
732
* This instance is immutable and unaffected by this method call.
733
*
734
* @param yearsToSubtract the years to subtract, positive or negative
735
* @return a {@code Period} based on this period with the specified years subtracted, not null
736
* @throws ArithmeticException if numeric overflow occurs
737
*/
738
public Period minusYears(long yearsToSubtract) {
739
return (yearsToSubtract == Long.MIN_VALUE ? plusYears(Long.MAX_VALUE).plusYears(1) : plusYears(-yearsToSubtract));
740
}
741
742
/**
743
* Returns a copy of this period with the specified months subtracted.
744
* <p>
745
* This subtracts the amount from the months unit in a copy of this period.
746
* The years and days units are unaffected.
747
* For example, "1 year, 6 months and 3 days" minus 2 months returns "1 year, 4 months and 3 days".
748
* <p>
749
* This instance is immutable and unaffected by this method call.
750
*
751
* @param monthsToSubtract the years to subtract, positive or negative
752
* @return a {@code Period} based on this period with the specified months subtracted, not null
753
* @throws ArithmeticException if numeric overflow occurs
754
*/
755
public Period minusMonths(long monthsToSubtract) {
756
return (monthsToSubtract == Long.MIN_VALUE ? plusMonths(Long.MAX_VALUE).plusMonths(1) : plusMonths(-monthsToSubtract));
757
}
758
759
/**
760
* Returns a copy of this period with the specified days subtracted.
761
* <p>
762
* This subtracts the amount from the days unit in a copy of this period.
763
* The years and months units are unaffected.
764
* For example, "1 year, 6 months and 3 days" minus 2 days returns "1 year, 6 months and 1 day".
765
* <p>
766
* This instance is immutable and unaffected by this method call.
767
*
768
* @param daysToSubtract the months to subtract, positive or negative
769
* @return a {@code Period} based on this period with the specified days subtracted, not null
770
* @throws ArithmeticException if numeric overflow occurs
771
*/
772
public Period minusDays(long daysToSubtract) {
773
return (daysToSubtract == Long.MIN_VALUE ? plusDays(Long.MAX_VALUE).plusDays(1) : plusDays(-daysToSubtract));
774
}
775
776
//-----------------------------------------------------------------------
777
/**
778
* Returns a new instance with each element in this period multiplied
779
* by the specified scalar.
780
* <p>
781
* This returns a period with each of the years, months and days units
782
* individually multiplied.
783
* For example, a period of "2 years, -3 months and 4 days" multiplied by
784
* 3 will return "6 years, -9 months and 12 days".
785
* No normalization is performed.
786
*
787
* @param scalar the scalar to multiply by, not null
788
* @return a {@code Period} based on this period with the amounts multiplied by the scalar, not null
789
* @throws ArithmeticException if numeric overflow occurs
790
*/
791
public Period multipliedBy(int scalar) {
792
if (this == ZERO || scalar == 1) {
793
return this;
794
}
795
return create(
796
Math.multiplyExact(years, scalar),
797
Math.multiplyExact(months, scalar),
798
Math.multiplyExact(days, scalar));
799
}
800
801
/**
802
* Returns a new instance with each amount in this period negated.
803
* <p>
804
* This returns a period with each of the years, months and days units
805
* individually negated.
806
* For example, a period of "2 years, -3 months and 4 days" will be
807
* negated to "-2 years, 3 months and -4 days".
808
* No normalization is performed.
809
*
810
* @return a {@code Period} based on this period with the amounts negated, not null
811
* @throws ArithmeticException if numeric overflow occurs, which only happens if
812
* one of the units has the value {@code Integer.MIN_VALUE}
813
*/
814
public Period negated() {
815
return multipliedBy(-1);
816
}
817
818
//-----------------------------------------------------------------------
819
/**
820
* Returns a copy of this period with the years and months normalized.
821
* <p>
822
* This normalizes the years and months units, leaving the days unit unchanged.
823
* The months unit is adjusted to have an absolute value less than 12,
824
* with the years unit being adjusted to compensate. For example, a period of
825
* "1 Year and 15 months" will be normalized to "2 years and 3 months".
826
* <p>
827
* The sign of the years and months units will be the same after normalization.
828
* For example, a period of "1 year and -25 months" will be normalized to
829
* "-1 year and -1 month".
830
* <p>
831
* This instance is immutable and unaffected by this method call.
832
*
833
* @return a {@code Period} based on this period with excess months normalized to years, not null
834
* @throws ArithmeticException if numeric overflow occurs
835
*/
836
public Period normalized() {
837
long totalMonths = toTotalMonths();
838
long splitYears = totalMonths / 12;
839
int splitMonths = (int) (totalMonths % 12); // no overflow
840
if (splitYears == years && splitMonths == months) {
841
return this;
842
}
843
return create(Math.toIntExact(splitYears), splitMonths, days);
844
}
845
846
/**
847
* Gets the total number of months in this period.
848
* <p>
849
* This returns the total number of months in the period by multiplying the
850
* number of years by 12 and adding the number of months.
851
* <p>
852
* This instance is immutable and unaffected by this method call.
853
*
854
* @return the total number of months in the period, may be negative
855
*/
856
public long toTotalMonths() {
857
return years * 12L + months; // no overflow
858
}
859
860
//-------------------------------------------------------------------------
861
/**
862
* Adds this period to the specified temporal object.
863
* <p>
864
* This returns a temporal object of the same observable type as the input
865
* with this period added.
866
* If the temporal has a chronology, it must be the ISO chronology.
867
* <p>
868
* In most cases, it is clearer to reverse the calling pattern by using
869
* {@link Temporal#plus(TemporalAmount)}.
870
* <pre>
871
* // these two lines are equivalent, but the second approach is recommended
872
* dateTime = thisPeriod.addTo(dateTime);
873
* dateTime = dateTime.plus(thisPeriod);
874
* </pre>
875
* <p>
876
* The calculation operates as follows.
877
* First, the chronology of the temporal is checked to ensure it is ISO chronology or null.
878
* Second, if the months are zero, the years are added if non-zero, otherwise
879
* the combination of years and months is added if non-zero.
880
* Finally, any days are added.
881
* <p>
882
* This approach ensures that a partial period can be added to a partial date.
883
* For example, a period of years and/or months can be added to a {@code YearMonth},
884
* but a period including days cannot.
885
* The approach also adds years and months together when necessary, which ensures
886
* correct behaviour at the end of the month.
887
* <p>
888
* This instance is immutable and unaffected by this method call.
889
*
890
* @param temporal the temporal object to adjust, not null
891
* @return an object of the same type with the adjustment made, not null
892
* @throws DateTimeException if unable to add
893
* @throws ArithmeticException if numeric overflow occurs
894
*/
895
@Override
896
public Temporal addTo(Temporal temporal) {
897
validateChrono(temporal);
898
if (months == 0) {
899
if (years != 0) {
900
temporal = temporal.plus(years, YEARS);
901
}
902
} else {
903
long totalMonths = toTotalMonths();
904
if (totalMonths != 0) {
905
temporal = temporal.plus(totalMonths, MONTHS);
906
}
907
}
908
if (days != 0) {
909
temporal = temporal.plus(days, DAYS);
910
}
911
return temporal;
912
}
913
914
/**
915
* Subtracts this period from the specified temporal object.
916
* <p>
917
* This returns a temporal object of the same observable type as the input
918
* with this period subtracted.
919
* If the temporal has a chronology, it must be the ISO chronology.
920
* <p>
921
* In most cases, it is clearer to reverse the calling pattern by using
922
* {@link Temporal#minus(TemporalAmount)}.
923
* <pre>
924
* // these two lines are equivalent, but the second approach is recommended
925
* dateTime = thisPeriod.subtractFrom(dateTime);
926
* dateTime = dateTime.minus(thisPeriod);
927
* </pre>
928
* <p>
929
* The calculation operates as follows.
930
* First, the chronology of the temporal is checked to ensure it is ISO chronology or null.
931
* Second, if the months are zero, the years are subtracted if non-zero, otherwise
932
* the combination of years and months is subtracted if non-zero.
933
* Finally, any days are subtracted.
934
* <p>
935
* This approach ensures that a partial period can be subtracted from a partial date.
936
* For example, a period of years and/or months can be subtracted from a {@code YearMonth},
937
* but a period including days cannot.
938
* The approach also subtracts years and months together when necessary, which ensures
939
* correct behaviour at the end of the month.
940
* <p>
941
* This instance is immutable and unaffected by this method call.
942
*
943
* @param temporal the temporal object to adjust, not null
944
* @return an object of the same type with the adjustment made, not null
945
* @throws DateTimeException if unable to subtract
946
* @throws ArithmeticException if numeric overflow occurs
947
*/
948
@Override
949
public Temporal subtractFrom(Temporal temporal) {
950
validateChrono(temporal);
951
if (months == 0) {
952
if (years != 0) {
953
temporal = temporal.minus(years, YEARS);
954
}
955
} else {
956
long totalMonths = toTotalMonths();
957
if (totalMonths != 0) {
958
temporal = temporal.minus(totalMonths, MONTHS);
959
}
960
}
961
if (days != 0) {
962
temporal = temporal.minus(days, DAYS);
963
}
964
return temporal;
965
}
966
967
/**
968
* Validates that the temporal has the correct chronology.
969
*/
970
private void validateChrono(TemporalAccessor temporal) {
971
Objects.requireNonNull(temporal, "temporal");
972
Chronology temporalChrono = temporal.query(TemporalQueries.chronology());
973
if (temporalChrono != null && IsoChronology.INSTANCE.equals(temporalChrono) == false) {
974
throw new DateTimeException("Chronology mismatch, expected: ISO, actual: " + temporalChrono.getId());
975
}
976
}
977
978
//-----------------------------------------------------------------------
979
/**
980
* Checks if this period is equal to another period.
981
* <p>
982
* The comparison is based on the type {@code Period} and each of the three amounts.
983
* To be equal, the years, months and days units must be individually equal.
984
* Note that this means that a period of "15 Months" is not equal to a period
985
* of "1 Year and 3 Months".
986
*
987
* @param obj the object to check, null returns false
988
* @return true if this is equal to the other period
989
*/
990
@Override
991
public boolean equals(Object obj) {
992
if (this == obj) {
993
return true;
994
}
995
return (obj instanceof Period other)
996
&& years == other.years
997
&& months == other.months
998
&& days == other.days;
999
}
1000
1001
/**
1002
* A hash code for this period.
1003
*
1004
* @return a suitable hash code
1005
*/
1006
@Override
1007
public int hashCode() {
1008
return years + Integer.rotateLeft(months, 8) + Integer.rotateLeft(days, 16);
1009
}
1010
1011
//-----------------------------------------------------------------------
1012
/**
1013
* Outputs this period as a {@code String}, such as {@code P6Y3M1D}.
1014
* <p>
1015
* The output will be in the ISO-8601 period format.
1016
* A zero period will be represented as zero days, 'P0D'.
1017
*
1018
* @return a string representation of this period, not null
1019
*/
1020
@Override
1021
public String toString() {
1022
if (this == ZERO) {
1023
return "P0D";
1024
} else {
1025
StringBuilder buf = new StringBuilder();
1026
buf.append('P');
1027
if (years != 0) {
1028
buf.append(years).append('Y');
1029
}
1030
if (months != 0) {
1031
buf.append(months).append('M');
1032
}
1033
if (days != 0) {
1034
buf.append(days).append('D');
1035
}
1036
return buf.toString();
1037
}
1038
}
1039
1040
//-----------------------------------------------------------------------
1041
/**
1042
* Writes the object using a
1043
* <a href="{@docRoot}/serialized-form.html#java.time.Ser">dedicated serialized form</a>.
1044
* @serialData
1045
* <pre>
1046
* out.writeByte(14); // identifies a Period
1047
* out.writeInt(years);
1048
* out.writeInt(months);
1049
* out.writeInt(days);
1050
* </pre>
1051
*
1052
* @return the instance of {@code Ser}, not null
1053
*/
1054
@java.io.Serial
1055
private Object writeReplace() {
1056
return new Ser(Ser.PERIOD_TYPE, this);
1057
}
1058
1059
/**
1060
* Defend against malicious streams.
1061
*
1062
* @param s the stream to read
1063
* @throws java.io.InvalidObjectException always
1064
*/
1065
@java.io.Serial
1066
private void readObject(ObjectInputStream s) throws InvalidObjectException {
1067
throw new InvalidObjectException("Deserialization via serialization delegate");
1068
}
1069
1070
void writeExternal(DataOutput out) throws IOException {
1071
out.writeInt(years);
1072
out.writeInt(months);
1073
out.writeInt(days);
1074
}
1075
1076
static Period readExternal(DataInput in) throws IOException {
1077
int years = in.readInt();
1078
int months = in.readInt();
1079
int days = in.readInt();
1080
return Period.of(years, months, days);
1081
}
1082
1083
}
1084
1085