ZoneInfo.java revision b7d5826277766003e36bb8e82a0c09020fc1c823
1/*
2 * Copyright (C) 2007 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/*
17 * Elements of the WallTime class are a port of Bionic's localtime.c to Java. That code had the
18 * following header:
19 *
20 * This file is in the public domain, so clarified as of
21 * 1996-06-05 by Arthur David Olson.
22 */
23package libcore.util;
24
25import java.util.Arrays;
26import java.util.Calendar;
27import java.util.Date;
28import java.util.GregorianCalendar;
29import java.util.TimeZone;
30import libcore.io.BufferIterator;
31
32/**
33 * Our concrete TimeZone implementation, backed by zoneinfo data.
34 *
35 * @hide - used to implement TimeZone
36 */
37public final class ZoneInfo extends TimeZone {
38    private static final long MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;
39    private static final long MILLISECONDS_PER_400_YEARS =
40            MILLISECONDS_PER_DAY * (400 * 365 + 100 - 3);
41
42    private static final long UNIX_OFFSET = 62167219200000L;
43
44    private static final int[] NORMAL = new int[] {
45        0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334,
46    };
47
48    private static final int[] LEAP = new int[] {
49        0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335,
50    };
51
52    private int mRawOffset;
53    private final int mEarliestRawOffset;
54    private final boolean mUseDst;
55    private final int mDstSavings; // Implements TimeZone.getDSTSavings.
56
57    private final int[] mTransitions;
58    private final int[] mOffsets;
59    private final byte[] mTypes;
60    private final byte[] mIsDsts;
61
62    public static ZoneInfo makeTimeZone(String id, BufferIterator it) {
63        // Variable names beginning tzh_ correspond to those in "tzfile.h".
64
65        // Check tzh_magic.
66        if (it.readInt() != 0x545a6966) { // "TZif"
67            return null;
68        }
69
70        // Skip the uninteresting part of the header.
71        it.skip(28);
72
73        // Read the sizes of the arrays we're about to read.
74        int tzh_timecnt = it.readInt();
75        int tzh_typecnt = it.readInt();
76
77        it.skip(4); // Skip tzh_charcnt.
78
79        int[] transitions = new int[tzh_timecnt];
80        it.readIntArray(transitions, 0, transitions.length);
81
82        byte[] type = new byte[tzh_timecnt];
83        it.readByteArray(type, 0, type.length);
84
85        int[] gmtOffsets = new int[tzh_typecnt];
86        byte[] isDsts = new byte[tzh_typecnt];
87        for (int i = 0; i < tzh_typecnt; ++i) {
88            gmtOffsets[i] = it.readInt();
89            isDsts[i] = it.readByte();
90            // We skip the abbreviation index. This would let us provide historically-accurate
91            // time zone abbreviations (such as "AHST", "YST", and "AKST" for standard time in
92            // America/Anchorage in 1982, 1983, and 1984 respectively). ICU only knows the current
93            // names, though, so even if we did use this data to provide the correct abbreviations
94            // for en_US, we wouldn't be able to provide correct abbreviations for other locales,
95            // nor would we be able to provide correct long forms (such as "Yukon Standard Time")
96            // for any locale. (The RI doesn't do any better than us here either.)
97            it.skip(1);
98        }
99
100        return new ZoneInfo(id, transitions, type, gmtOffsets, isDsts);
101    }
102
103    private ZoneInfo(String name, int[] transitions, byte[] types, int[] gmtOffsets, byte[] isDsts) {
104        mTransitions = transitions;
105        mTypes = types;
106        mIsDsts = isDsts;
107        setID(name);
108
109        // Find the latest daylight and standard offsets (if any).
110        int lastStd = 0;
111        boolean haveStd = false;
112        int lastDst = 0;
113        boolean haveDst = false;
114        for (int i = mTransitions.length - 1; (!haveStd || !haveDst) && i >= 0; --i) {
115            int type = mTypes[i] & 0xff;
116            if (!haveStd && mIsDsts[type] == 0) {
117                haveStd = true;
118                lastStd = i;
119            }
120            if (!haveDst && mIsDsts[type] != 0) {
121                haveDst = true;
122                lastDst = i;
123            }
124        }
125
126        // Use the latest non-daylight offset (if any) as the raw offset.
127        if (lastStd >= mTypes.length) {
128            mRawOffset = gmtOffsets[0];
129        } else {
130            mRawOffset = gmtOffsets[mTypes[lastStd] & 0xff];
131        }
132
133        // Use the latest transition's pair of offsets to compute the DST savings.
134        // This isn't generally useful, but it's exposed by TimeZone.getDSTSavings.
135        if (lastDst >= mTypes.length) {
136            mDstSavings = 0;
137        } else {
138            mDstSavings = Math.abs(gmtOffsets[mTypes[lastStd] & 0xff] - gmtOffsets[mTypes[lastDst] & 0xff]) * 1000;
139        }
140
141        // Cache the oldest known raw offset, in case we're asked about times that predate our
142        // transition data.
143        int firstStd = -1;
144        for (int i = 0; i < mTransitions.length; ++i) {
145            if (mIsDsts[mTypes[i] & 0xff] == 0) {
146                firstStd = i;
147                break;
148            }
149        }
150        int earliestRawOffset = (firstStd != -1) ? gmtOffsets[mTypes[firstStd] & 0xff] : mRawOffset;
151
152        // Rather than keep offsets from UTC, we use offsets from local time, so the raw offset
153        // can be changed and automatically affect all the offsets.
154        mOffsets = gmtOffsets;
155        for (int i = 0; i < mOffsets.length; i++) {
156            mOffsets[i] -= mRawOffset;
157        }
158
159        // Is this zone still observing DST?
160        // We don't care if they've historically used it: most places have at least once.
161        // We want to know whether the last "schedule info" (the unix times in the mTransitions
162        // array) is in the future. If it is, DST is still relevant.
163        // See http://code.google.com/p/android/issues/detail?id=877.
164        // This test means that for somewhere like Morocco, which tried DST in 2009 but has
165        // no future plans (and thus no future schedule info) will report "true" from
166        // useDaylightTime at the start of 2009 but "false" at the end. This seems appropriate.
167        boolean usesDst = false;
168        long currentUnixTime = System.currentTimeMillis() / 1000;
169        if (mTransitions.length > 0) {
170            // (We're really dealing with uint32_t values, so long is most convenient in Java.)
171            long latestScheduleTime = ((long) mTransitions[mTransitions.length - 1]) & 0xffffffff;
172            if (currentUnixTime < latestScheduleTime) {
173                usesDst = true;
174            }
175        }
176        mUseDst = usesDst;
177
178        // tzdata uses seconds, but Java uses milliseconds.
179        mRawOffset *= 1000;
180        mEarliestRawOffset = earliestRawOffset * 1000;
181    }
182
183    @Override
184    public int getOffset(int era, int year, int month, int day, int dayOfWeek, int millis) {
185        // XXX This assumes Gregorian always; Calendar switches from
186        // Julian to Gregorian in 1582.  What calendar system are the
187        // arguments supposed to come from?
188
189        long calc = (year / 400) * MILLISECONDS_PER_400_YEARS;
190        year %= 400;
191
192        calc += year * (365 * MILLISECONDS_PER_DAY);
193        calc += ((year + 3) / 4) * MILLISECONDS_PER_DAY;
194
195        if (year > 0) {
196            calc -= ((year - 1) / 100) * MILLISECONDS_PER_DAY;
197        }
198
199        boolean isLeap = (year == 0 || (year % 4 == 0 && year % 100 != 0));
200        int[] mlen = isLeap ? LEAP : NORMAL;
201
202        calc += mlen[month] * MILLISECONDS_PER_DAY;
203        calc += (day - 1) * MILLISECONDS_PER_DAY;
204        calc += millis;
205
206        calc -= mRawOffset;
207        calc -= UNIX_OFFSET;
208
209        return getOffset(calc);
210    }
211
212    @Override
213    public int getOffset(long when) {
214        int unix = (int) (when / 1000);
215        int transition = Arrays.binarySearch(mTransitions, unix);
216        if (transition < 0) {
217            transition = ~transition - 1;
218            if (transition < 0) {
219                // Assume that all times before our first transition correspond to the
220                // oldest-known non-daylight offset. The obvious alternative would be to
221                // use the current raw offset, but that seems like a greater leap of faith.
222                return mEarliestRawOffset;
223            }
224        }
225        return mRawOffset + mOffsets[mTypes[transition] & 0xff] * 1000;
226    }
227
228    @Override public boolean inDaylightTime(Date time) {
229        long when = time.getTime();
230        int unix = (int) (when / 1000);
231        int transition = Arrays.binarySearch(mTransitions, unix);
232        if (transition < 0) {
233            transition = ~transition - 1;
234            if (transition < 0) {
235                // Assume that all times before our first transition are non-daylight.
236                // Transition data tends to start with a transition to daylight, so just
237                // copying the first transition would assume the opposite.
238                // http://code.google.com/p/android/issues/detail?id=14395
239                return false;
240            }
241        }
242        return mIsDsts[mTypes[transition] & 0xff] == 1;
243    }
244
245    @Override public int getRawOffset() {
246        return mRawOffset;
247    }
248
249    @Override public void setRawOffset(int off) {
250        mRawOffset = off;
251    }
252
253    @Override public int getDSTSavings() {
254        return mUseDst ? mDstSavings: 0;
255    }
256
257    @Override public boolean useDaylightTime() {
258        return mUseDst;
259    }
260
261    @Override public boolean hasSameRules(TimeZone timeZone) {
262        if (!(timeZone instanceof ZoneInfo)) {
263            return false;
264        }
265        ZoneInfo other = (ZoneInfo) timeZone;
266        if (mUseDst != other.mUseDst) {
267            return false;
268        }
269        if (!mUseDst) {
270            return mRawOffset == other.mRawOffset;
271        }
272        return mRawOffset == other.mRawOffset
273                // Arrays.equals returns true if both arrays are null
274                && Arrays.equals(mOffsets, other.mOffsets)
275                && Arrays.equals(mIsDsts, other.mIsDsts)
276                && Arrays.equals(mTypes, other.mTypes)
277                && Arrays.equals(mTransitions, other.mTransitions);
278    }
279
280    @Override public boolean equals(Object obj) {
281        if (!(obj instanceof ZoneInfo)) {
282            return false;
283        }
284        ZoneInfo other = (ZoneInfo) obj;
285        return getID().equals(other.getID()) && hasSameRules(other);
286    }
287
288    @Override
289    public int hashCode() {
290        final int prime = 31;
291        int result = 1;
292        result = prime * result + getID().hashCode();
293        result = prime * result + Arrays.hashCode(mOffsets);
294        result = prime * result + Arrays.hashCode(mIsDsts);
295        result = prime * result + mRawOffset;
296        result = prime * result + Arrays.hashCode(mTransitions);
297        result = prime * result + Arrays.hashCode(mTypes);
298        result = prime * result + (mUseDst ? 1231 : 1237);
299        return result;
300    }
301
302    @Override
303    public String toString() {
304        return getClass().getName() + "[id=\"" + getID() + "\"" +
305            ",mRawOffset=" + mRawOffset +
306            ",mEarliestRawOffset=" + mEarliestRawOffset +
307            ",mUseDst=" + mUseDst +
308            ",mDstSavings=" + mDstSavings +
309            ",transitions=" + mTransitions.length +
310            "]";
311    }
312
313    @Override
314    public Object clone() {
315        // Overridden for documentation. The default clone() behavior is exactly what we want.
316        // Though mutable, the arrays of offset data are treated as immutable. Only ID and
317        // mRawOffset are mutable in this class, and those are an immutable object and a primitive
318        // respectively.
319        return super.clone();
320    }
321
322    /**
323     * A class that represents a "wall time". This class is modeled on the C tm struct and
324     * is used to support android.text.format.Time behavior. Unlike the tm struct the year is
325     * represented as the full year, not the years since 1900.
326     *
327     * <p>This class contains a rewrite of various native functions that android.text.format.Time
328     * once relied on such as mktime_tz and localtime_tz. This replacement does not support leap
329     * seconds but does try to preserve behavior around ambiguous date/times found in the BSD
330     * version of mktime that was previously used.
331     *
332     * <p>The original native code used a 32-bit value for time_t on 32-bit Android, which
333     * was the only variant of Android available at the time. To preserve old behavior this code
334     * deliberately uses {@code int} rather than {@code long} for most things and performs
335     * calculations in seconds. This creates deliberate truncation issues for date / times before
336     * 1901 and after 2038. This is intentional but might be fixed in future if all the knock-ons
337     * can be resolved: Application code may have come to rely on the range so previously values
338     * like zero for year could indicate an invalid date but if we move to long the year zero would
339     * be valid.
340     *
341     * <p>All offsets are considered to be safe for addition / subtraction / multiplication without
342     * worrying about overflow. All absolute time arithmetic is checked for overflow / underflow.
343     */
344    public static class WallTime {
345
346        // We use a GregorianCalendar (set to UTC) to handle all the date/time normalization logic
347        // and to convert from a broken-down date/time to a millis value.
348        // Unfortunately, it cannot represent an initial state with a zero day and would
349        // automatically normalize it, so we must copy values into and out of it as needed.
350        private final GregorianCalendar calendar;
351
352        private int year;
353        private int month;
354        private int monthDay;
355        private int hour;
356        private int minute;
357        private int second;
358        private int weekDay;
359        private int yearDay;
360        private int isDst;
361        private int gmtOffsetSeconds;
362
363        public WallTime() {
364            this.calendar = new GregorianCalendar(false);
365            calendar.setTimeZone(TimeZone.getTimeZone("UTC"));
366        }
367
368        /**
369         * Sets the wall time to a point in time using the time zone information provided. This
370         * is a replacement for the old native localtime_tz() function.
371         *
372         * <p>When going from an instant to a wall time it is always unambiguous because there
373         * is only one offset rule acting at any given instant. We do not consider leap seconds.
374         */
375        public void localtime(int timeSeconds, ZoneInfo zoneInfo) {
376            try {
377                int offsetSeconds = zoneInfo.mRawOffset / 1000;
378
379                // Find out the timezone DST state and adjustment.
380                byte isDst;
381                if (zoneInfo.mTransitions.length == 0) {
382                    isDst = 0;
383                } else {
384                    // transitionIndex can be in the range -1..zoneInfo.mTransitions.length - 1
385                    int transitionIndex = findTransitionIndex(zoneInfo, timeSeconds);
386                    if (transitionIndex < 0) {
387                        // -1 means timeSeconds is "before the first recorded transition". The first
388                        // recorded transition is treated as a transition from non-DST and the raw
389                        // offset.
390                        isDst = 0;
391                    } else {
392                        byte transitionType = zoneInfo.mTypes[transitionIndex];
393                        offsetSeconds += zoneInfo.mOffsets[transitionType];
394                        isDst = zoneInfo.mIsDsts[transitionType];
395                    }
396                }
397
398                // Perform arithmetic that might underflow before setting fields.
399                int wallTimeSeconds = checkedAdd(timeSeconds, offsetSeconds);
400
401                // Set fields.
402                calendar.setTimeInMillis(wallTimeSeconds * 1000L);
403                copyFieldsFromCalendar();
404                this.isDst = isDst;
405                this.gmtOffsetSeconds = offsetSeconds;
406            } catch (CheckedArithmeticException e) {
407                // Just stop, leaving fields untouched.
408            }
409        }
410
411        /**
412         * Returns the time in seconds since beginning of the Unix epoch for the wall time using the
413         * time zone information provided. This is a replacement for an old native mktime_tz() C
414         * function.
415         *
416         * <p>When going from a wall time to an instant the answer can be ambiguous. A wall
417         * time can map to zero, one or two instants given sane date/time transitions. Sane
418         * in this case means that transitions occur less frequently than the offset
419         * differences between them (which could cause all sorts of craziness like the
420         * skipping out of transitions).
421         *
422         * <p>For example, this is not fully supported:
423         * <ul>
424         *     <li>t1 { time = 1, offset = 0 }
425         *     <li>t2 { time = 2, offset = -1 }
426         *     <li>t3 { time = 3, offset = -2 }
427         * </ul>
428         * A wall time in this case might map to t1, t2 or t3.
429         *
430         * <p>We do not handle leap seconds.
431         * <p>We assume that no timezone offset transition has an absolute offset > 24 hours.
432         * <p>We do not assume that adjacent transitions modify the DST state; adjustments can
433         * occur for other reasons such as when a zone changes its raw offset.
434         */
435        public int mktime(ZoneInfo zoneInfo) {
436            // Normalize isDst to -1, 0 or 1 to simplify isDst equality checks below.
437            this.isDst = this.isDst > 0 ? this.isDst = 1 : this.isDst < 0 ? this.isDst = -1 : 0;
438
439            copyFieldsToCalendar();
440            final long longWallTimeSeconds = calendar.getTimeInMillis()  / 1000;
441            if (Integer.MIN_VALUE > longWallTimeSeconds
442                    || longWallTimeSeconds > Integer.MAX_VALUE) {
443                // For compatibility with the old native 32-bit implementation we must treat
444                // this as an error. Note: -1 could be confused with a real time.
445                return -1;
446            }
447
448            try {
449                final int wallTimeSeconds =  (int) longWallTimeSeconds;
450                final int rawOffsetSeconds = zoneInfo.mRawOffset / 1000;
451                final int rawTimeSeconds = checkedSubtract(wallTimeSeconds, rawOffsetSeconds);
452
453                if (zoneInfo.mTransitions.length == 0) {
454                    // There is no transition information. There is just a raw offset for all time.
455                    if (this.isDst > 0) {
456                        // Caller has asserted DST, but there is no DST information available.
457                        return -1;
458                    }
459                    copyFieldsFromCalendar();
460                    this.isDst = 0;
461                    this.gmtOffsetSeconds = rawOffsetSeconds;
462                    return rawTimeSeconds;
463                }
464
465                // We cannot know for sure what instant the wall time will map to. Unfortunately, in
466                // order to know for sure we need the timezone information, but to get the timezone
467                // information we need an instant. To resolve this we use the raw offset to find an
468                // OffsetInterval; this will get us the OffsetInterval we need or very close.
469
470                // The initialTransition can be between -1 and (zoneInfo.mTransitions - 1). -1
471                // indicates the rawTime is before the first transition and is handled gracefully by
472                // createOffsetInterval().
473                final int initialTransitionIndex = findTransitionIndex(zoneInfo, rawTimeSeconds);
474
475                if (isDst < 0) {
476                    // This is treated as a special case to get it out of the way:
477                    // When a caller has set isDst == -1 it means we can return the first match for
478                    // the wall time we find. If the caller has specified a wall time that cannot
479                    // exist this always returns -1.
480
481                    Integer result = doWallTimeSearch(zoneInfo, initialTransitionIndex,
482                            wallTimeSeconds, true /* mustMatchDst */);
483                    return result == null ? -1 : result;
484                }
485
486                // If the wall time asserts a DST (isDst == 0 or 1) the search is performed twice:
487                // 1) The first attempts to find a DST offset that matches isDst exactly.
488                // 2) If it fails, isDst is assumed to be incorrect and adjustments are made to see
489                // if a valid wall time can be created. The result can be somewhat arbitrary.
490
491                Integer result = doWallTimeSearch(zoneInfo, initialTransitionIndex, wallTimeSeconds,
492                        true /* mustMatchDst */);
493                if (result == null) {
494                    result = doWallTimeSearch(zoneInfo, initialTransitionIndex, wallTimeSeconds,
495                            false /* mustMatchDst */);
496                }
497                if (result == null) {
498                    result = -1;
499                }
500                return result;
501            } catch (CheckedArithmeticException e) {
502                return -1;
503            }
504        }
505
506        /**
507         * Attempt to apply DST adjustments to {@code oldWallTimeSeconds} to create a wall time in
508         * {@code targetInterval}.
509         *
510         * <p>This is used when a caller has made an assertion about standard time / DST that cannot
511         * be matched to any offset interval that exists. We must therefore assume that the isDst
512         * assertion is incorrect and the invalid wall time is the result of some modification the
513         * caller made to a valid wall time that pushed them outside of the offset interval they
514         * were in. We must correct for any DST change that should have been applied when they did
515         * so.
516         *
517         * <p>Unfortunately, we have no information about what adjustment they made and so cannot
518         * know which offset interval they were previously in. For example, they may have added a
519         * second or a year to a valid time to arrive at what they have.
520         *
521         * <p>We try all offset types that are not the same as the isDst the caller asserted. For
522         * each possible offset we work out the offset difference between that and
523         * {@code targetInterval}, apply it, and see if we are still in {@code targetInterval}. If
524         * we are, then we have found an adjustment.
525         */
526        private Integer tryOffsetAdjustments(ZoneInfo zoneInfo, int oldWallTimeSeconds,
527                OffsetInterval targetInterval, int transitionIndex, int isDstToFind)
528                throws CheckedArithmeticException {
529
530            int[] offsetsToTry = getOffsetsOfType(zoneInfo, transitionIndex, isDstToFind);
531            for (int j = 0; j < offsetsToTry.length; j++) {
532                int rawOffsetSeconds = zoneInfo.mRawOffset / 1000;
533                int jOffsetSeconds = rawOffsetSeconds + offsetsToTry[j];
534                int targetIntervalOffsetSeconds = targetInterval.getTotalOffsetSeconds();
535                int adjustmentSeconds = targetIntervalOffsetSeconds - jOffsetSeconds;
536                int adjustedWallTimeSeconds = checkedAdd(oldWallTimeSeconds, adjustmentSeconds);
537                if (targetInterval.containsWallTime(adjustedWallTimeSeconds)) {
538                    // Perform any arithmetic that might overflow.
539                    int returnValue = checkedSubtract(adjustedWallTimeSeconds,
540                            targetIntervalOffsetSeconds);
541
542                    // Modify field state and return the result.
543                    calendar.setTimeInMillis(adjustedWallTimeSeconds * 1000L);
544                    copyFieldsFromCalendar();
545                    this.isDst = targetInterval.getIsDst();
546                    this.gmtOffsetSeconds = targetIntervalOffsetSeconds;
547                    return returnValue;
548                }
549            }
550            return null;
551        }
552
553        /**
554         * Return an array of offsets that have the requested {@code isDst} value.
555         * The {@code startIndex} is used as a starting point so transitions nearest
556         * to that index are returned first.
557         */
558        private static int[] getOffsetsOfType(ZoneInfo zoneInfo, int startIndex, int isDst) {
559            // +1 to account for the synthetic transition we invent before the first recorded one.
560            int[] offsets = new int[zoneInfo.mOffsets.length + 1];
561            boolean[] seen = new boolean[zoneInfo.mOffsets.length];
562            int numFound = 0;
563
564            int delta = 0;
565            boolean clampTop = false;
566            boolean clampBottom = false;
567            do {
568                // delta = { 1, -1, 2, -2, 3, -3...}
569                delta *= -1;
570                if (delta >= 0) {
571                    delta++;
572                }
573
574                int transitionIndex = startIndex + delta;
575                if (delta < 0 && transitionIndex < -1) {
576                    clampBottom = true;
577                    continue;
578                } else if (delta > 0 && transitionIndex >=  zoneInfo.mTypes.length) {
579                    clampTop = true;
580                    continue;
581                }
582
583                if (transitionIndex == -1) {
584                    if (isDst == 0) {
585                        // Synthesize a non-DST transition before the first transition we have
586                        // data for.
587                        offsets[numFound++] = 0; // offset of 0 from raw offset
588                    }
589                    continue;
590                }
591                byte type = zoneInfo.mTypes[transitionIndex];
592                if (!seen[type]) {
593                    if (zoneInfo.mIsDsts[type] == isDst) {
594                        offsets[numFound++] = zoneInfo.mOffsets[type];
595                    }
596                    seen[type] = true;
597                }
598            } while (!(clampTop && clampBottom));
599
600            int[] toReturn = new int[numFound];
601            System.arraycopy(offsets, 0, toReturn, 0, numFound);
602            return toReturn;
603        }
604
605        /**
606         * Find a time <em>in seconds</em> the same or close to {@code wallTimeSeconds} that
607         * satisfies {@code mustMatchDst}. The search begins around the timezone offset transition
608         * with {@code initialTransitionIndex}.
609         *
610         * <p>If {@code mustMatchDst} is {@code true} the method can only return times that
611         * use timezone offsets that satisfy the {@code this.isDst} requirements.
612         * If {@code this.isDst == -1} it means that any offset can be used.
613         *
614         * <p>If {@code mustMatchDst} is {@code false} any offset that covers the
615         * currently set time is acceptable. That is: if {@code this.isDst} == -1, any offset
616         * transition can be used, if it is 0 or 1 the offset used must match {@code this.isDst}.
617         *
618         * <p>Note: This method both uses and can modify field state. It returns the matching time
619         * in seconds if a match has been found and modifies fields, or it returns {@code null} and
620         * leaves the field state unmodified.
621         */
622        private Integer doWallTimeSearch(ZoneInfo zoneInfo, int initialTransitionIndex,
623                int wallTimeSeconds, boolean mustMatchDst) throws CheckedArithmeticException {
624
625            // The loop below starts at the initialTransitionIndex and radiates out from that point
626            // up to 24 hours in either direction by applying transitionIndexDelta to inspect
627            // adjacent transitions (0, -1, +1, -2, +2). 24 hours is used because we assume that no
628            // total offset from UTC is ever > 24 hours. clampTop and clampBottom are used to
629            // indicate whether the search has either searched > 24 hours or exhausted the
630            // transition data in that direction. The search stops when a match is found or if
631            // clampTop and clampBottom are both true.
632            // The match logic employed is determined by the mustMatchDst parameter.
633            final int MAX_SEARCH_SECONDS = 24 * 60 * 60;
634            boolean clampTop = false, clampBottom = false;
635            int loop = 0;
636            do {
637                // transitionIndexDelta = { 0, -1, 1, -2, 2,..}
638                int transitionIndexDelta = (loop + 1) / 2;
639                if (loop % 2 == 1) {
640                    transitionIndexDelta *= -1;
641                }
642                loop++;
643
644                // Only do any work in this iteration if we need to.
645                if (transitionIndexDelta > 0 && clampTop
646                        || transitionIndexDelta < 0 && clampBottom) {
647                    continue;
648                }
649
650                // Obtain the OffsetInterval to use.
651                int currentTransitionIndex = initialTransitionIndex + transitionIndexDelta;
652                OffsetInterval offsetInterval =
653                        OffsetInterval.create(zoneInfo, currentTransitionIndex);
654                if (offsetInterval == null) {
655                    // No transition exists with the index we tried: Stop searching in the
656                    // current direction.
657                    clampTop |= (transitionIndexDelta > 0);
658                    clampBottom |= (transitionIndexDelta < 0);
659                    continue;
660                }
661
662                // Match the wallTimeSeconds against the OffsetInterval.
663                if (mustMatchDst) {
664                    // Work out if the interval contains the wall time the caller specified and
665                    // matches their isDst value.
666                    if (offsetInterval.containsWallTime(wallTimeSeconds)) {
667                        if (this.isDst == -1 || offsetInterval.getIsDst() == this.isDst) {
668                            // This always returns the first OffsetInterval it finds that matches
669                            // the wall time and isDst requirements. If this.isDst == -1 this means
670                            // the result might be a DST or a non-DST answer for wall times that can
671                            // exist in two OffsetIntervals.
672                            int totalOffsetSeconds = offsetInterval.getTotalOffsetSeconds();
673                            int returnValue = checkedSubtract(wallTimeSeconds,
674                                    totalOffsetSeconds);
675
676                            copyFieldsFromCalendar();
677                            this.isDst = offsetInterval.getIsDst();
678                            this.gmtOffsetSeconds = totalOffsetSeconds;
679                            return returnValue;
680                        }
681                    }
682                } else {
683                    // To retain similar behavior to the old native implementation: if the caller is
684                    // asserting the same isDst value as the OffsetInterval we are looking at we do
685                    // not try to find an adjustment from another OffsetInterval of the same isDst
686                    // type. If you remove this you get different results in situations like a
687                    // DST -> DST transition or STD -> STD transition that results in an interval of
688                    // "skipped" wall time. For example: if 01:30 (DST) is invalid and between two
689                    // DST intervals, and the caller has passed isDst == 1, this results in a -1
690                    // being returned.
691                    if (isDst != offsetInterval.getIsDst()) {
692                        final int isDstToFind = isDst;
693                        Integer returnValue = tryOffsetAdjustments(zoneInfo, wallTimeSeconds,
694                                offsetInterval, currentTransitionIndex, isDstToFind);
695                        if (returnValue != null) {
696                            return returnValue;
697                        }
698                    }
699                }
700
701                // See if we can avoid another loop in the current direction.
702                if (transitionIndexDelta > 0) {
703                    // If we are searching forward and the OffsetInterval we have ends
704                    // > MAX_SEARCH_SECONDS after the wall time, we don't need to look any further
705                    // forward.
706                    boolean endSearch = offsetInterval.getEndWallTimeSeconds() - wallTimeSeconds
707                            > MAX_SEARCH_SECONDS;
708                    if (endSearch) {
709                        clampTop = true;
710                    }
711                } else if (transitionIndexDelta < 0) {
712                    boolean endSearch = wallTimeSeconds - offsetInterval.getStartWallTimeSeconds()
713                            >= MAX_SEARCH_SECONDS;
714                    if (endSearch) {
715                        // If we are searching backward and the OffsetInterval starts
716                        // > MAX_SEARCH_SECONDS before the wall time, we don't need to look any
717                        // further backwards.
718                        clampBottom = true;
719                    }
720                }
721            } while (!(clampTop && clampBottom));
722            return null;
723        }
724
725        public void setYear(int year) {
726            this.year = year;
727        }
728
729        public void setMonth(int month) {
730            this.month = month;
731        }
732
733        public void setMonthDay(int monthDay) {
734            this.monthDay = monthDay;
735        }
736
737        public void setHour(int hour) {
738            this.hour = hour;
739        }
740
741        public void setMinute(int minute) {
742            this.minute = minute;
743        }
744
745        public void setSecond(int second) {
746            this.second = second;
747        }
748
749        public void setWeekDay(int weekDay) {
750            this.weekDay = weekDay;
751        }
752
753        public void setYearDay(int yearDay) {
754            this.yearDay = yearDay;
755        }
756
757        public void setIsDst(int isDst) {
758            this.isDst = isDst;
759        }
760
761        public void setGmtOffset(int gmtoff) {
762            this.gmtOffsetSeconds = gmtoff;
763        }
764
765        public int getYear() {
766            return year;
767        }
768
769        public int getMonth() {
770            return month;
771        }
772
773        public int getMonthDay() {
774            return monthDay;
775        }
776
777        public int getHour() {
778            return hour;
779        }
780
781        public int getMinute() {
782            return minute;
783        }
784
785        public int getSecond() {
786            return second;
787        }
788
789        public int getWeekDay() {
790            return weekDay;
791        }
792
793        public int getYearDay() {
794            return yearDay;
795        }
796
797        public int getGmtOffset() {
798            return gmtOffsetSeconds;
799        }
800
801        public int getIsDst() {
802            return isDst;
803        }
804
805        private void copyFieldsToCalendar() {
806            calendar.set(Calendar.YEAR, year);
807            calendar.set(Calendar.MONTH, month);
808            calendar.set(Calendar.DAY_OF_MONTH, monthDay);
809            calendar.set(Calendar.HOUR_OF_DAY, hour);
810            calendar.set(Calendar.MINUTE, minute);
811            calendar.set(Calendar.SECOND, second);
812        }
813
814        private void copyFieldsFromCalendar() {
815            year = calendar.get(Calendar.YEAR);
816            month = calendar.get(Calendar.MONTH);
817            monthDay = calendar.get(Calendar.DAY_OF_MONTH);
818            hour = calendar.get(Calendar.HOUR_OF_DAY);
819            minute = calendar.get(Calendar.MINUTE);
820            second =  calendar.get(Calendar.SECOND);
821
822            // Calendar uses Sunday == 1. Android Time uses Sunday = 0.
823            weekDay = calendar.get(Calendar.DAY_OF_WEEK) - 1;
824            // Calendar enumerates from 1, Android Time enumerates from 0.
825            yearDay = calendar.get(Calendar.DAY_OF_YEAR) - 1;
826        }
827
828        /**
829         * Find the transition in the {@code timezone} in effect at {@code timeSeconds}.
830         *
831         * <p>Returns an index in the range -1..timeZone.mTransitions.length - 1. -1 is used to
832         * indicate the time is before the first transition. Other values are an index into
833         * timeZone.mTransitions.
834         */
835        private static int findTransitionIndex(ZoneInfo timeZone, int timeSeconds) {
836            int matchingRawTransition = Arrays.binarySearch(timeZone.mTransitions, timeSeconds);
837            if (matchingRawTransition < 0) {
838                matchingRawTransition = ~matchingRawTransition - 1;
839            }
840            return matchingRawTransition;
841        }
842    }
843
844    /**
845     * A wall-time representation of a timezone offset interval.
846     *
847     * <p>Wall-time means "as it would appear locally in the timezone in which it applies".
848     * For example in 2007:
849     * PST was a -8:00 offset that ran until Mar 11, 2:00 AM.
850     * PDT was a -7:00 offset and ran from Mar 11, 3:00 AM to Nov 4, 2:00 AM.
851     * PST was a -8:00 offset and ran from Nov 4, 1:00 AM.
852     * Crucially this means that there was a "gap" after PST when PDT started, and an overlap when
853     * PDT ended and PST began.
854     *
855     * <p>For convenience all wall-time values are represented as the number of seconds since the
856     * beginning of the Unix epoch <em>in UTC</em>. To convert from a wall-time to the actual time
857     * in the offset it is necessary to <em>subtract</em> the {@code totalOffsetSeconds}.
858     * For example: If the offset in PST is -07:00 hours, then:
859     * timeInPstSeconds = wallTimeUtcSeconds - offsetSeconds
860     * i.e. 13:00 UTC - (-07:00) = 20:00 UTC = 13:00 PST
861     */
862    static class OffsetInterval {
863
864        private final int startWallTimeSeconds;
865        private final int endWallTimeSeconds;
866        private final int isDst;
867        private final int totalOffsetSeconds;
868
869        /**
870         * Creates an {@link OffsetInterval}.
871         *
872         * <p>If {@code transitionIndex} is -1, the transition is synthesized to be a non-DST offset
873         * that runs from the beginning of time until the first transition in {@code timeZone} and
874         * has an offset of {@code timezone.mRawOffset}. If {@code transitionIndex} is the last
875         * transition that transition is considered to run until the end of representable time.
876         * Otherwise, the information is extracted from {@code timeZone.mTransitions},
877         * {@code timeZone.mOffsets} an {@code timeZone.mIsDsts}.
878         */
879        public static OffsetInterval create(ZoneInfo timeZone, int transitionIndex)
880                throws CheckedArithmeticException {
881
882            if (transitionIndex < -1 || transitionIndex >= timeZone.mTransitions.length) {
883                return null;
884            }
885
886            int rawOffsetSeconds = timeZone.mRawOffset / 1000;
887            if (transitionIndex == -1) {
888                int endWallTimeSeconds = checkedAdd(timeZone.mTransitions[0], rawOffsetSeconds);
889                return new OffsetInterval(Integer.MIN_VALUE, endWallTimeSeconds, 0 /* isDst */,
890                        rawOffsetSeconds);
891            }
892
893            byte type = timeZone.mTypes[transitionIndex];
894            int totalOffsetSeconds = timeZone.mOffsets[type] + rawOffsetSeconds;
895            int endWallTimeSeconds;
896            if (transitionIndex == timeZone.mTransitions.length - 1) {
897                // If this is the last transition, make up the end time.
898                endWallTimeSeconds = Integer.MAX_VALUE;
899            } else {
900                endWallTimeSeconds = checkedAdd(timeZone.mTransitions[transitionIndex + 1],
901                        totalOffsetSeconds);
902            }
903            int isDst = timeZone.mIsDsts[type];
904            int startWallTimeSeconds =
905                    checkedAdd(timeZone.mTransitions[transitionIndex], totalOffsetSeconds);
906            return new OffsetInterval(
907                    startWallTimeSeconds, endWallTimeSeconds, isDst, totalOffsetSeconds);
908        }
909
910        private OffsetInterval(int startWallTimeSeconds, int endWallTimeSeconds, int isDst,
911                int totalOffsetSeconds) {
912            this.startWallTimeSeconds = startWallTimeSeconds;
913            this.endWallTimeSeconds = endWallTimeSeconds;
914            this.isDst = isDst;
915            this.totalOffsetSeconds = totalOffsetSeconds;
916        }
917
918        public boolean containsWallTime(long wallTimeSeconds) {
919            return wallTimeSeconds >= startWallTimeSeconds && wallTimeSeconds < endWallTimeSeconds;
920        }
921
922        public int getIsDst() {
923            return isDst;
924        }
925
926        public int getTotalOffsetSeconds() {
927            return totalOffsetSeconds;
928        }
929
930        public long getEndWallTimeSeconds() {
931            return endWallTimeSeconds;
932        }
933
934        public long getStartWallTimeSeconds() {
935            return startWallTimeSeconds;
936        }
937    }
938
939    /**
940     * An exception used to indicate an arithmetic overflow or underflow.
941     */
942    private static class CheckedArithmeticException extends Exception {
943    }
944
945    /**
946     * Calculate (a + b).
947     *
948     * @throws CheckedArithmeticException if overflow or underflow occurs
949     */
950    private static int checkedAdd(int a, int b) throws CheckedArithmeticException {
951        // Adapted from Guava IntMath.checkedAdd();
952        long result = (long) a + b;
953        if (result != (int) result) {
954            throw new CheckedArithmeticException();
955        }
956        return (int) result;
957    }
958
959    /**
960     * Calculate (a - b).
961     *
962     * @throws CheckedArithmeticException if overflow or underflow occurs
963     */
964    private static int checkedSubtract(int a, int b) throws CheckedArithmeticException {
965        // Adapted from Guava IntMath.checkedSubtract();
966        long result = (long) a - b;
967        if (result != (int) result) {
968            throw new CheckedArithmeticException();
969        }
970        return (int) result;
971    }
972}
973