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 java.io.InvalidObjectException; 035import java.io.Serializable; 036import java.util.GregorianCalendar; 037import java.util.Locale; 038import java.util.Objects; 039import java.util.concurrent.ConcurrentHashMap; 040import java.util.concurrent.ConcurrentMap; 041 042import org.threeten.bp.DayOfWeek; 043import org.threeten.bp.format.DateTimeBuilder; 044import org.threeten.bp.jdk8.Jdk8Methods; 045 046/** 047 * Localized definitions of the day-of-week, week-of-month and week-of-year fields. 048 * <p> 049 * A standard week is seven days long, but cultures have different definitions for some 050 * other aspects of a week. This class represents the definition of the week, for the 051 * purpose of providing {@link TemporalField} instances. 052 * <p> 053 * WeekFields provides three fields, 054 * {@link #dayOfWeek()}, {@link #weekOfMonth()}, and {@link #weekOfYear()} 055 * that provide access to the values from any {@link Temporal temporal object}. 056 * <p> 057 * The computations for day-of-week, week-of-month, and week-of-year are based 058 * on the {@link ChronoField#YEAR proleptic-year}, 059 * {@link ChronoField#MONTH_OF_YEAR month-of-year}, 060 * {@link ChronoField#DAY_OF_MONTH day-of-month}, and 061 * {@link ChronoField#DAY_OF_WEEK ISO day-of-week} which are based on the 062 * {@link ChronoField#EPOCH_DAY epoch-day} and the chronology. 063 * The values may not be aligned with the {@link ChronoField#YEAR_OF_ERA year-of-Era} 064 * depending on the Chronology. 065 * <p>A week is defined by: 066 * <ul> 067 * <li>The first day-of-week. 068 * For example, the ISO-8601 standard considers Monday to be the first day-of-week. 069 * <li>The minimal number of days in the first week. 070 * For example, the ISO-08601 standard counts the first week as needing at least 4 days. 071 * </ul><p> 072 * Together these two values allow a year or month to be divided into weeks. 073 * <p> 074 * <h3>Week of Month</h3> 075 * One field is used: week-of-month. 076 * The calculation ensures that weeks never overlap a month boundary. 077 * The month is divided into periods where each period starts on the defined first day-of-week. 078 * The earliest period is referred to as week 0 if it has less than the minimal number of days 079 * and week 1 if it has at least the minimal number of days. 080 * <p> 081 * <table cellpadding="0" cellspacing="3" border="0" style="text-align: left; width: 50%;"> 082 * <caption>Examples of WeekFields</caption> 083 * <tr><th>Date</th><td>Day-of-week</td> 084 * <td>First day: Monday<br>Minimal days: 4</td><td>First day: Monday<br>Minimal days: 5</td></tr> 085 * <tr><th>2008-12-31</th><td>Wednesday</td> 086 * <td>Week 5 of December 2008</td><td>Week 5 of December 2008</td></tr> 087 * <tr><th>2009-01-01</th><td>Thursday</td> 088 * <td>Week 1 of January 2009</td><td>Week 0 of January 2009</td></tr> 089 * <tr><th>2009-01-04</th><td>Sunday</td> 090 * <td>Week 1 of January 2009</td><td>Week 0 of January 2009</td></tr> 091 * <tr><th>2009-01-05</th><td>Monday</td> 092 * <td>Week 2 of January 2009</td><td>Week 1 of January 2009</td></tr> 093 * </table> 094 * <p> 095 * <h3>Week of Year</h3> 096 * One field is used: week-of-year. 097 * The calculation ensures that weeks never overlap a year boundary. 098 * The year is divided into periods where each period starts on the defined first day-of-week. 099 * The earliest period is referred to as week 0 if it has less than the minimal number of days 100 * and week 1 if it has at least the minimal number of days. 101 * <p> 102 * This class is immutable and thread-safe. 103 */ 104public final class WeekFields implements Serializable { 105 // implementation notes 106 // querying week-of-month or week-of-year should return the week value bound within the month/year 107 // however, setting the week value should be lenient (use plus/minus weeks) 108 // allow week-of-month outer range [0 to 5] 109 // allow week-of-year outer range [0 to 53] 110 // this is because callers shouldn't be expected to know the details of validity 111 112 /** 113 * The cache of rules by firstDayOfWeek plus minimalDays. 114 * Initialized first to be available for definition of ISO, etc. 115 */ 116 private static final ConcurrentMap<String, WeekFields> CACHE = new ConcurrentHashMap<>(4, 0.75f, 2); 117 118 /** 119 * The ISO-8601 definition, where a week starts on Monday and the first week 120 * has a minimum of 4 days. 121 * <p> 122 * The ISO-8601 standard defines a calendar system based on weeks. 123 * It uses the week-based-year and week-of-week-based-year concepts to split 124 * up the passage of days instead of the standard year/month/day. 125 * <p> 126 * Note that the first week may start in the previous calendar year. 127 * Note also that the first few days of a calendar year may be in the 128 * week-based-year corresponding to the previous calendar year. 129 */ 130 public static final WeekFields ISO = new WeekFields(DayOfWeek.MONDAY, 4); 131 132 /** 133 * The common definition of a week that starts on Sunday. 134 * <p> 135 * Defined as starting on Sunday and with a minimum of 1 day in the month. 136 * This week definition is in use in the US and other European countries. 137 * 138 */ 139 public static final WeekFields SUNDAY_START = WeekFields.of(DayOfWeek.SUNDAY, 1); 140 141 /** 142 * Serialization version. 143 */ 144 private static final long serialVersionUID = -1177360819670808121L; 145 146 /** 147 * The first day-of-week. 148 */ 149 private final DayOfWeek firstDayOfWeek; 150 /** 151 * The minimal number of days in the first week. 152 */ 153 private final int minimalDays; 154 155 /** 156 * The field used to access the computed DayOfWeek. 157 */ 158 private transient final TemporalField dayOfWeek = ComputedDayOfField.ofDayOfWeekField(this); 159 160 /** 161 * The field used to access the computed WeekOfMonth. 162 */ 163 private transient final TemporalField weekOfMonth = ComputedDayOfField.ofWeekOfMonthField(this); 164 165 /** 166 * The field used to access the computed WeekOfYear. 167 */ 168 private transient final TemporalField weekOfYear = ComputedDayOfField.ofWeekOfYearField(this); 169 170 /** 171 * Obtains an instance of {@code WeekFields} appropriate for a locale. 172 * <p> 173 * This will look up appropriate values from the provider of localization data. 174 * 175 * @param locale the locale to use, not null 176 * @return the week-definition, not null 177 */ 178 public static WeekFields of(Locale locale) { 179 Objects.requireNonNull(locale, "locale"); 180 locale = new Locale(locale.getLanguage(), locale.getCountry()); // elminate variants 181 182 // obtain these from GregorianCalendar for now 183 GregorianCalendar gcal = new GregorianCalendar(locale); 184 int calDow = gcal.getFirstDayOfWeek(); 185 DayOfWeek dow = DayOfWeek.SUNDAY.plus(calDow - 1); 186 int minDays = gcal.getMinimalDaysInFirstWeek(); 187 return WeekFields.of(dow, minDays); 188 } 189 190 /** 191 * Obtains an instance of {@code WeekFields} from the first day-of-week and minimal days. 192 * <p> 193 * The first day-of-week defines the ISO {@code DayOfWeek} that is day 1 of the week. 194 * The minimal number of days in the first week defines how many days must be present 195 * in a month or year, starting from the first day-of-week, before the week is counted 196 * as the first week. A value of 1 will count the first day of the month or year as part 197 * of the first week, whereas a value of 7 will require the whole seven days to be in 198 * the new month or year. 199 * <p> 200 * WeekFields instances are singletons; for each unique combination 201 * of {@code firstDayOfWeek} and {@code minimalDaysInFirstWeek} the 202 * the same instance will be returned. 203 * 204 * @param firstDayOfWeek the first day of the week, not null 205 * @param minimalDaysInFirstWeek the minimal number of days in the first week, from 1 to 7 206 * @return the week-definition, not null 207 * @throws IllegalArgumentException if the minimal days value is less than one 208 * or greater than 7 209 */ 210 public static WeekFields of(DayOfWeek firstDayOfWeek, int minimalDaysInFirstWeek) { 211 String key = firstDayOfWeek.toString() + minimalDaysInFirstWeek; 212 WeekFields rules = CACHE.get(key); 213 if (rules == null) { 214 rules = new WeekFields(firstDayOfWeek, minimalDaysInFirstWeek); 215 CACHE.putIfAbsent(key, rules); 216 rules = CACHE.get(key); 217 } 218 return rules; 219 } 220 221 //----------------------------------------------------------------------- 222 /** 223 * Creates an instance of the definition. 224 * 225 * @param firstDayOfWeek the first day of the week, not null 226 * @param minimalDaysInFirstWeek the minimal number of days in the first week, from 1 to 7 227 * @throws IllegalArgumentException if the minimal days value is invalid 228 */ 229 private WeekFields(DayOfWeek firstDayOfWeek, int minimalDaysInFirstWeek) { 230 Objects.requireNonNull(firstDayOfWeek, "firstDayOfWeek"); 231 if (minimalDaysInFirstWeek < 1 || minimalDaysInFirstWeek > 7) { 232 throw new IllegalArgumentException("Minimal number of days is invalid"); 233 } 234 this.firstDayOfWeek = firstDayOfWeek; 235 this.minimalDays = minimalDaysInFirstWeek; 236 } 237 238 /** 239 * Ensure valid singleton. 240 * 241 * @return the valid week fields instance, not null 242 * @throws InvalidObjectException if invalid 243 */ 244 private Object readResolve() throws InvalidObjectException { 245 try { 246 return WeekFields.of(firstDayOfWeek, minimalDays); 247 } catch (IllegalArgumentException ex) { 248 throw new InvalidObjectException("Invalid WeekFields" + ex.getMessage()); 249 } 250 } 251 252 //----------------------------------------------------------------------- 253 /** 254 * Gets the first day-of-week. 255 * <p> 256 * The first day-of-week varies by culture. 257 * For example, the US uses Sunday, while France and the ISO-8601 standard use Monday. 258 * This method returns the first day using the standard {@code DayOfWeek} enum. 259 * 260 * @return the first day-of-week, not null 261 */ 262 public DayOfWeek getFirstDayOfWeek() { 263 return firstDayOfWeek; 264 } 265 266 /** 267 * Gets the minimal number of days in the first week. 268 * <p> 269 * The number of days considered to define the first week of a month or year 270 * varies by culture. 271 * For example, the ISO-8601 requires 4 days (more than half a week) to 272 * be present before counting the first week. 273 * 274 * @return the minimal number of days in the first week of a month or year, from 1 to 7 275 */ 276 public int getMinimalDaysInFirstWeek() { 277 return minimalDays; 278 } 279 280 //----------------------------------------------------------------------- 281 /** 282 * Returns a field to access the day of week, 283 * computed based on this WeekFields. 284 * <p> 285 * The days of week are numbered from 1 to 7. 286 * Day number 1 is the {@link #getFirstDayOfWeek() first day-of-week}. 287 * 288 * @return the field for day-of-week using this week definition, not null 289 */ 290 public TemporalField dayOfWeek() { 291 return dayOfWeek; 292 } 293 294 /** 295 * Returns a field to access the week of month, 296 * computed based on this WeekFields. 297 * <p> 298 * This represents concept of the count of weeks within the month where weeks 299 * start on a fixed day-of-week, such as Monday. 300 * This field is typically used with {@link WeekFields#dayOfWeek()}. 301 * <p> 302 * Week one (1) is the week starting on the {@link WeekFields#getFirstDayOfWeek} 303 * where there are at least {@link WeekFields#getMinimalDaysInFirstWeek()} days in the month. 304 * Thus, week one may start up to {@code minDays} days before the start of the month. 305 * If the first week starts after the start of the month then the period before is week zero (0). 306 * <p> 307 * For example:<br> 308 * - if the 1st day of the month is a Monday, week one starts on the 1st and there is no week zero<br> 309 * - if the 2nd day of the month is a Monday, week one starts on the 2nd and the 1st is in week zero<br> 310 * - if the 4th day of the month is a Monday, week one starts on the 4th and the 1st to 3rd is in week zero<br> 311 * - if the 5th day of the month is a Monday, week two starts on the 5th and the 1st to 4th is in week one<br> 312 * <p> 313 * This field can be used with any calendar system. 314 * @return a TemporalField to access the WeekOfMonth, not null 315 */ 316 public TemporalField weekOfMonth() { 317 return weekOfMonth; 318 } 319 320 /** 321 * Returns a field to access the week of year, 322 * computed based on this WeekFields. 323 * <p> 324 * This represents concept of the count of weeks within the year where weeks 325 * start on a fixed day-of-week, such as Monday. 326 * This field is typically used with {@link WeekFields#dayOfWeek()}. 327 * <p> 328 * Week one(1) is the week starting on the {@link WeekFields#getFirstDayOfWeek} 329 * where there are at least {@link WeekFields#getMinimalDaysInFirstWeek()} days in the month. 330 * Thus, week one may start up to {@code minDays} days before the start of the year. 331 * If the first week starts after the start of the year then the period before is week zero (0). 332 * <p> 333 * For example:<br> 334 * - if the 1st day of the year is a Monday, week one starts on the 1st and there is no week zero<br> 335 * - if the 2nd day of the year is a Monday, week one starts on the 2nd and the 1st is in week zero<br> 336 * - if the 4th day of the year is a Monday, week one starts on the 4th and the 1st to 3rd is in week zero<br> 337 * - if the 5th day of the year is a Monday, week two starts on the 5th and the 1st to 4th is in week one<br> 338 * <p> 339 * This field can be used with any calendar system. 340 * @return a TemporalField to access the WeekOfYear, not null 341 */ 342 public TemporalField weekOfYear() { 343 return weekOfYear; 344 } 345 346 /** 347 * Checks if these rules are equal to the specified rules. 348 * <p> 349 * The comparison is based on the entire state of the rules, which is 350 * the first day-of-week and minimal days. 351 * 352 * @param object the other rules to compare to, null returns false 353 * @return true if this is equal to the specified rules 354 */ 355 @Override 356 public boolean equals(Object object) { 357 if (this == object) { 358 return true; 359 } 360 if (object instanceof WeekFields) { 361 return hashCode() == object.hashCode(); 362 } 363 return false; 364 } 365 366 /** 367 * A hash code for these rules. 368 * 369 * @return a suitable hash code 370 */ 371 @Override 372 public int hashCode() { 373 return firstDayOfWeek.ordinal() * 7 + minimalDays; 374 } 375 376 //----------------------------------------------------------------------- 377 /** 378 * A string representation of this definition. 379 * 380 * @return the string representation, not null 381 */ 382 @Override 383 public String toString() { 384 return "WeekFields[" + firstDayOfWeek + ',' + minimalDays + ']'; 385 } 386 387 //----------------------------------------------------------------------- 388 /** 389 * Field type that computes DayOfWeek, WeekOfMonth, and WeekOfYear 390 * based on a WeekFields. 391 * A separate Field instance is required for each different WeekFields; 392 * combination of start of week and minimum number of days. 393 * Constructors are provided to create fields for DayOfWeek, WeekOfMonth, 394 * and WeekOfYear. 395 */ 396 static class ComputedDayOfField implements TemporalField { 397 398 /** 399 * Returns a field to access the day of week, 400 * computed based on a WeekFields. 401 * <p> 402 * The WeekDefintion of the first day of the week is used with 403 * the ISO DAY_OF_WEEK field to compute week boundaries. 404 */ 405 static ComputedDayOfField ofDayOfWeekField(WeekFields weekDef) { 406 return new ComputedDayOfField("DayOfWeek", weekDef, 407 ChronoUnit.DAYS, ChronoUnit.WEEKS, DAY_OF_WEEK_RANGE); 408 } 409 410 /** 411 * Returns a field to access the week of month, 412 * computed based on a WeekFields. 413 * @see WeekFields#weekOfMonth() 414 */ 415 static ComputedDayOfField ofWeekOfMonthField(WeekFields weekDef) { 416 return new ComputedDayOfField("WeekOfMonth", weekDef, 417 ChronoUnit.WEEKS, ChronoUnit.MONTHS, WEEK_OF_MONTH_RANGE); 418 } 419 420 /** 421 * Returns a field to access the week of year, 422 * computed based on a WeekFields. 423 * @see WeekFields#weekOfYear() 424 */ 425 static ComputedDayOfField ofWeekOfYearField(WeekFields weekDef) { 426 return new ComputedDayOfField("WeekOfYear", weekDef, 427 ChronoUnit.WEEKS, ChronoUnit.YEARS, WEEK_OF_YEAR_RANGE); 428 } 429 private final String name; 430 private final WeekFields weekDef; 431 private final TemporalUnit baseUnit; 432 private final TemporalUnit rangeUnit; 433 private final ValueRange range; 434 435 private ComputedDayOfField(String name, WeekFields weekDef, TemporalUnit baseUnit, TemporalUnit rangeUnit, ValueRange range) { 436 this.name = name; 437 this.weekDef = weekDef; 438 this.baseUnit = baseUnit; 439 this.rangeUnit = rangeUnit; 440 this.range = range; 441 } 442 443 private static final ValueRange DAY_OF_WEEK_RANGE = ValueRange.of(1, 7); 444 private static final ValueRange WEEK_OF_MONTH_RANGE = ValueRange.of(0, 1, 4, 5); 445 private static final ValueRange WEEK_OF_YEAR_RANGE = ValueRange.of(0, 1, 52, 53); 446 447 @Override 448 public long doGet(TemporalAccessor temporal) { 449 // Offset the ISO DOW by the start of this week 450 int sow = weekDef.getFirstDayOfWeek().getValue(); 451 int isoDow = temporal.get(ChronoField.DAY_OF_WEEK); 452 int dow = Jdk8Methods.floorMod(isoDow - sow, 7) + 1; 453 454 if (rangeUnit == ChronoUnit.WEEKS) { 455 return dow; 456 } else if (rangeUnit == ChronoUnit.MONTHS) { 457 int dom = temporal.get(ChronoField.DAY_OF_MONTH); 458 int offset = startOfWeekOffset(dom, dow); 459 return computeWeek(offset, dom); 460 } else if (rangeUnit == ChronoUnit.YEARS) { 461 int doy = temporal.get(ChronoField.DAY_OF_YEAR); 462 int offset = startOfWeekOffset(doy, dow); 463 return computeWeek(offset, doy); 464 } else { 465 throw new IllegalStateException("unreachable"); 466 } 467 } 468 469 /** 470 * Returns an offset to align week start with a day of month or day of year. 471 * 472 * @param day the day; 1 through infinity 473 * @param dow the day of the week of that day; 1 through 7 474 * @return an offset in days to align a day with the start of the first 'full' week 475 */ 476 private int startOfWeekOffset(int day, int dow) { 477 // offset of first day corresponding to the day of week in first 7 days (zero origin) 478 int weekStart = Jdk8Methods.floorMod(day - dow, 7); 479 int offset = -weekStart; 480 if (weekStart + 1 > weekDef.getMinimalDaysInFirstWeek()) { 481 // The previous week has the minimum days in the current month to be a 'week' 482 offset = 7 - weekStart; 483 } 484 return offset; 485 } 486 487 /** 488 * Returns the week number computed from the reference day and reference dayOfWeek. 489 * 490 * @param offset the offset to align a date with the start of week 491 * from {@link #startOfWeekOffset}. 492 * @param day the day for which to compute the week number 493 * @return the week number where zero is used for a partial week and 1 for the first full week 494 */ 495 private int computeWeek(int offset, int day) { 496 return ((7 + offset + (day - 1)) / 7); 497 } 498 499 @Override 500 public <R extends Temporal> R doWith(R temporal, long newValue) { 501 // Check the new value and get the old value of the field 502 int newVal = range.checkValidIntValue(newValue, this); 503 int currentVal = temporal.get(this); 504 if (newVal == currentVal) { 505 return temporal; 506 } 507 // Compute the difference and add that using the base using of the field 508 int delta = newVal - currentVal; 509 return (R) temporal.plus(delta, baseUnit); 510 } 511 512 @SuppressWarnings("rawtypes") 513 @Override 514 public boolean resolve(DateTimeBuilder builder, long value) { 515 int newValue = range.checkValidIntValue(value, this); 516 // DOW and YEAR are necessary for all fields; Chrono defaults to ISO if not present 517 int sow = weekDef.getFirstDayOfWeek().getValue(); 518 int dow = builder.get(weekDef.dayOfWeek()); 519 int year = builder.get(ChronoField.YEAR); 520 Chrono chrono = Chrono.from(builder); 521 522 // The WOM and WOY fields are the critical values 523 if (rangeUnit == ChronoUnit.MONTHS) { 524 // Process WOM value by combining with DOW and MONTH, YEAR 525 int month = builder.get(ChronoField.MONTH_OF_YEAR); 526 ChronoLocalDate cd = chrono.date(year, month, 1); 527 int offset = startOfWeekOffset(1, cd.get(weekDef.dayOfWeek())); 528 offset += dow - 1; // offset to desired day of week 529 offset += 7 * (newValue - 1); // offset by week number 530 ChronoLocalDate result = cd.plus(offset, ChronoUnit.DAYS); 531 builder.addFieldValue(ChronoField.DAY_OF_MONTH, result.get(ChronoField.DAY_OF_MONTH)); 532 builder.removeFieldValue(this); 533 builder.removeFieldValue(weekDef.dayOfWeek()); 534 return true; 535 } else if (rangeUnit == ChronoUnit.YEARS) { 536 // Process WOY 537 ChronoLocalDate cd = chrono.date(year, 1, 1); 538 int offset = startOfWeekOffset(1, cd.get(weekDef.dayOfWeek())); 539 offset += dow - 1; // offset to desired day of week 540 offset += 7 * (newValue - 1); // offset by week number 541 ChronoLocalDate result = cd.plus(offset, ChronoUnit.DAYS); 542 builder.addFieldValue(ChronoField.DAY_OF_MONTH, result.get(ChronoField.DAY_OF_MONTH)); 543 builder.addFieldValue(ChronoField.MONTH_OF_YEAR, result.get(ChronoField.MONTH_OF_YEAR)); 544 builder.removeFieldValue(this); 545 builder.removeFieldValue(weekDef.dayOfWeek()); 546 return true; 547 } else { 548 // ignore DOW of WEEK field; the value will be processed by WOM or WOY 549 int isoDow = Jdk8Methods.floorMod((sow - 1) + (dow - 1), 7) + 1; 550 builder.addFieldValue(ChronoField.DAY_OF_WEEK, isoDow); 551 // Not removed, the week-of-xxx fields need this value 552 return true; 553 } 554 } 555 556 //----------------------------------------------------------------------- 557 @Override 558 public String getName() { 559 return name; 560 } 561 562 @Override 563 public TemporalUnit getBaseUnit() { 564 return baseUnit; 565 } 566 567 @Override 568 public TemporalUnit getRangeUnit() { 569 return rangeUnit; 570 } 571 572 @Override 573 public ValueRange range() { 574 return range; 575 } 576 577 //------------------------------------------------------------------------- 578 @Override 579 public int compare(TemporalAccessor temporal1, TemporalAccessor temporal2) { 580 return Long.compare(temporal1.getLong(this), temporal2.getLong(this)); 581 } 582 583 //----------------------------------------------------------------------- 584 @Override 585 public boolean doIsSupported(TemporalAccessor temporal) { 586 if (temporal.isSupported(ChronoField.DAY_OF_WEEK)) { 587 if (rangeUnit == ChronoUnit.WEEKS) { 588 return true; 589 } else if (rangeUnit == ChronoUnit.MONTHS) { 590 return temporal.isSupported(ChronoField.DAY_OF_MONTH); 591 } else if (rangeUnit == ChronoUnit.YEARS) { 592 return temporal.isSupported(ChronoField.DAY_OF_YEAR); 593 } 594 } 595 return false; 596 } 597 598 @Override 599 public ValueRange doRange(TemporalAccessor temporal) { 600 if (rangeUnit == ChronoUnit.WEEKS) { 601 return range; 602 } 603 604 TemporalField field = null; 605 if (rangeUnit == ChronoUnit.MONTHS) { 606 field = ChronoField.DAY_OF_MONTH; 607 } else if (rangeUnit == ChronoUnit.YEARS) { 608 field = ChronoField.DAY_OF_YEAR; 609 } else { 610 throw new IllegalStateException("unreachable"); 611 } 612 613 // Offset the ISO DOW by the start of this week 614 int sow = weekDef.getFirstDayOfWeek().getValue(); 615 int isoDow = temporal.get(ChronoField.DAY_OF_WEEK); 616 int dow = Jdk8Methods.floorMod(isoDow - sow, 7) + 1; 617 618 int offset = startOfWeekOffset(temporal.get(field), dow); 619 ValueRange fieldRange = temporal.range(field); 620 return ValueRange.of(computeWeek(offset, (int) fieldRange.getMinimum()), 621 computeWeek(offset, (int) fieldRange.getMaximum())); 622 } 623 624 //----------------------------------------------------------------------- 625 @Override 626 public String toString() { 627 return getName() + "[" + weekDef.toString() + "]"; 628 } 629 } 630}