| /* |
| * Copyright (C) 2007 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package libcore.util; |
| |
| import java.text.SimpleDateFormat; |
| import java.util.Arrays; |
| import java.util.Date; |
| import java.util.Formatter; |
| import java.util.TimeZone; |
| |
| /** |
| * Our concrete TimeZone implementation, backed by zoneinfo data. |
| * |
| * @hide - used to implement TimeZone |
| */ |
| public final class ZoneInfo extends TimeZone { |
| private static final long MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000; |
| private static final long MILLISECONDS_PER_400_YEARS = |
| MILLISECONDS_PER_DAY * (400 * 365 + 100 - 3); |
| |
| private static final long UNIX_OFFSET = 62167219200000L; |
| |
| private static final int[] NORMAL = new int[] { |
| 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, |
| }; |
| |
| private static final int[] LEAP = new int[] { |
| 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, |
| }; |
| |
| private int mRawOffset; |
| |
| private final int mEarliestRawOffset; |
| |
| private final int[] mTransitions; |
| private final int[] mOffsets; |
| private final byte[] mTypes; |
| private final byte[] mIsDsts; |
| private final boolean mUseDst; |
| |
| ZoneInfo(String name, int[] transitions, byte[] type, int[] gmtOffsets, byte[] isDsts) { |
| mTransitions = transitions; |
| mTypes = type; |
| mIsDsts = isDsts; |
| setID(name); |
| |
| // Use the latest non-daylight offset (if any) as the raw offset. |
| int lastStd; |
| for (lastStd = mTransitions.length - 1; lastStd >= 0; lastStd--) { |
| if (mIsDsts[mTypes[lastStd] & 0xff] == 0) { |
| break; |
| } |
| } |
| if (lastStd < 0) { |
| lastStd = 0; |
| } |
| if (lastStd >= mTypes.length) { |
| mRawOffset = gmtOffsets[0]; |
| } else { |
| mRawOffset = gmtOffsets[mTypes[lastStd] & 0xff]; |
| } |
| |
| // Cache the oldest known raw offset, in case we're asked about times that predate our |
| // transition data. |
| int firstStd = -1; |
| for (int i = 0; i < mTransitions.length; ++i) { |
| if (mIsDsts[mTypes[i] & 0xff] == 0) { |
| firstStd = i; |
| break; |
| } |
| } |
| int earliestRawOffset = (firstStd != -1) ? gmtOffsets[mTypes[firstStd] & 0xff] : mRawOffset; |
| |
| // Rather than keep offsets from UTC, we use offsets from local time, so the raw offset |
| // can be changed and automatically affect all the offsets. |
| mOffsets = gmtOffsets; |
| for (int i = 0; i < mOffsets.length; i++) { |
| mOffsets[i] -= mRawOffset; |
| } |
| |
| // Is this zone still observing DST? |
| // We don't care if they've historically used it: most places have at least once. |
| // We want to know whether the last "schedule info" (the unix times in the mTransitions |
| // array) is in the future. If it is, DST is still relevant. |
| // See http://code.google.com/p/android/issues/detail?id=877. |
| // This test means that for somewhere like Morocco, which tried DST in 2009 but has |
| // no future plans (and thus no future schedule info) will report "true" from |
| // useDaylightTime at the start of 2009 but "false" at the end. This seems appropriate. |
| boolean usesDst = false; |
| long currentUnixTime = System.currentTimeMillis() / 1000; |
| if (mTransitions.length > 0) { |
| // (We're really dealing with uint32_t values, so long is most convenient in Java.) |
| long latestScheduleTime = ((long) mTransitions[mTransitions.length - 1]) & 0xffffffff; |
| if (currentUnixTime < latestScheduleTime) { |
| usesDst = true; |
| } |
| } |
| mUseDst = usesDst; |
| |
| mRawOffset *= 1000; |
| mEarliestRawOffset = earliestRawOffset * 1000; |
| } |
| |
| @Override |
| public int getOffset(int era, int year, int month, int day, int dayOfWeek, int millis) { |
| // XXX This assumes Gregorian always; Calendar switches from |
| // Julian to Gregorian in 1582. What calendar system are the |
| // arguments supposed to come from? |
| |
| long calc = (year / 400) * MILLISECONDS_PER_400_YEARS; |
| year %= 400; |
| |
| calc += year * (365 * MILLISECONDS_PER_DAY); |
| calc += ((year + 3) / 4) * MILLISECONDS_PER_DAY; |
| |
| if (year > 0) { |
| calc -= ((year - 1) / 100) * MILLISECONDS_PER_DAY; |
| } |
| |
| boolean isLeap = (year == 0 || (year % 4 == 0 && year % 100 != 0)); |
| int[] mlen = isLeap ? LEAP : NORMAL; |
| |
| calc += mlen[month] * MILLISECONDS_PER_DAY; |
| calc += (day - 1) * MILLISECONDS_PER_DAY; |
| calc += millis; |
| |
| calc -= mRawOffset; |
| calc -= UNIX_OFFSET; |
| |
| return getOffset(calc); |
| } |
| |
| @Override |
| public int getOffset(long when) { |
| int unix = (int) (when / 1000); |
| int transition = Arrays.binarySearch(mTransitions, unix); |
| if (transition < 0) { |
| transition = ~transition - 1; |
| if (transition < 0) { |
| // Assume that all times before our first transition correspond to the |
| // oldest-known non-daylight offset. The obvious alternative would be to |
| // use the current raw offset, but that seems like a greater leap of faith. |
| return mEarliestRawOffset; |
| } |
| } |
| return mRawOffset + mOffsets[mTypes[transition] & 0xff] * 1000; |
| } |
| |
| @Override public boolean inDaylightTime(Date time) { |
| long when = time.getTime(); |
| int unix = (int) (when / 1000); |
| int transition = Arrays.binarySearch(mTransitions, unix); |
| if (transition < 0) { |
| transition = ~transition - 1; |
| if (transition < 0) { |
| // Assume that all times before our first transition are non-daylight. |
| // Transition data tends to start with a transition to daylight, so just |
| // copying the first transition would assume the opposite. |
| // http://code.google.com/p/android/issues/detail?id=14395 |
| return false; |
| } |
| } |
| return mIsDsts[mTypes[transition] & 0xff] == 1; |
| } |
| |
| @Override public int getRawOffset() { |
| return mRawOffset; |
| } |
| |
| @Override public void setRawOffset(int off) { |
| mRawOffset = off; |
| } |
| |
| @Override public boolean useDaylightTime() { |
| return mUseDst; |
| } |
| |
| @Override public boolean hasSameRules(TimeZone timeZone) { |
| if (!(timeZone instanceof ZoneInfo)) { |
| return false; |
| } |
| ZoneInfo other = (ZoneInfo) timeZone; |
| if (mUseDst != other.mUseDst) { |
| return false; |
| } |
| if (!mUseDst) { |
| return mRawOffset == other.mRawOffset; |
| } |
| return mRawOffset == other.mRawOffset |
| // Arrays.equals returns true if both arrays are null |
| && Arrays.equals(mOffsets, other.mOffsets) |
| && Arrays.equals(mIsDsts, other.mIsDsts) |
| && Arrays.equals(mTypes, other.mTypes) |
| && Arrays.equals(mTransitions, other.mTransitions); |
| } |
| |
| @Override public boolean equals(Object obj) { |
| if (!(obj instanceof ZoneInfo)) { |
| return false; |
| } |
| ZoneInfo other = (ZoneInfo) obj; |
| return getID().equals(other.getID()) && hasSameRules(other); |
| } |
| |
| @Override |
| public int hashCode() { |
| final int prime = 31; |
| int result = 1; |
| result = prime * result + getID().hashCode(); |
| result = prime * result + Arrays.hashCode(mOffsets); |
| result = prime * result + Arrays.hashCode(mIsDsts); |
| result = prime * result + mRawOffset; |
| result = prime * result + Arrays.hashCode(mTransitions); |
| result = prime * result + Arrays.hashCode(mTypes); |
| result = prime * result + (mUseDst ? 1231 : 1237); |
| return result; |
| } |
| |
| @Override |
| public String toString() { |
| StringBuilder sb = new StringBuilder(); |
| // First the basics... |
| sb.append(getClass().getName() + "[" + getID() + ",mRawOffset=" + mRawOffset + |
| ",mUseDst=" + mUseDst + "]"); |
| // ...followed by a zdump(1)-like description of all our transition data. |
| sb.append("\n"); |
| Formatter f = new Formatter(sb); |
| for (int i = 0; i < mTransitions.length; ++i) { |
| int type = mTypes[i] & 0xff; |
| String utcTime = formatTime(mTransitions[i], TimeZone.getTimeZone("UTC")); |
| String localTime = formatTime(mTransitions[i], this); |
| int offset = mOffsets[type]; |
| int gmtOffset = mRawOffset/1000 + offset; |
| f.format("%4d : time=%11d %s = %s isDst=%d offset=%5d gmtOffset=%d\n", |
| i, mTransitions[i], utcTime, localTime, mIsDsts[type], offset, gmtOffset); |
| } |
| return sb.toString(); |
| } |
| |
| private static String formatTime(int s, TimeZone tz) { |
| SimpleDateFormat sdf = new SimpleDateFormat("EEE MMM dd HH:mm:ss yyyy zzz"); |
| sdf.setTimeZone(tz); |
| long ms = ((long) s) * 1000L; |
| return sdf.format(new Date(ms)); |
| } |
| } |