330 lines
12 KiB
Java
330 lines
12 KiB
Java
/*
|
|
* Copyright (C) 2016 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.i18n.timezone;
|
|
|
|
import java.io.File;
|
|
import java.io.FileInputStream;
|
|
import java.io.IOException;
|
|
import java.nio.charset.StandardCharsets;
|
|
import java.util.Locale;
|
|
import java.util.regex.Matcher;
|
|
import java.util.regex.Pattern;
|
|
|
|
/**
|
|
* Version information associated with the set of time zone data on a device.
|
|
*
|
|
* <p>Time Zone Data Sets have a major ({@link #getFormatMajorVersion()}) and minor
|
|
* ({@link #currentFormatMinorVersion()}) version number:
|
|
* <ul>
|
|
* <li>Major version numbers are mutually incompatible. e.g. v2 is not compatible with a v1 or a
|
|
* v3 device.</li>
|
|
* <li>Minor version numbers are backwards compatible. e.g. a v2.2 data set will work
|
|
* on a v2.1 device but not a v2.3 device. The minor version is reset to 1 when the major version
|
|
* is incremented.</li>
|
|
* </ul>
|
|
*
|
|
* <p>Data sets contain time zone rules and other data associated wtih a tzdb release
|
|
* ({@link #getRulesVersion()}) and an additional Android-specific revision number
|
|
* ({@link #getRevision()}).
|
|
*
|
|
* <p>See platform/system/timezone/README.android for more information.
|
|
* @hide
|
|
*/
|
|
@libcore.api.CorePlatformApi
|
|
public final class TzDataSetVersion {
|
|
|
|
// Remove from CorePlatformApi when all users in platform code are removed. http://b/123398797
|
|
/**
|
|
* The name typically given to the {@link TzDataSetVersion} file. See
|
|
* {@link TzDataSetVersion#readFromFile(File)}.
|
|
*/
|
|
@libcore.api.CorePlatformApi
|
|
public static final String DEFAULT_FILE_NAME = "tz_version";
|
|
|
|
/**
|
|
* The major tz data format version supported by this device.
|
|
* Increment this for non-backwards compatible changes to the tz data format. Reset the minor
|
|
* version to 1 when doing so.
|
|
*/
|
|
// @VisibleForTesting : Keep this inline-able: it is used from CTS tests.
|
|
public static final int CURRENT_FORMAT_MAJOR_VERSION = 8; // Android V
|
|
|
|
/**
|
|
* Returns the major tz data format version supported by this device.
|
|
*/
|
|
@libcore.api.CorePlatformApi
|
|
public static int currentFormatMajorVersion() {
|
|
return CURRENT_FORMAT_MAJOR_VERSION;
|
|
}
|
|
|
|
/**
|
|
* The minor tz data format version supported by this device. Increment this for
|
|
* backwards-compatible changes to the tz data format.
|
|
*/
|
|
// @VisibleForTesting : Keep this inline-able: it is used from CTS tests.
|
|
public static final int CURRENT_FORMAT_MINOR_VERSION = 1;
|
|
|
|
/**
|
|
* Returns the minor tz data format version supported by this device.
|
|
*/
|
|
@libcore.api.CorePlatformApi
|
|
public static int currentFormatMinorVersion() {
|
|
return CURRENT_FORMAT_MINOR_VERSION;
|
|
}
|
|
|
|
/** The full major + minor tz data format version for this device. */
|
|
private static final String FULL_CURRENT_FORMAT_VERSION_STRING =
|
|
toFormatVersionString(CURRENT_FORMAT_MAJOR_VERSION, CURRENT_FORMAT_MINOR_VERSION);
|
|
|
|
private static final int FORMAT_VERSION_STRING_LENGTH =
|
|
FULL_CURRENT_FORMAT_VERSION_STRING.length();
|
|
private static final Pattern FORMAT_VERSION_PATTERN = Pattern.compile("(\\d{3})\\.(\\d{3})");
|
|
|
|
/** A pattern that matches the IANA rules value of a rules update. e.g. "2016g" */
|
|
private static final Pattern RULES_VERSION_PATTERN = Pattern.compile("(\\d{4}\\w)");
|
|
|
|
private static final int RULES_VERSION_LENGTH = 5;
|
|
|
|
/** A pattern that matches the revision of a rules update. e.g. "001" */
|
|
private static final Pattern REVISION_PATTERN = Pattern.compile("(\\d{3})");
|
|
|
|
private static final int REVISION_LENGTH = 3;
|
|
|
|
/**
|
|
* The length of a well-formed tz data set version file:
|
|
* {Format version}|{Rule version}|{Revision}
|
|
*/
|
|
private static final int TZ_DATA_VERSION_FILE_LENGTH = FORMAT_VERSION_STRING_LENGTH + 1
|
|
+ RULES_VERSION_LENGTH
|
|
+ 1 + REVISION_LENGTH;
|
|
|
|
private static final Pattern TZ_DATA_VERSION_FILE_PATTERN = Pattern.compile(
|
|
FORMAT_VERSION_PATTERN.pattern() + "\\|"
|
|
+ RULES_VERSION_PATTERN.pattern() + "\\|"
|
|
+ REVISION_PATTERN.pattern()
|
|
+ ".*" /* ignore trailing */);
|
|
|
|
private final int formatMajorVersion;
|
|
private final int formatMinorVersion;
|
|
private final String rulesVersion;
|
|
private final int revision;
|
|
|
|
@libcore.api.CorePlatformApi
|
|
public TzDataSetVersion(int formatMajorVersion, int formatMinorVersion, String rulesVersion,
|
|
int revision) throws TzDataSetException {
|
|
this.formatMajorVersion = validate3DigitVersion(formatMajorVersion);
|
|
this.formatMinorVersion = validate3DigitVersion(formatMinorVersion);
|
|
if (!RULES_VERSION_PATTERN.matcher(rulesVersion).matches()) {
|
|
throw new TzDataSetException("Invalid rulesVersion: " + rulesVersion);
|
|
}
|
|
this.rulesVersion = rulesVersion;
|
|
this.revision = validate3DigitVersion(revision);
|
|
}
|
|
|
|
// VisibleForTesting
|
|
public static TzDataSetVersion fromBytes(byte[] bytes) throws TzDataSetException {
|
|
String tzDataVersion = new String(bytes, StandardCharsets.US_ASCII);
|
|
try {
|
|
Matcher matcher = TZ_DATA_VERSION_FILE_PATTERN.matcher(tzDataVersion);
|
|
if (!matcher.matches()) {
|
|
throw new TzDataSetException(
|
|
"Invalid tz data version string: \"" + tzDataVersion + "\"");
|
|
}
|
|
String formatMajorVersion = matcher.group(1);
|
|
String formatMinorVersion = matcher.group(2);
|
|
String rulesVersion = matcher.group(3);
|
|
String revision = matcher.group(4);
|
|
return new TzDataSetVersion(
|
|
from3DigitVersionString(formatMajorVersion),
|
|
from3DigitVersionString(formatMinorVersion),
|
|
rulesVersion,
|
|
from3DigitVersionString(revision));
|
|
} catch (IndexOutOfBoundsException e) {
|
|
// The use of the regexp above should make this impossible.
|
|
throw new TzDataSetException(
|
|
"tz data version string too short: \"" + tzDataVersion + "\"");
|
|
}
|
|
}
|
|
|
|
// Remove from CorePlatformApi when all users in platform code are removed. http://b/123398797
|
|
@libcore.api.CorePlatformApi
|
|
public static TzDataSetVersion readFromFile(File file) throws IOException, TzDataSetException {
|
|
byte[] versionBytes = readBytes(file, TzDataSetVersion.TZ_DATA_VERSION_FILE_LENGTH);
|
|
return fromBytes(versionBytes);
|
|
}
|
|
|
|
/** Returns the major version number. See {@link TzDataSetVersion}. */
|
|
@libcore.api.CorePlatformApi
|
|
public int getFormatMajorVersion() {
|
|
return formatMajorVersion;
|
|
}
|
|
|
|
/** Returns the minor version number. See {@link TzDataSetVersion}. */
|
|
@libcore.api.CorePlatformApi
|
|
public int getFormatMinorVersion() {
|
|
return formatMinorVersion;
|
|
}
|
|
|
|
/** Returns the tzdb version string. See {@link TzDataSetVersion}. */
|
|
@libcore.api.CorePlatformApi
|
|
public String getRulesVersion() {
|
|
return rulesVersion;
|
|
}
|
|
|
|
// Remove from CorePlatformApi when all users in platform code are removed. http://b/123398797
|
|
/** Returns the Android revision. See {@link TzDataSetVersion}. */
|
|
@libcore.api.CorePlatformApi
|
|
public int getRevision() {
|
|
return revision;
|
|
}
|
|
|
|
// Remove from CorePlatformApi when all users in platform code are removed. http://b/123398797
|
|
@libcore.api.CorePlatformApi
|
|
public byte[] toBytes() {
|
|
return toBytes(formatMajorVersion, formatMinorVersion, rulesVersion, revision);
|
|
}
|
|
|
|
private static byte[] toBytes(
|
|
int majorFormatVersion, int minorFormatVerison, String rulesVersion, int revision) {
|
|
return (toFormatVersionString(majorFormatVersion, minorFormatVerison)
|
|
+ "|" + rulesVersion + "|" + to3DigitVersionString(revision))
|
|
.getBytes(StandardCharsets.US_ASCII);
|
|
}
|
|
|
|
@libcore.api.CorePlatformApi
|
|
public static boolean isCompatibleWithThisDevice(TzDataSetVersion tzDataVersion) {
|
|
return (CURRENT_FORMAT_MAJOR_VERSION == tzDataVersion.formatMajorVersion)
|
|
&& (CURRENT_FORMAT_MINOR_VERSION <= tzDataVersion.formatMinorVersion);
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object o) {
|
|
if (this == o) {
|
|
return true;
|
|
}
|
|
if (o == null || getClass() != o.getClass()) {
|
|
return false;
|
|
}
|
|
TzDataSetVersion that = (TzDataSetVersion) o;
|
|
if (formatMajorVersion != that.formatMajorVersion) {
|
|
return false;
|
|
}
|
|
if (formatMinorVersion != that.formatMinorVersion) {
|
|
return false;
|
|
}
|
|
if (revision != that.revision) {
|
|
return false;
|
|
}
|
|
return rulesVersion.equals(that.rulesVersion);
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
int result = formatMajorVersion;
|
|
result = 31 * result + formatMinorVersion;
|
|
result = 31 * result + rulesVersion.hashCode();
|
|
result = 31 * result + revision;
|
|
return result;
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return "TzDataSetVersion{" +
|
|
"formatMajorVersion=" + formatMajorVersion +
|
|
", formatMinorVersion=" + formatMinorVersion +
|
|
", rulesVersion='" + rulesVersion + '\'' +
|
|
", revision=" + revision +
|
|
'}';
|
|
}
|
|
|
|
/**
|
|
* Returns a version as a zero-padded three-digit String value.
|
|
*/
|
|
private static String to3DigitVersionString(int version) {
|
|
try {
|
|
return String.format(Locale.ROOT, "%03d", validate3DigitVersion(version));
|
|
} catch (TzDataSetException e) {
|
|
throw new IllegalArgumentException(e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Validates and parses a zero-padded three-digit String value.
|
|
*/
|
|
private static int from3DigitVersionString(String versionString) throws TzDataSetException {
|
|
final String parseErrorMessage = "versionString must be a zero padded, 3 digit, positive"
|
|
+ " decimal integer";
|
|
if (versionString.length() != 3) {
|
|
throw new TzDataSetException(parseErrorMessage);
|
|
}
|
|
try {
|
|
int version = Integer.parseInt(versionString);
|
|
return validate3DigitVersion(version);
|
|
} catch (NumberFormatException e) {
|
|
throw new TzDataSetException(parseErrorMessage, e);
|
|
}
|
|
}
|
|
|
|
private static int validate3DigitVersion(int value) throws TzDataSetException {
|
|
// 0 is allowed but is reserved for testing.
|
|
if (value < 0 || value > 999) {
|
|
throw new TzDataSetException("Expected 0 <= value <= 999, was " + value);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
private static String toFormatVersionString(int majorFormatVersion, int minorFormatVersion) {
|
|
return to3DigitVersionString(majorFormatVersion)
|
|
+ "." + to3DigitVersionString(minorFormatVersion);
|
|
}
|
|
|
|
/**
|
|
* Reads up to {@code maxBytes} bytes from the specified file. The returned array can be
|
|
* shorter than {@code maxBytes} if the file is shorter.
|
|
*/
|
|
private static byte[] readBytes(File file, int maxBytes) throws IOException {
|
|
if (maxBytes <= 0) {
|
|
throw new IllegalArgumentException("maxBytes ==" + maxBytes);
|
|
}
|
|
|
|
try (FileInputStream in = new FileInputStream(file)) {
|
|
byte[] max = new byte[maxBytes];
|
|
int bytesRead = in.read(max, 0, maxBytes);
|
|
byte[] toReturn = new byte[bytesRead];
|
|
System.arraycopy(max, 0, toReturn, 0, bytesRead);
|
|
return toReturn;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A checked exception used in connection with time zone data sets.
|
|
*/
|
|
@libcore.api.CorePlatformApi
|
|
public static class TzDataSetException extends Exception {
|
|
|
|
@libcore.api.CorePlatformApi
|
|
public TzDataSetException(String message) {
|
|
super(message);
|
|
}
|
|
|
|
@libcore.api.CorePlatformApi
|
|
public TzDataSetException(String message, Throwable cause) {
|
|
super(message, cause);
|
|
}
|
|
}
|
|
}
|