• 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 
17 package com.android.settings.datetime.timezone;
18 
19 import android.content.Context;
20 import android.icu.text.DateFormat;
21 import android.icu.text.DisplayContext;
22 import android.icu.text.SimpleDateFormat;
23 import android.icu.util.Calendar;
24 import android.icu.util.TimeZone;
25 
26 import androidx.annotation.VisibleForTesting;
27 
28 import com.android.settings.R;
29 import com.android.settings.core.BasePreferenceController;
30 
31 import java.time.Instant;
32 import java.time.zone.ZoneOffsetTransition;
33 import java.time.zone.ZoneRules;
34 import java.util.Date;
35 
36 public class TimeZoneInfoPreferenceController extends BasePreferenceController {
37 
38     @VisibleForTesting
39     Date mDate;
40     private TimeZoneInfo mTimeZoneInfo;
41     private final DateFormat mDateFormat;
42 
TimeZoneInfoPreferenceController(Context context, String key)43     public TimeZoneInfoPreferenceController(Context context, String key) {
44         super(context, key);
45         mDateFormat = DateFormat.getDateInstance(SimpleDateFormat.LONG);
46         mDateFormat.setContext(DisplayContext.CAPITALIZATION_NONE);
47         mDate = new Date();
48     }
49 
50     @Override
getAvailabilityStatus()51     public int getAvailabilityStatus() {
52         return mTimeZoneInfo != null ? AVAILABLE_UNSEARCHABLE : UNSUPPORTED_ON_DEVICE;
53     }
54 
55     @Override
getSummary()56     public CharSequence getSummary() {
57         return mTimeZoneInfo == null ? "" : formatInfo(mTimeZoneInfo);
58     }
59 
setTimeZoneInfo(TimeZoneInfo timeZoneInfo)60     public void setTimeZoneInfo(TimeZoneInfo timeZoneInfo) {
61         mTimeZoneInfo = timeZoneInfo;
62     }
63 
formatOffsetAndName(TimeZoneInfo item)64     private CharSequence formatOffsetAndName(TimeZoneInfo item) {
65         String name = item.getGenericName();
66         if (name == null) {
67             if (item.getTimeZone().inDaylightTime(mDate)) {
68                 name = item.getDaylightName();
69             } else {
70                 name = item.getStandardName();
71             }
72         }
73         if (name == null) {
74             return item.getGmtOffset().toString();
75         } else {
76             return SpannableUtil.getResourcesText(mContext.getResources(),
77                     R.string.zone_info_offset_and_name, item.getGmtOffset(),
78                     name);
79         }
80     }
81 
formatInfo(TimeZoneInfo item)82     private CharSequence formatInfo(TimeZoneInfo item) {
83         final CharSequence offsetAndName = formatOffsetAndName(item);
84         final TimeZone timeZone = item.getTimeZone();
85         if (!timeZone.observesDaylightTime()) {
86             return mContext.getString(R.string.zone_info_footer_no_dst, offsetAndName);
87         }
88 
89         final ZoneOffsetTransition nextDstTransition = findNextDstTransition(item);
90         if (nextDstTransition == null) { // No future transition
91             return mContext.getString(R.string.zone_info_footer_no_dst, offsetAndName);
92         }
93         final boolean toDst = getDSTSavings(timeZone, nextDstTransition.getInstant()) != 0;
94         String timeType = toDst ? item.getDaylightName() : item.getStandardName();
95         if (timeType == null) {
96             // Fall back to generic "summer time" and "standard time" if the time zone has no
97             // specific names.
98             timeType = toDst ?
99                     mContext.getString(R.string.zone_time_type_dst) :
100                     mContext.getString(R.string.zone_time_type_standard);
101 
102         }
103         final Calendar transitionTime = Calendar.getInstance(timeZone);
104         transitionTime.setTimeInMillis(nextDstTransition.getInstant().toEpochMilli());
105         final String date = mDateFormat.format(transitionTime);
106         return SpannableUtil.getResourcesText(mContext.getResources(),
107                 R.string.zone_info_footer, offsetAndName, timeType, date);
108     }
109 
findNextDstTransition(TimeZoneInfo timeZoneInfo)110     private ZoneOffsetTransition findNextDstTransition(TimeZoneInfo timeZoneInfo) {
111         TimeZone timeZone = timeZoneInfo.getTimeZone();
112         ZoneRules zoneRules = timeZoneInfo.getJavaTimeZone().toZoneId().getRules();
113 
114         Instant from = mDate.toInstant();
115 
116         ZoneOffsetTransition transition;
117         while (true) { // Find next transition with different DST offsets
118             transition = zoneRules.nextTransition(from);
119             if (transition == null) {
120                 break;
121             }
122             Instant to = transition.getInstant();
123             if (getDSTSavings(timeZone, from) != getDSTSavings(timeZone, to)) {
124                 break;
125             }
126             from = to;
127         }
128 
129         return transition;
130     }
131 
getDSTSavings(TimeZone timeZone, Instant instant)132     private static int getDSTSavings(TimeZone timeZone, Instant instant) {
133         int[] offsets = new int[2];
134         timeZone.getOffset(instant.toEpochMilli(), false /* local time */, offsets);
135         return offsets[1];
136     }
137 }
138