66 import static java.time.temporal.ChronoField.DAY_OF_YEAR; |
66 import static java.time.temporal.ChronoField.DAY_OF_YEAR; |
67 import static java.time.temporal.ChronoField.EPOCH_DAY; |
67 import static java.time.temporal.ChronoField.EPOCH_DAY; |
68 import static java.time.temporal.ChronoField.MONTH_OF_YEAR; |
68 import static java.time.temporal.ChronoField.MONTH_OF_YEAR; |
69 import static java.time.temporal.ChronoField.YEAR; |
69 import static java.time.temporal.ChronoField.YEAR; |
70 import static java.time.temporal.ChronoUnit.DAYS; |
70 import static java.time.temporal.ChronoUnit.DAYS; |
|
71 import static java.time.temporal.ChronoUnit.FOREVER; |
71 import static java.time.temporal.ChronoUnit.MONTHS; |
72 import static java.time.temporal.ChronoUnit.MONTHS; |
72 import static java.time.temporal.ChronoUnit.WEEKS; |
73 import static java.time.temporal.ChronoUnit.WEEKS; |
73 import static java.time.temporal.ChronoUnit.YEARS; |
74 import static java.time.temporal.ChronoUnit.YEARS; |
74 |
75 |
75 import java.io.InvalidObjectException; |
76 import java.io.InvalidObjectException; |
76 import java.io.Serializable; |
77 import java.io.Serializable; |
77 import java.time.DayOfWeek; |
78 import java.time.DayOfWeek; |
78 import java.time.chrono.ChronoLocalDate; |
79 import java.time.chrono.ChronoLocalDate; |
79 import java.time.chrono.Chronology; |
80 import java.time.chrono.Chronology; |
|
81 import java.time.format.ResolverStyle; |
80 import java.util.Collections; |
82 import java.util.Collections; |
81 import java.util.HashMap; |
83 import java.util.HashMap; |
82 import java.util.Locale; |
84 import java.util.Locale; |
83 import java.util.Map; |
85 import java.util.Map; |
84 import java.util.Objects; |
86 import java.util.Objects; |
|
87 import java.util.ResourceBundle; |
85 import java.util.concurrent.ConcurrentHashMap; |
88 import java.util.concurrent.ConcurrentHashMap; |
86 import java.util.concurrent.ConcurrentMap; |
89 import java.util.concurrent.ConcurrentMap; |
87 import sun.util.locale.provider.CalendarDataUtility; |
90 import sun.util.locale.provider.CalendarDataUtility; |
|
91 import sun.util.locale.provider.LocaleProviderAdapter; |
|
92 import sun.util.locale.provider.LocaleResources; |
88 |
93 |
89 /** |
94 /** |
90 * Localized definitions of the day-of-week, week-of-month and week-of-year fields. |
95 * Localized definitions of the day-of-week, week-of-month and week-of-year fields. |
91 * <p> |
96 * <p> |
92 * A standard week is seven days long, but cultures have different definitions for some |
97 * A standard week is seven days long, but cultures have different definitions for some |
93 * other aspects of a week. This class represents the definition of the week, for the |
98 * other aspects of a week. This class represents the definition of the week, for the |
94 * purpose of providing {@link TemporalField} instances. |
99 * purpose of providing {@link TemporalField} instances. |
95 * <p> |
100 * <p> |
96 * WeekFields provides three fields, |
101 * WeekFields provides five fields, |
97 * {@link #dayOfWeek()}, {@link #weekOfMonth()}, and {@link #weekOfYear()} |
102 * {@link #dayOfWeek()}, {@link #weekOfMonth()}, {@link #weekOfYear()}, |
|
103 * {@link #weekOfWeekBasedYear()}, and {@link #weekBasedYear()} |
98 * that provide access to the values from any {@linkplain Temporal temporal object}. |
104 * that provide access to the values from any {@linkplain Temporal temporal object}. |
99 * <p> |
105 * <p> |
100 * The computations for day-of-week, week-of-month, and week-of-year are based |
106 * The computations for day-of-week, week-of-month, and week-of-year are based |
101 * on the {@linkplain ChronoField#YEAR proleptic-year}, |
107 * on the {@linkplain ChronoField#YEAR proleptic-year}, |
102 * {@linkplain ChronoField#MONTH_OF_YEAR month-of-year}, |
108 * {@linkplain ChronoField#MONTH_OF_YEAR month-of-year}, |
132 * <tr><th>2009-01-04</th><td>Sunday</td> |
138 * <tr><th>2009-01-04</th><td>Sunday</td> |
133 * <td>Week 1 of January 2009</td><td>Week 0 of January 2009</td></tr> |
139 * <td>Week 1 of January 2009</td><td>Week 0 of January 2009</td></tr> |
134 * <tr><th>2009-01-05</th><td>Monday</td> |
140 * <tr><th>2009-01-05</th><td>Monday</td> |
135 * <td>Week 2 of January 2009</td><td>Week 1 of January 2009</td></tr> |
141 * <td>Week 2 of January 2009</td><td>Week 1 of January 2009</td></tr> |
136 * </table> |
142 * </table> |
137 * <p> |
143 * |
138 * <h3>Week of Year</h3> |
144 * <h3>Week of Year</h3> |
139 * One field is used: week-of-year. |
145 * One field is used: week-of-year. |
140 * The calculation ensures that weeks never overlap a year boundary. |
146 * The calculation ensures that weeks never overlap a year boundary. |
141 * The year is divided into periods where each period starts on the defined first day-of-week. |
147 * The year is divided into periods where each period starts on the defined first day-of-week. |
142 * The earliest period is referred to as week 0 if it has less than the minimal number of days |
148 * The earliest period is referred to as week 0 if it has less than the minimal number of days |
143 * and week 1 if it has at least the minimal number of days. |
149 * and week 1 if it has at least the minimal number of days. |
144 * <p> |
150 * |
|
151 * <h3>Week Based Year</h3> |
|
152 * Two fields are used for week-based-year, one for the |
|
153 * {@link #weekOfWeekBasedYear() week-of-week-based-year} and one for |
|
154 * {@link #weekBasedYear() week-based-year}. In a week-based-year, each week |
|
155 * belongs to only a single year. Week 1 of a year is the first week that |
|
156 * starts on the first day-of-week and has at least the minimum number of days. |
|
157 * The first and last weeks of a year may contain days from the |
|
158 * previous calendar year or next calendar year respectively. |
|
159 * |
|
160 * <table cellpadding="0" cellspacing="3" border="0" style="text-align: left; width: 50%;"> |
|
161 * <caption>Examples of WeekFields for week-based-year</caption> |
|
162 * <tr><th>Date</th><td>Day-of-week</td> |
|
163 * <td>First day: Monday<br>Minimal days: 4</td><td>First day: Monday<br>Minimal days: 5</td></tr> |
|
164 * <tr><th>2008-12-31</th><td>Wednesday</td> |
|
165 * <td>Week 1 of 2009</td><td>Week 53 of 2008</td></tr> |
|
166 * <tr><th>2009-01-01</th><td>Thursday</td> |
|
167 * <td>Week 1 of 2009</td><td>Week 53 of 2008</td></tr> |
|
168 * <tr><th>2009-01-04</th><td>Sunday</td> |
|
169 * <td>Week 1 of 2009</td><td>Week 53 of 2008</td></tr> |
|
170 * <tr><th>2009-01-05</th><td>Monday</td> |
|
171 * <td>Week 2 of 2009</td><td>Week 1 of 2009</td></tr> |
|
172 * </table> |
|
173 * <h3>Specification for implementors</h3> |
145 * This class is immutable and thread-safe. |
174 * This class is immutable and thread-safe. |
146 * |
175 * |
147 * @since 1.8 |
176 * @since 1.8 |
148 */ |
177 */ |
149 public final class WeekFields implements Serializable { |
178 public final class WeekFields implements Serializable { |
169 * up the passage of days instead of the standard year/month/day. |
198 * up the passage of days instead of the standard year/month/day. |
170 * <p> |
199 * <p> |
171 * Note that the first week may start in the previous calendar year. |
200 * Note that the first week may start in the previous calendar year. |
172 * Note also that the first few days of a calendar year may be in the |
201 * Note also that the first few days of a calendar year may be in the |
173 * week-based-year corresponding to the previous calendar year. |
202 * week-based-year corresponding to the previous calendar year. |
|
203 * <p> |
|
204 * This field is an immutable and thread-safe singleton. |
174 */ |
205 */ |
175 public static final WeekFields ISO = new WeekFields(DayOfWeek.MONDAY, 4); |
206 public static final WeekFields ISO = new WeekFields(DayOfWeek.MONDAY, 4); |
176 |
207 |
177 /** |
208 /** |
178 * The common definition of a week that starts on Sunday. |
209 * The common definition of a week that starts on Sunday and the first week |
|
210 * has a minimum of 1 day. |
179 * <p> |
211 * <p> |
180 * Defined as starting on Sunday and with a minimum of 1 day in the month. |
212 * Defined as starting on Sunday and with a minimum of 1 day in the month. |
181 * This week definition is in use in the US and other European countries. |
213 * This week definition is in use in the US and other European countries. |
182 * |
214 * <p> |
|
215 * This field is an immutable and thread-safe singleton. |
183 */ |
216 */ |
184 public static final WeekFields SUNDAY_START = WeekFields.of(DayOfWeek.SUNDAY, 1); |
217 public static final WeekFields SUNDAY_START = WeekFields.of(DayOfWeek.SUNDAY, 1); |
185 |
218 |
186 /** |
219 /** |
|
220 * The unit that represents week-based-years for the purpose of addition and subtraction. |
|
221 * <p> |
|
222 * This allows a number of week-based-years to be added to, or subtracted from, a date. |
|
223 * The unit is equal to either 52 or 53 weeks. |
|
224 * The estimated duration of a week-based-year is the same as that of a standard ISO |
|
225 * year at {@code 365.2425 Days}. |
|
226 * <p> |
|
227 * The rules for addition add the number of week-based-years to the existing value |
|
228 * for the week-based-year field retaining the week-of-week-based-year |
|
229 * and day-of-week, unless the week number it too large for the target year. |
|
230 * In that case, the week is set to the last week of the year |
|
231 * with the same day-of-week. |
|
232 * <p> |
|
233 * This field is an immutable and thread-safe singleton. |
|
234 */ |
|
235 public static final TemporalUnit WEEK_BASED_YEARS = IsoFields.WEEK_BASED_YEARS; |
|
236 |
|
237 /** |
187 * Serialization version. |
238 * Serialization version. |
188 */ |
239 */ |
189 private static final long serialVersionUID = -1177360819670808121L; |
240 private static final long serialVersionUID = -1177360819670808121L; |
190 |
241 |
191 /** |
242 /** |
388 public TemporalField weekOfYear() { |
457 public TemporalField weekOfYear() { |
389 return weekOfYear; |
458 return weekOfYear; |
390 } |
459 } |
391 |
460 |
392 /** |
461 /** |
393 * Checks if these rules are equal to the specified rules. |
462 * Returns a field to access the week of a week-based-year, |
|
463 * computed based on this WeekFields. |
|
464 * <p> |
|
465 * This represents the concept of the count of weeks within the year where weeks |
|
466 * start on a fixed day-of-week, such as Monday and each week belongs to exactly one year. |
|
467 * This field is typically used with {@link WeekFields#dayOfWeek()} and |
|
468 * {@link WeekFields#weekBasedYear()}. |
|
469 * <p> |
|
470 * Week one(1) is the week starting on the {@link WeekFields#getFirstDayOfWeek} |
|
471 * where there are at least {@link WeekFields#getMinimalDaysInFirstWeek()} days in the year. |
|
472 * If the first week starts after the start of the year then the period before |
|
473 * is in the last week of the previous year. |
|
474 * <p> |
|
475 * For example:<br> |
|
476 * - if the 1st day of the year is a Monday, week one starts on the 1st<br> |
|
477 * - if the 2nd day of the year is a Monday, week one starts on the 2nd and |
|
478 * the 1st is in the last week of the previous year<br> |
|
479 * - if the 4th day of the year is a Monday, week one starts on the 4th and |
|
480 * the 1st to 3rd is in the last week of the previous year<br> |
|
481 * - if the 5th day of the year is a Monday, week two starts on the 5th and |
|
482 * the 1st to 4th is in week one<br> |
|
483 * <p> |
|
484 * This field can be used with any calendar system. |
|
485 * @return a TemporalField to access the week of week-based-year, not null |
|
486 */ |
|
487 public TemporalField weekOfWeekBasedYear() { |
|
488 return weekOfWeekBasedYear; |
|
489 } |
|
490 |
|
491 /** |
|
492 * Returns a field to access the year of a week-based-year, |
|
493 * computed based on this WeekFields. |
|
494 * <p> |
|
495 * This represents the concept of the year where weeks start on a fixed day-of-week, |
|
496 * such as Monday and each week belongs to exactly one year. |
|
497 * This field is typically used with {@link WeekFields#dayOfWeek()} and |
|
498 * {@link WeekFields#weekOfWeekBasedYear()}. |
|
499 * <p> |
|
500 * Week one(1) is the week starting on the {@link WeekFields#getFirstDayOfWeek} |
|
501 * where there are at least {@link WeekFields#getMinimalDaysInFirstWeek()} days in the year. |
|
502 * Thus, week one may start before the start of the year. |
|
503 * If the first week starts after the start of the year then the period before |
|
504 * is in the last week of the previous year. |
|
505 * <p> |
|
506 * This field can be used with any calendar system. |
|
507 * @return a TemporalField to access the year of week-based-year, not null |
|
508 */ |
|
509 public TemporalField weekBasedYear() { |
|
510 return weekBasedYear; |
|
511 } |
|
512 |
|
513 /** |
|
514 * Checks if this WeekFields is equal to the specified object. |
394 * <p> |
515 * <p> |
395 * The comparison is based on the entire state of the rules, which is |
516 * The comparison is based on the entire state of the rules, which is |
396 * the first day-of-week and minimal days. |
517 * the first day-of-week and minimal days. |
397 * |
518 * |
398 * @param object the other rules to compare to, null returns false |
519 * @param object the other rules to compare to, null returns false |
467 * @see WeekFields#weekOfYear() |
588 * @see WeekFields#weekOfYear() |
468 */ |
589 */ |
469 static ComputedDayOfField ofWeekOfYearField(WeekFields weekDef) { |
590 static ComputedDayOfField ofWeekOfYearField(WeekFields weekDef) { |
470 return new ComputedDayOfField("WeekOfYear", weekDef, WEEKS, YEARS, WEEK_OF_YEAR_RANGE); |
591 return new ComputedDayOfField("WeekOfYear", weekDef, WEEKS, YEARS, WEEK_OF_YEAR_RANGE); |
471 } |
592 } |
|
593 |
|
594 /** |
|
595 * Returns a field to access the week of week-based-year, |
|
596 * computed based on a WeekFields. |
|
597 * @see WeekFields#weekOfWeekBasedYear() |
|
598 */ |
|
599 static ComputedDayOfField ofWeekOfWeekBasedYearField(WeekFields weekDef) { |
|
600 return new ComputedDayOfField("WeekOfWeekBasedYear", weekDef, WEEKS, IsoFields.WEEK_BASED_YEARS, WEEK_OF_YEAR_RANGE); |
|
601 } |
|
602 |
|
603 /** |
|
604 * Returns a field to access the week of week-based-year, |
|
605 * computed based on a WeekFields. |
|
606 * @see WeekFields#weekBasedYear() |
|
607 */ |
|
608 static ComputedDayOfField ofWeekBasedYearField(WeekFields weekDef) { |
|
609 return new ComputedDayOfField("WeekBasedYear", weekDef, IsoFields.WEEK_BASED_YEARS, FOREVER, ChronoField.YEAR.range()); |
|
610 } |
|
611 |
|
612 /** |
|
613 * Return a new week-based-year date of the Chronology, year, week-of-year, |
|
614 * and dow of week. |
|
615 * @param chrono The chronology of the new date |
|
616 * @param yowby the year of the week-based-year |
|
617 * @param wowby the week of the week-based-year |
|
618 * @param dow the day of the week |
|
619 * @return a ChronoLocalDate for the requested year, week of year, and day of week |
|
620 */ |
|
621 private ChronoLocalDate<?> ofWeekBasedYear(Chronology chrono, |
|
622 int yowby, int wowby, int dow) { |
|
623 ChronoLocalDate<?> date = chrono.date(yowby, 1, 1); |
|
624 int ldow = localizedDayOfWeek(date); |
|
625 int offset = startOfWeekOffset(1, ldow); |
|
626 |
|
627 // Clamp the week of year to keep it in the same year |
|
628 int yearLen = date.lengthOfYear(); |
|
629 int newYearWeek = computeWeek(offset, yearLen + weekDef.getMinimalDaysInFirstWeek()); |
|
630 wowby = Math.min(wowby, newYearWeek - 1); |
|
631 |
|
632 int days = -offset + (dow - 1) + (wowby - 1) * 7; |
|
633 return date.plus(days, DAYS); |
|
634 } |
|
635 |
472 private final String name; |
636 private final String name; |
473 private final WeekFields weekDef; |
637 private final WeekFields weekDef; |
474 private final TemporalUnit baseUnit; |
638 private final TemporalUnit baseUnit; |
475 private final TemporalUnit rangeUnit; |
639 private final TemporalUnit rangeUnit; |
476 private final ValueRange range; |
640 private final ValueRange range; |
487 private static final ValueRange WEEK_OF_MONTH_RANGE = ValueRange.of(0, 1, 4, 6); |
651 private static final ValueRange WEEK_OF_MONTH_RANGE = ValueRange.of(0, 1, 4, 6); |
488 private static final ValueRange WEEK_OF_YEAR_RANGE = ValueRange.of(0, 1, 52, 54); |
652 private static final ValueRange WEEK_OF_YEAR_RANGE = ValueRange.of(0, 1, 52, 54); |
489 |
653 |
490 @Override |
654 @Override |
491 public long getFrom(TemporalAccessor temporal) { |
655 public long getFrom(TemporalAccessor temporal) { |
492 // Offset the ISO DOW by the start of this week |
656 if (rangeUnit == WEEKS) { // day-of-week |
|
657 return localizedDayOfWeek(temporal); |
|
658 } else if (rangeUnit == MONTHS) { // week-of-month |
|
659 return localizedWeekOfMonth(temporal); |
|
660 } else if (rangeUnit == YEARS) { // week-of-year |
|
661 return localizedWeekOfYear(temporal); |
|
662 } else if (rangeUnit == WEEK_BASED_YEARS) { |
|
663 return localizedWeekOfWeekBasedYear(temporal); |
|
664 } else if (rangeUnit == FOREVER) { |
|
665 return localizedWeekBasedYear(temporal); |
|
666 } else { |
|
667 throw new IllegalStateException("unreachable, rangeUnit: " + rangeUnit + ", this: " + this); |
|
668 } |
|
669 } |
|
670 |
|
671 private int localizedDayOfWeek(TemporalAccessor temporal) { |
493 int sow = weekDef.getFirstDayOfWeek().getValue(); |
672 int sow = weekDef.getFirstDayOfWeek().getValue(); |
494 int dow = localizedDayOfWeek(temporal, sow); |
|
495 |
|
496 if (rangeUnit == WEEKS) { // day-of-week |
|
497 return dow; |
|
498 } else if (rangeUnit == MONTHS) { // week-of-month |
|
499 return localizedWeekOfMonth(temporal, dow); |
|
500 } else if (rangeUnit == YEARS) { // week-of-year |
|
501 return localizedWeekOfYear(temporal, dow); |
|
502 } else { |
|
503 throw new IllegalStateException("unreachable"); |
|
504 } |
|
505 } |
|
506 |
|
507 private int localizedDayOfWeek(TemporalAccessor temporal, int sow) { |
|
508 int isoDow = temporal.get(DAY_OF_WEEK); |
673 int isoDow = temporal.get(DAY_OF_WEEK); |
509 return Math.floorMod(isoDow - sow, 7) + 1; |
674 return Math.floorMod(isoDow - sow, 7) + 1; |
510 } |
675 } |
511 |
676 |
512 private long localizedWeekOfMonth(TemporalAccessor temporal, int dow) { |
677 private long localizedWeekOfMonth(TemporalAccessor temporal) { |
|
678 int dow = localizedDayOfWeek(temporal); |
513 int dom = temporal.get(DAY_OF_MONTH); |
679 int dom = temporal.get(DAY_OF_MONTH); |
514 int offset = startOfWeekOffset(dom, dow); |
680 int offset = startOfWeekOffset(dom, dow); |
515 return computeWeek(offset, dom); |
681 return computeWeek(offset, dom); |
516 } |
682 } |
517 |
683 |
518 private long localizedWeekOfYear(TemporalAccessor temporal, int dow) { |
684 private long localizedWeekOfYear(TemporalAccessor temporal) { |
|
685 int dow = localizedDayOfWeek(temporal); |
519 int doy = temporal.get(DAY_OF_YEAR); |
686 int doy = temporal.get(DAY_OF_YEAR); |
520 int offset = startOfWeekOffset(doy, dow); |
687 int offset = startOfWeekOffset(doy, dow); |
521 return computeWeek(offset, doy); |
688 return computeWeek(offset, doy); |
522 } |
689 } |
523 |
690 |
524 /** |
691 /** |
|
692 * Returns the year of week-based-year for the temporal. |
|
693 * The year can be the previous year, the current year, or the next year. |
|
694 * @param temporal a date of any chronology, not null |
|
695 * @return the year of week-based-year for the date |
|
696 */ |
|
697 private int localizedWeekBasedYear(TemporalAccessor temporal) { |
|
698 int dow = localizedDayOfWeek(temporal); |
|
699 int year = temporal.get(YEAR); |
|
700 int doy = temporal.get(DAY_OF_YEAR); |
|
701 int offset = startOfWeekOffset(doy, dow); |
|
702 int week = computeWeek(offset, doy); |
|
703 if (week == 0) { |
|
704 // Day is in end of week of previous year; return the previous year |
|
705 return year - 1; |
|
706 } else { |
|
707 // If getting close to end of year, use higher precision logic |
|
708 // Check if date of year is in partial week associated with next year |
|
709 ValueRange dayRange = temporal.range(DAY_OF_YEAR); |
|
710 int yearLen = (int)dayRange.getMaximum(); |
|
711 int newYearWeek = computeWeek(offset, yearLen + weekDef.getMinimalDaysInFirstWeek()); |
|
712 if (week >= newYearWeek) { |
|
713 return year + 1; |
|
714 } |
|
715 } |
|
716 return year; |
|
717 } |
|
718 |
|
719 /** |
|
720 * Returns the week of week-based-year for the temporal. |
|
721 * The week can be part of the previous year, the current year, |
|
722 * or the next year depending on the week start and minimum number |
|
723 * of days. |
|
724 * @param temporal a date of any chronology |
|
725 * @return the week of the year |
|
726 * @see #localizedWeekBasedYear(java.time.temporal.TemporalAccessor) |
|
727 */ |
|
728 private int localizedWeekOfWeekBasedYear(TemporalAccessor temporal) { |
|
729 int dow = localizedDayOfWeek(temporal); |
|
730 int doy = temporal.get(DAY_OF_YEAR); |
|
731 int offset = startOfWeekOffset(doy, dow); |
|
732 int week = computeWeek(offset, doy); |
|
733 if (week == 0) { |
|
734 // Day is in end of week of previous year |
|
735 // Recompute from the last day of the previous year |
|
736 ChronoLocalDate date = Chronology.from(temporal).date(temporal); |
|
737 date = date.minus(doy, DAYS); // Back down into previous year |
|
738 return localizedWeekOfWeekBasedYear(date); |
|
739 } else if (week > 50) { |
|
740 // If getting close to end of year, use higher precision logic |
|
741 // Check if date of year is in partial week associated with next year |
|
742 ValueRange dayRange = temporal.range(DAY_OF_YEAR); |
|
743 int yearLen = (int)dayRange.getMaximum(); |
|
744 int newYearWeek = computeWeek(offset, yearLen + weekDef.getMinimalDaysInFirstWeek()); |
|
745 if (week >= newYearWeek) { |
|
746 // Overlaps with week of following year; reduce to week in following year |
|
747 week = week - newYearWeek + 1; |
|
748 } |
|
749 } |
|
750 return week; |
|
751 } |
|
752 |
|
753 /** |
525 * Returns an offset to align week start with a day of month or day of year. |
754 * Returns an offset to align week start with a day of month or day of year. |
526 * |
755 * |
527 * @param day the day; 1 through infinity |
756 * @param day the day; 1 through infinity |
528 * @param dow the day of the week of that day; 1 through 7 |
757 * @param dow the day of the week of that day; 1 through 7 |
529 * @return an offset in days to align a day with the start of the first 'full' week |
758 * @return an offset in days to align a day with the start of the first 'full' week |
530 */ |
759 */ |
531 private int startOfWeekOffset(int day, int dow) { |
760 private int startOfWeekOffset(int day, int dow) { |
532 // offset of first day corresponding to the day of week in first 7 days (zero origin) |
761 // offset of first day corresponding to the day of week in first 7 days (zero origin) |
533 int weekStart = Math.floorMod(day - dow, 7); |
762 int weekStart = Math.floorMod(day - dow, 7); |
534 int offset = -weekStart; |
763 int offset = -weekStart; |
558 int newVal = range.checkValidIntValue(newValue, this); // lenient check range |
787 int newVal = range.checkValidIntValue(newValue, this); // lenient check range |
559 int currentVal = temporal.get(this); |
788 int currentVal = temporal.get(this); |
560 if (newVal == currentVal) { |
789 if (newVal == currentVal) { |
561 return temporal; |
790 return temporal; |
562 } |
791 } |
563 // Compute the difference and add that using the base using of the field |
792 |
564 return (R) temporal.plus(newVal - currentVal, baseUnit); |
793 if (rangeUnit == FOREVER) { // replace year of WeekBasedYear |
565 } |
794 // Create a new date object with the same chronology, |
566 |
795 // the desired year and the same week and dow. |
567 @Override |
796 int idow = temporal.get(weekDef.dayOfWeek); |
568 public Map<TemporalField, Long> resolve(TemporalAccessor temporal, long value) { |
797 int wowby = temporal.get(weekDef.weekOfWeekBasedYear); |
|
798 return (R) ofWeekBasedYear(Chronology.from(temporal), (int)newValue, wowby, idow); |
|
799 } else { |
|
800 // Compute the difference and add that using the base unit of the field |
|
801 return (R) temporal.plus(newVal - currentVal, baseUnit); |
|
802 } |
|
803 } |
|
804 |
|
805 @Override |
|
806 public Map<TemporalField, Long> resolve(TemporalAccessor temporal, long value, ResolverStyle resolverStyle) { |
569 int newValue = range.checkValidIntValue(value, this); |
807 int newValue = range.checkValidIntValue(value, this); |
570 int sow = weekDef.getFirstDayOfWeek().getValue(); |
808 int sow = weekDef.getFirstDayOfWeek().getValue(); |
571 if (rangeUnit == WEEKS) { // day-of-week |
809 if (rangeUnit == WEEKS) { // day-of-week |
572 int isoDow = Math.floorMod((sow - 1) + (newValue - 1), 7) + 1; |
810 int isoDow = Math.floorMod((sow - 1) + (newValue - 1), 7) + 1; |
573 return Collections.<TemporalField, Long>singletonMap(DAY_OF_WEEK, (long) isoDow); |
811 return Collections.<TemporalField, Long>singletonMap(DAY_OF_WEEK, (long) isoDow); |
574 } |
812 } |
575 if ((temporal.isSupported(YEAR) && temporal.isSupported(DAY_OF_WEEK)) == false) { |
813 if (temporal.isSupported(DAY_OF_WEEK) == false) { |
576 return null; |
814 return null; |
577 } |
815 } |
578 int dow = localizedDayOfWeek(temporal, sow); |
|
579 int year = temporal.get(YEAR); |
|
580 Chronology chrono = Chronology.from(temporal); // defaults to ISO |
816 Chronology chrono = Chronology.from(temporal); // defaults to ISO |
581 if (rangeUnit == MONTHS) { // week-of-month |
817 int dow = localizedDayOfWeek(temporal); |
582 if (temporal.isSupported(MONTH_OF_YEAR) == false) { |
818 if (temporal.isSupported(YEAR)) { |
583 return null; |
819 int year = temporal.get(YEAR); |
|
820 if (rangeUnit == MONTHS) { // week-of-month |
|
821 if (temporal.isSupported(MONTH_OF_YEAR) == false) { |
|
822 return null; |
|
823 } |
|
824 int month = temporal.get(ChronoField.MONTH_OF_YEAR); |
|
825 @SuppressWarnings("rawtypes") |
|
826 ChronoLocalDate date = chrono.date(year, month, 1); |
|
827 int dateDow = localizedDayOfWeek(date); |
|
828 long weeks = newValue - localizedWeekOfMonth(date); |
|
829 int days = dow - dateDow; |
|
830 date = date.plus(weeks * 7 + days, DAYS); |
|
831 Map<TemporalField, Long> result = new HashMap<>(4, 1.0f); |
|
832 result.put(EPOCH_DAY, date.toEpochDay()); |
|
833 result.put(YEAR, null); |
|
834 result.put(MONTH_OF_YEAR, null); |
|
835 result.put(DAY_OF_WEEK, null); |
|
836 return result; |
|
837 } else if (rangeUnit == YEARS) { // week-of-year |
|
838 @SuppressWarnings("rawtypes") |
|
839 ChronoLocalDate date = chrono.date(year, 1, 1); |
|
840 int dateDow = localizedDayOfWeek(date); |
|
841 long weeks = newValue - localizedWeekOfYear(date); |
|
842 int days = dow - dateDow; |
|
843 date = date.plus(weeks * 7 + days, DAYS); |
|
844 Map<TemporalField, Long> result = new HashMap<>(4, 1.0f); |
|
845 result.put(EPOCH_DAY, date.toEpochDay()); |
|
846 result.put(YEAR, null); |
|
847 result.put(DAY_OF_WEEK, null); |
|
848 return result; |
584 } |
849 } |
585 int month = temporal.get(ChronoField.MONTH_OF_YEAR); |
850 } else if (rangeUnit == WEEK_BASED_YEARS || rangeUnit == FOREVER) { |
586 ChronoLocalDate date = chrono.date(year, month, 1); |
851 if (temporal.isSupported(weekDef.weekBasedYear) && |
587 int dateDow = localizedDayOfWeek(date, sow); |
852 temporal.isSupported(weekDef.weekOfWeekBasedYear)) { |
588 long weeks = newValue - localizedWeekOfMonth(date, dateDow); |
853 // week-of-week-based-year and year-of-week-based-year |
589 int days = dow - dateDow; |
854 int yowby = temporal.get(weekDef.weekBasedYear); |
590 date = date.plus(weeks * 7 + days, DAYS); |
855 int wowby = temporal.get(weekDef.weekOfWeekBasedYear); |
591 Map<TemporalField, Long> result = new HashMap<>(4, 1.0f); |
856 ChronoLocalDate<?> date = ofWeekBasedYear(Chronology.from(temporal), yowby, wowby, dow); |
592 result.put(EPOCH_DAY, date.toEpochDay()); |
857 |
593 result.put(YEAR, null); |
858 Map<TemporalField, Long> result = new HashMap<>(4, 1.0f); |
594 result.put(MONTH_OF_YEAR, null); |
859 result.put(EPOCH_DAY, date.toEpochDay()); |
595 result.put(DAY_OF_WEEK, null); |
860 result.put(DAY_OF_WEEK, null); |
596 return result; |
861 result.put(weekDef.weekOfWeekBasedYear, null); |
597 } else if (rangeUnit == YEARS) { // week-of-year |
862 result.put(weekDef.weekBasedYear, null); |
598 ChronoLocalDate date = chrono.date(year, 1, 1); |
863 return result; |
599 int dateDow = localizedDayOfWeek(date, sow); |
864 } |
600 long weeks = newValue - localizedWeekOfYear(date, dateDow); |
865 } |
601 int days = dow - dateDow; |
866 return null; |
602 date = date.plus(weeks * 7 + days, DAYS); |
|
603 Map<TemporalField, Long> result = new HashMap<>(4, 1.0f); |
|
604 result.put(EPOCH_DAY, date.toEpochDay()); |
|
605 result.put(YEAR, null); |
|
606 result.put(DAY_OF_WEEK, null); |
|
607 return result; |
|
608 } else { |
|
609 throw new IllegalStateException("unreachable"); |
|
610 } |
|
611 } |
867 } |
612 |
868 |
613 //----------------------------------------------------------------------- |
869 //----------------------------------------------------------------------- |
614 @Override |
870 @Override |
615 public String getName() { |
871 public String getName() { |
616 return name; |
872 return name; |
617 } |
873 } |
618 |
874 |
619 @Override |
875 @Override |
|
876 public String getDisplayName(Locale locale) { |
|
877 Objects.requireNonNull(locale, "locale"); |
|
878 if (rangeUnit == YEARS) { // only have values for week-of-year |
|
879 LocaleResources lr = LocaleProviderAdapter.getResourceBundleBased() |
|
880 .getLocaleResources(locale); |
|
881 ResourceBundle rb = lr.getJavaTimeFormatData(); |
|
882 return rb.containsKey("field.week") ? rb.getString("field.week") : getName(); |
|
883 } |
|
884 return getName(); |
|
885 } |
|
886 |
|
887 @Override |
620 public TemporalUnit getBaseUnit() { |
888 public TemporalUnit getBaseUnit() { |
621 return baseUnit; |
889 return baseUnit; |
622 } |
890 } |
623 |
891 |
624 @Override |
892 @Override |
625 public TemporalUnit getRangeUnit() { |
893 public TemporalUnit getRangeUnit() { |
626 return rangeUnit; |
894 return rangeUnit; |
|
895 } |
|
896 |
|
897 @Override |
|
898 public boolean isDateBased() { |
|
899 return true; |
627 } |
900 } |
628 |
901 |
629 @Override |
902 @Override |
630 public ValueRange range() { |
903 public ValueRange range() { |
631 return range; |
904 return range; |
639 return true; |
912 return true; |
640 } else if (rangeUnit == MONTHS) { // week-of-month |
913 } else if (rangeUnit == MONTHS) { // week-of-month |
641 return temporal.isSupported(DAY_OF_MONTH); |
914 return temporal.isSupported(DAY_OF_MONTH); |
642 } else if (rangeUnit == YEARS) { // week-of-year |
915 } else if (rangeUnit == YEARS) { // week-of-year |
643 return temporal.isSupported(DAY_OF_YEAR); |
916 return temporal.isSupported(DAY_OF_YEAR); |
|
917 } else if (rangeUnit == WEEK_BASED_YEARS) { |
|
918 return temporal.isSupported(DAY_OF_YEAR); |
|
919 } else if (rangeUnit == FOREVER) { |
|
920 return temporal.isSupported(YEAR); |
644 } |
921 } |
645 } |
922 } |
646 return false; |
923 return false; |
647 } |
924 } |
648 |
925 |
649 @Override |
926 @Override |
650 public ValueRange rangeRefinedBy(TemporalAccessor temporal) { |
927 public ValueRange rangeRefinedBy(TemporalAccessor temporal) { |
651 if (rangeUnit == ChronoUnit.WEEKS) { // day-of-week |
928 if (rangeUnit == ChronoUnit.WEEKS) { // day-of-week |
652 return range; |
929 return range; |
653 } |
930 } else if (rangeUnit == MONTHS) { // week-of-month |
654 |
931 return rangeByWeek(temporal, DAY_OF_MONTH); |
655 TemporalField field = null; |
|
656 if (rangeUnit == MONTHS) { // week-of-month |
|
657 field = DAY_OF_MONTH; |
|
658 } else if (rangeUnit == YEARS) { // week-of-year |
932 } else if (rangeUnit == YEARS) { // week-of-year |
659 field = DAY_OF_YEAR; |
933 return rangeByWeek(temporal, DAY_OF_YEAR); |
|
934 } else if (rangeUnit == WEEK_BASED_YEARS) { |
|
935 return rangeWeekOfWeekBasedYear(temporal); |
|
936 } else if (rangeUnit == FOREVER) { |
|
937 return YEAR.range(); |
660 } else { |
938 } else { |
661 throw new IllegalStateException("unreachable"); |
939 throw new IllegalStateException("unreachable, rangeUnit: " + rangeUnit + ", this: " + this); |
662 } |
940 } |
663 |
941 } |
664 // Offset the ISO DOW by the start of this week |
942 |
665 int sow = weekDef.getFirstDayOfWeek().getValue(); |
943 /** |
666 int dow = localizedDayOfWeek(temporal, sow); |
944 * Map the field range to a week range |
667 |
945 * @param temporal the temporal |
|
946 * @param field the field to get the range of |
|
947 * @return the ValueRange with the range adjusted to weeks. |
|
948 */ |
|
949 private ValueRange rangeByWeek(TemporalAccessor temporal, TemporalField field) { |
|
950 int dow = localizedDayOfWeek(temporal); |
668 int offset = startOfWeekOffset(temporal.get(field), dow); |
951 int offset = startOfWeekOffset(temporal.get(field), dow); |
669 ValueRange fieldRange = temporal.range(field); |
952 ValueRange fieldRange = temporal.range(field); |
670 return ValueRange.of(computeWeek(offset, (int) fieldRange.getMinimum()), |
953 return ValueRange.of(computeWeek(offset, (int) fieldRange.getMinimum()), |
671 computeWeek(offset, (int) fieldRange.getMaximum())); |
954 computeWeek(offset, (int) fieldRange.getMaximum())); |
672 } |
955 } |
673 |
956 |
|
957 /** |
|
958 * Map the field range to a week range of a week year. |
|
959 * @param temporal the temporal |
|
960 * @param field the field to get the range of |
|
961 * @return the ValueRange with the range adjusted to weeks. |
|
962 */ |
|
963 private ValueRange rangeWeekOfWeekBasedYear(TemporalAccessor temporal) { |
|
964 if (!temporal.isSupported(DAY_OF_YEAR)) { |
|
965 return WEEK_OF_YEAR_RANGE; |
|
966 } |
|
967 int dow = localizedDayOfWeek(temporal); |
|
968 int doy = temporal.get(DAY_OF_YEAR); |
|
969 int offset = startOfWeekOffset(doy, dow); |
|
970 int week = computeWeek(offset, doy); |
|
971 if (week == 0) { |
|
972 // Day is in end of week of previous year |
|
973 // Recompute from the last day of the previous year |
|
974 ChronoLocalDate date = Chronology.from(temporal).date(temporal); |
|
975 date = date.minus(doy + 7, DAYS); // Back down into previous year |
|
976 return rangeWeekOfWeekBasedYear(date); |
|
977 } |
|
978 // Check if day of year is in partial week associated with next year |
|
979 ValueRange dayRange = temporal.range(DAY_OF_YEAR); |
|
980 int yearLen = (int)dayRange.getMaximum(); |
|
981 int newYearWeek = computeWeek(offset, yearLen + weekDef.getMinimalDaysInFirstWeek()); |
|
982 |
|
983 if (week >= newYearWeek) { |
|
984 // Overlaps with weeks of following year; recompute from a week in following year |
|
985 ChronoLocalDate date = Chronology.from(temporal).date(temporal); |
|
986 date = date.plus(yearLen - doy + 1 + 7, ChronoUnit.DAYS); |
|
987 return rangeWeekOfWeekBasedYear(date); |
|
988 } |
|
989 return ValueRange.of(1, newYearWeek-1); |
|
990 } |
|
991 |
674 //----------------------------------------------------------------------- |
992 //----------------------------------------------------------------------- |
675 @Override |
993 @Override |
676 public String toString() { |
994 public String toString() { |
677 return getName() + "[" + weekDef.toString() + "]"; |
995 return getName() + "[" + weekDef.toString() + "]"; |
678 } |
996 } |