• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.libcore.timezone.tzlookup.zonetree;
17 
18 import com.ibm.icu.text.TimeZoneNames;
19 import com.ibm.icu.util.BasicTimeZone;
20 import com.ibm.icu.util.TimeZone;
21 import com.ibm.icu.util.TimeZoneTransition;
22 
23 import java.time.Instant;
24 import java.util.List;
25 import java.util.Objects;
26 
27 /**
28  * A period of time when all time-zone related properties are expected to remain the same.
29  */
30 final class ZoneOffsetPeriod {
31     /** The start of the period (inclusive) */
32     private final Instant start;
33     /** The end of the period (exclusive) */
34     private final Instant end;
35     /** The offset from UTC in milliseconds. */
36     private final int rawOffsetMillis;
37     /** The additional offset to apply due to DST */
38     private final int dstOffsetMillis;
39     /** A name for the time. */
40     private final String name;
41 
ZoneOffsetPeriod(Instant start, Instant end, int rawOffsetMillis, int dstOffsetMillis, String name)42     private ZoneOffsetPeriod(Instant start, Instant end, int rawOffsetMillis, int dstOffsetMillis,
43             String name) {
44         this.start = start;
45         this.end = end;
46         this.rawOffsetMillis = rawOffsetMillis;
47         this.dstOffsetMillis = dstOffsetMillis;
48         this.name = name;
49     }
50 
51     /**
52      * Constructs an instance using ICU data.
53      */
create(TimeZoneNames timeZoneNames, BasicTimeZone timeZone, Instant minTime, Instant maxTime)54     public static ZoneOffsetPeriod create(TimeZoneNames timeZoneNames, BasicTimeZone timeZone,
55             Instant minTime, Instant maxTime) {
56 
57         long startMillis = minTime.toEpochMilli();
58         TimeZoneTransition transition =
59                 timeZone.getNextTransition(startMillis, true /* inclusive */);
60         Instant end;
61         if (transition == null) {
62             // The zone has no transitions from start, so we create a ZoneOffsetPeriod
63             // from minTime to maxTime.
64             end = maxTime;
65         } else {
66             TimeZoneTransition nextTransition =
67                     timeZone.getNextTransition(startMillis, false /* inclusive */);
68             if (nextTransition != null) {
69                 long endTimeMillis = Math.min(nextTransition.getTime(), maxTime.toEpochMilli());
70                 end = Instant.ofEpochMilli(endTimeMillis);
71             } else {
72                 // The zone has no next transition after minTime, so we create a ZoneOffsetPeriod
73                 // from minTime to maxTime.
74                 end = maxTime;
75             }
76         }
77 
78         String longName = getNameAtTime(timeZoneNames, timeZone, startMillis);
79         int[] offsets = new int[2];
80         timeZone.getOffset(startMillis, false /* local */, offsets);
81         return new ZoneOffsetPeriod(minTime, end, offsets[0], offsets[1], longName);
82     }
83 
84 
85     /** Splits a period in two at the specified instant, returning the generated periods. */
splitAtTime( ZoneOffsetPeriod toSplit, TimeZoneNames timeZoneNames, BasicTimeZone timeZone, Instant partitionInstant)86     public static ZoneOffsetPeriod[] splitAtTime(
87             ZoneOffsetPeriod toSplit, TimeZoneNames timeZoneNames, BasicTimeZone timeZone,
88             Instant partitionInstant) {
89         if (!partitionInstant.isAfter(toSplit.start)
90                 || !partitionInstant.isBefore(toSplit.end)) {
91             throw new IllegalArgumentException(partitionInstant + " is not between "
92                     + toSplit.start + " and " + toSplit.end);
93         }
94         // Work out the name at the split so the name is always the name at the beginning of the
95         // zone offset period.
96         String nameAtSplit =
97                 getNameAtTime(timeZoneNames, timeZone, partitionInstant.toEpochMilli());
98         int rawOffsetMillis = toSplit.rawOffsetMillis;
99         int dstOffsetMillis = toSplit.dstOffsetMillis;
100         return new ZoneOffsetPeriod[] {
101                 new ZoneOffsetPeriod(toSplit.start, partitionInstant, rawOffsetMillis,
102                         dstOffsetMillis, toSplit.name),
103                 new ZoneOffsetPeriod(partitionInstant, toSplit.end, rawOffsetMillis,
104                         dstOffsetMillis, nameAtSplit)
105         };
106     }
107 
getStartInstant()108     public Instant getStartInstant() {
109         return start;
110     }
111 
getEndInstant()112     public Instant getEndInstant() {
113         return end;
114     }
115 
getStartMillis()116     public long getStartMillis() {
117         return start.toEpochMilli();
118     }
119 
getEndMillis()120     public long getEndMillis() {
121         return end.toEpochMilli();
122     }
123 
getName()124     public String getName() {
125         return name;
126     }
127 
getRawOffsetMillis()128     public int getRawOffsetMillis() {
129         return rawOffsetMillis;
130     }
131 
getDstOffsetMillis()132     public int getDstOffsetMillis() {
133         return dstOffsetMillis;
134     }
135 
136     @Override
equals(Object o)137     public boolean equals(Object o) {
138         if (this == o) {
139             return true;
140         }
141         if (o == null || getClass() != o.getClass()) {
142             return false;
143         }
144         ZoneOffsetPeriod that = (ZoneOffsetPeriod) o;
145         return rawOffsetMillis == that.rawOffsetMillis &&
146                 dstOffsetMillis == that.dstOffsetMillis &&
147                 Objects.equals(start, that.start) &&
148                 Objects.equals(end, that.end) &&
149                 Objects.equals(name, that.name);
150     }
151 
152     @Override
hashCode()153     public int hashCode() {
154         return Objects.hash(start, end, rawOffsetMillis, dstOffsetMillis, name);
155     }
156 
157     @Override
toString()158     public String toString() {
159         return "ZoneOffsetPeriod{" +
160                 "start=" + start +
161                 ", end=" + end +
162                 ", rawOffsetMillis=" + rawOffsetMillis +
163                 ", dstOffsetMillis=" + dstOffsetMillis +
164                 ", name='" + name + '\'' +
165                 '}';
166     }
167 
168     /**
169      * A class for establishing when multiple periods are identical.
170      */
171     static final class ZonePeriodsKey {
172 
173         private final List<ZoneOffsetPeriod> periods;
174 
ZonePeriodsKey(List<ZoneOffsetPeriod> periods)175         public ZonePeriodsKey(List<ZoneOffsetPeriod> periods) {
176             this.periods = periods;
177         }
178 
179         @Override
equals(Object o)180         public boolean equals(Object o) {
181             if (this == o) {
182                 return true;
183             }
184             if (o == null || getClass() != o.getClass()) {
185                 return false;
186             }
187             ZonePeriodsKey zoneKey = (ZonePeriodsKey) o;
188             return Objects.equals(periods, zoneKey.periods);
189         }
190 
191         @Override
hashCode()192         public int hashCode() {
193             return Objects.hash(periods);
194         }
195 
196         @Override
toString()197         public String toString() {
198             return "ZonePeriodsKey{" +
199                     "periods=" + periods +
200                     '}';
201         }
202     }
203 
getNameAtTime( TimeZoneNames timeZoneNames, BasicTimeZone timeZone, long startMillis)204     private static String getNameAtTime(
205             TimeZoneNames timeZoneNames, BasicTimeZone timeZone, long startMillis) {
206         int[] offsets = new int[2];
207         timeZone.getOffset(startMillis, false /* local */, offsets);
208         String canonicalID = TimeZone.getCanonicalID(timeZone.getID());
209         TimeZoneNames.NameType longNameType = offsets[1] == 0
210                 ? TimeZoneNames.NameType.LONG_STANDARD : TimeZoneNames.NameType.LONG_DAYLIGHT;
211         return timeZoneNames.getDisplayName(canonicalID, longNameType, startMillis);
212     }
213 }
214