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}