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

org.threeten.bp.zone.ZoneRulesBuilder.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 org.threeten.bp.temporal.ChronoField.YEAR
import org.threeten.bp.temporal.TemporalAdjusters.nextOrSame
import org.threeten.bp.temporal.TemporalAdjusters.previousOrSame
import java.util.{ Collections, Comparator, Objects }
import org.threeten.bp.DayOfWeek
import org.threeten.bp.LocalDate
import org.threeten.bp.LocalDateTime
import org.threeten.bp.LocalTime
import org.threeten.bp.Month
import org.threeten.bp.Year
import org.threeten.bp.ZoneOffset
import org.threeten.bp.chrono.IsoChronology

/**
 * A mutable builder used to create all the rules for a historic time-zone.
 *
 * The rules of a time-zone describe how the offset changes over time. The rules are created by
 * building windows on the time-line within which the different rules apply. The rules may be one of
 * two kinds: 
  • Fixed savings - A single fixed amount of savings from the standard offset * will apply.
  • Rules - A set of one or more rules describe how daylight savings changes * during the window.

* *

Specification for implementors

This class is a mutable builder used to create zone * instances. It must only be used from a single thread. The created instances are immutable and * thread-safe. * * Constructs an instance of the builder that can be used to create zone rules. * * The builder is used by adding one or more windows representing portions of the time-line. The * standard offset from UTC/Greenwich will be constant within a window, although two adjacent * windows can have the same standard offset. * * Within each window, there can either be a {@link #setFixedSavingsToWindow fixed savings amount} * or a {@link #addRuleToWindow list of rules}. */ class ZoneRulesBuilder() { /** * The list of windows. */ private val windowList: java.util.List[ZoneRulesBuilder#TZWindow] = new java.util.ArrayList[ZoneRulesBuilder#TZWindow] /** * A map for deduplicating the output. */ private var deduplicateMap: java.util.Map[AnyRef, AnyRef] = null private val ruleComparator = new TZRuleComparator() /** * Adds a window to the builder that can be used to filter a set of rules. * * This method defines and adds a window to the zone where the standard offset is specified. The * window limits the effect of subsequent additions of transition rules or fixed savings. If * neither rules or fixed savings are added to the window then the window will default to no * savings. * * Each window must be added sequentially, as the start instant of the window is derived from the * until instant of the previous window. * * @param standardOffset * the standard offset, not null * @param until * the date-time that the offset applies until, not null * @param untilDefinition * the time type for the until date-time, not null * @return * this, for chaining * @throws IllegalStateException * if the window order is invalid */ private[zone] def addWindow( standardOffset: ZoneOffset, until: LocalDateTime, untilDefinition: ZoneOffsetTransitionRule.TimeDefinition ): ZoneRulesBuilder = { Objects.requireNonNull(standardOffset, "standardOffset") Objects.requireNonNull(until, "until") Objects.requireNonNull(untilDefinition, "untilDefinition") val window: ZoneRulesBuilder#TZWindow = new TZWindow(standardOffset, until, untilDefinition) if (windowList.size > 0) { val previous: ZoneRulesBuilder#TZWindow = windowList.get(windowList.size - 1) window.validateWindowOrder(previous) } windowList.add(window) this } /** * Adds a window that applies until the end of time to the builder that can be used to filter a * set of rules. * * This method defines and adds a window to the zone where the standard offset is specified. The * window limits the effect of subsequent additions of transition rules or fixed savings. If * neither rules or fixed savings are added to the window then the window will default to no * savings. * * This must be added after all other windows. No more windows can be added after this one. * * @param standardOffset * the standard offset, not null * @return * this, for chaining * @throws IllegalStateException * if a forever window has already been added */ def addWindowForever(standardOffset: ZoneOffset): ZoneRulesBuilder = addWindow(standardOffset, LocalDateTime.MAX, ZoneOffsetTransitionRule.TimeDefinition.WALL) /** * Sets the previously added window to have fixed savings. * * Setting a window to have fixed savings simply means that a single daylight savings amount * applies throughout the window. The window could be small, such as a single summer, or large, * such as a multi-year daylight savings. * * A window can either have fixed savings or rules but not both. * * @param fixedSavingAmountSecs * the amount of saving to use for the whole window, not null * @return * this, for chaining * @throws IllegalStateException * if no window has yet been added * @throws IllegalStateException * if the window already has rules */ def setFixedSavingsToWindow(fixedSavingAmountSecs: Int): ZoneRulesBuilder = { if (windowList.isEmpty) throw new IllegalStateException("Must add a window before setting the fixed savings") val window: ZoneRulesBuilder#TZWindow = windowList.get(windowList.size - 1) window.setFixedSavings(fixedSavingAmountSecs) this } /** * Adds a single transition rule to the current window. * * This adds a rule such that the offset, expressed as a daylight savings amount, changes at the * specified date-time. * * @param transitionDateTime * the date-time that the transition occurs as defined by timeDefintion, not null * @param timeDefinition * the definition of how to convert local to actual time, not null * @param savingAmountSecs * the amount of saving from the standard offset after the transition in seconds * @return * this, for chaining * @throws IllegalStateException * if no window has yet been added * @throws IllegalStateException * if the window already has fixed savings * @throws IllegalStateException * if the window has reached the maximum capacity of 2000 rules */ private[zone] def addRuleToWindow( transitionDateTime: LocalDateTime, timeDefinition: ZoneOffsetTransitionRule.TimeDefinition, savingAmountSecs: Int ): ZoneRulesBuilder = { Objects.requireNonNull(transitionDateTime, "transitionDateTime") addRuleToWindow( transitionDateTime.getYear, transitionDateTime.getYear, transitionDateTime.getMonth, transitionDateTime.getDayOfMonth, null, transitionDateTime.toLocalTime, false, timeDefinition, savingAmountSecs ) } /** * Adds a single transition rule to the current window. * * This adds a rule such that the offset, expressed as a daylight savings amount, changes at the * specified date-time. * * @param year * the year of the transition, from MIN_VALUE to MAX_VALUE * @param month * the month of the transition, not null * @param dayOfMonthIndicator * the day-of-month of the transition, adjusted by dayOfWeek, from 1 to 31 adjusted later, or -1 * to -28 adjusted earlier from the last day of the month * @param time * the time that the transition occurs as defined by timeDefintion, not null * @param timeEndOfDay * whether midnight is at the end of day * @param timeDefinition * the definition of how to convert local to actual time, not null * @param savingAmountSecs * the amount of saving from the standard offset after the transition in seconds * @return * this, for chaining * @throws DateTimeException * if a date-time field is out of range * @throws IllegalStateException * if no window has yet been added * @throws IllegalStateException * if the window already has fixed savings * @throws IllegalStateException * if the window has reached the maximum capacity of 2000 rules */ private[zone] def addRuleToWindow( year: Int, month: Month, dayOfMonthIndicator: Int, time: LocalTime, timeEndOfDay: Boolean, timeDefinition: ZoneOffsetTransitionRule.TimeDefinition, savingAmountSecs: Int ): ZoneRulesBuilder = addRuleToWindow(year, year, month, dayOfMonthIndicator, null, time, timeEndOfDay, timeDefinition, savingAmountSecs ) /** * Adds a multi-year transition rule to the current window. * * This adds a rule such that the offset, expressed as a daylight savings amount, changes at the * specified date-time for each year in the range. * * @param startYear * the start year of the rule, from MIN_VALUE to MAX_VALUE * @param endYear * the end year of the rule, from MIN_VALUE to MAX_VALUE * @param month * the month of the transition, not null * @param dayOfMonthIndicator * the day-of-month of the transition, adjusted by dayOfWeek, from 1 to 31 adjusted later, or -1 * to -28 adjusted earlier from the last day of the month * @param dayOfWeek * the day-of-week to adjust to, null if day-of-month should not be adjusted * @param time * the time that the transition occurs as defined by timeDefintion, not null * @param timeEndOfDay * whether midnight is at the end of day * @param timeDefinition * the definition of how to convert local to actual time, not null * @param savingAmountSecs * the amount of saving from the standard offset after the transition in seconds * @return * this, for chaining * @throws DateTimeException * if a date-time field is out of range * @throws IllegalArgumentException * if the day of month indicator is invalid * @throws IllegalArgumentException * if the end of day midnight flag does not match the time * @throws IllegalStateException * if no window has yet been added * @throws IllegalStateException * if the window already has fixed savings * @throws IllegalStateException * if the window has reached the maximum capacity of 2000 rules */ private[zone] def addRuleToWindow( startYear: Int, endYear: Int, month: Month, dayOfMonthIndicator: Int, dayOfWeek: DayOfWeek, time: LocalTime, timeEndOfDay: Boolean, timeDefinition: ZoneOffsetTransitionRule.TimeDefinition, savingAmountSecs: Int ): ZoneRulesBuilder = { Objects.requireNonNull(month, "month") Objects.requireNonNull(time, "time") Objects.requireNonNull(timeDefinition, "timeDefinition") YEAR.checkValidValue(startYear.toLong) YEAR.checkValidValue(endYear.toLong) if (dayOfMonthIndicator < -28 || dayOfMonthIndicator > 31 || dayOfMonthIndicator == 0) throw new IllegalArgumentException( "Day of month indicator must be between -28 and 31 inclusive excluding zero" ) if (timeEndOfDay && !(time == LocalTime.MIDNIGHT)) throw new IllegalArgumentException("Time must be midnight when end of day flag is true") if (windowList.isEmpty) throw new IllegalStateException("Must add a window before adding a rule") val window: ZoneRulesBuilder#TZWindow = windowList.get(windowList.size - 1) window.addRule(startYear, endYear, month, dayOfMonthIndicator, dayOfWeek, time, timeEndOfDay, timeDefinition, savingAmountSecs ) this } /** * Completes the build converting the builder to a set of time-zone rules. * * Calling this method alters the state of the builder. Further rules should not be added to this * builder once this method is called. * * @param zoneId * the time-zone ID, not null * @return * the zone rules, not null * @throws IllegalStateException * if no windows have been added * @throws IllegalStateException * if there is only one rule defined as being forever for any given window */ def toRules(zoneId: String): ZoneRules = toRules(zoneId, new java.util.HashMap[AnyRef, AnyRef]) /** * Completes the build converting the builder to a set of time-zone rules. * * Calling this method alters the state of the builder. Further rules should not be added to this * builder once this method is called. * * @param zoneId * the time-zone ID, not null * @param deduplicateMap * a map for deduplicating the values, not null * @return * the zone rules, not null * @throws IllegalStateException * if no windows have been added * @throws IllegalStateException * if there is only one rule defined as being forever for any given window */ private[zone] def toRules( zoneId: String, deduplicateMap: java.util.Map[AnyRef, AnyRef] ): ZoneRules = { Objects.requireNonNull(zoneId, "zoneId") this.deduplicateMap = deduplicateMap if (windowList.isEmpty) throw new IllegalStateException("No windows have been added to the builder") val standardTransitionList: java.util.List[ZoneOffsetTransition] = new java.util.ArrayList[ZoneOffsetTransition](4) val transitionList: java.util.List[ZoneOffsetTransition] = new java.util.ArrayList[ZoneOffsetTransition](256) val lastTransitionRuleList: java.util.List[ZoneOffsetTransitionRule] = new java.util.ArrayList[ZoneOffsetTransitionRule](2) val firstWindow: ZoneRulesBuilder#TZWindow = windowList.get(0) var loopStandardOffset: ZoneOffset = firstWindow.standardOffset var loopSavings: Int = 0 if (firstWindow.fixedSavingAmountSecs != null) loopSavings = firstWindow.fixedSavingAmountSecs val firstWallOffset: ZoneOffset = deduplicate( ZoneOffset.ofTotalSeconds(loopStandardOffset.getTotalSeconds + loopSavings) ) var loopWindowStart: LocalDateTime = deduplicate(LocalDateTime.of(Year.MIN_VALUE, 1, 1, 0, 0)) var loopWindowOffset: ZoneOffset = firstWallOffset val windows = windowList.iterator while (windows.hasNext) { val window = windows.next() window.tidy(loopWindowStart.getYear) var effectiveSavings: Integer = window.fixedSavingAmountSecs if (effectiveSavings == null) { effectiveSavings = 0 var break = false val rules = window.ruleList.iterator while (!break && rules.hasNext) { val rule = rules.next() val trans: ZoneOffsetTransition = rule.toTransition(loopStandardOffset, loopSavings) if (trans.toEpochSecond > loopWindowStart.toEpochSecond(loopWindowOffset)) break = true if (!break) effectiveSavings = rule.savingAmountSecs } } if (loopStandardOffset != window.standardOffset) { standardTransitionList.add( deduplicate( new ZoneOffsetTransition(LocalDateTime.ofEpochSecond(loopWindowStart .toEpochSecond(loopWindowOffset), 0, loopStandardOffset ), loopStandardOffset, window.standardOffset ) ) ) loopStandardOffset = deduplicate(window.standardOffset) } val effectiveWallOffset: ZoneOffset = deduplicate( ZoneOffset.ofTotalSeconds(loopStandardOffset.getTotalSeconds + effectiveSavings) ) if (loopWindowOffset != effectiveWallOffset) { val trans: ZoneOffsetTransition = deduplicate( new ZoneOffsetTransition(loopWindowStart, loopWindowOffset, effectiveWallOffset) ) transitionList.add(trans) } loopSavings = effectiveSavings val rules = window.ruleList.iterator while (rules.hasNext) { val rule = rules.next() val trans: ZoneOffsetTransition = deduplicate( rule.toTransition(loopStandardOffset, loopSavings) ) if ( (trans.toEpochSecond >= loopWindowStart.toEpochSecond( loopWindowOffset )) && (trans.toEpochSecond < window .createDateTimeEpochSecond( loopSavings )) && (trans.getOffsetBefore != trans.getOffsetAfter) ) { transitionList.add(trans) loopSavings = rule.savingAmountSecs } } val lastRules = window.lastRuleList.iterator while (lastRules.hasNext) { val lastRule = lastRules.next() val transitionRule: ZoneOffsetTransitionRule = deduplicate( lastRule.toTransitionRule(loopStandardOffset, loopSavings) ) lastTransitionRuleList.add(transitionRule) loopSavings = lastRule.savingAmountSecs } loopWindowOffset = deduplicate(window.createWallOffset(loopSavings)) loopWindowStart = deduplicate( LocalDateTime .ofEpochSecond(window.createDateTimeEpochSecond(loopSavings), 0, loopWindowOffset) ) } StandardZoneRules(firstWindow.standardOffset, firstWallOffset, standardTransitionList, transitionList, lastTransitionRuleList ) } /** * Deduplicates an object instance. * * @tparam T * the generic type * @param object * the object to deduplicate * @return * the deduplicated object */ private[zone] def deduplicate[T <: AnyRef](`object`: T): T = { if (!deduplicateMap.containsKey(`object`)) deduplicateMap.put(`object`, `object`) deduplicateMap.get(`object`).asInstanceOf[T] } /** * A definition of a window in the time-line. The window will have one standard offset and will * either have a fixed DST savings or a set of rules. * * @constructor * * @param standardOffset * the standard offset applicable during the window, not null * @param windowEnd * the end of the window, relative to the time definition, null if forever * @param timeDefinition * the time definition for calculating the true end, not null */ private[zone] class TZWindow private[zone] ( private[zone] val standardOffset: ZoneOffset, private val windowEnd: LocalDateTime, private val timeDefinition: ZoneOffsetTransitionRule.TimeDefinition ) { /** The fixed amount of the saving to be applied during this window. */ private[zone] var fixedSavingAmountSecs: Integer = null /** The rules for the current window. */ private[zone] var ruleList: java.util.List[ZoneRulesBuilder#TZRule] = new java.util.ArrayList[ZoneRulesBuilder#TZRule] /** The latest year that the last year starts at. */ private var maxLastRuleStartYear: Int = Year.MIN_VALUE /** The last rules. */ private[zone] var lastRuleList: java.util.List[ZoneRulesBuilder#TZRule] = new java.util.ArrayList[ZoneRulesBuilder#TZRule] /** * Sets the fixed savings amount for the window. * * @param fixedSavingAmount * the amount of daylight saving to apply throughout the window, may be null * @throws IllegalStateException * if the window already has rules */ private[zone] def setFixedSavings(fixedSavingAmount: Int): Unit = if (ruleList.size > 0 || lastRuleList.size > 0) throw new IllegalStateException("Window has DST rules, so cannot have fixed savings") else this.fixedSavingAmountSecs = fixedSavingAmount /** * Adds a rule to the current window. * * @param startYear * the start year of the rule, from MIN_VALUE to MAX_VALUE * @param endYear * the end year of the rule, from MIN_VALUE to MAX_VALUE * @param month * the month of the transition, not null * @param dayOfMonthIndicator * the day-of-month of the transition, adjusted by dayOfWeek, from 1 to 31 adjusted later, or * -1 to -28 adjusted earlier from the last day of the month * @param dayOfWeek * the day-of-week to adjust to, null if day-of-month should not be adjusted * @param time * the time that the transition occurs as defined by timeDefintion, not null * @param timeEndOfDay * whether midnight is at the end of day * @param timeDefinition * the definition of how to convert local to actual time, not null * @param savingAmountSecs * the amount of saving from the standard offset in seconds * @throws IllegalStateException * if the window already has fixed savings * @throws IllegalStateException * if the window has reached the maximum capacity of 2000 rules */ private[zone] def addRule( startYear: Int, endYear: Int, month: Month, dayOfMonthIndicator: Int, dayOfWeek: DayOfWeek, time: LocalTime, timeEndOfDay: Boolean, timeDefinition: ZoneOffsetTransitionRule.TimeDefinition, savingAmountSecs: Int ): Unit = { var _endYear = endYear if (fixedSavingAmountSecs != null) throw new IllegalStateException("Window has a fixed DST saving, so cannot have DST rules") if (ruleList.size >= 2000) throw new IllegalStateException("Window has reached the maximum number of allowed rules") var lastRule: Boolean = false if (_endYear == Year.MAX_VALUE) { lastRule = true _endYear = startYear } var year: Int = startYear while (year <= _endYear) { val rule: ZoneRulesBuilder#TZRule = new TZRule(year, month, dayOfMonthIndicator, dayOfWeek, time, timeEndOfDay, timeDefinition, savingAmountSecs ) if (lastRule) { lastRuleList.add(rule) maxLastRuleStartYear = Math.max(startYear, maxLastRuleStartYear) } else ruleList.add(rule) year += 1 } } /** * Validates that this window is after the previous one. * * @param previous * the previous window, not null * @throws IllegalStateException * if the window order is invalid */ private[zone] def validateWindowOrder(previous: ZoneRulesBuilder#TZWindow): Unit = if (windowEnd.isBefore(previous.windowEnd)) throw new IllegalStateException( s"Windows must be added in date-time order: $windowEnd < ${previous.windowEnd}" ) /** * Adds rules to make the last rules all start from the same year. Also add one more year to * avoid weird case where penultimate year has odd offset. * * @param windowStartYear * the window start year * @throws IllegalStateException * if there is only one rule defined as being forever */ private[zone] def tidy(windowStartYear: Int): Unit = { if (lastRuleList.size == 1) throw new IllegalStateException("Cannot have only one rule defined as being forever") if (windowEnd == LocalDateTime.MAX) { maxLastRuleStartYear = Math.max(maxLastRuleStartYear, windowStartYear) + 1 val lastRules = lastRuleList.iterator while (lastRules.hasNext) { val lastRule = lastRules.next() addRule( lastRule.year, maxLastRuleStartYear, lastRule.month, lastRule.dayOfMonthIndicator, lastRule.dayOfWeek, lastRule.time, lastRule.timeEndOfDay, lastRule.timeDefinition, lastRule.savingAmountSecs ) lastRule.year = maxLastRuleStartYear + 1 } if (maxLastRuleStartYear == Year.MAX_VALUE) lastRuleList.clear() else maxLastRuleStartYear += 1 } else { val endYear: Int = windowEnd.getYear val lastRules = lastRuleList.iterator while (lastRules.hasNext) { val lastRule = lastRules.next() addRule( lastRule.year, endYear + 1, lastRule.month, lastRule.dayOfMonthIndicator, lastRule.dayOfWeek, lastRule.time, lastRule.timeEndOfDay, lastRule.timeDefinition, lastRule.savingAmountSecs ) } lastRuleList.clear() maxLastRuleStartYear = Year.MAX_VALUE } Collections.sort(ruleList, ruleComparator) Collections.sort(lastRuleList, ruleComparator) if (ruleList.size == 0 && fixedSavingAmountSecs == null) fixedSavingAmountSecs = 0 } /** * Checks if the window is empty. * * @return * true if the window is only a standard offset */ private[zone] def isSingleWindowStandardOffset: Boolean = (windowEnd == LocalDateTime.MAX) && (timeDefinition eq ZoneOffsetTransitionRule.TimeDefinition.WALL) && fixedSavingAmountSecs == null && lastRuleList.isEmpty && ruleList.isEmpty /** * Creates the wall offset for the local date-time at the end of the window. * * @param savingsSecs * the amount of savings in use in seconds * @return * the created date-time epoch second in the wall offset, not null */ private[zone] def createWallOffset(savingsSecs: Int): ZoneOffset = ZoneOffset.ofTotalSeconds(standardOffset.getTotalSeconds + savingsSecs) /** * Creates the offset date-time for the local date-time at the end of the window. * * @param savingsSecs * the amount of savings in use in seconds * @return * the created date-time epoch second in the wall offset, not null */ private[zone] def createDateTimeEpochSecond(savingsSecs: Int): Long = { val wallOffset: ZoneOffset = createWallOffset(savingsSecs) val ldt: LocalDateTime = timeDefinition.createDateTime(windowEnd, standardOffset, wallOffset) ldt.toEpochSecond(wallOffset) } } /** * A definition of the way a local time can be converted to an offset time. * * @constructor * * @param year * the year * @param month * the month, not null * @param dayOfMonthIndicator * the day-of-month of the transition, adjusted by dayOfWeek, from 1 to 31 adjusted later, or -1 * to -28 adjusted earlier from the last day of the month * @param dayOfWeek * the day-of-week, null if day-of-month is exact * @param time * the time, not null * @param timeEndOfDay * whether midnight is at the end of day * @param timeDefinition * the time definition, not null * @param savingAmountSecs * the savings amount in seconds */ protected[zone] class TZRule private[zone] ( private[zone] var year: Int, private[zone] var month: Month, private[zone] var dayOfMonthIndicator: Int, private[zone] var dayOfWeek: DayOfWeek, private[zone] var time: LocalTime, private[zone] var timeEndOfDay: Boolean, private[zone] var timeDefinition: ZoneOffsetTransitionRule.TimeDefinition, private[zone] var savingAmountSecs: Int ) { /** * Converts this to a transition. * * @param standardOffset * the active standard offset, not null * @param savingsBeforeSecs * the active savings in seconds * @return * the transition, not null */ private[zone] def toTransition( standardOffset: ZoneOffset, savingsBeforeSecs: Int ): ZoneOffsetTransition = { var date: LocalDate = toLocalDate date = deduplicate(date) val ldt: LocalDateTime = deduplicate(LocalDateTime.of(date, time)) val wallOffset: ZoneOffset = deduplicate( ZoneOffset.ofTotalSeconds(standardOffset.getTotalSeconds + savingsBeforeSecs) ) val dt: LocalDateTime = deduplicate( timeDefinition.createDateTime(ldt, standardOffset, wallOffset) ) val offsetAfter: ZoneOffset = deduplicate( ZoneOffset.ofTotalSeconds(standardOffset.getTotalSeconds + savingAmountSecs) ) new ZoneOffsetTransition(dt, wallOffset, offsetAfter) } /** * Converts this to a transition rule. * * @param standardOffset * the active standard offset, not null * @param savingsBeforeSecs * the active savings before the transition in seconds * @return * the transition, not null */ private[zone] def toTransitionRule( standardOffset: ZoneOffset, savingsBeforeSecs: Int ): ZoneOffsetTransitionRule = { if (dayOfMonthIndicator < 0) if (month ne Month.FEBRUARY) dayOfMonthIndicator = month.maxLength - 6 if ( timeEndOfDay && dayOfMonthIndicator > 0 && !(dayOfMonthIndicator == 28 && (month eq Month.FEBRUARY)) ) { val date: LocalDate = LocalDate.of(2004, month, dayOfMonthIndicator).plusDays(1) month = date.getMonth dayOfMonthIndicator = date.getDayOfMonth if (dayOfWeek != null) dayOfWeek = dayOfWeek.plus(1) timeEndOfDay = false } val trans: ZoneOffsetTransition = toTransition(standardOffset, savingsBeforeSecs) new ZoneOffsetTransitionRule(month, dayOfMonthIndicator, dayOfWeek, time, timeEndOfDay, timeDefinition, standardOffset, trans.getOffsetBefore, trans.getOffsetAfter ) } private[zone] def toLocalDate: LocalDate = { var date: LocalDate = null if (dayOfMonthIndicator < 0) { val monthLen: Int = month.length(IsoChronology.INSTANCE.isLeapYear(year.toLong)) date = LocalDate.of(year, month, monthLen + 1 + dayOfMonthIndicator) if (dayOfWeek != null) date = date.`with`(previousOrSame(dayOfWeek)) } else { date = LocalDate.of(year, month, dayOfMonthIndicator) if (dayOfWeek != null) date = date.`with`(nextOrSame(dayOfWeek)) } if (timeEndOfDay) date = date.plusDays(1) date } } class TZRuleComparator extends Comparator[ZoneRulesBuilder#TZRule] { override def compare(that: ZoneRulesBuilder#TZRule, other: ZoneRulesBuilder#TZRule): Int = { var cmp: Int = that.year - other.year cmp = if (cmp == 0) that.month.compareTo(other.month) else cmp if (cmp == 0) { val thisDate: LocalDate = that.toLocalDate val otherDate: LocalDate = other.toLocalDate cmp = thisDate.compareTo(otherDate) } cmp = if (cmp == 0) that.time.compareTo(other.time) else cmp cmp } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy