• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 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.i18n.timezone;
18 
19 import java.io.File;
20 import java.io.FileInputStream;
21 import java.io.IOException;
22 import java.nio.charset.StandardCharsets;
23 import java.util.Locale;
24 import java.util.regex.Matcher;
25 import java.util.regex.Pattern;
26 
27 /**
28  * Version information associated with the set of time zone data on a device.
29  *
30  * <p>Time Zone Data Sets have a major ({@link #getFormatMajorVersion()}) and minor
31  * ({@link #currentFormatMinorVersion()}) version number:
32  * <ul>
33  *   <li>Major version numbers are mutually incompatible. e.g. v2 is not compatible with a v1 or a
34  *   v3 device.</li>
35  *   <li>Minor version numbers are backwards compatible. e.g. a v2.2 data set will work
36  *   on a v2.1 device but not a v2.3 device. The minor version is reset to 1 when the major version
37  *   is incremented.</li>
38  * </ul>
39  *
40  * <p>Data sets contain time zone rules and other data associated wtih a tzdb release
41  * ({@link #getRulesVersion()}) and an additional Android-specific revision number
42  * ({@link #getRevision()}).
43  *
44  * <p>See platform/system/timezone/README.android for more information.
45  * @hide
46  */
47 @libcore.api.CorePlatformApi
48 public final class TzDataSetVersion {
49 
50     // Remove from CorePlatformApi when all users in platform code are removed. http://b/123398797
51     /**
52      * The name typically given to the {@link TzDataSetVersion} file. See
53      * {@link TzDataSetVersion#readFromFile(File)}.
54      */
55     @libcore.api.CorePlatformApi
56     public static final String DEFAULT_FILE_NAME = "tz_version";
57 
58     /**
59      * The major tz data format version supported by this device.
60      * Increment this for non-backwards compatible changes to the tz data format. Reset the minor
61      * version to 1 when doing so.
62      */
63     // @VisibleForTesting : Keep this inline-able: it is used from CTS tests.
64     // LINT.IfChange
65     public static final int CURRENT_FORMAT_MAJOR_VERSION = 8; // Android V
66     // LINT.ThenChange(external/icu/android_icu4j/src/main/java/android/icu/platform/AndroidDataFiles.java)
67 
68     /**
69      * Returns the major tz data format version supported by this device.
70      */
71     @libcore.api.CorePlatformApi
currentFormatMajorVersion()72     public static int currentFormatMajorVersion() {
73         return CURRENT_FORMAT_MAJOR_VERSION;
74     }
75 
76     /**
77      * The minor tz data format version supported by this device. Increment this for
78      * backwards-compatible changes to the tz data format.
79      */
80     // @VisibleForTesting : Keep this inline-able: it is used from CTS tests.
81     public static final int CURRENT_FORMAT_MINOR_VERSION = 1;
82 
83     /**
84      * Returns the minor tz data format version supported by this device.
85      */
86     @libcore.api.CorePlatformApi
currentFormatMinorVersion()87     public static int currentFormatMinorVersion() {
88         return CURRENT_FORMAT_MINOR_VERSION;
89     }
90 
91     /** The full major + minor tz data format version's length for this device. */
92     // @VisibleForTesting
93     public static final int FORMAT_VERSION_STRING_LENGTH = 7;
94     private static final Pattern FORMAT_VERSION_PATTERN = Pattern.compile("(\\d{3})\\.(\\d{3})");
95 
96     /** A pattern that matches the IANA rules value of a rules update. e.g. "2016g" */
97     private static final Pattern RULES_VERSION_PATTERN = Pattern.compile("(\\d{4}\\w)");
98 
99     private static final int RULES_VERSION_LENGTH = 5;
100 
101     /** A pattern that matches the revision of a rules update. e.g. "001" */
102     private static final Pattern REVISION_PATTERN = Pattern.compile("(\\d{3})");
103 
104     private static final int REVISION_LENGTH = 3;
105 
106     /**
107      * The length of a well-formed tz data set version file:
108      * {Format version}|{Rule version}|{Revision}
109      */
110     private static final int TZ_DATA_VERSION_FILE_LENGTH = FORMAT_VERSION_STRING_LENGTH + 1
111             + RULES_VERSION_LENGTH
112             + 1 + REVISION_LENGTH;
113 
114     private static final Pattern TZ_DATA_VERSION_FILE_PATTERN = Pattern.compile(
115             FORMAT_VERSION_PATTERN.pattern() + "\\|"
116                     + RULES_VERSION_PATTERN.pattern() + "\\|"
117                     + REVISION_PATTERN.pattern()
118                     + ".*" /* ignore trailing */);
119 
120     private final int formatMajorVersion;
121     private final int formatMinorVersion;
122     private final String rulesVersion;
123     private final int revision;
124 
125     @libcore.api.CorePlatformApi
TzDataSetVersion(int formatMajorVersion, int formatMinorVersion, String rulesVersion, int revision)126     public TzDataSetVersion(int formatMajorVersion, int formatMinorVersion, String rulesVersion,
127             int revision) throws TzDataSetException {
128         this.formatMajorVersion = validate3DigitVersion(formatMajorVersion);
129         this.formatMinorVersion = validate3DigitVersion(formatMinorVersion);
130         if (!RULES_VERSION_PATTERN.matcher(rulesVersion).matches()) {
131             throw new TzDataSetException("Invalid rulesVersion: " + rulesVersion);
132         }
133         this.rulesVersion = rulesVersion;
134         this.revision = validate3DigitVersion(revision);
135     }
136 
137     // VisibleForTesting
fromBytes(byte[] bytes)138     public static TzDataSetVersion fromBytes(byte[] bytes) throws TzDataSetException {
139         String tzDataVersion = new String(bytes, StandardCharsets.US_ASCII);
140         try {
141             Matcher matcher = TZ_DATA_VERSION_FILE_PATTERN.matcher(tzDataVersion);
142             if (!matcher.matches()) {
143                 throw new TzDataSetException(
144                         "Invalid tz data version string: \"" + tzDataVersion + "\"");
145             }
146             String formatMajorVersion = matcher.group(1);
147             String formatMinorVersion = matcher.group(2);
148             String rulesVersion = matcher.group(3);
149             String revision = matcher.group(4);
150             return new TzDataSetVersion(
151                     from3DigitVersionString(formatMajorVersion),
152                     from3DigitVersionString(formatMinorVersion),
153                     rulesVersion,
154                     from3DigitVersionString(revision));
155         } catch (IndexOutOfBoundsException e) {
156             // The use of the regexp above should make this impossible.
157             throw new TzDataSetException(
158                     "tz data version string too short: \"" + tzDataVersion + "\"");
159         }
160     }
161 
162     // Remove from CorePlatformApi when all users in platform code are removed. http://b/123398797
163     @libcore.api.CorePlatformApi
readFromFile(File file)164     public static TzDataSetVersion readFromFile(File file) throws IOException, TzDataSetException {
165         byte[] versionBytes = readBytes(file, TzDataSetVersion.TZ_DATA_VERSION_FILE_LENGTH);
166         return fromBytes(versionBytes);
167     }
168 
169     /** Returns the major version number. See {@link TzDataSetVersion}. */
170     @libcore.api.CorePlatformApi
getFormatMajorVersion()171     public int getFormatMajorVersion() {
172         return formatMajorVersion;
173     }
174 
175     /** Returns the minor version number. See {@link TzDataSetVersion}. */
176     @libcore.api.CorePlatformApi
getFormatMinorVersion()177     public int getFormatMinorVersion() {
178         return formatMinorVersion;
179     }
180 
181     /** Returns the tzdb version string. See {@link TzDataSetVersion}. */
182     @libcore.api.CorePlatformApi
getRulesVersion()183     public String getRulesVersion() {
184         return rulesVersion;
185     }
186 
187     // Remove from CorePlatformApi when all users in platform code are removed. http://b/123398797
188     /** Returns the Android revision. See {@link TzDataSetVersion}. */
189     @libcore.api.CorePlatformApi
getRevision()190     public int getRevision() {
191         return revision;
192     }
193 
194     // Remove from CorePlatformApi when all users in platform code are removed. http://b/123398797
195     @libcore.api.CorePlatformApi
toBytes()196     public byte[] toBytes() {
197         return toBytes(formatMajorVersion, formatMinorVersion, rulesVersion, revision);
198     }
199 
toBytes( int majorFormatVersion, int minorFormatVerison, String rulesVersion, int revision)200     private static byte[] toBytes(
201             int majorFormatVersion, int minorFormatVerison, String rulesVersion, int revision) {
202         return (toFormatVersionString(majorFormatVersion, minorFormatVerison)
203                 + "|" + rulesVersion + "|" + to3DigitVersionString(revision))
204                 .getBytes(StandardCharsets.US_ASCII);
205     }
206 
207     @libcore.api.CorePlatformApi
isCompatibleWithThisDevice(TzDataSetVersion tzDataVersion)208     public static boolean isCompatibleWithThisDevice(TzDataSetVersion tzDataVersion) {
209         return (CURRENT_FORMAT_MAJOR_VERSION == tzDataVersion.formatMajorVersion)
210                 && (CURRENT_FORMAT_MINOR_VERSION <= tzDataVersion.formatMinorVersion);
211     }
212 
213     @Override
equals(Object o)214     public boolean equals(Object o) {
215         if (this == o) {
216             return true;
217         }
218         if (o == null || getClass() != o.getClass()) {
219             return false;
220         }
221         TzDataSetVersion that = (TzDataSetVersion) o;
222         if (formatMajorVersion != that.formatMajorVersion) {
223             return false;
224         }
225         if (formatMinorVersion != that.formatMinorVersion) {
226             return false;
227         }
228         if (revision != that.revision) {
229             return false;
230         }
231         return rulesVersion.equals(that.rulesVersion);
232     }
233 
234     @Override
hashCode()235     public int hashCode() {
236         int result = formatMajorVersion;
237         result = 31 * result + formatMinorVersion;
238         result = 31 * result + rulesVersion.hashCode();
239         result = 31 * result + revision;
240         return result;
241     }
242 
243     @Override
toString()244     public String toString() {
245         return "TzDataSetVersion{" +
246                 "formatMajorVersion=" + formatMajorVersion +
247                 ", formatMinorVersion=" + formatMinorVersion +
248                 ", rulesVersion='" + rulesVersion + '\'' +
249                 ", revision=" + revision +
250                 '}';
251     }
252 
253     /**
254      * Returns a version as a zero-padded three-digit String value.
255      */
to3DigitVersionString(int version)256     private static String to3DigitVersionString(int version) {
257         try {
258             return String.format(Locale.ROOT, "%03d", validate3DigitVersion(version));
259         } catch (TzDataSetException e) {
260             throw new IllegalArgumentException(e);
261         }
262     }
263 
264     /**
265      * Validates and parses a zero-padded three-digit String value.
266      */
from3DigitVersionString(String versionString)267     private static int from3DigitVersionString(String versionString) throws TzDataSetException {
268         final String parseErrorMessage = "versionString must be a zero padded, 3 digit, positive"
269                 + " decimal integer";
270         if (versionString.length() != 3) {
271             throw new TzDataSetException(parseErrorMessage);
272         }
273         try {
274             int version = Integer.parseInt(versionString);
275             return validate3DigitVersion(version);
276         } catch (NumberFormatException e) {
277             throw new TzDataSetException(parseErrorMessage, e);
278         }
279     }
280 
validate3DigitVersion(int value)281     private static int validate3DigitVersion(int value) throws TzDataSetException {
282         // 0 is allowed but is reserved for testing.
283         if (value < 0 || value > 999) {
284             throw new TzDataSetException("Expected 0 <= value <= 999, was " + value);
285         }
286         return value;
287     }
288 
289     // @VisibleForTesting
toFormatVersionString(int majorFormatVersion, int minorFormatVersion)290     public static String toFormatVersionString(int majorFormatVersion, int minorFormatVersion) {
291         return to3DigitVersionString(majorFormatVersion)
292                 + "." + to3DigitVersionString(minorFormatVersion);
293     }
294 
295     /**
296      * Reads up to {@code maxBytes} bytes from the specified file. The returned array can be
297      * shorter than {@code maxBytes} if the file is shorter.
298      */
readBytes(File file, int maxBytes)299     private static byte[] readBytes(File file, int maxBytes) throws IOException {
300         if (maxBytes <= 0) {
301             throw new IllegalArgumentException("maxBytes ==" + maxBytes);
302         }
303 
304         try (FileInputStream in = new FileInputStream(file)) {
305             byte[] max = new byte[maxBytes];
306             int bytesRead = in.read(max, 0, maxBytes);
307             byte[] toReturn = new byte[bytesRead];
308             System.arraycopy(max, 0, toReturn, 0, bytesRead);
309             return toReturn;
310         }
311     }
312 
313     /**
314      * A checked exception used in connection with time zone data sets.
315      */
316     @libcore.api.CorePlatformApi
317     public static class TzDataSetException extends Exception {
318 
319         @libcore.api.CorePlatformApi
TzDataSetException(String message)320         public TzDataSetException(String message) {
321             super(message);
322         }
323 
324         @libcore.api.CorePlatformApi
TzDataSetException(String message, Throwable cause)325         public TzDataSetException(String message, Throwable cause) {
326             super(message, cause);
327         }
328     }
329 }
330