001/*
002 * Copyright (c) 2007-2013, Stephen Colebourne & Michael Nascimento Santos
003 *
004 * All rights reserved.
005 *
006 * Redistribution and use in source and binary forms, with or without
007 * modification, are permitted provided that the following conditions are met:
008 *
009 *  * Redistributions of source code must retain the above copyright notice,
010 *    this list of conditions and the following disclaimer.
011 *
012 *  * Redistributions in binary form must reproduce the above copyright notice,
013 *    this list of conditions and the following disclaimer in the documentation
014 *    and/or other materials provided with the distribution.
015 *
016 *  * Neither the name of JSR-310 nor the names of its contributors
017 *    may be used to endorse or promote products derived from this software
018 *    without specific prior written permission.
019 *
020 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
021 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
022 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
023 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
024 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
025 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
026 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
027 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
028 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
029 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
030 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
031 */
032package org.threeten.bp.temporal;
033
034import static org.threeten.bp.DayOfWeek.THURSDAY;
035import static org.threeten.bp.DayOfWeek.WEDNESDAY;
036import static org.threeten.bp.temporal.ChronoField.DAY_OF_WEEK;
037import static org.threeten.bp.temporal.ChronoField.DAY_OF_YEAR;
038import static org.threeten.bp.temporal.ChronoField.EPOCH_DAY;
039import static org.threeten.bp.temporal.ChronoField.MONTH_OF_YEAR;
040import static org.threeten.bp.temporal.ChronoField.YEAR;
041import static org.threeten.bp.temporal.ChronoUnit.DAYS;
042import static org.threeten.bp.temporal.ChronoUnit.FOREVER;
043import static org.threeten.bp.temporal.ChronoUnit.MONTHS;
044import static org.threeten.bp.temporal.ChronoUnit.WEEKS;
045import static org.threeten.bp.temporal.ChronoUnit.YEARS;
046
047import org.threeten.bp.DateTimeException;
048import org.threeten.bp.Duration;
049import org.threeten.bp.LocalDate;
050import org.threeten.bp.format.DateTimeBuilder;
051import org.threeten.bp.jdk8.Jdk8Methods;
052
053/**
054 * Fields and units specific to the ISO-8601 calendar system,
055 * including quarter-of-year and week-based-year.
056 * <p>
057 * This class defines fields and units that are specific to the ISO calendar system.
058 *
059 * <h3>Quarter of year</h3>
060 * The ISO-8601 standard is based on the standard civic 12 month year.
061 * This is commonly divided into four quarters, often abbreviated as Q1, Q2, Q3 and Q4.
062 * <p>
063 * January, February and March are in Q1.
064 * April, May and June are in Q2.
065 * July, August and September are in Q3.
066 * October, November and December are in Q4.
067 * <p>
068 * The complete date is expressed using three fields:
069 * <p><ul>
070 * <li>{@link #DAY_OF_QUARTER DAY_OF_QUARTER} - the day within the quarter, from 1 to 90, 91 or 92
071 * <li>{@link #QUARTER_OF_YEAR QUARTER_OF_YEAR} - the week within the week-based-year
072 * <li>{@link ChronoField#YEAR YEAR} - the standard ISO year
073 * </ul><p>
074 *
075 * <h3>Week based years</h3>
076 * The ISO-8601 standard was originally intended as a data interchange format,
077 * defining a string format for dates and times. However, it also defines an
078 * alternate way of expressing the date, based on the concept of week-based-year.
079 * <p>
080 * The date is expressed using three fields:
081 * <p><ul>
082 * <li>{@link ChronoField#DAY_OF_WEEK DAY_OF_WEEK} - the standard field defining the
083 *  day-of-week from Monday (1) to Sunday (7)
084 * <li>{@link #WEEK_OF_WEEK_BASED_YEAR} - the week within the week-based-year
085 * <li>{@link #WEEK_BASED_YEAR WEEK_BASED_YEAR} - the week-based-year
086 * </ul><p>
087 * The week-based-year itself is defined relative to the standard ISO proleptic year.
088 * It differs from the standard year in that it always starts on a Monday.
089 * <p>
090 * The first week of a week-based-year is the first Monday-based week of the standard
091 * ISO year that has at least 4 days in the new year.
092 * <p><ul>
093 * <li>If January 1st is Monday then week 1 starts on January 1st
094 * <li>If January 1st is Tuesday then week 1 starts on December 31st of the previous standard year
095 * <li>If January 1st is Wednesday then week 1 starts on December 30th of the previous standard year
096 * <li>If January 1st is Thursday then week 1 starts on December 29th of the previous standard year
097 * <li>If January 1st is Friday then week 1 starts on January 4th
098 * <li>If January 1st is Saturday then week 1 starts on January 3rd
099 * <li>If January 1st is Sunday then week 1 starts on January 2nd
100 * </ul><p>
101 * There are 52 weeks in most week-based years, however on occasion there are 53 weeks.
102 * <p>
103 * For example:
104 * <p>
105 * <table cellpadding="0" cellspacing="3" border="0" style="text-align: left; width: 50%;">
106 * <caption>Examples of Week based Years</caption>
107 * <tr><th>Date</th><th>Day-of-week</th><th>Field values</th></tr>
108 * <tr><th>2008-12-28</th><td>Sunday</td><td>Week 52 of week-based-year 2008</td></tr>
109 * <tr><th>2008-12-29</th><td>Monday</td><td>Week 1 of week-based-year 2009</td></tr>
110 * <tr><th>2008-12-31</th><td>Wednesday</td><td>Week 1 of week-based-year 2009</td></tr>
111 * <tr><th>2009-01-01</th><td>Thursday</td><td>Week 1 of week-based-year 2009</td></tr>
112 * <tr><th>2009-01-04</th><td>Sunday</td><td>Week 1 of week-based-year 2009</td></tr>
113 * <tr><th>2009-01-05</th><td>Monday</td><td>Week 2 of week-based-year 2009</td></tr>
114 * </table>
115 *
116 * <h3>Specification for implementors</h3>
117 * <p>
118 * This class is immutable and thread-safe.
119 */
120public final class ISOFields {
121
122    /**
123     * The field that represents the day-of-quarter.
124     * <p>
125     * This field allows the day-of-quarter value to be queried and set.
126     * The day-of-quarter has values from 1 to 90 in Q1 of a standard year, from 1 to 91
127     * in Q1 of a leap year, from 1 to 91 in Q2 and from 1 to 92 in Q3 and Q4.
128     * <p>
129     * The day-of-quarter can only be calculated if the day-of-year, month-of-year and year
130     * are available.
131     * <p>
132     * When setting this field, the value is allowed to be partially lenient, taking any
133     * value from 1 to 92. If the quarter has less than 92 days, then day 92, and
134     * potentially day 91, is in the following quarter.
135     * <p>
136     * This unit is an immutable and thread-safe singleton.
137     */
138    public static final TemporalField DAY_OF_QUARTER = Field.DAY_OF_QUARTER;
139    /**
140     * The field that represents the quarter-of-year.
141     * <p>
142     * This field allows the quarter-of-year value to be queried and set.
143     * The quarter-of-year has values from 1 to 4.
144     * <p>
145     * The day-of-quarter can only be calculated if the month-of-year is available.
146     * <p>
147     * This unit is an immutable and thread-safe singleton.
148     */
149    public static final TemporalField QUARTER_OF_YEAR = Field.QUARTER_OF_YEAR;
150    /**
151     * The field that represents the week-of-week-based-year.
152     * <p>
153     * This field allows the week of the week-based-year value to be queried and set.
154     * <p>
155     * This unit is an immutable and thread-safe singleton.
156     */
157    public static final TemporalField WEEK_OF_WEEK_BASED_YEAR = Field.WEEK_OF_WEEK_BASED_YEAR;
158    /**
159     * The field that represents the week-based-year.
160     * <p>
161     * This field allows the week-based-year value to be queried and set.
162     * <p>
163     * This unit is an immutable and thread-safe singleton.
164     */
165    public static final TemporalField WEEK_BASED_YEAR = Field.WEEK_BASED_YEAR;
166    /**
167     * The unit that represents week-based-years for the purpose of addition and subtraction.
168     * <p>
169     * This allows a number of week-based-years to be added to, or subtracted from, a date.
170     * The unit is equal to either 52 or 53 weeks.
171     * The estimated duration of a week-based-year is the same as that of a standard ISO
172     * year at {@code 365.2425 Days}.
173     * <p>
174     * The rules for addition add the number of week-based-years to the existing value
175     * for the week-based-year field. If the resulting week-based-year only has 52 weeks,
176     * then the date will be in week 1 of the following week-based-year.
177     * <p>
178     * This unit is an immutable and thread-safe singleton.
179     */
180    public static final TemporalUnit WEEK_BASED_YEARS = Unit.WEEK_BASED_YEARS;
181    /**
182     * Unit that represents the concept of a quarter-year.
183     * For the ISO calendar system, it is equal to 3 months.
184     * The estimated duration of a quarter-year is one quarter of {@code 365.2425 Days}.
185     * <p>
186     * This unit is an immutable and thread-safe singleton.
187     */
188    public static final TemporalUnit QUARTER_YEARS = Unit.QUARTER_YEARS;
189
190    /**
191     * Restricted constructor.
192     */
193    private ISOFields() {
194        throw new AssertionError("Not instantiable");
195    }
196
197    //-----------------------------------------------------------------------
198    /**
199     * Implementation of the field.
200     */
201    private static enum Field implements TemporalField {
202        DAY_OF_QUARTER {
203            @Override
204            public String getName() {
205                return "DayOfQuarter";
206            }
207            @Override
208            public TemporalUnit getBaseUnit() {
209                return DAYS;
210            }
211            @Override
212            public TemporalUnit getRangeUnit() {
213                return QUARTER_YEARS;
214            }
215            @Override
216            public ValueRange range() {
217                return ValueRange.of(1, 90, 92);
218            }
219            @Override
220            public boolean doIsSupported(TemporalAccessor temporal) {
221                return temporal.isSupported(DAY_OF_YEAR) && temporal.isSupported(MONTH_OF_YEAR) &&
222                        temporal.isSupported(YEAR) && Chrono.from(temporal).equals(ISOChrono.INSTANCE);
223            }
224            @Override
225            public ValueRange doRange(TemporalAccessor temporal) {
226                if (doIsSupported(temporal) == false) {
227                    throw new DateTimeException("Unsupported field: DayOfQuarter");
228                }
229                long qoy = temporal.getLong(QUARTER_OF_YEAR);
230                if (qoy == 1) {
231                    long year = temporal.getLong(YEAR);
232                    return (ISOChrono.INSTANCE.isLeapYear(year) ? ValueRange.of(1, 91) : ValueRange.of(1, 90));
233                } else if (qoy == 2) {
234                    return ValueRange.of(1, 91);
235                } else if (qoy == 3 || qoy == 4) {
236                    return ValueRange.of(1, 92);
237                } // else value not from 1 to 4, so drop through
238                return range();
239            }
240            @Override
241            public long doGet(TemporalAccessor temporal) {
242                if (doIsSupported(temporal) == false) {
243                    throw new DateTimeException("Unsupported field: DayOfQuarter");
244                }
245                int doy = temporal.get(DAY_OF_YEAR);
246                int moy = temporal.get(MONTH_OF_YEAR);
247                long year = temporal.getLong(YEAR);
248                return doy - QUARTER_DAYS[((moy - 1) / 3) + (ISOChrono.INSTANCE.isLeapYear(year) ? 4 : 0)];
249            }
250            @Override
251            public <R extends Temporal> R doWith(R temporal, long newValue) {
252                long curValue = doGet(temporal);
253                range().checkValidValue(newValue, this);
254                return (R) temporal.with(DAY_OF_YEAR, temporal.getLong(DAY_OF_YEAR) + (newValue - curValue));
255            }
256        },
257        QUARTER_OF_YEAR {
258            @Override
259            public String getName() {
260                return "QuarterOfYear";
261            }
262            @Override
263            public TemporalUnit getBaseUnit() {
264                return QUARTER_YEARS;
265            }
266            @Override
267            public TemporalUnit getRangeUnit() {
268                return YEARS;
269            }
270            @Override
271            public ValueRange range() {
272                return ValueRange.of(1, 4);
273            }
274            @Override
275            public boolean doIsSupported(TemporalAccessor temporal) {
276                return temporal.isSupported(MONTH_OF_YEAR) && Chrono.from(temporal).equals(ISOChrono.INSTANCE);
277            }
278            @Override
279            public ValueRange doRange(TemporalAccessor temporal) {
280                return range();
281            }
282            @Override
283            public long doGet(TemporalAccessor temporal) {
284                if (doIsSupported(temporal) == false) {
285                    throw new DateTimeException("Unsupported field: DayOfQuarter");
286                }
287                long moy = temporal.getLong(MONTH_OF_YEAR);
288                return ((moy + 2) / 3);
289            }
290            @Override
291            public <R extends Temporal> R doWith(R temporal, long newValue) {
292                long curValue = doGet(temporal);
293                range().checkValidValue(newValue, this);
294                return (R) temporal.with(MONTH_OF_YEAR, temporal.getLong(MONTH_OF_YEAR) + (newValue - curValue) * 3);
295            }
296            @Override
297            public boolean resolve(DateTimeBuilder builder, long value) {
298                Long[] values = builder.queryFieldValues(YEAR, QUARTER_OF_YEAR, DAY_OF_QUARTER);
299                if (values[0] != null && values[1] != null && values[2] != null) {
300                    int y = YEAR.range().checkValidIntValue(values[0], YEAR);
301                    int qoy = QUARTER_OF_YEAR.range().checkValidIntValue(values[1], QUARTER_OF_YEAR);
302                    int doq = DAY_OF_QUARTER.range().checkValidIntValue(values[2], DAY_OF_QUARTER);
303                    LocalDate date = LocalDate.of(y, ((qoy - 1) * 3) + 1, 1).plusDays(doq - 1);
304                    builder.addFieldValue(EPOCH_DAY, date.toEpochDay());
305                    builder.removeFieldValues(QUARTER_OF_YEAR, DAY_OF_QUARTER);
306                }
307                return false;
308            }
309        },
310        WEEK_OF_WEEK_BASED_YEAR {
311            @Override
312            public String getName() {
313                return "WeekOfWeekBasedYear";
314            }
315            @Override
316            public TemporalUnit getBaseUnit() {
317                return WEEKS;
318            }
319            @Override
320            public TemporalUnit getRangeUnit() {
321                return WEEK_BASED_YEARS;
322            }
323            @Override
324            public ValueRange range() {
325                return ValueRange.of(1, 52, 53);
326            }
327            @Override
328            public boolean doIsSupported(TemporalAccessor temporal) {
329                return temporal.isSupported(EPOCH_DAY);
330            }
331            @Override
332            public ValueRange doRange(TemporalAccessor temporal) {
333                return getWeekRange(LocalDate.from(temporal));
334            }
335            @Override
336            public long doGet(TemporalAccessor temporal) {
337                return getWeek(LocalDate.from(temporal));
338            }
339            @Override
340            public <R extends Temporal> R doWith(R temporal, long newValue) {
341                ValueRange.of(1, 53).checkValidValue(newValue, this);
342                return (R) temporal.plus(Jdk8Methods.safeSubtract(newValue, doGet(temporal)), WEEKS);
343            }
344        },
345        WEEK_BASED_YEAR {
346            @Override
347            public String getName() {
348                return "WeekBasedYear";
349            }
350            @Override
351            public TemporalUnit getBaseUnit() {
352                return WEEK_BASED_YEARS;
353            }
354            @Override
355            public TemporalUnit getRangeUnit() {
356                return FOREVER;
357            }
358            @Override
359            public ValueRange range() {
360                return YEAR.range();
361            }
362            @Override
363            public boolean doIsSupported(TemporalAccessor temporal) {
364                return temporal.isSupported(EPOCH_DAY);
365            }
366            @Override
367            public ValueRange doRange(TemporalAccessor temporal) {
368                return YEAR.range();
369            }
370            @Override
371            public long doGet(TemporalAccessor temporal) {
372                return getWeekBasedYear(LocalDate.from(temporal));
373            }
374            @Override
375            public <R extends Temporal> R doWith(R temporal, long newValue) {
376                int newVal = range().checkValidIntValue(newValue, WEEK_BASED_YEAR);
377                LocalDate date = LocalDate.from(temporal);
378                int week = getWeek(date);
379                date = date.withDayOfYear(180).withYear(newVal).with(WEEK_OF_WEEK_BASED_YEAR, week);
380                return (R) date.with(date);
381            }
382            @Override
383            public boolean resolve(DateTimeBuilder builder, long value) {
384                Long[] values = builder.queryFieldValues(WEEK_BASED_YEAR, WEEK_OF_WEEK_BASED_YEAR, DAY_OF_WEEK);
385                if (values[0] != null && values[1] != null && values[2] != null) {
386                    int wby = WEEK_BASED_YEAR.range().checkValidIntValue(values[0], WEEK_BASED_YEAR);
387                    int week = WEEK_OF_WEEK_BASED_YEAR.range().checkValidIntValue(values[1], WEEK_OF_WEEK_BASED_YEAR);
388                    int dow = DAY_OF_WEEK.range().checkValidIntValue(values[2], DAY_OF_WEEK);
389                    LocalDate date = LocalDate.of(wby, 2, 1).with(WEEK_OF_WEEK_BASED_YEAR, week).with(DAY_OF_WEEK, dow);
390                    builder.addFieldValue(EPOCH_DAY, date.toEpochDay());
391                    builder.removeFieldValues(WEEK_BASED_YEAR, WEEK_OF_WEEK_BASED_YEAR, DAY_OF_WEEK);
392                }
393                return false;
394            }
395        };
396
397        @Override
398        public int compare(TemporalAccessor temporal1, TemporalAccessor temporal2) {
399            return Long.compare(temporal1.getLong(this), temporal2.getLong(this));
400        }
401
402        @Override
403        public boolean resolve(DateTimeBuilder builder, long value) {
404            return false;
405        }
406
407        @Override
408        public String toString() {
409            return getName();
410        }
411
412        //-------------------------------------------------------------------------
413        private static final int[] QUARTER_DAYS = {0, 90, 181, 273, 0, 91, 182, 274};
414
415        private static ValueRange getWeekRange(LocalDate date) {
416            int wby = getWeekBasedYear(date);
417            date = date.withDayOfYear(1).withYear(wby);
418            // 53 weeks if standard year starts on Thursday, or Wed in a leap year
419            if (date.getDayOfWeek() == THURSDAY || (date.getDayOfWeek() == WEDNESDAY && date.isLeapYear())) {
420                return ValueRange.of(1, 53);
421            }
422            return ValueRange.of(1, 52);
423        }
424
425        private static int getWeek(LocalDate date) {
426            int dow0 = date.getDayOfWeek().ordinal();
427            int doy0 = date.getDayOfYear() - 1;
428            int doyThu0 = doy0 + (3 - dow0);  // adjust to mid-week Thursday (which is 3 indexed from zero)
429            int alignedWeek = doyThu0 / 7;
430            int firstThuDoy0 = doyThu0 - (alignedWeek * 7);
431            int firstMonDoy0 = firstThuDoy0 - 3;
432            if (firstMonDoy0 < -3) {
433                firstMonDoy0 += 7;
434            }
435            if (doy0 < firstMonDoy0) {
436                return (int) getWeekRange(date.withDayOfYear(180).minusYears(1)).getMaximum();
437            }
438            int week = ((doy0 - firstMonDoy0) / 7) + 1;
439            if (week == 53) {
440                if ((firstMonDoy0 == -3 || (firstMonDoy0 == -2 && date.isLeapYear())) == false) {
441                    week = 1;
442                }
443            }
444            return week;
445        }
446
447        private static int getWeekBasedYear(LocalDate date) {
448            int year = date.getYear();
449            int doy = date.getDayOfYear();
450            if (doy <= 3) {
451                int dow = date.getDayOfWeek().ordinal();
452                if (doy - dow < -2) {
453                    year--;
454                }
455            } else if (doy >= 363) {
456                int dow = date.getDayOfWeek().ordinal();
457                doy = doy - 363 - (date.isLeapYear() ? 1 : 0);
458                if (doy - dow >= 0) {
459                    year++;
460                }
461            }
462            return year;
463        }
464    }
465
466    //-----------------------------------------------------------------------
467    /**
468     * Implementation of the period unit.
469     */
470    private static enum Unit implements TemporalUnit {
471        WEEK_BASED_YEARS("WeekBasedYears", Duration.ofSeconds(31556952L)),
472        QUARTER_YEARS("QuarterYears", Duration.ofSeconds(31556952L / 4));
473
474        private final String name;
475        private final Duration duration;
476
477        private Unit(String name, Duration estimatedDuration) {
478            this.name = name;
479            this.duration = estimatedDuration;
480        }
481
482        @Override
483        public String getName() {
484            return name;
485        }
486
487        @Override
488        public Duration getDuration() {
489            return duration;
490        }
491
492        @Override
493        public boolean isDurationEstimated() {
494            return true;
495        }
496
497        @Override
498        public boolean isSupported(Temporal temporal) {
499            return temporal.isSupported(EPOCH_DAY);
500        }
501
502        @Override
503        public <R extends Temporal> R doPlus(R temporal, long periodToAdd) {
504            switch(this) {
505                case WEEK_BASED_YEARS:
506                    long added = Jdk8Methods.safeAdd(temporal.get(WEEK_BASED_YEAR), periodToAdd);
507                    return (R) temporal.with(WEEK_BASED_YEAR, added);
508                case QUARTER_YEARS:
509                    // no overflow (256 is multiple of 4)
510                    return (R) temporal.plus(periodToAdd / 256, YEARS).plus((periodToAdd % 256) * 3, MONTHS);
511                default:
512                    throw new IllegalStateException("Unreachable");
513            }
514        }
515
516        @Override
517        public <R extends Temporal> SimplePeriod between(R temporal1, R temporal2) {
518            switch(this) {
519                case WEEK_BASED_YEARS:
520                    long period = Jdk8Methods.safeSubtract(temporal2.getLong(WEEK_BASED_YEAR), temporal1.getLong(WEEK_BASED_YEAR));
521                    return new SimplePeriod(period, WEEK_BASED_YEARS);
522                case QUARTER_YEARS:
523                    long quarters = temporal1.periodUntil(temporal2, MONTHS) / 3;
524                    return new SimplePeriod(quarters, QUARTER_YEARS);
525                default:
526                    throw new IllegalStateException("Unreachable");
527            }
528        }
529
530        @Override
531        public String toString() {
532            return getName();
533
534        }
535    }
536}