• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2017 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.internal.telephony;
18 
19 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
20 
21 import com.android.internal.annotations.VisibleForTesting;
22 import com.android.telephony.Rlog;
23 
24 import java.time.LocalDateTime;
25 import java.time.ZoneOffset;
26 import java.util.Objects;
27 import java.util.TimeZone;
28 import java.util.regex.Pattern;
29 
30 /**
31  * Represents NITZ data. Various static methods are provided to help with parsing and interpretation
32  * of NITZ data.
33  *
34  * {@hide}
35  */
36 @VisibleForTesting(visibility = PACKAGE)
37 public final class NitzData {
38     private static final String LOG_TAG = ServiceStateTracker.LOG_TAG;
39     private static final int MS_PER_QUARTER_HOUR = 15 * 60 * 1000;
40     private static final int MS_PER_HOUR = 60 * 60 * 1000;
41 
42     /* Time stamp after 19 January 2038 is not supported under 32 bit */
43     private static final int MAX_NITZ_YEAR = 2037;
44 
45     private static final Pattern NITZ_SPLIT_PATTERN = Pattern.compile("[/:,+-]");
46 
47     // Stored For logging / debugging only.
48     private final String mOriginalString;
49 
50     private final int mZoneOffset;
51 
52     private final Integer mDstOffset;
53 
54     private final long mCurrentTimeMillis;
55 
56     private final TimeZone mEmulatorHostTimeZone;
57 
NitzData(String originalString, int zoneOffsetMillis, Integer dstOffsetMillis, long utcTimeMillis, TimeZone emulatorHostTimeZone)58     private NitzData(String originalString, int zoneOffsetMillis, Integer dstOffsetMillis,
59             long utcTimeMillis, TimeZone emulatorHostTimeZone) {
60         if (originalString == null) {
61             throw new NullPointerException("originalString==null");
62         }
63         this.mOriginalString = originalString;
64         this.mZoneOffset = zoneOffsetMillis;
65         this.mDstOffset = dstOffsetMillis;
66         this.mCurrentTimeMillis = utcTimeMillis;
67         this.mEmulatorHostTimeZone = emulatorHostTimeZone;
68     }
69 
70     /**
71      * Parses the supplied NITZ string, returning the encoded data.
72      */
parse(String nitz)73     public static NitzData parse(String nitz) {
74         // "yy/mm/dd,hh:mm:ss(+/-)tz[,dt[,tzid]]"
75         // tz, dt are in number of quarter-hours
76 
77         try {
78             String[] nitzSubs = NITZ_SPLIT_PATTERN.split(nitz);
79 
80             int year = 2000 + Integer.parseInt(nitzSubs[0]);
81             if (year > MAX_NITZ_YEAR) {
82                 if (ServiceStateTracker.DBG) {
83                     Rlog.e(LOG_TAG, "NITZ year: " + year + " exceeds limit, skip NITZ time update");
84                 }
85                 return null;
86             }
87 
88             int month = Integer.parseInt(nitzSubs[1]);
89             int date = Integer.parseInt(nitzSubs[2]);
90             int hour = Integer.parseInt(nitzSubs[3]);
91             int minute = Integer.parseInt(nitzSubs[4]);
92             int second = Integer.parseInt(nitzSubs[5]);
93 
94             /* NITZ time (hour:min:sec) will be in UTC but it supplies the timezone
95              * offset as well (which we won't worry about until later) */
96             long epochMillis = LocalDateTime.of(year, month, date, hour, minute, second)
97                     .toInstant(ZoneOffset.UTC)
98                     .toEpochMilli();
99 
100             // The offset received from NITZ is the offset to add to get current local time.
101             boolean sign = (nitz.indexOf('-') == -1);
102             int totalUtcOffsetQuarterHours = Integer.parseInt(nitzSubs[6]);
103             int totalUtcOffsetMillis =
104                     (sign ? 1 : -1) * totalUtcOffsetQuarterHours * MS_PER_QUARTER_HOUR;
105 
106             // DST correction is already applied to the UTC offset. We could subtract it if we
107             // wanted the raw offset.
108             Integer dstAdjustmentHours =
109                     (nitzSubs.length >= 8) ? Integer.parseInt(nitzSubs[7]) : null;
110             Integer dstAdjustmentMillis = null;
111             if (dstAdjustmentHours != null) {
112                 dstAdjustmentMillis = dstAdjustmentHours * MS_PER_HOUR;
113             }
114 
115             // As a special extension, the Android emulator appends the name of
116             // the host computer's timezone to the nitz string. This is zoneinfo
117             // timezone name of the form Area!Location or Area!Location!SubLocation
118             // so we need to convert the ! into /
119             TimeZone zone = null;
120             if (nitzSubs.length >= 9) {
121                 String tzname = nitzSubs[8].replace('!', '/');
122                 zone = TimeZone.getTimeZone(tzname);
123             }
124             return new NitzData(nitz, totalUtcOffsetMillis, dstAdjustmentMillis, epochMillis, zone);
125         } catch (RuntimeException ex) {
126             Rlog.e(LOG_TAG, "NITZ: Parsing NITZ time " + nitz + " ex=" + ex);
127             return null;
128         }
129     }
130 
131     /** A method for use in tests to create NitzData instances. */
createForTests(int zoneOffsetMillis, Integer dstOffsetMillis, long utcTimeMillis, TimeZone emulatorHostTimeZone)132     public static NitzData createForTests(int zoneOffsetMillis, Integer dstOffsetMillis,
133             long utcTimeMillis, TimeZone emulatorHostTimeZone) {
134         return new NitzData("Test data", zoneOffsetMillis, dstOffsetMillis, utcTimeMillis,
135                 emulatorHostTimeZone);
136     }
137 
138     /**
139      * Returns the current time as the number of milliseconds since the beginning of the Unix epoch
140      * (1/1/1970 00:00:00 UTC).
141      */
getCurrentTimeInMillis()142     public long getCurrentTimeInMillis() {
143         return mCurrentTimeMillis;
144     }
145 
146     /**
147      * Returns the total offset to apply to the {@link #getCurrentTimeInMillis()} to arrive at a
148      * local time. NITZ is limited in only being able to express total offsets in multiples of 15
149      * minutes.
150      *
151      * <p>Note that some time zones change offset during the year for reasons other than "daylight
152      * savings", e.g. for Ramadan. This is not well handled by most date / time APIs.
153      */
getLocalOffsetMillis()154     public int getLocalOffsetMillis() {
155         return mZoneOffset;
156     }
157 
158     /**
159      * Returns the offset (already included in {@link #getLocalOffsetMillis()}) associated with
160      * Daylight Savings Time (DST). This field is optional: {@code null} means the DST offset is
161      * unknown. NITZ is limited in only being able to express DST offsets in positive multiples of
162      * one or two hours.
163      *
164      * <p>Callers should remember that standard time / DST is a matter of convention: it has
165      * historically been assumed by NITZ and many date/time APIs that DST happens in the summer and
166      * the "raw" offset will increase during this time, usually by one hour. However, the tzdb
167      * maintainers have moved to different conventions on a country-by-country basis so that some
168      * summer times are considered the "standard" time (i.e. in this model winter time is the "DST"
169      * and a negative adjustment, usually of (negative) one hour.
170      *
171      * <p>There is nothing that says NITZ and tzdb need to treat DST conventions the same.
172      *
173      * <p>At the time of writing Android date/time APIs are sticking with the historic tzdb
174      * convention that DST is used in summer time and is <em>always</em> a positive offset but this
175      * could change in future. If Android or carriers change the conventions used then it might make
176      * NITZ comparisons with tzdb information more error-prone.
177      *
178      * <p>See also {@link #getLocalOffsetMillis()} for other reasons besides DST that a local offset
179      * may change.
180      */
getDstAdjustmentMillis()181     public Integer getDstAdjustmentMillis() {
182         return mDstOffset;
183     }
184 
185     /**
186      * Returns {@link true} if the time is in Daylight Savings Time (DST), {@link false} if it is
187      * unknown or not in DST. See {@link #getDstAdjustmentMillis()}.
188      */
isDst()189     public boolean isDst() {
190         return mDstOffset != null && mDstOffset != 0;
191     }
192 
193 
194     /**
195      * Returns the time zone of the host computer when Android is running in an emulator. It is
196      * {@code null} for real devices. This information is communicated via a non-standard Android
197      * extension to NITZ.
198      */
getEmulatorHostTimeZone()199     public TimeZone getEmulatorHostTimeZone() {
200         return mEmulatorHostTimeZone;
201     }
202 
203     @Override
equals(Object o)204     public boolean equals(Object o) {
205         if (this == o) {
206             return true;
207         }
208         if (o == null || getClass() != o.getClass()) {
209             return false;
210         }
211 
212         NitzData nitzData = (NitzData) o;
213 
214         if (mZoneOffset != nitzData.mZoneOffset) {
215             return false;
216         }
217         if (mCurrentTimeMillis != nitzData.mCurrentTimeMillis) {
218             return false;
219         }
220         if (!mOriginalString.equals(nitzData.mOriginalString)) {
221             return false;
222         }
223         if (!Objects.equals(mDstOffset, nitzData.mDstOffset)) {
224             return false;
225         }
226         return Objects.equals(mEmulatorHostTimeZone, nitzData.mEmulatorHostTimeZone);
227     }
228 
229     @Override
hashCode()230     public int hashCode() {
231         int result = mOriginalString.hashCode();
232         result = 31 * result + mZoneOffset;
233         result = 31 * result + (mDstOffset != null ? mDstOffset.hashCode() : 0);
234         result = 31 * result + Long.hashCode(mCurrentTimeMillis);
235         result = 31 * result + (mEmulatorHostTimeZone != null ? mEmulatorHostTimeZone.hashCode()
236                 : 0);
237         return result;
238     }
239 
240     @Override
toString()241     public String toString() {
242         return "NitzData{"
243                 + "mOriginalString=" + mOriginalString
244                 + ", mZoneOffset=" + mZoneOffset
245                 + ", mDstOffset=" + mDstOffset
246                 + ", mCurrentTimeMillis=" + mCurrentTimeMillis
247                 + ", mEmulatorHostTimeZone=" + mEmulatorHostTimeZone
248                 + '}';
249     }
250 }
251