1 /*
2 * Copyright (c) 2012, 2018, 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) 2011-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.temporal;
58
59 import static java.time.DayOfWeek.THURSDAY;
60 import static java.time.DayOfWeek.WEDNESDAY;
61 import static java.time.temporal.ChronoField.DAY_OF_WEEK;
62 import static java.time.temporal.ChronoField.DAY_OF_YEAR;
63 import static java.time.temporal.ChronoField.EPOCH_DAY;
64 import static java.time.temporal.ChronoField.MONTH_OF_YEAR;
65 import static java.time.temporal.ChronoField.YEAR;
66 import static java.time.temporal.ChronoUnit.DAYS;
67 import static java.time.temporal.ChronoUnit.FOREVER;
68 import static java.time.temporal.ChronoUnit.MONTHS;
69 import static java.time.temporal.ChronoUnit.WEEKS;
70 import static java.time.temporal.ChronoUnit.YEARS;
71
72 import java.time.DateTimeException;
73 import java.time.Duration;
74 import java.time.LocalDate;
75 import java.time.chrono.ChronoLocalDate;
76 import java.time.chrono.Chronology;
77 import java.time.chrono.IsoChronology;
78 import java.time.format.ResolverStyle;
79 import java.util.Locale;
80 import java.util.Map;
81 import java.util.Objects;
82 import java.util.ResourceBundle;
83
84 import sun.util.locale.provider.CalendarDataUtility;
85 import sun.util.locale.provider.LocaleProviderAdapter;
86 import sun.util.locale.provider.LocaleResources;
87
88 /**
89 * Fields and units specific to the ISO-8601 calendar system,
90 * including quarter-of-year and week-based-year.
91 * <p>
92 * This class defines fields and units that are specific to the ISO calendar system.
93 *
94 * <h3>Quarter of year</h3>
95 * The ISO-8601 standard is based on the standard civic 12 month year.
96 * This is commonly divided into four quarters, often abbreviated as Q1, Q2, Q3 and Q4.
97 * <p>
98 * January, February and March are in Q1.
99 * April, May and June are in Q2.
100 * July, August and September are in Q3.
101 * October, November and December are in Q4.
102 * <p>
103 * The complete date is expressed using three fields:
104 * <ul>
105 * <li>{@link #DAY_OF_QUARTER DAY_OF_QUARTER} - the day within the quarter, from 1 to 90, 91 or 92
106 * <li>{@link #QUARTER_OF_YEAR QUARTER_OF_YEAR} - the quarter within the year, from 1 to 4
107 * <li>{@link ChronoField#YEAR YEAR} - the standard ISO year
108 * </ul>
109 *
110 * <h3>Week based years</h3>
111 * The ISO-8601 standard was originally intended as a data interchange format,
112 * defining a string format for dates and times. However, it also defines an
113 * alternate way of expressing the date, based on the concept of week-based-year.
114 * <p>
115 * The date is expressed using three fields:
116 * <ul>
117 * <li>{@link ChronoField#DAY_OF_WEEK DAY_OF_WEEK} - the standard field defining the
118 * day-of-week from Monday (1) to Sunday (7)
119 * <li>{@link #WEEK_OF_WEEK_BASED_YEAR} - the week within the week-based-year
120 * <li>{@link #WEEK_BASED_YEAR WEEK_BASED_YEAR} - the week-based-year
121 * </ul>
122 * The week-based-year itself is defined relative to the standard ISO proleptic year.
123 * It differs from the standard year in that it always starts on a Monday.
124 * <p>
125 * The first week of a week-based-year is the first Monday-based week of the standard
126 * ISO year that has at least 4 days in the new year.
127 * <ul>
128 * <li>If January 1st is Monday then week 1 starts on January 1st
129 * <li>If January 1st is Tuesday then week 1 starts on December 31st of the previous standard year
130 * <li>If January 1st is Wednesday then week 1 starts on December 30th of the previous standard year
131 * <li>If January 1st is Thursday then week 1 starts on December 29th of the previous standard year
132 * <li>If January 1st is Friday then week 1 starts on January 4th
133 * <li>If January 1st is Saturday then week 1 starts on January 3rd
134 * <li>If January 1st is Sunday then week 1 starts on January 2nd
135 * </ul>
136 * There are 52 weeks in most week-based years, however on occasion there are 53 weeks.
137 * <p>
138 * For example:
139 *
140 * <table class=striped style="text-align: left">
141 * <caption>Examples of Week based Years</caption>
142 * <thead>
143 * <tr><th scope="col">Date</th><th scope="col">Day-of-week</th><th scope="col">Field values</th></tr>
144 * </thead>
145 * <tbody>
146 * <tr><th scope="row">2008-12-28</th><td>Sunday</td><td>Week 52 of week-based-year 2008</td></tr>
147 * <tr><th scope="row">2008-12-29</th><td>Monday</td><td>Week 1 of week-based-year 2009</td></tr>
148 * <tr><th scope="row">2008-12-31</th><td>Wednesday</td><td>Week 1 of week-based-year 2009</td></tr>
149 * <tr><th scope="row">2009-01-01</th><td>Thursday</td><td>Week 1 of week-based-year 2009</td></tr>
150 * <tr><th scope="row">2009-01-04</th><td>Sunday</td><td>Week 1 of week-based-year 2009</td></tr>
151 * <tr><th scope="row">2009-01-05</th><td>Monday</td><td>Week 2 of week-based-year 2009</td></tr>
152 * </tbody>
153 * </table>
154 *
155 * @implSpec
156 * <p>
157 * This class is immutable and thread-safe.
158 *
159 * @since 1.8
160 */
161 public final class IsoFields {
162
163 /**
164 * The field that represents the day-of-quarter.
165 * <p>
166 * This field allows the day-of-quarter value to be queried and set.
167 * The day-of-quarter has values from 1 to 90 in Q1 of a standard year, from 1 to 91
168 * in Q1 of a leap year, from 1 to 91 in Q2 and from 1 to 92 in Q3 and Q4.
169 * <p>
170 * The day-of-quarter can only be calculated if the day-of-year, month-of-year and year
171 * are available.
172 * <p>
173 * When setting this field, the value is allowed to be partially lenient, taking any
174 * value from 1 to 92. If the quarter has less than 92 days, then day 92, and
175 * potentially day 91, is in the following quarter.
176 * <p>
177 * In the resolving phase of parsing, a date can be created from a year,
178 * quarter-of-year and day-of-quarter.
179 * <p>
180 * In {@linkplain ResolverStyle#STRICT strict mode}, all three fields are
181 * validated against their range of valid values. The day-of-quarter field
182 * is validated from 1 to 90, 91 or 92 depending on the year and quarter.
183 * <p>
184 * In {@linkplain ResolverStyle#SMART smart mode}, all three fields are
185 * validated against their range of valid values. The day-of-quarter field is
186 * validated between 1 and 92, ignoring the actual range based on the year and quarter.
187 * If the day-of-quarter exceeds the actual range by one day, then the resulting date
188 * is one day later. If the day-of-quarter exceeds the actual range by two days,
189 * then the resulting date is two days later.
190 * <p>
191 * In {@linkplain ResolverStyle#LENIENT lenient mode}, only the year is validated
192 * against the range of valid values. The resulting date is calculated equivalent to
193 * the following three stage approach. First, create a date on the first of January
194 * in the requested year. Then take the quarter-of-year, subtract one, and add the
195 * amount in quarters to the date. Finally, take the day-of-quarter, subtract one,
196 * and add the amount in days to the date.
197 * <p>
198 * This unit is an immutable and thread-safe singleton.
199 */
200 public static final TemporalField DAY_OF_QUARTER = Field.DAY_OF_QUARTER;
201 /**
202 * The field that represents the quarter-of-year.
203 * <p>
204 * This field allows the quarter-of-year value to be queried and set.
205 * The quarter-of-year has values from 1 to 4.
206 * <p>
207 * The quarter-of-year can only be calculated if the month-of-year is available.
208 * <p>
209 * In the resolving phase of parsing, a date can be created from a year,
210 * quarter-of-year and day-of-quarter.
211 * See {@link #DAY_OF_QUARTER} for details.
212 * <p>
213 * This unit is an immutable and thread-safe singleton.
214 */
215 public static final TemporalField QUARTER_OF_YEAR = Field.QUARTER_OF_YEAR;
216 /**
217 * The field that represents the week-of-week-based-year.
218 * <p>
219 * This field allows the week of the week-based-year value to be queried and set.
220 * The week-of-week-based-year has values from 1 to 52, or 53 if the
221 * week-based-year has 53 weeks.
222 * <p>
223 * In the resolving phase of parsing, a date can be created from a
224 * week-based-year, week-of-week-based-year and day-of-week.
225 * <p>
226 * In {@linkplain ResolverStyle#STRICT strict mode}, all three fields are
227 * validated against their range of valid values. The week-of-week-based-year
228 * field is validated from 1 to 52 or 53 depending on the week-based-year.
229 * <p>
230 * In {@linkplain ResolverStyle#SMART smart mode}, all three fields are
231 * validated against their range of valid values. The week-of-week-based-year
232 * field is validated between 1 and 53, ignoring the week-based-year.
233 * If the week-of-week-based-year is 53, but the week-based-year only has
234 * 52 weeks, then the resulting date is in week 1 of the following week-based-year.
235 * <p>
236 * In {@linkplain ResolverStyle#LENIENT lenient mode}, only the week-based-year
237 * is validated against the range of valid values. If the day-of-week is outside
238 * the range 1 to 7, then the resulting date is adjusted by a suitable number of
239 * weeks to reduce the day-of-week to the range 1 to 7. If the week-of-week-based-year
240 * value is outside the range 1 to 52, then any excess weeks are added or subtracted
241 * from the resulting date.
242 * <p>
243 * This unit is an immutable and thread-safe singleton.
244 */
245 public static final TemporalField WEEK_OF_WEEK_BASED_YEAR = Field.WEEK_OF_WEEK_BASED_YEAR;
246 /**
247 * The field that represents the week-based-year.
248 * <p>
249 * This field allows the week-based-year value to be queried and set.
250 * <p>
251 * The field has a range that matches {@link LocalDate#MAX} and {@link LocalDate#MIN}.
252 * <p>
253 * In the resolving phase of parsing, a date can be created from a
254 * week-based-year, week-of-week-based-year and day-of-week.
255 * See {@link #WEEK_OF_WEEK_BASED_YEAR} for details.
256 * <p>
257 * This unit is an immutable and thread-safe singleton.
258 */
259 public static final TemporalField WEEK_BASED_YEAR = Field.WEEK_BASED_YEAR;
260 /**
261 * The unit that represents week-based-years for the purpose of addition and subtraction.
262 * <p>
263 * This allows a number of week-based-years to be added to, or subtracted from, a date.
264 * The unit is equal to either 52 or 53 weeks.
265 * The estimated duration of a week-based-year is the same as that of a standard ISO
266 * year at {@code 365.2425 Days}.
267 * <p>
268 * The rules for addition add the number of week-based-years to the existing value
269 * for the week-based-year field. If the resulting week-based-year only has 52 weeks,
270 * then the date will be in week 1 of the following week-based-year.
271 * <p>
272 * This unit is an immutable and thread-safe singleton.
273 */
274 public static final TemporalUnit WEEK_BASED_YEARS = Unit.WEEK_BASED_YEARS;
275 /**
276 * Unit that represents the concept of a quarter-year.
277 * For the ISO calendar system, it is equal to 3 months.
278 * The estimated duration of a quarter-year is one quarter of {@code 365.2425 Days}.
279 * <p>
280 * This unit is an immutable and thread-safe singleton.
281 */
282 public static final TemporalUnit QUARTER_YEARS = Unit.QUARTER_YEARS;
283
284 /**
285 * Restricted constructor.
286 */
287 private IsoFields() {
288 throw new AssertionError("Not instantiable");
289 }
290
291 //-----------------------------------------------------------------------
292 /**
293 * Implementation of the field.
294 */
295 private static enum Field implements TemporalField {
296 DAY_OF_QUARTER {
297 @Override
298 public TemporalUnit getBaseUnit() {
299 return DAYS;
300 }
301 @Override
302 public TemporalUnit getRangeUnit() {
303 return QUARTER_YEARS;
304 }
305 @Override
306 public ValueRange range() {
307 return ValueRange.of(1, 90, 92);
308 }
309 @Override
310 public boolean isSupportedBy(TemporalAccessor temporal) {
311 return temporal.isSupported(DAY_OF_YEAR) && temporal.isSupported(MONTH_OF_YEAR) &&
312 temporal.isSupported(YEAR) && isIso(temporal);
313 }
314 @Override
315 public ValueRange rangeRefinedBy(TemporalAccessor temporal) {
316 if (isSupportedBy(temporal) == false) {
317 throw new UnsupportedTemporalTypeException("Unsupported field: DayOfQuarter");
318 }
319 long qoy = temporal.getLong(QUARTER_OF_YEAR);
320 if (qoy == 1) {
321 long year = temporal.getLong(YEAR);
322 return (IsoChronology.INSTANCE.isLeapYear(year) ? ValueRange.of(1, 91) : ValueRange.of(1, 90));
323 } else if (qoy == 2) {
324 return ValueRange.of(1, 91);
325 } else if (qoy == 3 || qoy == 4) {
326 return ValueRange.of(1, 92);
327 } // else value not from 1 to 4, so drop through
328 return range();
329 }
330 @Override
331 public long getFrom(TemporalAccessor temporal) {
332 if (isSupportedBy(temporal) == false) {
333 throw new UnsupportedTemporalTypeException("Unsupported field: DayOfQuarter");
334 }
335 int doy = temporal.get(DAY_OF_YEAR);
336 int moy = temporal.get(MONTH_OF_YEAR);
337 long year = temporal.getLong(YEAR);
338 return doy - QUARTER_DAYS[((moy - 1) / 3) + (IsoChronology.INSTANCE.isLeapYear(year) ? 4 : 0)];
339 }
340 @SuppressWarnings("unchecked")
341 @Override
342 public <R extends Temporal> R adjustInto(R temporal, long newValue) {
343 // calls getFrom() to check if supported
344 long curValue = getFrom(temporal);
345 range().checkValidValue(newValue, this); // leniently check from 1 to 92 TODO: check
346 return (R) temporal.with(DAY_OF_YEAR, temporal.getLong(DAY_OF_YEAR) + (newValue - curValue));
347 }
348 @Override
349 public ChronoLocalDate resolve(
350 Map<TemporalField, Long> fieldValues, TemporalAccessor partialTemporal, ResolverStyle resolverStyle) {
351 Long yearLong = fieldValues.get(YEAR);
352 Long qoyLong = fieldValues.get(QUARTER_OF_YEAR);
353 if (yearLong == null || qoyLong == null) {
354 return null;
355 }
356 int y = YEAR.checkValidIntValue(yearLong); // always validate
357 long doq = fieldValues.get(DAY_OF_QUARTER);
358 ensureIso(partialTemporal);
359 LocalDate date;
360 if (resolverStyle == ResolverStyle.LENIENT) {
361 date = LocalDate.of(y, 1, 1).plusMonths(Math.multiplyExact(Math.subtractExact(qoyLong, 1), 3));
362 doq = Math.subtractExact(doq, 1);
363 } else {
364 int qoy = QUARTER_OF_YEAR.range().checkValidIntValue(qoyLong, QUARTER_OF_YEAR); // validated
365 date = LocalDate.of(y, ((qoy - 1) * 3) + 1, 1);
366 if (doq < 1 || doq > 90) {
367 if (resolverStyle == ResolverStyle.STRICT) {
368 rangeRefinedBy(date).checkValidValue(doq, this); // only allow exact range
369 } else { // SMART
370 range().checkValidValue(doq, this); // allow 1-92 rolling into next quarter
371 }
372 }
373 doq--;
374 }
375 fieldValues.remove(this);
376 fieldValues.remove(YEAR);
377 fieldValues.remove(QUARTER_OF_YEAR);
378 return date.plusDays(doq);
379 }
380 @Override
381 public String toString() {
382 return "DayOfQuarter";
383 }
384 },
385 QUARTER_OF_YEAR {
386 @Override
387 public TemporalUnit getBaseUnit() {
388 return QUARTER_YEARS;
389 }
390 @Override
391 public TemporalUnit getRangeUnit() {
392 return YEARS;
393 }
394 @Override
395 public ValueRange range() {
396 return ValueRange.of(1, 4);
397 }
398 @Override
399 public boolean isSupportedBy(TemporalAccessor temporal) {
400 return temporal.isSupported(MONTH_OF_YEAR) && isIso(temporal);
401 }
402 @Override
403 public long getFrom(TemporalAccessor temporal) {
404 if (isSupportedBy(temporal) == false) {
405 throw new UnsupportedTemporalTypeException("Unsupported field: QuarterOfYear");
406 }
407 long moy = temporal.getLong(MONTH_OF_YEAR);
408 return ((moy + 2) / 3);
409 }
410 public ValueRange rangeRefinedBy(TemporalAccessor temporal) {
411 if (isSupportedBy(temporal) == false) {
412 throw new UnsupportedTemporalTypeException("Unsupported field: QuarterOfYear");
413 }
414 return super.rangeRefinedBy(temporal);
415 }
416 @SuppressWarnings("unchecked")
417 @Override
418 public <R extends Temporal> R adjustInto(R temporal, long newValue) {
419 // calls getFrom() to check if supported
420 long curValue = getFrom(temporal);
421 range().checkValidValue(newValue, this); // strictly check from 1 to 4
422 return (R) temporal.with(MONTH_OF_YEAR, temporal.getLong(MONTH_OF_YEAR) + (newValue - curValue) * 3);
423 }
424 @Override
425 public String toString() {
426 return "QuarterOfYear";
427 }
428 },
429 WEEK_OF_WEEK_BASED_YEAR {
430 @Override
431 public String getDisplayName(Locale locale) {
432 Objects.requireNonNull(locale, "locale");
433 LocaleResources lr = LocaleProviderAdapter.getResourceBundleBased()
434 .getLocaleResources(
435 CalendarDataUtility
436 .findRegionOverride(locale));
437 ResourceBundle rb = lr.getJavaTimeFormatData();
438 return rb.containsKey("field.week") ? rb.getString("field.week") : toString();
439 }
440
441 @Override
442 public TemporalUnit getBaseUnit() {
443 return WEEKS;
444 }
445 @Override
446 public TemporalUnit getRangeUnit() {
447 return WEEK_BASED_YEARS;
448 }
449 @Override
450 public ValueRange range() {
451 return ValueRange.of(1, 52, 53);
452 }
453 @Override
454 public boolean isSupportedBy(TemporalAccessor temporal) {
455 return temporal.isSupported(EPOCH_DAY) && isIso(temporal);
456 }
457 @Override
458 public ValueRange rangeRefinedBy(TemporalAccessor temporal) {
459 if (isSupportedBy(temporal) == false) {
460 throw new UnsupportedTemporalTypeException("Unsupported field: WeekOfWeekBasedYear");
461 }
462 return getWeekRange(LocalDate.from(temporal));
463 }
464 @Override
465 public long getFrom(TemporalAccessor temporal) {
466 if (isSupportedBy(temporal) == false) {
467 throw new UnsupportedTemporalTypeException("Unsupported field: WeekOfWeekBasedYear");
468 }
469 return getWeek(LocalDate.from(temporal));
470 }
471 @SuppressWarnings("unchecked")
472 @Override
473 public <R extends Temporal> R adjustInto(R temporal, long newValue) {
474 // calls getFrom() to check if supported
475 range().checkValidValue(newValue, this); // lenient range
476 return (R) temporal.plus(Math.subtractExact(newValue, getFrom(temporal)), WEEKS);
477 }
478 @Override
479 public ChronoLocalDate resolve(
480 Map<TemporalField, Long> fieldValues, TemporalAccessor partialTemporal, ResolverStyle resolverStyle) {
481 Long wbyLong = fieldValues.get(WEEK_BASED_YEAR);
482 Long dowLong = fieldValues.get(DAY_OF_WEEK);
483 if (wbyLong == null || dowLong == null) {
484 return null;
485 }
486 int wby = WEEK_BASED_YEAR.range().checkValidIntValue(wbyLong, WEEK_BASED_YEAR); // always validate
487 long wowby = fieldValues.get(WEEK_OF_WEEK_BASED_YEAR);
488 ensureIso(partialTemporal);
489 LocalDate date = LocalDate.of(wby, 1, 4);
490 if (resolverStyle == ResolverStyle.LENIENT) {
491 long dow = dowLong; // unvalidated
492 if (dow > 7) {
493 date = date.plusWeeks((dow - 1) / 7);
494 dow = ((dow - 1) % 7) + 1;
495 } else if (dow < 1) {
496 date = date.plusWeeks(Math.subtractExact(dow, 7) / 7);
497 dow = ((dow + 6) % 7) + 1;
498 }
499 date = date.plusWeeks(Math.subtractExact(wowby, 1)).with(DAY_OF_WEEK, dow);
500 } else {
501 int dow = DAY_OF_WEEK.checkValidIntValue(dowLong); // validated
502 if (wowby < 1 || wowby > 52) {
503 if (resolverStyle == ResolverStyle.STRICT) {
504 getWeekRange(date).checkValidValue(wowby, this); // only allow exact range
505 } else { // SMART
506 range().checkValidValue(wowby, this); // allow 1-53 rolling into next year
507 }
508 }
509 date = date.plusWeeks(wowby - 1).with(DAY_OF_WEEK, dow);
510 }
511 fieldValues.remove(this);
512 fieldValues.remove(WEEK_BASED_YEAR);
513 fieldValues.remove(DAY_OF_WEEK);
514 return date;
515 }
516 @Override
517 public String toString() {
518 return "WeekOfWeekBasedYear";
519 }
520 },
521 WEEK_BASED_YEAR {
522 @Override
523 public TemporalUnit getBaseUnit() {
524 return WEEK_BASED_YEARS;
525 }
526 @Override
527 public TemporalUnit getRangeUnit() {
528 return FOREVER;
529 }
530 @Override
531 public ValueRange range() {
532 return YEAR.range();
533 }
534 @Override
535 public boolean isSupportedBy(TemporalAccessor temporal) {
536 return temporal.isSupported(EPOCH_DAY) && isIso(temporal);
537 }
538 @Override
539 public long getFrom(TemporalAccessor temporal) {
540 if (isSupportedBy(temporal) == false) {
541 throw new UnsupportedTemporalTypeException("Unsupported field: WeekBasedYear");
542 }
543 return getWeekBasedYear(LocalDate.from(temporal));
544 }
545 public ValueRange rangeRefinedBy(TemporalAccessor temporal) {
546 if (isSupportedBy(temporal) == false) {
547 throw new UnsupportedTemporalTypeException("Unsupported field: WeekBasedYear");
548 }
549 return super.rangeRefinedBy(temporal);
550 }
551 @SuppressWarnings("unchecked")
552 @Override
553 public <R extends Temporal> R adjustInto(R temporal, long newValue) {
554 if (isSupportedBy(temporal) == false) {
555 throw new UnsupportedTemporalTypeException("Unsupported field: WeekBasedYear");
556 }
557 int newWby = range().checkValidIntValue(newValue, WEEK_BASED_YEAR); // strict check
558 LocalDate date = LocalDate.from(temporal);
559 int dow = date.get(DAY_OF_WEEK);
560 int week = getWeek(date);
561 if (week == 53 && getWeekRange(newWby) == 52) {
562 week = 52;
563 }
564 LocalDate resolved = LocalDate.of(newWby, 1, 4); // 4th is guaranteed to be in week one
565 int days = (dow - resolved.get(DAY_OF_WEEK)) + ((week - 1) * 7);
566 resolved = resolved.plusDays(days);
567 return (R) temporal.with(resolved);
568 }
569 @Override
570 public String toString() {
571 return "WeekBasedYear";
572 }
573 };
574
575 @Override
576 public boolean isDateBased() {
577 return true;
578 }
579
580 @Override
581 public boolean isTimeBased() {
582 return false;
583 }
584
585 @Override
586 public ValueRange rangeRefinedBy(TemporalAccessor temporal) {
587 return range();
588 }
589
590 //-------------------------------------------------------------------------
591 private static final int[] QUARTER_DAYS = {0, 90, 181, 273, 0, 91, 182, 274};
592
593
594 private static void ensureIso(TemporalAccessor temporal) {
595 if (isIso(temporal) == false) {
596 throw new DateTimeException("Resolve requires IsoChronology");
597 }
598 }
599
600 private static ValueRange getWeekRange(LocalDate date) {
601 int wby = getWeekBasedYear(date);
602 return ValueRange.of(1, getWeekRange(wby));
603 }
604
605 private static int getWeekRange(int wby) {
606 LocalDate date = LocalDate.of(wby, 1, 1);
607 // 53 weeks if standard year starts on Thursday, or Wed in a leap year
608 if (date.getDayOfWeek() == THURSDAY || (date.getDayOfWeek() == WEDNESDAY && date.isLeapYear())) {
609 return 53;
610 }
611 return 52;
612 }
613
614 private static int getWeek(LocalDate date) {
615 int dow0 = date.getDayOfWeek().ordinal();
616 int doy0 = date.getDayOfYear() - 1;
617 int doyThu0 = doy0 + (3 - dow0); // adjust to mid-week Thursday (which is 3 indexed from zero)
618 int alignedWeek = doyThu0 / 7;
619 int firstThuDoy0 = doyThu0 - (alignedWeek * 7);
620 int firstMonDoy0 = firstThuDoy0 - 3;
621 if (firstMonDoy0 < -3) {
622 firstMonDoy0 += 7;
623 }
624 if (doy0 < firstMonDoy0) {
625 return (int) getWeekRange(date.withDayOfYear(180).minusYears(1)).getMaximum();
626 }
627 int week = ((doy0 - firstMonDoy0) / 7) + 1;
628 if (week == 53) {
629 if ((firstMonDoy0 == -3 || (firstMonDoy0 == -2 && date.isLeapYear())) == false) {
630 week = 1;
631 }
632 }
633 return week;
634 }
635
636 private static int getWeekBasedYear(LocalDate date) {
637 int year = date.getYear();
638 int doy = date.getDayOfYear();
639 if (doy <= 3) {
640 int dow = date.getDayOfWeek().ordinal();
641 if (doy - dow < -2) {
642 year--;
643 }
644 } else if (doy >= 363) {
645 int dow = date.getDayOfWeek().ordinal();
646 doy = doy - 363 - (date.isLeapYear() ? 1 : 0);
647 if (doy - dow >= 0) {
648 year++;
649 }
650 }
651 return year;
652 }
653 }
654
655 //-----------------------------------------------------------------------
656 /**
657 * Implementation of the unit.
658 */
659 private static enum Unit implements TemporalUnit {
660
661 /**
662 * Unit that represents the concept of a week-based-year.
663 */
664 WEEK_BASED_YEARS("WeekBasedYears", Duration.ofSeconds(31556952L)),
665 /**
666 * Unit that represents the concept of a quarter-year.
667 */
668 QUARTER_YEARS("QuarterYears", Duration.ofSeconds(31556952L / 4));
669
670 private final String name;
671 private final Duration duration;
672
673 private Unit(String name, Duration estimatedDuration) {
674 this.name = name;
675 this.duration = estimatedDuration;
676 }
677
678 @Override
679 public Duration getDuration() {
680 return duration;
681 }
682
683 @Override
684 public boolean isDurationEstimated() {
685 return true;
686 }
687
688 @Override
689 public boolean isDateBased() {
690 return true;
691 }
692
693 @Override
694 public boolean isTimeBased() {
695 return false;
696 }
697
698 @Override
699 public boolean isSupportedBy(Temporal temporal) {
700 return temporal.isSupported(EPOCH_DAY) && isIso(temporal);
701 }
702
703 @SuppressWarnings("unchecked")
704 @Override
705 public <R extends Temporal> R addTo(R temporal, long amount) {
706 switch (this) {
707 case WEEK_BASED_YEARS:
708 return (R) temporal.with(WEEK_BASED_YEAR,
709 Math.addExact(temporal.get(WEEK_BASED_YEAR), amount));
710 case QUARTER_YEARS:
711 return (R) temporal.plus(amount / 4, YEARS)
712 .plus((amount % 4) * 3, MONTHS);
713 default:
714 throw new IllegalStateException("Unreachable");
715 }
716 }
717
718 @Override
719 public long between(Temporal temporal1Inclusive, Temporal temporal2Exclusive) {
720 if (temporal1Inclusive.getClass() != temporal2Exclusive.getClass()) {
721 return temporal1Inclusive.until(temporal2Exclusive, this);
722 }
723 switch(this) {
724 case WEEK_BASED_YEARS:
725 return Math.subtractExact(temporal2Exclusive.getLong(WEEK_BASED_YEAR),
726 temporal1Inclusive.getLong(WEEK_BASED_YEAR));
727 case QUARTER_YEARS:
728 return temporal1Inclusive.until(temporal2Exclusive, MONTHS) / 3;
729 default:
730 throw new IllegalStateException("Unreachable");
731 }
732 }
733
734 @Override
735 public String toString() {
736 return name;
737 }
738 }
739
740 static boolean isIso(TemporalAccessor temporal) {
741 return Chronology.from(temporal).equals(IsoChronology.INSTANCE);
742 }
743 }
744