/*
* Copyright (c) 2000, 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.util.calendar;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.ref.SoftReference;
import java.nio.file.FileSystems;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
ZoneInfoFile
reads Zone information files in the <java.home>/lib/zi directory and provides time zone information in the form of a ZoneInfo
object. Also, it reads the ZoneInfoMappings file to obtain time zone IDs information that is used by the ZoneInfo
class. The directory layout and data file formats are as follows. Directory layout
All zone data files and ZoneInfoMappings are put under the
<java.home>/lib/zi directory. A path name for a given time
zone ID is a concatenation of <java.home>/lib/zi/ and the
time zone ID. (The file separator is replaced with the platform
dependent value. e.g., '\' for Win32.) An example layout will look
like as follows.
<java.home>/lib/zi/Africa/Addis_Ababa
/Africa/Dakar
/America/Los_Angeles
/Asia/Singapore
/EET
/Europe/Oslo
/GMT
/Pacific/Galapagos
...
/ZoneInfoMappings
A zone data file has specific information of each zone.
ZoneInfoMappings
has global information of zone IDs so
that the information can be obtained without instantiating all time
zones.
File format
Two binary-file formats based on a simple Tag-Length-Value format are used
to describe TimeZone information. The generic format of a data file is:
DataFile {
u1 magic[7];
u1 version;
data_item data[];
}
where magic
is a magic number identifying a file
format, version
is the format version number, and
data
is one or more data_item
s. The
data_item
structure is:
data_item {
u1 tag;
u2 length;
u1 value[length];
}
where tag
indicates the data type of the item,
length
is a byte count of the following
value
that is the content of item data.
All data is stored in the big-endian order. There is no boundary
alignment between date items.
1. ZoneInfo data file
Each ZoneInfo data file consists of the following members.
ZoneInfoDataFile {
u1 magic[7];
u1 version;
SET OF1 {
transition transitions2;
offset_table offsets2;
simpletimezone stzparams2;
raw_offset rawoffset;
dstsaving dst;
checksum crc32;
gmtoffsetwillchange gmtflag2;
}
}
1: an unordered collection of zero or one occurrences of each item
2: optional item
magic
is a byte-string constant identifying the
ZoneInfo data file. This field must be "javazi\0"
defined as JAVAZI_LABEL
.
version
is the version number of the file format. This
will be used for compatibility check. This field must be
0x01
in this version.
transition
, offset_table
and
simpletimezone
have information of time transition
from the past to the future. Therefore, these structures don't
exist if the zone didn't change zone names and haven't applied DST in
the past, and haven't planned to apply it. (e.g. Asia/Tokyo zone)
raw_offset
, dstsaving
and checksum
exist in every zoneinfo file. They are used by TimeZone.class indirectly.
1.1 transition
structure
transition {
u1 tag; // 0x04 : constant
u2 length; // byte length of whole values
s8 value[length/8]; // transitions in `long'
}
See ZoneInfo.transitions
about the value. 1.2 offset_table
structure
offset_table {
u1 tag; // 0x05 : constant
u2 length; // byte length of whole values
s4 value[length/4]; // offset values in `int'
}
1.3 simpletimezone
structure
See ZoneInfo.simpleTimeZoneParams
about the value.
simpletimezone {
u1 tag; // 0x06 : constant
u2 length; // byte length of whole values
s4 value[length/4]; // SimpleTimeZone parameters
}
See ZoneInfo.offsets
about the value. 1.4 raw_offset
structure
raw_offset {
u1 tag; // 0x01 : constant
u2 length; // must be 4.
s4 value; // raw GMT offset [millisecond]
}
See ZoneInfo.rawOffset
about the value. 1.5 dstsaving
structure
Value has dstSaving in seconds.
dstsaving {
u1 tag; // 0x02 : constant
u2 length; // must be 2.
s2 value; // DST save value [second]
}
See ZoneInfo.dstSavings
about value. 1.6 checksum
structure
checksum {
u1 tag; // 0x03 : constant
u2 length; // must be 4.
s4 value; // CRC32 value of transitions
}
See ZoneInfo.checksum
. 1.7 gmtoffsetwillchange
structure
This record has a flag value for ZoneInfo.rawOffsetWillChange
. If this record is not present in a zoneinfo file, 0 is assumed for the value.
gmtoffsetwillchange {
u1 tag; // 0x07 : constant
u2 length; // must be 1.
u1 value; // 1: if the GMT raw offset will change
// in the future, 0, otherwise.
}
2. ZoneInfoMappings file
The ZoneInfoMappings file consists of the following members.
ZoneInfoMappings {
u1 magic[7];
u1 version;
SET OF {
versionName version;
zone_id_table zoneIDs;
raw_offset_table rawoffsets;
raw_offset_index_table rawoffsetindices;
alias_table aliases;
excluded_list excludedList;
}
}
magic
is a byte-string constant which has the file type.
This field must be "javazm\0"
defined as JAVAZM_LABEL
.
version
is the version number of this file
format. This will be used for compatibility check. This field must
be 0x01
in this version.
versionName
shows which version of Olson's data has been used
to generate this ZoneInfoMappings. (e.g. tzdata2000g
)
This field is for trouble-shooting and isn't usually used in runtime.
zone_id_table
, raw_offset_index_table
and
alias_table
are general information of supported
zones.
2.1 zone_id_table
structure
The list of zone IDs included in the zi database. The list does
not include zone IDs, if any, listed in excludedList.
zone_id_table {
u1 tag; // 0x40 : constant
u2 length; // byte length of whole values
u2 zone_id_count;
zone_id value[zone_id_count];
}
zone_id {
u1 byte_length; // byte length of id
u1 id[byte_length]; // zone name string
}
2.2 raw_offset_table
structure
raw_offset_table {
u1 tag; // 0x41 : constant
u2 length; // byte length of whole values
s4 value[length/4]; // raw GMT offset in milliseconds
}
2.3 raw_offset_index_table
structure
raw_offset_index_table {
u1 tag; // 0x42 : constant
u2 length; // byte length of whole values
u1 value[length];
}
2.4 alias_table
structure
alias_table {
u1 tag; // 0x43 : constant
u2 length; // byte length of whole values
u2 nentries; // number of id-pairs
id_pair value[nentries];
}
id_pair {
zone_id aliasname;
zone_id ID;
}
2.5 versionName
structure
versionName {
u1 tag; // 0x44 : constant
u2 length; // byte length of whole values
u1 value[length];
}
2.6 excludeList
structure
The list of zone IDs whose zones will change their GMT offsets
(a.k.a. raw offsets) some time in the future. Those IDs must be
added to the list of zone IDs for getAvailableIDs(). Also they must
be examined for getAvailableIDs(int) to determine the
current GMT offsets.
excluded_list {
u1 tag; // 0x45 : constant
u2 length; // byte length of whole values
u2 nentries; // number of zone_ids
zone_id value[nentries]; // excluded zone IDs
}
Since: 1.4
/**
* <code>ZoneInfoFile</code> reads Zone information files in the
* <java.home>/lib/zi directory and provides time zone
* information in the form of a {@link ZoneInfo} object. Also, it
* reads the ZoneInfoMappings file to obtain time zone IDs information
* that is used by the {@link ZoneInfo} class. The directory layout
* and data file formats are as follows.
*
* <p><strong>Directory layout</strong><p>
*
* All zone data files and ZoneInfoMappings are put under the
* <java.home>/lib/zi directory. A path name for a given time
* zone ID is a concatenation of <java.home>/lib/zi/ and the
* time zone ID. (The file separator is replaced with the platform
* dependent value. e.g., '\' for Win32.) An example layout will look
* like as follows.
* <blockquote>
* <pre>
* <java.home>/lib/zi/Africa/Addis_Ababa
* /Africa/Dakar
* /America/Los_Angeles
* /Asia/Singapore
* /EET
* /Europe/Oslo
* /GMT
* /Pacific/Galapagos
* ...
* /ZoneInfoMappings
* </pre>
* </blockquote>
*
* A zone data file has specific information of each zone.
* <code>ZoneInfoMappings</code> has global information of zone IDs so
* that the information can be obtained without instantiating all time
* zones.
*
* <p><strong>File format</strong><p>
*
* Two binary-file formats based on a simple Tag-Length-Value format are used
* to describe TimeZone information. The generic format of a data file is:
* <blockquote>
* <pre>
* DataFile {
* u1 magic[7];
* u1 version;
* data_item data[];
* }
* </pre>
* </blockquote>
* where <code>magic</code> is a magic number identifying a file
* format, <code>version</code> is the format version number, and
* <code>data</code> is one or more <code>data_item</code>s. The
* <code>data_item</code> structure is:
* <blockquote>
* <pre>
* data_item {
* u1 tag;
* u2 length;
* u1 value[length];
* }
* </pre>
* </blockquote>
* where <code>tag</code> indicates the data type of the item,
* <code>length</code> is a byte count of the following
* <code>value</code> that is the content of item data.
* <p>
* All data is stored in the big-endian order. There is no boundary
* alignment between date items.
*
* <p><strong>1. ZoneInfo data file</strong><p>
*
* Each ZoneInfo data file consists of the following members.
* <br>
* <blockquote>
* <pre>
* ZoneInfoDataFile {
* u1 magic[7];
* u1 version;
* SET OF<sup>1</sup> {
* transition transitions<sup>2</sup>;
* offset_table offsets<sup>2</sup>;
* simpletimezone stzparams<sup>2</sup>;
* raw_offset rawoffset;
* dstsaving dst;
* checksum crc32;
* gmtoffsetwillchange gmtflag<sup>2</sup>;
* }
* }
* 1: an unordered collection of zero or one occurrences of each item
* 2: optional item
* </pre>
* </blockquote>
* <code>magic</code> is a byte-string constant identifying the
* ZoneInfo data file. This field must be <code>"javazi\0"</code>
* defined as {@link #JAVAZI_LABEL}.
* <p>
* <code>version</code> is the version number of the file format. This
* will be used for compatibility check. This field must be
* <code>0x01</code> in this version.
* <p>
* <code>transition</code>, <code>offset_table</code> and
* <code>simpletimezone</code> have information of time transition
* from the past to the future. Therefore, these structures don't
* exist if the zone didn't change zone names and haven't applied DST in
* the past, and haven't planned to apply it. (e.g. Asia/Tokyo zone)
* <p>
* <code>raw_offset</code>, <code>dstsaving</code> and <code>checksum</code>
* exist in every zoneinfo file. They are used by TimeZone.class indirectly.
*
* <p><strong>1.1 <code>transition</code> structure</strong><p><a name="transition"></a>
* <blockquote>
* <pre>
* transition {
* u1 tag; // 0x04 : constant
* u2 length; // byte length of whole values
* s8 value[length/8]; // transitions in `long'
* }
* </pre>
* </blockquote>
* See {@link ZoneInfo#transitions ZoneInfo.transitions} about the value.
*
* <p><strong>1.2 <code>offset_table</code> structure</strong><p>
* <blockquote>
* <pre>
* offset_table {
* u1 tag; // 0x05 : constant
* u2 length; // byte length of whole values
* s4 value[length/4]; // offset values in `int'
* }
* </pre>
* </blockquote>
*
* <p><strong>1.3 <code>simpletimezone</code> structure</strong><p>
* See {@link ZoneInfo#simpleTimeZoneParams ZoneInfo.simpleTimeZoneParams}
* about the value.
* <blockquote>
* <pre>
* simpletimezone {
* u1 tag; // 0x06 : constant
* u2 length; // byte length of whole values
* s4 value[length/4]; // SimpleTimeZone parameters
* }
* </pre>
* </blockquote>
* See {@link ZoneInfo#offsets ZoneInfo.offsets} about the value.
*
* <p><strong>1.4 <code>raw_offset</code> structure</strong><p>
* <blockquote>
* <pre>
* raw_offset {
* u1 tag; // 0x01 : constant
* u2 length; // must be 4.
* s4 value; // raw GMT offset [millisecond]
* }
* </pre>
* </blockquote>
* See {@link ZoneInfo#rawOffset ZoneInfo.rawOffset} about the value.
*
* <p><strong>1.5 <code>dstsaving</code> structure</strong><p>
* Value has dstSaving in seconds.
* <blockquote>
* <pre>
* dstsaving {
* u1 tag; // 0x02 : constant
* u2 length; // must be 2.
* s2 value; // DST save value [second]
* }
* </pre>
* </blockquote>
* See {@link ZoneInfo#dstSavings ZoneInfo.dstSavings} about value.
*
* <p><strong>1.6 <code>checksum</code> structure</strong><p>
* <blockquote>
* <pre>
* checksum {
* u1 tag; // 0x03 : constant
* u2 length; // must be 4.
* s4 value; // CRC32 value of transitions
* }
* </pre>
* </blockquote>
* See {@link ZoneInfo#checksum ZoneInfo.checksum}.
*
* <p><strong>1.7 <code>gmtoffsetwillchange</code> structure</strong><p>
* This record has a flag value for {@link ZoneInfo#rawOffsetWillChange}.
* If this record is not present in a zoneinfo file, 0 is assumed for
* the value.
* <blockquote>
* <pre>
* gmtoffsetwillchange {
* u1 tag; // 0x07 : constant
* u2 length; // must be 1.
* u1 value; // 1: if the GMT raw offset will change
* // in the future, 0, otherwise.
* }
* </pre>
* </blockquote>
*
*
* <p><strong>2. ZoneInfoMappings file</strong><p>
*
* The ZoneInfoMappings file consists of the following members.
* <br>
* <blockquote>
* <pre>
* ZoneInfoMappings {
* u1 magic[7];
* u1 version;
* SET OF {
* versionName version;
* zone_id_table zoneIDs;
* raw_offset_table rawoffsets;
* raw_offset_index_table rawoffsetindices;
* alias_table aliases;
* excluded_list excludedList;
* }
* }
* </pre>
* </blockquote>
*
* <code>magic</code> is a byte-string constant which has the file type.
* This field must be <code>"javazm\0"</code> defined as {@link #JAVAZM_LABEL}.
* <p>
* <code>version</code> is the version number of this file
* format. This will be used for compatibility check. This field must
* be <code>0x01</code> in this version.
* <p>
* <code>versionName</code> shows which version of Olson's data has been used
* to generate this ZoneInfoMappings. (e.g. <code>tzdata2000g</code>) <br>
* This field is for trouble-shooting and isn't usually used in runtime.
* <p>
* <code>zone_id_table</code>, <code>raw_offset_index_table</code> and
* <code>alias_table</code> are general information of supported
* zones.
*
* <p><strong>2.1 <code>zone_id_table</code> structure</strong><p>
* The list of zone IDs included in the zi database. The list does
* <em>not</em> include zone IDs, if any, listed in excludedList.
* <br>
* <blockquote>
* <pre>
* zone_id_table {
* u1 tag; // 0x40 : constant
* u2 length; // byte length of whole values
* u2 zone_id_count;
* zone_id value[zone_id_count];
* }
*
* zone_id {
* u1 byte_length; // byte length of id
* u1 id[byte_length]; // zone name string
* }
* </pre>
* </blockquote>
*
* <p><strong>2.2 <code>raw_offset_table</code> structure</strong><p>
* <br>
* <blockquote>
* <pre>
* raw_offset_table {
* u1 tag; // 0x41 : constant
* u2 length; // byte length of whole values
* s4 value[length/4]; // raw GMT offset in milliseconds
* }
* </pre>
* </blockquote>
*
* <p><strong>2.3 <code>raw_offset_index_table</code> structure</strong><p>
* <br>
* <blockquote>
* <pre>
* raw_offset_index_table {
* u1 tag; // 0x42 : constant
* u2 length; // byte length of whole values
* u1 value[length];
* }
* </pre>
* </blockquote>
*
* <p><strong>2.4 <code>alias_table</code> structure</strong><p>
* <br>
* <blockquote>
* <pre>
* alias_table {
* u1 tag; // 0x43 : constant
* u2 length; // byte length of whole values
* u2 nentries; // number of id-pairs
* id_pair value[nentries];
* }
*
* id_pair {
* zone_id aliasname;
* zone_id ID;
* }
* </pre>
* </blockquote>
*
* <p><strong>2.5 <code>versionName</code> structure</strong><p>
* <br>
* <blockquote>
* <pre>
* versionName {
* u1 tag; // 0x44 : constant
* u2 length; // byte length of whole values
* u1 value[length];
* }
* </pre>
* </blockquote>
*
* <p><strong>2.6 <code>excludeList</code> structure</strong><p>
* The list of zone IDs whose zones will change their GMT offsets
* (a.k.a. raw offsets) some time in the future. Those IDs must be
* added to the list of zone IDs for getAvailableIDs(). Also they must
* be examined for getAvailableIDs(int) to determine the
* <em>current</em> GMT offsets.
* <br>
* <blockquote>
* <pre>
* excluded_list {
* u1 tag; // 0x45 : constant
* u2 length; // byte length of whole values
* u2 nentries; // number of zone_ids
* zone_id value[nentries]; // excluded zone IDs
* }
* </pre>
* </blockquote>
*
* @since 1.4
*/
public class ZoneInfoFile {
The magic number for the ZoneInfo data file format.
/**
* The magic number for the ZoneInfo data file format.
*/
public static final byte[] JAVAZI_LABEL = {
(byte)'j', (byte)'a', (byte)'v', (byte)'a', (byte)'z', (byte)'i', (byte)'\0'
};
private static final int JAVAZI_LABEL_LENGTH = JAVAZI_LABEL.length;
The ZoneInfo data file format version number. Must increase
one when any incompatible change has been made.
/**
* The ZoneInfo data file format version number. Must increase
* one when any incompatible change has been made.
*/
public static final byte JAVAZI_VERSION = 0x01;
Raw offset data item tag.
/**
* Raw offset data item tag.
*/
public static final byte TAG_RawOffset = 1;
Known last Daylight Saving Time save value data item tag.
/**
* Known last Daylight Saving Time save value data item tag.
*/
public static final byte TAG_LastDSTSaving = 2;
Checksum data item tag.
/**
* Checksum data item tag.
*/
public static final byte TAG_CRC32 = 3;
Transition data item tag.
/**
* Transition data item tag.
*/
public static final byte TAG_Transition = 4;
Offset table data item tag.
/**
* Offset table data item tag.
*/
public static final byte TAG_Offset = 5;
SimpleTimeZone parameters data item tag.
/**
* SimpleTimeZone parameters data item tag.
*/
public static final byte TAG_SimpleTimeZone = 6;
Raw GMT offset will change in the future.
/**
* Raw GMT offset will change in the future.
*/
public static final byte TAG_GMTOffsetWillChange = 7;
The ZoneInfoMappings file name.
/**
* The ZoneInfoMappings file name.
*/
public static final String JAVAZM_FILE_NAME = "ZoneInfoMappings";
The magic number for the ZoneInfoMappings file format.
/**
* The magic number for the ZoneInfoMappings file format.
*/
public static final byte[] JAVAZM_LABEL = {
(byte)'j', (byte)'a', (byte)'v', (byte)'a', (byte)'z', (byte)'m', (byte)'\0'
};
private static final int JAVAZM_LABEL_LENGTH = JAVAZM_LABEL.length;
The ZoneInfoMappings file format version number. Must increase
one when any incompatible change has been made.
/**
* The ZoneInfoMappings file format version number. Must increase
* one when any incompatible change has been made.
*/
public static final byte JAVAZM_VERSION = 0x01;
Time zone IDs data item tag.
/**
* Time zone IDs data item tag.
*/
public static final byte TAG_ZoneIDs = 64;
Raw GMT offsets table data item tag.
/**
* Raw GMT offsets table data item tag.
*/
public static final byte TAG_RawOffsets = 65;
Indices to the raw GMT offset table data item tag.
/**
* Indices to the raw GMT offset table data item tag.
*/
public static final byte TAG_RawOffsetIndices = 66;
Time zone aliases table data item tag.
/**
* Time zone aliases table data item tag.
*/
public static final byte TAG_ZoneAliases = 67;
Olson's public zone information version tag.
/**
* Olson's public zone information version tag.
*/
public static final byte TAG_TZDataVersion = 68;
Excluded zones item tag. (Added in Mustang)
/**
* Excluded zones item tag. (Added in Mustang)
*/
public static final byte TAG_ExcludedZones = 69;
private static Map<String, ZoneInfo> zoneInfoObjects = null;
private static final ZoneInfo GMT = new ZoneInfo("GMT", 0);
private static final String ziDir = AccessController.doPrivileged(
new PrivilegedAction<String>() {
public String run() {
String zi = System.getProperty("java.home") +
File.separator + "lib" + File.separator + "zi";
try {
zi = FileSystems.getDefault().getPath(zi).toRealPath().toString();
} catch(Exception e) {
}
return zi;
}
});
Converts the given time zone ID to a platform dependent path
name. For example, "America/Los_Angeles" is converted to
"America\Los_Angeles" on Win32.
Returns: a modified ID replacing '/' with File.separatorChar
if needed.
/**
* Converts the given time zone ID to a platform dependent path
* name. For example, "America/Los_Angeles" is converted to
* "America\Los_Angeles" on Win32.
* @return a modified ID replacing '/' with {@link
* java.io.File#separatorChar File.separatorChar} if needed.
*/
public static String getFileName(String ID) {
if (File.separatorChar == '/') {
return ID;
}
return ID.replace('/', File.separatorChar);
}
Gets a ZoneInfo with the given GMT offset. The object
has its ID in the format of GMT{+|-}hh:mm.
Params: - originalId – the given custom id (before normalized such as "GMT+9")
- gmtOffset – GMT offset in milliseconds
Returns: a ZoneInfo constructed with the given GMT offset
/**
* Gets a ZoneInfo with the given GMT offset. The object
* has its ID in the format of GMT{+|-}hh:mm.
*
* @param originalId the given custom id (before normalized such as "GMT+9")
* @param gmtOffset GMT offset <em>in milliseconds</em>
* @return a ZoneInfo constructed with the given GMT offset
*/
public static ZoneInfo getCustomTimeZone(String originalId, int gmtOffset) {
String id = toCustomID(gmtOffset);
ZoneInfo zi = getFromCache(id);
if (zi == null) {
zi = new ZoneInfo(id, gmtOffset);
zi = addToCache(id, zi);
if (!id.equals(originalId)) {
zi = addToCache(originalId, zi);
}
}
return (ZoneInfo) zi.clone();
}
public static String toCustomID(int gmtOffset) {
char sign;
int offset = gmtOffset / 60000;
if (offset >= 0) {
sign = '+';
} else {
sign = '-';
offset = -offset;
}
int hh = offset / 60;
int mm = offset % 60;
char[] buf = new char[] { 'G', 'M', 'T', sign, '0', '0', ':', '0', '0' };
if (hh >= 10) {
buf[4] += hh / 10;
}
buf[5] += hh % 10;
if (mm != 0) {
buf[7] += mm / 10;
buf[8] += mm % 10;
}
return new String(buf);
}
Returns: a ZoneInfo instance created for the specified id, or
null if there is no time zone data file found for the specified
id.
/**
* @return a ZoneInfo instance created for the specified id, or
* null if there is no time zone data file found for the specified
* id.
*/
public static ZoneInfo getZoneInfo(String id) {
//treat GMT zone as special
if ("GMT".equals(id))
return (ZoneInfo) GMT.clone();
ZoneInfo zi = getFromCache(id);
if (zi == null) {
Map<String, String> aliases = ZoneInfo.getCachedAliasTable();
if (aliases != null && aliases.get(id) != null) {
return null;
}
zi = createZoneInfo(id);
if (zi == null) {
return null;
}
zi = addToCache(id, zi);
}
return (ZoneInfo) zi.clone();
}
synchronized static ZoneInfo getFromCache(String id) {
if (zoneInfoObjects == null) {
return null;
}
return zoneInfoObjects.get(id);
}
synchronized static ZoneInfo addToCache(String id, ZoneInfo zi) {
if (zoneInfoObjects == null) {
zoneInfoObjects = new HashMap<>();
} else {
ZoneInfo zone = zoneInfoObjects.get(id);
if (zone != null) {
return zone;
}
}
zoneInfoObjects.put(id, zi);
return zi;
}
private static ZoneInfo createZoneInfo(String id) {
byte[] buf = readZoneInfoFile(getFileName(id));
if (buf == null) {
return null;
}
int index = 0;
int filesize = buf.length;
int rawOffset = 0;
int dstSavings = 0;
int checksum = 0;
boolean willGMTOffsetChange = false;
long[] transitions = null;
int[] offsets = null;
int[] simpleTimeZoneParams = null;
try {
for (index = 0; index < JAVAZI_LABEL.length; index++) {
if (buf[index] != JAVAZI_LABEL[index]) {
System.err.println("ZoneInfo: wrong magic number: " + id);
return null;
}
}
if (buf[index++] > JAVAZI_VERSION) {
System.err.println("ZoneInfo: incompatible version ("
+ buf[index - 1] + "): " + id);
return null;
}
while (index < filesize) {
byte tag = buf[index++];
int len = ((buf[index++] & 0xFF) << 8) + (buf[index++] & 0xFF);
if (filesize < index+len) {
break;
}
switch (tag) {
case TAG_CRC32:
{
int val = buf[index++] & 0xff;
val = (val << 8) + (buf[index++] & 0xff);
val = (val << 8) + (buf[index++] & 0xff);
val = (val << 8) + (buf[index++] & 0xff);
checksum = val;
}
break;
case TAG_LastDSTSaving:
{
short val = (short)(buf[index++] & 0xff);
val = (short)((val << 8) + (buf[index++] & 0xff));
dstSavings = val * 1000;
}
break;
case TAG_RawOffset:
{
int val = buf[index++] & 0xff;
val = (val << 8) + (buf[index++] & 0xff);
val = (val << 8) + (buf[index++] & 0xff);
val = (val << 8) + (buf[index++] & 0xff);
rawOffset = val;
}
break;
case TAG_Transition:
{
int n = len / 8;
transitions = new long[n];
for (int i = 0; i < n; i ++) {
long val = buf[index++] & 0xff;
val = (val << 8) + (buf[index++] & 0xff);
val = (val << 8) + (buf[index++] & 0xff);
val = (val << 8) + (buf[index++] & 0xff);
val = (val << 8) + (buf[index++] & 0xff);
val = (val << 8) + (buf[index++] & 0xff);
val = (val << 8) + (buf[index++] & 0xff);
val = (val << 8) + (buf[index++] & 0xff);
transitions[i] = val;
}
}
break;
case TAG_Offset:
{
int n = len / 4;
offsets = new int[n];
for (int i = 0; i < n; i ++) {
int val = buf[index++] & 0xff;
val = (val << 8) + (buf[index++] & 0xff);
val = (val << 8) + (buf[index++] & 0xff);
val = (val << 8) + (buf[index++] & 0xff);
offsets[i] = val;
}
}
break;
case TAG_SimpleTimeZone:
{
if (len != 32 && len != 40) {
System.err.println("ZoneInfo: wrong SimpleTimeZone parameter size");
return null;
}
int n = len / 4;
simpleTimeZoneParams = new int[n];
for (int i = 0; i < n; i++) {
int val = buf[index++] & 0xff;
val = (val << 8) + (buf[index++] & 0xff);
val = (val << 8) + (buf[index++] & 0xff);
val = (val << 8) + (buf[index++] & 0xff);
simpleTimeZoneParams[i] = val;
}
}
break;
case TAG_GMTOffsetWillChange:
{
if (len != 1) {
System.err.println("ZoneInfo: wrong byte length for TAG_GMTOffsetWillChange");
}
willGMTOffsetChange = buf[index++] == 1;
}
break;
default:
System.err.println("ZoneInfo: unknown tag < " + tag + ">. ignored.");
index += len;
break;
}
}
} catch (Exception e) {
System.err.println("ZoneInfo: corrupted zoneinfo file: " + id);
return null;
}
if (index != filesize) {
System.err.println("ZoneInfo: wrong file size: " + id);
return null;
}
return new ZoneInfo(id, rawOffset, dstSavings, checksum,
transitions, offsets, simpleTimeZoneParams,
willGMTOffsetChange);
}
private volatile static SoftReference<List<String>> zoneIDs = null;
static List<String> getZoneIDs() {
List<String> ids = null;
SoftReference<List<String>> cache = zoneIDs;
if (cache != null) {
ids = cache.get();
if (ids != null) {
return ids;
}
}
byte[] buf = null;
buf = getZoneInfoMappings();
int index = JAVAZM_LABEL_LENGTH + 1;
int filesize = buf.length;
try {
loop:
while (index < filesize) {
byte tag = buf[index++];
int len = ((buf[index++] & 0xFF) << 8) + (buf[index++] & 0xFF);
switch (tag) {
case TAG_ZoneIDs:
{
int n = (buf[index++] << 8) + (buf[index++] & 0xFF);
ids = new ArrayList<>(n);
for (int i = 0; i < n; i++) {
byte m = buf[index++];
ids.add(new String(buf, index, m, "UTF-8"));
index += m;
}
}
break loop;
default:
index += len;
break;
}
}
} catch (Exception e) {
System.err.println("ZoneInfo: corrupted " + JAVAZM_FILE_NAME);
}
zoneIDs = new SoftReference<>(ids);
return ids;
}
Returns: an alias table in HashMap where a key is an alias ID
(e.g., "PST") and its value is a real time zone ID (e.g.,
"America/Los_Angeles").
/**
* @return an alias table in HashMap where a key is an alias ID
* (e.g., "PST") and its value is a real time zone ID (e.g.,
* "America/Los_Angeles").
*/
static Map<String, String> getZoneAliases() {
byte[] buf = getZoneInfoMappings();
int index = JAVAZM_LABEL_LENGTH + 1;
int filesize = buf.length;
Map<String, String> aliases = null;
try {
loop:
while (index < filesize) {
byte tag = buf[index++];
int len = ((buf[index++] & 0xFF) << 8) + (buf[index++] & 0xFF);
switch (tag) {
case TAG_ZoneAliases:
{
int n = (buf[index++] << 8) + (buf[index++] & 0xFF);
aliases = new HashMap<>(n);
for (int i = 0; i < n; i++) {
byte m = buf[index++];
String name = new String(buf, index, m, "UTF-8");
index += m;
m = buf[index++];
String realName = new String(buf, index, m, "UTF-8");
index += m;
aliases.put(name, realName);
}
}
break loop;
default:
index += len;
break;
}
}
} catch (Exception e) {
System.err.println("ZoneInfo: corrupted " + JAVAZM_FILE_NAME);
return null;
}
return aliases;
}
private volatile static SoftReference<List<String>> excludedIDs = null;
private volatile static boolean hasNoExcludeList = false;
Returns: a List of zone IDs for zones that will change their GMT
offsets in some future time. Since: 1.6
/**
* @return a List of zone IDs for zones that will change their GMT
* offsets in some future time.
*
* @since 1.6
*/
static List<String> getExcludedZones() {
if (hasNoExcludeList) {
return null;
}
List<String> excludeList = null;
SoftReference<List<String>> cache = excludedIDs;
if (cache != null) {
excludeList = cache.get();
if (excludeList != null) {
return excludeList;
}
}
byte[] buf = getZoneInfoMappings();
int index = JAVAZM_LABEL_LENGTH + 1;
int filesize = buf.length;
try {
loop:
while (index < filesize) {
byte tag = buf[index++];
int len = ((buf[index++] & 0xFF) << 8) + (buf[index++] & 0xFF);
switch (tag) {
case TAG_ExcludedZones:
{
int n = (buf[index++] << 8) + (buf[index++] & 0xFF);
excludeList = new ArrayList<>();
for (int i = 0; i < n; i++) {
byte m = buf[index++];
String name = new String(buf, index, m, "UTF-8");
index += m;
excludeList.add(name);
}
}
break loop;
default:
index += len;
break;
}
}
} catch (Exception e) {
System.err.println("ZoneInfo: corrupted " + JAVAZM_FILE_NAME);
return null;
}
if (excludeList != null) {
excludedIDs = new SoftReference<>(excludeList);
} else {
hasNoExcludeList = true;
}
return excludeList;
}
private volatile static SoftReference<byte[]> rawOffsetIndices = null;
static byte[] getRawOffsetIndices() {
byte[] indices = null;
SoftReference<byte[]> cache = rawOffsetIndices;
if (cache != null) {
indices = cache.get();
if (indices != null) {
return indices;
}
}
byte[] buf = getZoneInfoMappings();
int index = JAVAZM_LABEL_LENGTH + 1;
int filesize = buf.length;
try {
loop:
while (index < filesize) {
byte tag = buf[index++];
int len = ((buf[index++] & 0xFF) << 8) + (buf[index++] & 0xFF);
switch (tag) {
case TAG_RawOffsetIndices:
{
indices = new byte[len];
for (int i = 0; i < len; i++) {
indices[i] = buf[index++];
}
}
break loop;
default:
index += len;
break;
}
}
} catch (ArrayIndexOutOfBoundsException e) {
System.err.println("ZoneInfo: corrupted " + JAVAZM_FILE_NAME);
}
rawOffsetIndices = new SoftReference<>(indices);
return indices;
}
private volatile static SoftReference<int[]> rawOffsets = null;
static int[] getRawOffsets() {
int[] offsets = null;
SoftReference<int[]> cache = rawOffsets;
if (cache != null) {
offsets = cache.get();
if (offsets != null) {
return offsets;
}
}
byte[] buf = getZoneInfoMappings();
int index = JAVAZM_LABEL_LENGTH + 1;
int filesize = buf.length;
try {
loop:
while (index < filesize) {
byte tag = buf[index++];
int len = ((buf[index++] & 0xFF) << 8) + (buf[index++] & 0xFF);
switch (tag) {
case TAG_RawOffsets:
{
int n = len/4;
offsets = new int[n];
for (int i = 0; i < n; i++) {
int val = buf[index++] & 0xff;
val = (val << 8) + (buf[index++] & 0xff);
val = (val << 8) + (buf[index++] & 0xff);
val = (val << 8) + (buf[index++] & 0xff);
offsets[i] = val;
}
}
break loop;
default:
index += len;
break;
}
}
} catch (ArrayIndexOutOfBoundsException e) {
System.err.println("ZoneInfo: corrupted " + JAVAZM_FILE_NAME);
}
rawOffsets = new SoftReference<>(offsets);
return offsets;
}
private volatile static SoftReference<byte[]> zoneInfoMappings = null;
private static byte[] getZoneInfoMappings() {
byte[] data;
SoftReference<byte[]> cache = zoneInfoMappings;
if (cache != null) {
data = cache.get();
if (data != null) {
return data;
}
}
data = readZoneInfoFile(JAVAZM_FILE_NAME);
if (data == null) {
return null;
}
int index;
for (index = 0; index < JAVAZM_LABEL.length; index++) {
if (data[index] != JAVAZM_LABEL[index]) {
System.err.println("ZoneInfo: wrong magic number: " + JAVAZM_FILE_NAME);
return null;
}
}
if (data[index++] > JAVAZM_VERSION) {
System.err.println("ZoneInfo: incompatible version ("
+ data[index - 1] + "): " + JAVAZM_FILE_NAME);
return null;
}
zoneInfoMappings = new SoftReference<>(data);
return data;
}
Reads the specified file under <java.home>/lib/zi into a buffer.
Returns: the buffer, or null if any I/O error occurred.
/**
* Reads the specified file under <java.home>/lib/zi into a buffer.
* @return the buffer, or null if any I/O error occurred.
*/
private static byte[] readZoneInfoFile(final String fileName) {
if (fileName.indexOf("..") >= 0) {
return null;
}
byte[] buffer = null;
try {
buffer = AccessController.doPrivileged(new PrivilegedExceptionAction<byte[]>() {
public byte[] run() throws IOException {
File file = new File(ziDir, fileName);
byte[] buf = null;
int filesize = (int)file.length();
if (filesize > 0) {
FileInputStream fis = new FileInputStream(file);
buf = new byte[filesize];
try {
if (fis.read(buf) != filesize) {
throw new IOException("read error on " + fileName);
}
} finally {
fis.close();
}
}
return buf;
}
});
} catch (PrivilegedActionException e) {
Exception ex = e.getException();
if (!(ex instanceof FileNotFoundException) || JAVAZM_FILE_NAME.equals(fileName)) {
System.err.println("ZoneInfo: " + ex.getMessage());
}
}
return buffer;
}
private ZoneInfoFile() {
}
}