All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.threeten.bp.temporal.IsoFields.scala Maven / Gradle / Ivy

/*
 * Copyright (c) 2007-present, Stephen Colebourne & Michael Nascimento Santos
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  * Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 *  * Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 *  * Neither the name of JSR-310 nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package org.threeten.bp.temporal

import org.threeten.bp.DayOfWeek.THURSDAY
import org.threeten.bp.DayOfWeek.WEDNESDAY
import org.threeten.bp.temporal.ChronoField.DAY_OF_WEEK
import org.threeten.bp.temporal.ChronoField.DAY_OF_YEAR
import org.threeten.bp.temporal.ChronoField.EPOCH_DAY
import org.threeten.bp.temporal.ChronoField.MONTH_OF_YEAR
import org.threeten.bp.temporal.ChronoField.YEAR
import org.threeten.bp.temporal.ChronoUnit.DAYS
import org.threeten.bp.temporal.ChronoUnit.FOREVER
import org.threeten.bp.temporal.ChronoUnit.MONTHS
import org.threeten.bp.temporal.ChronoUnit.WEEKS
import org.threeten.bp.temporal.ChronoUnit.YEARS
import java.util.{ Locale, Objects }
import org.threeten.bp.Duration
import org.threeten.bp.LocalDate
import org.threeten.bp.chrono.Chronology
import org.threeten.bp.chrono.IsoChronology
import org.threeten.bp.format.ResolverStyle

/**
 * Fields and units specific to the ISO-8601 calendar system, including quarter-of-year and
 * week-based-year.
 *
 * This class defines fields and units that are specific to the ISO calendar system.
 *
 * 

Quarter of year

The ISO-8601 standard is based on the standard civic 12 month year. This * is commonly divided into four quarters, often abbreviated as Q1, Q2, Q3 and Q4. * * January, February and March are in Q1. April, May and June are in Q2. July, August and September * are in Q3. October, November and December are in Q4. * * The complete date is expressed using three fields:
  • {@link #DAY_OF_QUARTER * DAY_OF_QUARTER} - the day within the quarter, from 1 to 90, 91 or 92
  • {@link #QUARTER_OF_YEAR * QUARTER_OF_YEAR} - the week within the week-based-year
  • {@link ChronoField#YEAR YEAR} - the * standard ISO year

* *

Week based years

The ISO-8601 standard was originally intended as a data interchange * format, defining a string format for dates and times. However, it also defines an alternate way * of expressing the date, based on the concept of week-based-year. * * The date is expressed using three fields:
  • {@link ChronoField#DAY_OF_WEEK DAY_OF_WEEK} - * the standard field defining the day-of-week from Monday (1) to Sunday (7)
  • {@link * #WEEK_OF_WEEK_BASED_YEAR} - the week within the week-based-year
  • {@link #WEEK_BASED_YEAR * WEEK_BASED_YEAR} - the week-based-year

The week-based-year itself is defined relative to * the standard ISO proleptic year. It differs from the standard year in that it always starts on a * Monday. * * The first week of a week-based-year is the first Monday-based week of the standard ISO year that * has at least 4 days in the new year.

  • If January 1st is Monday then week 1 starts on * January 1st
  • If January 1st is Tuesday then week 1 starts on December 31st of the previous * standard year
  • If January 1st is Wednesday then week 1 starts on December 30th of the previous * standard year
  • If January 1st is Thursday then week 1 starts on December 29th of the previous * standard year
  • If January 1st is Friday then week 1 starts on January 4th
  • If January 1st * is Saturday then week 1 starts on January 3rd
  • If January 1st is Sunday then week 1 starts on * January 2nd

There are 52 weeks in most week-based years, however on occasion there are * 53 weeks. * * For example: * *

* * * * *
Examples of Week based Years
DateDay-of-weekField * values
2008-12-28SundayWeek 52 of week-based-year * 2008
2008-12-29MondayWeek 1 of week-based-year 2009
2008-12-31WednesdayWeek 1 of week-based-year 2009
2009-01-01ThursdayWeek 1 of week-based-year 2009
2009-01-04SundayWeek 1 of week-based-year 2009
2009-01-05MondayWeek 2 of week-based-year 2009
* *

Specification for implementors

* * This class is immutable and thread-safe. */ object IsoFields { /** * The field that represents the day-of-quarter. * * This field allows the day-of-quarter value to be queried and set. The day-of-quarter has values * from 1 to 90 in Q1 of a standard year, from 1 to 91 in Q1 of a leap year, from 1 to 91 in Q2 * and from 1 to 92 in Q3 and Q4. * * The day-of-quarter can only be calculated if the day-of-year, month-of-year and year are * available. * * When setting this field, the value is allowed to be partially lenient, taking any value from 1 * to 92. If the quarter has less than 92 days, then day 92, and potentially day 91, is in the * following quarter. * * This unit is an immutable and thread-safe singleton. */ lazy val DAY_OF_QUARTER: TemporalField = Field.DAY_OF_QUARTER /** * The field that represents the quarter-of-year. * * This field allows the quarter-of-year value to be queried and set. The quarter-of-year has * values from 1 to 4. * * The day-of-quarter can only be calculated if the month-of-year is available. * * This unit is an immutable and thread-safe singleton. */ lazy val QUARTER_OF_YEAR: TemporalField = Field.QUARTER_OF_YEAR /** * The field that represents the week-of-week-based-year. * * This field allows the week of the week-based-year value to be queried and set. * * This unit is an immutable and thread-safe singleton. */ lazy val WEEK_OF_WEEK_BASED_YEAR: TemporalField = Field.WEEK_OF_WEEK_BASED_YEAR /** * The field that represents the week-based-year. * * This field allows the week-based-year value to be queried and set. * * This unit is an immutable and thread-safe singleton. */ lazy val WEEK_BASED_YEAR: TemporalField = Field.WEEK_BASED_YEAR /** * The unit that represents week-based-years for the purpose of addition and subtraction. * * This allows a number of week-based-years to be added to, or subtracted from, a date. The unit * is equal to either 52 or 53 weeks. The estimated duration of a week-based-year is the same as * that of a standard ISO year at {@code 365.2425 Days}. * * The rules for addition add the number of week-based-years to the existing value for the * week-based-year field. If the resulting week-based-year only has 52 weeks, then the date will * be in week 1 of the following week-based-year. * * This unit is an immutable and thread-safe singleton. */ lazy val WEEK_BASED_YEARS: TemporalUnit = Unit.WEEK_BASED_YEARS /** * Unit that represents the concept of a quarter-year. For the ISO calendar system, it is equal to * 3 months. The estimated duration of a quarter-year is one quarter of {@code 365.2425 Days}. * * This unit is an immutable and thread-safe singleton. */ lazy val QUARTER_YEARS: TemporalUnit = Unit.QUARTER_YEARS private def DQ = "DayOfQuarter" /** Implementation of the field. */ private object Field { lazy val DAY_OF_QUARTER: Field = new Field("DAY_OF_QUARTER", 0) { override def toString: String = DQ def getBaseUnit: TemporalUnit = DAYS def getRangeUnit: TemporalUnit = QUARTER_YEARS def range: ValueRange = ValueRange.of(1, 90, 92) def isSupportedBy(temporal: TemporalAccessor): Boolean = temporal.isSupported(DAY_OF_YEAR) && temporal.isSupported(MONTH_OF_YEAR) && temporal .isSupported(YEAR) && isIso(temporal) def rangeRefinedBy(temporal: TemporalAccessor): ValueRange = { if (!temporal.isSupported(this)) throw unsupportedEx(DQ) val qoy: Long = temporal.getLong(QUARTER_OF_YEAR) if (qoy == 1) { val year: Long = temporal.getLong(YEAR) return if (IsoChronology.INSTANCE.isLeapYear(year)) ValueRange.of(1, 91) else ValueRange.of(1, 90) } else if (qoy == 2) return ValueRange.of(1, 91) else if (qoy == 3 || qoy == 4) return ValueRange.of(1, 92) range } def getFrom(temporal: TemporalAccessor): Long = { if (!temporal.isSupported(this)) throw unsupportedEx(DQ) val doy: Int = temporal.get(DAY_OF_YEAR) val moy: Int = temporal.get(MONTH_OF_YEAR) val year: Long = temporal.getLong(YEAR) doy.toLong - QUARTER_DAYS( ((moy - 1) / 3) + (if (IsoChronology.INSTANCE.isLeapYear(year)) 4 else 0) ).toInt } def adjustInto[R <: Temporal](temporal: R, newValue: Long): R = { val curValue: Long = getFrom(temporal) range.checkValidValue(newValue, this) temporal .`with`(DAY_OF_YEAR, temporal.getLong(DAY_OF_YEAR) + (newValue - curValue)) .asInstanceOf[R] } override def resolve( fieldValues: java.util.Map[TemporalField, java.lang.Long], partialTemporal: TemporalAccessor, resolverStyle: ResolverStyle ): TemporalAccessor = { val yearLong: java.lang.Long = fieldValues.get(YEAR) val qoyLong: java.lang.Long = fieldValues.get(QUARTER_OF_YEAR) if (yearLong == null || qoyLong == null) return null val y: Int = YEAR.checkValidIntValue(yearLong) val doq: Long = fieldValues.get(DAY_OF_QUARTER) var date: LocalDate = null if (resolverStyle eq ResolverStyle.LENIENT) { val qoy: Long = qoyLong date = LocalDate.of(y, 1, 1) date = date.plusMonths(Math.multiplyExact(Math.subtractExact(qoy, 1), 3)) date = date.plusDays(Math.subtractExact(doq, 1)) } else { val qoy: Int = QUARTER_OF_YEAR.range.checkValidIntValue(qoyLong, QUARTER_OF_YEAR) if (resolverStyle eq ResolverStyle.STRICT) { val max: Int = if (qoy == 1) if (IsoChronology.INSTANCE.isLeapYear(y.toLong)) 91 else 90 else if (qoy == 2) 91 else 92 ValueRange.of(1, max.toLong).checkValidValue(doq, this) } else range.checkValidValue(doq, this) date = LocalDate.of(y, ((qoy - 1) * 3) + 1, 1).plusDays(doq - 1) } fieldValues.remove(this) fieldValues.remove(YEAR) fieldValues.remove(QUARTER_OF_YEAR) date } } private def QY = "QuarterOfYear" lazy val QUARTER_OF_YEAR: Field = new Field("QUARTER_OF_YEAR", 1) { override def toString: String = QY def getBaseUnit: TemporalUnit = QUARTER_YEARS def getRangeUnit: TemporalUnit = YEARS def range: ValueRange = ValueRange.of(1, 4) def isSupportedBy(temporal: TemporalAccessor): Boolean = temporal.isSupported(MONTH_OF_YEAR) && isIso(temporal) def rangeRefinedBy(temporal: TemporalAccessor): ValueRange = range def getFrom(temporal: TemporalAccessor): Long = { if (!temporal.isSupported(this)) throw unsupportedEx(QY) val moy: Long = temporal.getLong(MONTH_OF_YEAR) (moy + 2) / 3 } def adjustInto[R <: Temporal](temporal: R, newValue: Long): R = { val curValue: Long = getFrom(temporal) range.checkValidValue(newValue, this) temporal .`with`(MONTH_OF_YEAR, temporal.getLong(MONTH_OF_YEAR) + (newValue - curValue) * 3) .asInstanceOf[R] } } lazy val WEEK_OF_WEEK_BASED_YEAR: Field = new Field("WEEK_OF_WEEK_BASED_YEAR", 2) { override def toString: String = WWBY def getBaseUnit: TemporalUnit = WEEKS def getRangeUnit: TemporalUnit = WEEK_BASED_YEARS override def getDisplayName(locale: Locale): String = { Objects.requireNonNull(locale, "locale") "Week" } def range: ValueRange = ValueRange.of(1, 52, 53) def isSupportedBy(temporal: TemporalAccessor): Boolean = temporal.isSupported(EPOCH_DAY) && isIso(temporal) def rangeRefinedBy(temporal: TemporalAccessor): ValueRange = if (!temporal.isSupported(this)) throw unsupportedEx(WWBY) else getWeekRange(LocalDate.from(temporal)) def getFrom(temporal: TemporalAccessor): Long = if (!temporal.isSupported(this)) throw unsupportedEx(WWBY) else getWeek(LocalDate.from(temporal)).toLong def adjustInto[R <: Temporal](temporal: R, newValue: Long): R = { range.checkValidValue(newValue, this) temporal.plus(Math.subtractExact(newValue, getFrom(temporal)), WEEKS).asInstanceOf[R] } override def resolve( fieldValues: java.util.Map[TemporalField, java.lang.Long], partialTemporal: TemporalAccessor, resolverStyle: ResolverStyle ): TemporalAccessor = { val wbyLong: java.lang.Long = fieldValues.get(WEEK_BASED_YEAR) val dowLong: Long = fieldValues.get(DAY_OF_WEEK) if (wbyLong == null || !fieldValues.containsKey(DAY_OF_WEEK)) return null val wby: Int = WEEK_BASED_YEAR.range.checkValidIntValue(wbyLong, WEEK_BASED_YEAR) val wowby: Long = fieldValues.get(WEEK_OF_WEEK_BASED_YEAR) var date: LocalDate = null if (resolverStyle eq ResolverStyle.LENIENT) { val (dow, weeks) = if (dowLong > 7) (((dowLong - 1L) % 7) + 1, (dowLong - 1) / 7) else if (dowLong < 1) ((dowLong % 7L) + 7, (dowLong / 7) - 1) else (dowLong, 0L) date = LocalDate.of(wby, 1, 4).plusWeeks(wowby - 1).plusWeeks(weeks).`with`(DAY_OF_WEEK, dow) } else { val dow: Int = DAY_OF_WEEK.checkValidIntValue(dowLong) if (resolverStyle eq ResolverStyle.STRICT) { val temp: LocalDate = LocalDate.of(wby, 1, 4) val range: ValueRange = getWeekRange(temp) range.checkValidValue(wowby, this) } else range.checkValidValue(wowby, this) date = LocalDate.of(wby, 1, 4).plusWeeks(wowby - 1).`with`(DAY_OF_WEEK, dow.toLong) } fieldValues.remove(this) fieldValues.remove(WEEK_BASED_YEAR) fieldValues.remove(DAY_OF_WEEK) date } } private def unsupportedEx(f: String): UnsupportedTemporalTypeException = new UnsupportedTemporalTypeException(s"Unsupported field: $f") lazy val WEEK_BASED_YEAR: Field = new Field("WEEK_BASED_YEAR", 3) { override def toString: String = WBY def getBaseUnit: TemporalUnit = WEEK_BASED_YEARS def getRangeUnit: TemporalUnit = FOREVER def range: ValueRange = YEAR.range def isSupportedBy(temporal: TemporalAccessor): Boolean = temporal.isSupported(EPOCH_DAY) && isIso(temporal) def rangeRefinedBy(temporal: TemporalAccessor): ValueRange = YEAR.range def getFrom(temporal: TemporalAccessor): Long = if (!temporal.isSupported(this)) throw unsupportedEx(WBY) else getWeekBasedYear(LocalDate.from(temporal)).toLong def adjustInto[R <: Temporal](temporal: R, newValue: Long): R = { if (!isSupportedBy(temporal)) throw unsupportedEx(WBY) val newWby: Int = range.checkValidIntValue(newValue, WEEK_BASED_YEAR) val date: LocalDate = LocalDate.from(temporal) val dow: Int = date.get(DAY_OF_WEEK) var week: Int = getWeek(date) if (week == 53 && getWeekRange(newWby) == 52) week = 52 val resolved: LocalDate = LocalDate.of(newWby, 1, 4) val days: Int = (dow - resolved.get(DAY_OF_WEEK)) + ((week - 1) * 7) temporal.`with`(resolved.plusDays(days.toLong)).asInstanceOf[R] } } private lazy val QUARTER_DAYS: Array[Int] = Array(0, 90, 181, 273, 0, 91, 182, 274) private def isIso(temporal: TemporalAccessor): Boolean = Chronology.from(temporal) == IsoChronology.INSTANCE private def getWeekRange(date: LocalDate): ValueRange = { val wby: Int = getWeekBasedYear(date) ValueRange.of(1, getWeekRange(wby).toLong) } private def getWeekRange(wby: Int): Int = { val date: LocalDate = LocalDate.of(wby, 1, 1) if ((date.getDayOfWeek eq THURSDAY) || ((date.getDayOfWeek eq WEDNESDAY) && date.isLeapYear)) 53 else 52 } private def getWeek(date: LocalDate): Int = { val dow0: Int = date.getDayOfWeek.ordinal val doy0: Int = date.getDayOfYear - 1 val doyThu0: Int = doy0 + (3 - dow0) val alignedWeek: Int = doyThu0 / 7 val firstThuDoy0: Int = doyThu0 - (alignedWeek * 7) var firstMonDoy0: Int = firstThuDoy0 - 3 if (firstMonDoy0 < -3) firstMonDoy0 += 7 if (doy0 < firstMonDoy0) return getWeekRange(date.withDayOfYear(180).minusYears(1)).getMaximum.toInt var week: Int = ((doy0 - firstMonDoy0) / 7) + 1 if (week == 53) if (!(firstMonDoy0 == -3 || firstMonDoy0 == -2 && date.isLeapYear)) week = 1 week } private def getWeekBasedYear(date: LocalDate): Int = { var year: Int = date.getYear var doy: Int = date.getDayOfYear if (doy <= 3) { val dow: Int = date.getDayOfWeek.ordinal if (doy - dow < -2) year -= 1 } else if (doy >= 363) { val dow: Int = date.getDayOfWeek.ordinal doy = doy - 363 - (if (date.isLeapYear) 1 else 0) if (doy - dow >= 0) year += 1 } year } } private sealed abstract class Field(name: String, ordinal: Int) extends Enum[Field](name, ordinal) with TemporalField { def getDisplayName(locale: Locale): String = { Objects.requireNonNull(locale, "locale") toString } def resolve( fieldValues: java.util.Map[TemporalField, java.lang.Long], partialTemporal: TemporalAccessor, resolverStyle: ResolverStyle ): TemporalAccessor = null def isDateBased: Boolean = true def isTimeBased: Boolean = false } /** Implementation of the period unit. */ private object Unit { lazy val WEEK_BASED_YEARS = new Unit(WBY, 0, Duration.ofSeconds(31556952L)) lazy val QUARTER_YEARS = new Unit("QuarterYears", 1, Duration.ofSeconds(31556952L / 4)) } // / !!! FIXME: Passing of name to the Enum constructor is not quite right. // We should have a look at the compiled code to figure out what's happening exactly in the Java version. private final class Unit(name: String, ordinal: Int, private val duration: Duration) extends Enum[Unit](name, ordinal) with TemporalUnit { def getDuration: Duration = duration def isDurationEstimated: Boolean = true def isDateBased: Boolean = true def isTimeBased: Boolean = false def isSupportedBy(temporal: Temporal): Boolean = temporal.isSupported(EPOCH_DAY) def addTo[R <: Temporal](temporal: R, periodToAdd: Long): R = this match { case Unit.WEEK_BASED_YEARS => val added: Long = Math.addExact(temporal.get(WEEK_BASED_YEAR).toLong, periodToAdd) temporal.`with`(WEEK_BASED_YEAR, added).asInstanceOf[R] case Unit.QUARTER_YEARS => temporal .plus(periodToAdd / 256, YEARS) .plus((periodToAdd % 256) * 3, MONTHS) .asInstanceOf[R] case _ => throw new IllegalStateException("Unreachable") } def between(temporal1: Temporal, temporal2: Temporal): Long = this match { case Unit.WEEK_BASED_YEARS => Math.subtractExact(temporal2.getLong(WEEK_BASED_YEAR), temporal1.getLong(WEEK_BASED_YEAR)) case Unit.QUARTER_YEARS => temporal1.until(temporal2, MONTHS) / 3 case _ => throw new IllegalStateException("Unreachable") } override def toString: String = name } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy