/* * ----------------------------------------------------------------------- * Copyright © 2013-2016 Meno Hochschild, <http://www.menodata.de/> * ----------------------------------------------------------------------- * This file (TransitionModel.java) is part of project Time4J. * * Time4J is free software: You can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 2.1 of the License, or * (at your option) any later version. * * Time4J is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Time4J. If not, see <http://www.gnu.org/licenses/>. * ----------------------------------------------------------------------- */ package net.time4j.tz.model; import net.time4j.Moment; import net.time4j.base.GregorianDate; import net.time4j.base.GregorianMath; import net.time4j.base.MathUtils; import net.time4j.base.UnixTime; import net.time4j.base.WallTime; import net.time4j.engine.EpochDays; import net.time4j.scale.TimeScale; import net.time4j.tz.TransitionHistory; import net.time4j.tz.ZonalOffset; import net.time4j.tz.ZonalTransition; import java.io.IOException; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Optional; /** * <p>Factory class for creating zonal transition histories. </p> * * @author Meno Hochschild * @since 2.2 * @serial exclude * @doctags.concurrency {immutable} */ /*[deutsch] * <p>Fabrikklasse für die Erzeugung einer {@code TransitionHistory}. </p> * * @author Meno Hochschild * @since 2.2 * @serial exclude * @doctags.concurrency {immutable} */ public abstract class TransitionModel implements TransitionHistory, Serializable { //~ Statische Felder/Initialisierungen -------------------------------- static final String NEW_LINE = System.getProperty("line.separator"); //~ Konstruktoren ----------------------------------------------------- // package-private for subclasses only TransitionModel() { super(); } //~ Methoden ---------------------------------------------------------- /** * <p>Creates a new array-based and finite transition history. </p> * * @param transitions list of zonal transitions * @return new transition history * @throws IllegalArgumentException in any case of inconsistencies * or if there are no transitions at all * @since 2.2 */ /*[deutsch] * <p>Erzeugt eine Array-basierte endliche {@code TransitionHistory}. </p> * * @param transitions list of zonal transitions * @return new transition history * @throws IllegalArgumentException in any case of inconsistencies * or if there are no transitions at all * @since 2.2 */ public static TransitionHistory of(List<ZonalTransition> transitions) { return new ArrayTransitionModel(transitions); } /** * <p>Creates a new rule-based transition history. </p> * * @param standardOffset standard offset * @param rules list of daylight saving rules * @return new transition history * @throws IllegalArgumentException in any case of inconsistencies * or if there are more than 127 rules * @since 2.2 */ /*[deutsch] * <p>Erzeugt eine regelbasierte {@code TransitionHistory}. </p> * * @param standardOffset standard offset * @param rules list of daylight saving rules * @return new transition history * @throws IllegalArgumentException in any case of inconsistencies * or if there are more than 127 rules * @since 2.2 */ public static TransitionHistory of( ZonalOffset standardOffset, List<DaylightSavingRule> rules ) { if (rules.isEmpty()) { return new EmptyTransitionModel(standardOffset); } else { return new RuleBasedTransitionModel(standardOffset, rules); } } /** * <p>Creates a transition history of both history transitions and * rules for future transitions as well. </p> * * @param initialOffset initial offset * @param transitions list of zonal transitions * @param rules list of daylight saving rules * @return new transition history * @throws IllegalArgumentException in any case of inconsistencies * or if there are more than 127 rules * @since 2.2 */ /*[deutsch] * <p>Erzeugt eine {@code TransitionHistory}, die sowohl historische * Übergänge als auch Regeln für die Zukunft definiert. </p> * * @param initialOffset initial offset * @param transitions list of zonal transitions * @param rules list of daylight saving rules * @return new transition history * @throws IllegalArgumentException in any case of inconsistencies * or if there are more than 127 rules * @since 2.2 */ public static TransitionHistory of( ZonalOffset initialOffset, List<ZonalTransition> transitions, List<DaylightSavingRule> rules ) { return TransitionModel.of( initialOffset, transitions, rules, true, true); } @Override public boolean isEmpty() { return false; } @Override public final ZonalTransition getNextTransition(UnixTime ut) { Optional<ZonalTransition> opt = this.findNextTransition(ut); return (opt.isPresent() ? opt.get() : null); } // Hauptmethode static TransitionHistory of( ZonalOffset initialOffset, List<ZonalTransition> transitions, List<DaylightSavingRule> rules, boolean create, boolean sanityCheck ) { List<ZonalTransition> t; List<DaylightSavingRule> r; if (create) { t = new ArrayList<>(transitions); r = new ArrayList<>(rules); Collections.sort(t); Collections.sort(r, RuleComparator.INSTANCE); } else { t = transitions; r = rules; } int n = t.size(); if (n == 0) { if (r.isEmpty()) { return new EmptyTransitionModel(initialOffset); } else { return new RuleBasedTransitionModel( initialOffset, r, false); } } ZonalOffset first = ZonalOffset.ofTotalSeconds(t.get(0).getPreviousOffset()); if (sanityCheck && !initialOffset.equals(first)) { throw new IllegalArgumentException( "Initial offset " + initialOffset + " not equal " + "to previous offset of first transition: " + first); } if (r.isEmpty()) { return new ArrayTransitionModel(t, false, sanityCheck); } ZonalTransition last = t.get(n - 1); long t1 = last.getPosixTime() + 1; long t2 = getFutureMoment(1); if (t1 < t2) { t.addAll( // enhance array part RuleBasedTransitionModel.getTransitions(last, r, t1, t2)); } return new CompositeTransitionModel(n, t, r, false, sanityCheck); } static List<ZonalOffset> toList(int offset) { return Collections.singletonList(ZonalOffset.ofTotalSeconds(offset)); } static List<ZonalOffset> toList( int offset1, int offset2 ) { ZonalOffset zo1 = ZonalOffset.ofTotalSeconds(offset1); ZonalOffset zo2 = ZonalOffset.ofTotalSeconds(offset2); List<ZonalOffset> offsets = new ArrayList<>(2); offsets.add(zo1); offsets.add(zo2); return Collections.unmodifiableList(offsets); } static long toLocalSecs( GregorianDate localDate, WallTime localTime ) { long mjd = GregorianMath.toMJD( localDate.getYear(), localDate.getMonth(), localDate.getDayOfMonth()); long localSecs = MathUtils.safeMultiply( EpochDays.UNIX.transform(mjd, EpochDays.MODIFIED_JULIAN_DATE), 86400); localSecs += localTime.getHour() * 3600; localSecs += localTime.getMinute() * 60; localSecs += localTime.getSecond(); return localSecs; } static void dump( ZonalTransition transition, Appendable buffer ) throws IOException { Moment ut = Moment.of(transition.getPosixTime(), TimeScale.POSIX); buffer.append(">>> Transition at: ").append(ut.toString()); buffer.append(" from ").append(format(transition.getPreviousOffset())); buffer.append(" to ").append(format(transition.getTotalOffset())); buffer.append(", DST="); buffer.append(format(transition.getDaylightSavingOffset())); buffer.append(NEW_LINE); } static long getFutureMoment(int years) { long y = (long) (365.2425 * 86400L * years); return (System.currentTimeMillis() / 1000) + y; } private static String format(int offset) { return ZonalOffset.ofTotalSeconds(offset).toString(); } }