Last active
October 23, 2016 23:08
-
-
Save kleisauke/5a74ab95269f5df3ae37 to your computer and use it in GitHub Desktop.
Fixed TimeZoneInfo.java for Android M. (Because in SDK 23 TimeZone.mTransition is now long[])
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright (C) 2013 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 com.android.timezonepicker; | |
import android.content.Context; | |
import android.os.Build; | |
import android.text.Spannable; | |
import android.text.Spannable.Factory; | |
import android.text.format.DateUtils; | |
import android.text.format.Time; | |
import android.text.style.ForegroundColorSpan; | |
import android.util.Log; | |
import android.util.SparseArray; | |
import java.lang.reflect.Field; | |
import java.text.DateFormat; | |
import java.util.Arrays; | |
import java.util.Date; | |
import java.util.Formatter; | |
import java.util.Locale; | |
import java.util.TimeZone; | |
public class TimeZoneInfo implements Comparable<TimeZoneInfo> { | |
private static final int GMT_TEXT_COLOR = TimeZonePickerUtils.GMT_TEXT_COLOR; | |
private static final int DST_SYMBOL_COLOR = TimeZonePickerUtils.DST_SYMBOL_COLOR; | |
private static final char SEPARATOR = ','; | |
private static final String TAG = null; | |
public static int NUM_OF_TRANSITIONS = 6; | |
public static long time = System.currentTimeMillis() / 1000; | |
public static boolean is24HourFormat; | |
private static final Factory mSpannableFactory = Spannable.Factory.getInstance(); | |
TimeZone mTz; | |
public String mTzId; | |
int mRawoffset; | |
public long[] mTransitions; // may have trailing 0's. | |
public String mCountry; | |
public int groupId; | |
public String mDisplayName; | |
private Time recycledTime = new Time(); | |
private static StringBuilder mSB = new StringBuilder(50); | |
private static Formatter mFormatter = new Formatter(mSB, Locale.getDefault()); | |
public TimeZoneInfo(TimeZone tz, String country) { | |
mTz = tz; | |
mTzId = tz.getID(); | |
mCountry = country; | |
mRawoffset = tz.getRawOffset(); | |
try { | |
mTransitions = getTransitions(tz, time); | |
} catch (NoSuchFieldException ignored) { | |
} catch (IllegalAccessException ignored) { | |
ignored.printStackTrace(); | |
} | |
} | |
SparseArray<String> mLocalTimeCache = new SparseArray<String>(); | |
long mLocalTimeCacheReferenceTime = 0; | |
static private long mGmtDisplayNameUpdateTime; | |
static private SparseArray<CharSequence> mGmtDisplayNameCache = | |
new SparseArray<CharSequence>(); | |
public String getLocalTime(long referenceTime) { | |
recycledTime.timezone = TimeZone.getDefault().getID(); | |
recycledTime.set(referenceTime); | |
int currYearDay = recycledTime.year * 366 + recycledTime.yearDay; | |
recycledTime.timezone = mTzId; | |
recycledTime.set(referenceTime); | |
String localTimeStr = null; | |
int hourMinute = recycledTime.hour * 60 + | |
recycledTime.minute; | |
if (mLocalTimeCacheReferenceTime != referenceTime) { | |
mLocalTimeCacheReferenceTime = referenceTime; | |
mLocalTimeCache.clear(); | |
} else { | |
localTimeStr = mLocalTimeCache.get(hourMinute); | |
} | |
if (localTimeStr == null) { | |
String format = "%I:%M %p"; | |
if (currYearDay != (recycledTime.year * 366 + recycledTime.yearDay)) { | |
if (is24HourFormat) { | |
format = "%b %d %H:%M"; | |
} else { | |
format = "%b %d %I:%M %p"; | |
} | |
} else if (is24HourFormat) { | |
format = "%H:%M"; | |
} | |
// format = "%Y-%m-%d %H:%M"; | |
localTimeStr = recycledTime.format(format); | |
mLocalTimeCache.put(hourMinute, localTimeStr); | |
} | |
return localTimeStr; | |
} | |
public int getLocalHr(long referenceTime) { | |
recycledTime.timezone = mTzId; | |
recycledTime.set(referenceTime); | |
return recycledTime.hour; | |
} | |
public int getNowOffsetMillis() { | |
return mTz.getOffset(System.currentTimeMillis()); | |
} | |
/* | |
* The method is synchronized because there's one mSB, which is used by | |
* mFormatter, per instance. If there are multiple callers for | |
* getGmtDisplayName, the output may be mangled. | |
*/ | |
public synchronized CharSequence getGmtDisplayName(Context context) { | |
// TODO Note: The local time is shown in current time (current GMT | |
// offset) which may be different from the time specified by | |
// mTimeMillis | |
final long nowMinute = System.currentTimeMillis() / DateUtils.MINUTE_IN_MILLIS; | |
final long now = nowMinute * DateUtils.MINUTE_IN_MILLIS; | |
final int gmtOffset = mTz.getOffset(now); | |
int cacheKey; | |
boolean hasFutureDST = mTz.useDaylightTime(); | |
if (hasFutureDST) { | |
cacheKey = (int) (gmtOffset + 36 * DateUtils.HOUR_IN_MILLIS); | |
} else { | |
cacheKey = (int) (gmtOffset - 36 * DateUtils.HOUR_IN_MILLIS); | |
} | |
CharSequence displayName = null; | |
if (mGmtDisplayNameUpdateTime != nowMinute) { | |
mGmtDisplayNameUpdateTime = nowMinute; | |
mGmtDisplayNameCache.clear(); | |
} else { | |
displayName = mGmtDisplayNameCache.get(cacheKey); | |
} | |
if (displayName == null) { | |
mSB.setLength(0); | |
int flags = DateUtils.FORMAT_ABBREV_ALL; | |
flags |= DateUtils.FORMAT_SHOW_TIME; | |
if (TimeZoneInfo.is24HourFormat) { | |
flags |= DateUtils.FORMAT_24HOUR; | |
} | |
// mFormatter writes to mSB | |
DateUtils.formatDateRange(context, mFormatter, now, now, flags, mTzId); | |
mSB.append(" "); | |
int gmtStart = mSB.length(); | |
TimeZonePickerUtils.appendGmtOffset(mSB, gmtOffset); | |
int gmtEnd = mSB.length(); | |
int symbolStart = 0; | |
int symbolEnd = 0; | |
if (hasFutureDST) { | |
mSB.append(' '); | |
symbolStart = mSB.length(); | |
mSB.append(TimeZonePickerUtils.getDstSymbol()); // Sun symbol | |
symbolEnd = mSB.length(); | |
} | |
// Set the gray colors. | |
Spannable spannableText = mSpannableFactory.newSpannable(mSB); | |
spannableText.setSpan(new ForegroundColorSpan(GMT_TEXT_COLOR), | |
gmtStart, gmtEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); | |
if (hasFutureDST) { | |
spannableText.setSpan(new ForegroundColorSpan(DST_SYMBOL_COLOR), | |
symbolStart, symbolEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); | |
} | |
displayName = spannableText; | |
mGmtDisplayNameCache.put(cacheKey, displayName); | |
} | |
return displayName; | |
} | |
private static long[] getTransitions(TimeZone tz, long time) throws IllegalAccessException, NoSuchFieldException { | |
Class<?> zoneInfoClass = tz.getClass(); | |
Field mTransitionsField = zoneInfoClass.getDeclaredField("mTransitions"); | |
mTransitionsField.setAccessible(true); | |
long[] objTransitions; | |
long[] transitions = null; | |
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { | |
objTransitions = (long[]) mTransitionsField.get(tz); | |
} else { | |
objTransitions = copyFromIntArray((int[]) mTransitionsField.get(tz)); | |
} | |
if (objTransitions.length != 0) { | |
transitions = new long[NUM_OF_TRANSITIONS]; | |
int numOfTransitions = 0; | |
for (int i = 0; i < objTransitions.length; ++i) { | |
if (objTransitions[i] < time) { | |
continue; | |
} | |
transitions[numOfTransitions++] = objTransitions[i]; | |
if (numOfTransitions == NUM_OF_TRANSITIONS) { | |
break; | |
} | |
} | |
} | |
return transitions; | |
} | |
public static long[] copyFromIntArray(int[] source) { | |
if (source == null) { | |
return new long[0]; | |
} | |
long[] dest = new long[source.length]; | |
for (int i = 0; i < source.length; i++) { | |
dest[i] = source[i]; | |
} | |
return dest; | |
} | |
public boolean hasSameRules(TimeZoneInfo tzi) { | |
// this.mTz.hasSameRules(tzi.mTz) | |
return this.mRawoffset == tzi.mRawoffset | |
&& Arrays.equals(this.mTransitions, tzi.mTransitions); | |
} | |
@Override | |
public String toString() { | |
StringBuilder sb = new StringBuilder(); | |
final String country = this.mCountry; | |
final TimeZone tz = this.mTz; | |
sb.append(mTzId); | |
sb.append(SEPARATOR); | |
sb.append(tz.getDisplayName(false /* daylightTime */, TimeZone.LONG)); | |
sb.append(SEPARATOR); | |
sb.append(tz.getDisplayName(false /* daylightTime */, TimeZone.SHORT)); | |
sb.append(SEPARATOR); | |
if (tz.useDaylightTime()) { | |
sb.append(tz.getDisplayName(true, TimeZone.LONG)); | |
sb.append(SEPARATOR); | |
sb.append(tz.getDisplayName(true, TimeZone.SHORT)); | |
} else { | |
sb.append(SEPARATOR); | |
} | |
sb.append(SEPARATOR); | |
sb.append(tz.getRawOffset() / 3600000f); | |
sb.append(SEPARATOR); | |
sb.append(tz.getDSTSavings() / 3600000f); | |
sb.append(SEPARATOR); | |
sb.append(country); | |
sb.append(SEPARATOR); | |
// 1-1-2013 noon GMT | |
sb.append(getLocalTime(1357041600000L)); | |
sb.append(SEPARATOR); | |
// 3-15-2013 noon GMT | |
sb.append(getLocalTime(1363348800000L)); | |
sb.append(SEPARATOR); | |
// 7-1-2013 noon GMT | |
sb.append(getLocalTime(1372680000000L)); | |
sb.append(SEPARATOR); | |
// 11-01-2013 noon GMT | |
sb.append(getLocalTime(1383307200000L)); | |
sb.append(SEPARATOR); | |
// if (this.mTransitions != null && this.mTransitions.length != 0) { | |
// sb.append('"'); | |
// DateFormat df = new SimpleDateFormat("yyyy-MM-dd' 'HH:mm:ss Z", | |
// Locale.US); | |
// df.setTimeZone(tz); | |
// DateFormat weekdayFormat = new SimpleDateFormat("EEEE", Locale.US); | |
// weekdayFormat.setTimeZone(tz); | |
// Formatter f = new Formatter(sb); | |
// for (int i = 0; i < this.mTransitions.length; ++i) { | |
// if (this.mTransitions[i] < time) { | |
// continue; | |
// } | |
// | |
// String fromTime = formatTime(df, this.mTransitions[i] - 1); | |
// String toTime = formatTime(df, this.mTransitions[i]); | |
// f.format("%s -> %s (%d)", fromTime, toTime, this.mTransitions[i]); | |
// | |
// String weekday = weekdayFormat.format(new Date(1000L * | |
// this.mTransitions[i])); | |
// if (!weekday.equals("Sunday")) { | |
// f.format(" -- %s", weekday); | |
// } | |
// sb.append("##"); | |
// } | |
// sb.append('"'); | |
// } | |
// sb.append(SEPARATOR); | |
sb.append('\n'); | |
return sb.toString(); | |
} | |
private static String formatTime(DateFormat df, int s) { | |
long ms = s * 1000L; | |
return df.format(new Date(ms)); | |
} | |
/* | |
* Returns a negative integer if this instance is less than the other; a | |
* positive integer if this instance is greater than the other; 0 if this | |
* instance has the same order as the other. | |
*/ | |
@Override | |
public int compareTo(TimeZoneInfo other) { | |
if (this.getNowOffsetMillis() != other.getNowOffsetMillis()) { | |
return (other.getNowOffsetMillis() < this.getNowOffsetMillis()) ? -1 : 1; | |
} | |
// By country | |
if (this.mCountry == null) { | |
if (other.mCountry != null) { | |
return 1; | |
} | |
} | |
if (other.mCountry == null) { | |
return -1; | |
} else { | |
int diff = this.mCountry.compareTo(other.mCountry); | |
if (diff != 0) { | |
return diff; | |
} | |
} | |
if (Arrays.equals(this.mTransitions, other.mTransitions)) { | |
Log.e(TAG, "Not expected to be comparing tz with the same country, same offset," + | |
" same dst, same transitions:\n" + this.toString() + "\n" + other.toString()); | |
} | |
// Finally diff by display name | |
if (mDisplayName != null && other.mDisplayName != null) { | |
return this.mDisplayName.compareTo(other.mDisplayName); | |
} | |
return this.mTz.getDisplayName(Locale.getDefault()).compareTo( | |
other.mTz.getDisplayName(Locale.getDefault())); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
When I try and apply this fix, I get a build fail with AOSP 6.0.1_r72. Do I need to do something in addition to applying the fix? This is my output:
target thumb C++: libbccCore <= frameworks/compile/libbcc/lib/Core/BCCContext.cpp
target thumb C++: libbccCore <= frameworks/compile/libbcc/lib/Core/BCCContextImpl.cpp
target thumb C++: libbccCore <= frameworks/compile/libbcc/lib/Core/Compiler.cpp
target thumb C++: libbccCore <= frameworks/compile/libbcc/lib/Core/Script.cpp
target thumb C++: libbccCore <= frameworks/compile/libbcc/lib/Core/Source.cpp
bc lib: libclcore.bc (out/target/product/hammerhead/obj/SHARED_LIBRARIES/libclcore.bc_intermediates/libclcore.bc)
WARNING: Linking two modules of different data layouts: 'out/target/product/hammerhead/obj/SHARED_LIBRARIES/libclcore.bc_intermediates/ll32/allocation.bc' is 'e-p:32:32:32-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v64:64:64-v128:64:128-a0:0:64-n32-S64' whereas 'llvm-link' is 'e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64'
WARNING: Linking two modules of different data layouts: 'out/target/product/hammerhead/obj/SHARED_LIBRARIES/libclcore.bc_intermediates/ll32/math.bc' is 'e-p:32:32:32-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v64:64:64-v128:64:128-a0:0:64-n32-S64' whereas 'llvm-link' is 'e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64'
bc lib: libclcore_debug.bc (out/target/product/hammerhead/obj/SHARED_LIBRARIES/libclcore_debug.bc_intermediates/libclcore_debug.bc)
WARNING: Linking two modules of different data layouts: 'out/target/product/hammerhead/obj/SHARED_LIBRARIES/libclcore_debug.bc_intermediates/ll32/allocation.bc' is 'e-p:32:32:32-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v64:64:64-v128:64:128-a0:0:64-n32-S64' whereas 'llvm-link' is 'e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64'
WARNING: Linking two modules of different data layouts: 'out/target/product/hammerhead/obj/SHARED_LIBRARIES/libclcore_debug.bc_intermediates/ll32/math.bc' is 'e-p:32:32:32-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v64:64:64-v128:64:128-a0:0:64-n32-S64' whereas 'llvm-link' is 'e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64'
bc lib: libclcore_neon.bc (out/target/product/hammerhead/obj/SHARED_LIBRARIES/libclcore_neon.bc_intermediates/libclcore_neon.bc)
target R.java/Manifest.java: BluetoothMidiService (out/target/common/obj/APPS/BluetoothMidiService_intermediates/src/R.stamp)
WARNING: Linking two modules of different data layouts: 'out/target/product/hammerhead/obj/SHARED_LIBRARIES/libclcore_neon.bc_intermediates/ll32/allocation.bc' is 'e-p:32:32:32-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v64:64:64-v128:64:128-a0:0:64-n32-S64' whereas 'llvm-link' is 'e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64'
WARNING: Linking two modules of different data layouts: 'out/target/product/hammerhead/obj/SHARED_LIBRARIES/libclcore_neon.bc_intermediates/ll32/math.bc' is 'e-p:32:32:32-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v64:64:64-v128:64:128-a0:0:64-n32-S64' whereas 'llvm-link' is 'e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64'
target R.java/Manifest.java: Calendar (out/target/common/obj/APPS/Calendar_intermediates/src/R.stamp)
Building with Jack: out/target/common/obj/JAVA_LIBRARIES/android-common_intermediates/classes.jack
WARNING: Linking two modules of different data layouts: 'out/target/product/hammerhead/obj/SHARED_LIBRARIES/libclcore_neon.bc_intermediates/arch/neon.bc' is 'e-p:32:32:32-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v64:64:64-v128:64:128-a0:0:64-n32-S64' whereas 'llvm-link' is 'e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64'
Launching background server java -Dfile.encoding=UTF-8 -Xms2560m -XX:+TieredCompilation -jar out/host/linux-x86/framework/jack-launcher.jar -cp out/host/linux-x86/framework/jack.jar com.android.jack.server.JackSimpleServer
target R.java/Manifest.java: libchips (out/target/common/obj/JAVA_LIBRARIES/libchips_intermediates/src/R.stamp)
Building with Jack: out/target/common/obj/JAVA_LIBRARIES/colorpicker_intermediates/classes.jack
warning: string 'done' has no default translation.
Warning: AndroidManifest.xml already defines versionCode (in http://schemas.android.com/apk/res/android); using existing value in manifest.
Warning: AndroidManifest.xml already defines minSdkVersion (in http://schemas.android.com/apk/res/android); using existing value in manifest.
Warning: AndroidManifest.xml already defines targetSdkVersion (in http://schemas.android.com/apk/res/android); using existing value in manifest.
Building with Jack: out/target/common/obj/JAVA_LIBRARIES/android-opt-timezonepicker_intermediates/classes.jack
packages/apps/Calendar/res/values-ar-rXB/arrays.xml:0: warning: Resource file packages/apps/Calendar/res/values-ar-rXB/arrays.xml is skipped as pseudolocalization was done automatically.
packages/apps/Calendar/res/values-ar-rXB/strings.xml:0: warning: Resource file packages/apps/Calendar/res/values-ar-rXB/strings.xml is skipped as pseudolocalization was done automatically.
warning: string 'done' has no default translation.
Warning: AndroidManifest.xml already defines minSdkVersion (in http://schemas.android.com/apk/res/android); using existing value in manifest.
Warning: AndroidManifest.xml already defines targetSdkVersion (in http://schemas.android.com/apk/res/android); using existing value in manifest.
ERROR: /media/speedy/6952930d-d22f-4af1-af0d-650723b7deb3/devel/AOSP/frameworks/opt/timezonepicker/src/com/android/timezonepicker/TimeZoneInfo.java:205: M cannot be resolved or is not a field
make: *** [out/target/common/obj/JAVA_LIBRARIES/android-opt-timezonepicker_intermediates/classes.jack] Error 41
make: *** Waiting for unfinished jobs....
make failed to build some targets (01:39:25 (hh:mm:ss))