
org.threeten.bp.zone.StandardZoneRules.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.zone
import java.io.Serializable
import java.util.Arrays
import java.util.Collections
import java.util.HashMap
import java.util.Map
import org.threeten.bp.Duration
import org.threeten.bp.Instant
import org.threeten.bp.LocalDate
import org.threeten.bp.LocalDateTime
import org.threeten.bp.Year
import org.threeten.bp.ZoneOffset
@SerialVersionUID(3044319355680032515L)
object StandardZoneRules {
/** The last year to have its transitions cached. */
private val LAST_CACHED_YEAR: Int = 2100
/**
* Creates an instance.
*
* @param baseStandardOffset
* the standard offset to use before legal rules were set, not null
* @param baseWallOffset
* the wall offset to use before legal rules were set, not null
* @param standardOffsetTransitionList
* the list of changes to the standard offset, not null
* @param transitionList
* the list of transitions, not null
* @param lastRules
* the recurring last rules, size 15 or less, not null
*/
def apply(
baseStandardOffset: ZoneOffset,
baseWallOffset: ZoneOffset,
standardOffsetTransitionList: java.util.List[ZoneOffsetTransition],
transitionList: java.util.List[ZoneOffsetTransition],
lastRules: java.util.List[ZoneOffsetTransitionRule]
): StandardZoneRules = {
val standardTransitions = new Array[Long](standardOffsetTransitionList.size)
val standardOffsets = new Array[ZoneOffset](standardOffsetTransitionList.size + 1)
standardOffsets(0) = baseStandardOffset
{
var i: Int = 0
while (i < standardOffsetTransitionList.size) {
standardTransitions(i) = standardOffsetTransitionList.get(i).toEpochSecond
standardOffsets(i + 1) = standardOffsetTransitionList.get(i).getOffsetAfter
i += 1
}
}
val localTransitionList: java.util.List[LocalDateTime] = new java.util.ArrayList[LocalDateTime]
val localTransitionOffsetList: java.util.List[ZoneOffset] = new java.util.ArrayList[ZoneOffset]
localTransitionOffsetList.add(baseWallOffset)
val transitions = transitionList.iterator
while (transitions.hasNext) {
val trans = transitions.next()
if (trans.isGap) {
localTransitionList.add(trans.getDateTimeBefore)
localTransitionList.add(trans.getDateTimeAfter)
} else {
localTransitionList.add(trans.getDateTimeAfter)
localTransitionList.add(trans.getDateTimeBefore)
}
localTransitionOffsetList.add(trans.getOffsetAfter)
}
val savingsLocalTransitions =
localTransitionList.toArray(new Array[LocalDateTime](localTransitionList.size))
val wallOffsets =
localTransitionOffsetList.toArray(new Array[ZoneOffset](localTransitionOffsetList.size))
val savingsInstantTransitions = new Array[Long](transitionList.size)
{
var i: Int = 0
while (i < transitionList.size) {
savingsInstantTransitions(i) = transitionList.get(i).getInstant.getEpochSecond
i += 1
}
}
if (lastRules.size > 15)
throw new IllegalArgumentException("Too many transition rules")
val resultLastRules = lastRules.toArray(new Array[ZoneOffsetTransitionRule](lastRules.size))
new StandardZoneRules(standardTransitions,
standardOffsets,
savingsInstantTransitions,
wallOffsets,
resultLastRules,
savingsLocalTransitions
)
}
}
/**
* The rules describing how the zone offset varies through the year and historically.
*
* This class is used by the TZDB time-zone rules.
*
* Specification for implementors
This class is immutable and thread-safe.
*
* @constructor
* Utility constructor.
*
* @param standardTransitions
* the standard transitions, not null
* @param standardOffsets
* the standard offsets, not null
* @param savingsInstantTransitions
* the standard transitions, not null
* @param wallOffsets
* the wall offsets, not null
* @param lastRules
* the recurring last rules, size 15 or less, not null
* @param savingsLocalTransitions
* The transitions between local date-times, sorted. This is a paired array, where the first entry
* is the start of the transition and the second entry is the end of the transition.
*/
@SerialVersionUID(3044319355680032515L)
final class StandardZoneRules private (
private val standardTransitions: Array[Long],
private val standardOffsets: Array[ZoneOffset],
private val savingsInstantTransitions: Array[Long],
private val wallOffsets: Array[ZoneOffset],
private val lastRules: Array[ZoneOffsetTransitionRule],
private val savingsLocalTransitions: Array[LocalDateTime]
) extends ZoneRules
with Serializable {
/** The map of recent transitions. */
private val lastRulesCache: Map[Integer, Array[ZoneOffsetTransition]] =
new HashMap[Integer, Array[ZoneOffsetTransition]]()
/** Creates an instance. */
/* // Can't be implemented with Scala's constructor rules. Replaced with apply factory method.
private[zone] def this(baseStandardOffset: ZoneOffset,
baseWallOffset: ZoneOffset,
standardOffsetTransitionList:
java.util.List[ZoneOffsetTransition],
transitionList: java.util.List[ZoneOffsetTransition],
lastRules: java.util.List[ZoneOffsetTransitionRule]) {
}*/
/**
* @constructor
*
* @param standardTransitions
* the standard transitions, not null
* @param standardOffsets
* the standard offsets, not null
* @param savingsInstantTransitions
* the standard transitions, not null
* @param wallOffsets
* the wall offsets, not null
* @param lastRules
* the recurring last rules, size 15 or less, not null
*/
private[zone] def this(
standardTransitions: Array[Long],
standardOffsets: Array[ZoneOffset],
savingsInstantTransitions: Array[Long],
wallOffsets: Array[ZoneOffset],
lastRules: Array[ZoneOffsetTransitionRule]
) =
this(standardTransitions,
standardOffsets,
savingsInstantTransitions,
wallOffsets,
lastRules, {
val localTransitionList: java.util.List[LocalDateTime] =
new java.util.ArrayList[LocalDateTime]
var i: Int = 0
while (i < savingsInstantTransitions.length) {
val before: ZoneOffset = wallOffsets(i)
val after: ZoneOffset = wallOffsets(i + 1)
val trans: ZoneOffsetTransition =
new ZoneOffsetTransition(savingsInstantTransitions(i), before, after)
if (trans.isGap) {
localTransitionList.add(trans.getDateTimeBefore)
localTransitionList.add(trans.getDateTimeAfter)
} else {
localTransitionList.add(trans.getDateTimeAfter)
localTransitionList.add(trans.getDateTimeBefore)
}
i += 1
}
localTransitionList.toArray(new Array[LocalDateTime](localTransitionList.size))
}
)
def isFixedOffset: Boolean = savingsInstantTransitions.length == 0
def getOffset(instant: Instant): ZoneOffset = {
val epochSec: Long = instant.getEpochSecond
if (
lastRules.length > 0 && epochSec > savingsInstantTransitions(
savingsInstantTransitions.length - 1
)
) {
val year: Int = findYear(epochSec, wallOffsets(wallOffsets.length - 1))
val transArray: Array[ZoneOffsetTransition] = findTransitionArray(year)
var trans: ZoneOffsetTransition = null
{
var i: Int = 0
while (i < transArray.length) {
trans = transArray(i)
if (epochSec < trans.toEpochSecond)
return trans.getOffsetBefore
i += 1
}
}
return trans.getOffsetAfter
}
var index: Int = Arrays.binarySearch(savingsInstantTransitions, epochSec)
if (index < 0)
index = -index - 2
wallOffsets(index + 1)
}
def getOffset(localDateTime: LocalDateTime): ZoneOffset =
getOffsetInfo(localDateTime) match {
case transition: ZoneOffsetTransition => transition.getOffsetBefore
case offset: ZoneOffset => offset
}
def getValidOffsets(localDateTime: LocalDateTime): java.util.List[ZoneOffset] =
getOffsetInfo(localDateTime) match {
case transition: ZoneOffsetTransition => transition.getValidOffsets
case offset: ZoneOffset => Collections.singletonList(offset)
}
def getTransition(localDateTime: LocalDateTime): ZoneOffsetTransition =
getOffsetInfo(localDateTime) match {
case transition: ZoneOffsetTransition => transition
case _ => null
}
private def getOffsetInfo(dt: LocalDateTime): AnyRef = {
if (
lastRules.length > 0 && dt.isAfter(
savingsLocalTransitions(savingsLocalTransitions.length - 1)
)
) {
val transArray: Array[ZoneOffsetTransition] = findTransitionArray(dt.getYear)
var info: AnyRef = null
for (trans <- transArray) {
info = findOffsetInfo(dt, trans)
if (info.isInstanceOf[ZoneOffsetTransition] || (info == trans.getOffsetBefore))
return info
}
return info
}
var index: Int = Arrays.binarySearch(savingsLocalTransitions.asInstanceOf[Array[AnyRef]], dt)
if (index == -1)
return wallOffsets(0)
if (index < 0)
index = -index - 2
else if (
index < savingsLocalTransitions.length - 1 && (savingsLocalTransitions(
index
) == savingsLocalTransitions(
index + 1
))
)
index += 1
if ((index & 1) == 0) {
val dtBefore: LocalDateTime = savingsLocalTransitions(index)
val dtAfter: LocalDateTime = savingsLocalTransitions(index + 1)
val offsetBefore: ZoneOffset = wallOffsets(index / 2)
val offsetAfter: ZoneOffset = wallOffsets(index / 2 + 1)
if (offsetAfter.getTotalSeconds > offsetBefore.getTotalSeconds)
new ZoneOffsetTransition(dtBefore, offsetBefore, offsetAfter)
else
new ZoneOffsetTransition(dtAfter, offsetBefore, offsetAfter)
} else
wallOffsets(index / 2 + 1)
}
/**
* Finds the offset info for a local date-time and transition.
*
* @param dt
* the date-time, not null
* @param trans
* the transition, not null
* @return
* the offset info, not null
*/
private def findOffsetInfo(dt: LocalDateTime, trans: ZoneOffsetTransition): AnyRef = {
val localTransition: LocalDateTime = trans.getDateTimeBefore
if (trans.isGap)
if (dt.isBefore(localTransition))
trans.getOffsetBefore
else if (dt.isBefore(trans.getDateTimeAfter))
trans
else
trans.getOffsetAfter
else if (!dt.isBefore(localTransition))
trans.getOffsetAfter
else if (dt.isBefore(trans.getDateTimeAfter))
trans.getOffsetBefore
else
trans
}
def isValidOffset(localDateTime: LocalDateTime, offset: ZoneOffset): Boolean =
getValidOffsets(localDateTime).contains(offset)
/**
* Finds the appropriate transition array for the given year.
*
* @param year
* the year, not null
* @return
* the transition array, not null
*/
private def findTransitionArray(year: Int): Array[ZoneOffsetTransition] = {
val yearObj: Integer = year
var transArray: Array[ZoneOffsetTransition] = lastRulesCache.get(yearObj)
if (transArray != null)
return transArray
val ruleArray: Array[ZoneOffsetTransitionRule] = lastRules
transArray = new Array[ZoneOffsetTransition](ruleArray.length)
var i: Int = 0
while (i < ruleArray.length) {
transArray(i) = ruleArray(i).createTransition(year)
i += 1
}
if (year < StandardZoneRules.LAST_CACHED_YEAR)
lastRulesCache.put(yearObj, transArray)
transArray
}
def getStandardOffset(instant: Instant): ZoneOffset = {
val epochSec: Long = instant.getEpochSecond
var index: Int = Arrays.binarySearch(standardTransitions, epochSec)
if (index < 0)
index = -index - 2
standardOffsets(index + 1)
}
def getDaylightSavings(instant: Instant): Duration = {
val standardOffset: ZoneOffset = getStandardOffset(instant)
val actualOffset: ZoneOffset = getOffset(instant)
Duration.ofSeconds(actualOffset.getTotalSeconds.toLong - standardOffset.getTotalSeconds.toLong)
}
def isDaylightSavings(instant: Instant): Boolean =
getStandardOffset(instant) != getOffset(instant)
def nextTransition(instant: Instant): ZoneOffsetTransition = {
if (savingsInstantTransitions.length == 0)
return null
val epochSec: Long = instant.getEpochSecond
if (epochSec >= savingsInstantTransitions(savingsInstantTransitions.length - 1)) {
if (lastRules.length == 0)
return null
val year: Int = findYear(epochSec, wallOffsets(wallOffsets.length - 1))
var transArray: Array[ZoneOffsetTransition] = findTransitionArray(year)
for (trans <- transArray)
if (epochSec < trans.toEpochSecond)
return trans
if (year < Year.MAX_VALUE) {
transArray = findTransitionArray(year + 1)
return transArray(0)
}
return null
}
var index: Int = Arrays.binarySearch(savingsInstantTransitions, epochSec)
if (index < 0)
index = -index - 1
else
index += 1
new ZoneOffsetTransition(savingsInstantTransitions(index),
wallOffsets(index),
wallOffsets(index + 1)
)
}
def previousTransition(instant: Instant): ZoneOffsetTransition = {
if (savingsInstantTransitions.length == 0)
return null
var epochSec: Long = instant.getEpochSecond
if (instant.getNano > 0 && epochSec < Long.MaxValue)
epochSec += 1
val lastHistoric: Long = savingsInstantTransitions(savingsInstantTransitions.length - 1)
if (lastRules.length > 0 && epochSec > lastHistoric) {
val lastHistoricOffset: ZoneOffset = wallOffsets(wallOffsets.length - 1)
var year: Int = findYear(epochSec, lastHistoricOffset)
var transArray: Array[ZoneOffsetTransition] = findTransitionArray(year)
{
var i: Int = transArray.length - 1
while (i >= 0) {
if (epochSec > transArray(i).toEpochSecond)
return transArray(i)
i -= 1
}
}
val lastHistoricYear: Int = findYear(lastHistoric, lastHistoricOffset)
if ({ year -= 1; year } > lastHistoricYear) {
transArray = findTransitionArray(year)
return transArray(transArray.length - 1)
}
}
var index: Int = Arrays.binarySearch(savingsInstantTransitions, epochSec)
if (index < 0)
index = -index - 1
if (index <= 0)
return null
new ZoneOffsetTransition(savingsInstantTransitions(index - 1),
wallOffsets(index - 1),
wallOffsets(index)
)
}
private def findYear(epochSecond: Long, offset: ZoneOffset): Int = {
val localSecond: Long = epochSecond + offset.getTotalSeconds
val localEpochDay: Long = Math.floorDiv(localSecond, 86400L)
LocalDate.ofEpochDay(localEpochDay).getYear
}
def getTransitions: java.util.List[ZoneOffsetTransition] = {
val list: java.util.List[ZoneOffsetTransition] = new java.util.ArrayList[ZoneOffsetTransition]
var i: Int = 0
while (i < savingsInstantTransitions.length) {
list.add(
new ZoneOffsetTransition(savingsInstantTransitions(i), wallOffsets(i), wallOffsets(i + 1))
)
i += 1
}
Collections.unmodifiableList(list)
}
def getTransitionRules: java.util.List[ZoneOffsetTransitionRule] =
Collections.unmodifiableList(Arrays.asList(lastRules: _*))
override def equals(obj: Any): Boolean =
obj match {
case other: StandardZoneRules =>
(this eq other) || (Arrays.equals(standardTransitions, other.standardTransitions) && Arrays
.equals(standardOffsets.asInstanceOf[Array[AnyRef]],
other.standardOffsets.asInstanceOf[Array[AnyRef]]
) && Arrays.equals(
savingsInstantTransitions,
other.savingsInstantTransitions
) && Arrays.equals(wallOffsets.asInstanceOf[Array[AnyRef]],
other.wallOffsets.asInstanceOf[Array[AnyRef]]
) && Arrays.equals(
lastRules.asInstanceOf[Array[AnyRef]],
other.lastRules.asInstanceOf[Array[AnyRef]]
))
case _: ZoneRules.Fixed =>
isFixedOffset && (getOffset(Instant.EPOCH) == obj
.asInstanceOf[ZoneRules.Fixed]
.getOffset(Instant.EPOCH))
case _ => false
}
override def hashCode: Int =
Arrays.hashCode(standardTransitions) ^ Arrays.hashCode(
standardOffsets.asInstanceOf[Array[AnyRef]]
) ^ Arrays.hashCode(savingsInstantTransitions) ^ Arrays.hashCode(
wallOffsets.asInstanceOf[Array[AnyRef]]
) ^ Arrays.hashCode(lastRules.asInstanceOf[Array[AnyRef]])
/**
* Returns a string describing this object.
*
* @return
* a string for debugging, not null
*/
override def toString: String =
s"StandardZoneRules[currentStandardOffset=${standardOffsets(standardOffsets.length - 1)}]"
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy