• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 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 package com.android.libcore.timezone.tzlookup;
17 
18 import java.io.FileOutputStream;
19 import java.io.IOException;
20 import java.io.OutputStreamWriter;
21 import java.io.StringReader;
22 import java.io.StringWriter;
23 import java.io.Writer;
24 import java.nio.charset.StandardCharsets;
25 import java.time.Instant;
26 import java.util.ArrayList;
27 import java.util.List;
28 import javax.xml.stream.XMLOutputFactory;
29 import javax.xml.stream.XMLStreamException;
30 import javax.xml.stream.XMLStreamWriter;
31 import javax.xml.transform.OutputKeys;
32 import javax.xml.transform.Transformer;
33 import javax.xml.transform.TransformerException;
34 import javax.xml.transform.TransformerFactory;
35 import javax.xml.transform.stream.StreamResult;
36 import javax.xml.transform.stream.StreamSource;
37 
38 /**
39  * A class that knows about the structure of the tzlookup.xml file.
40  */
41 final class TzLookupFile {
42 
43     // <timezones ianaversion="2017b">
44     private static final String TIMEZONES_ELEMENT = "timezones";
45     private static final String IANA_VERSION_ATTRIBUTE = "ianaversion";
46 
47     // <countryzones>
48     private static final String COUNTRY_ZONES_ELEMENT = "countryzones";
49 
50     // <country code="iso_code" default="olson_id" everutc="n|y">
51     private static final String COUNTRY_ELEMENT = "country";
52     private static final String COUNTRY_CODE_ATTRIBUTE = "code";
53     private static final String DEFAULT_ATTRIBUTE = "default";
54     private static final String EVER_USES_UTC_ATTRIBUTE = "everutc";
55 
56     // <id [picker="n|y"]>
57     private static final String ZONE_ID_ELEMENT = "id";
58     // Default when unspecified is "y" / true.
59     private static final String ZONE_SHOW_IN_PICKER_ATTRIBUTE = "picker";
60     // The time when the zone stops being distinct from another of the country's zones (inclusive).
61     private static final String ZONE_NOT_USED_AFTER_ATTRIBUTE = "notafter";
62 
63 
64     // Short encodings for boolean attributes.
65     private static final String ATTRIBUTE_FALSE = "n";
66     private static final String ATTRIBUTE_TRUE = "y";
67 
write(TimeZones timeZones, String outputFile)68     static void write(TimeZones timeZones, String outputFile)
69             throws XMLStreamException, IOException {
70         /*
71          * The required XML structure is:
72          * <timezones ianaversion="2017b">
73          *   <countryzones>
74          *     <country code="us" default="America/New_York" everutc="n">
75          *       <!-- -5:00 -->
76          *       <id notafter="1234">America/New_York"</id>
77          *       ...
78          *       <!-- -8:00 -->
79          *       <id picker="n">America/Los_Angeles</id>
80          *       ...
81          *     </country>
82          *     <country code="gb" default="Europe/London" everutc="y">
83          *       <!-- 0:00 -->
84          *       <id>Europe/London</id>
85          *     </country>
86          *   </countryzones>
87          * </timezones>
88          */
89 
90         StringWriter writer = new StringWriter();
91         writeRaw(timeZones, writer);
92         String rawXml = writer.getBuffer().toString();
93 
94         TransformerFactory factory = TransformerFactory.newInstance();
95         try (Writer fileWriter = new OutputStreamWriter(
96                 new FileOutputStream(outputFile), StandardCharsets.UTF_8)) {
97 
98             // Transform the XML with the identity transform but with indenting
99             // so it's more human-readable.
100             Transformer transformer = factory.newTransformer();
101             transformer.setOutputProperty(OutputKeys.INDENT, "yes");
102             transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "1");
103             transformer.transform(
104                     new StreamSource(new StringReader(rawXml)), new StreamResult(fileWriter));
105         } catch (TransformerException e) {
106             throw new XMLStreamException(e);
107         }
108     }
109 
writeRaw(TimeZones timeZones, Writer fileWriter)110     private static void writeRaw(TimeZones timeZones, Writer fileWriter)
111             throws XMLStreamException {
112         XMLOutputFactory xmlOutputFactory = XMLOutputFactory.newFactory();
113         XMLStreamWriter xmlWriter = xmlOutputFactory.createXMLStreamWriter(fileWriter);
114         xmlWriter.writeStartDocument();
115         xmlWriter.writeComment("\n\n **** Autogenerated file - DO NOT EDIT ****\n\n");
116         TimeZones.writeXml(timeZones, xmlWriter);
117         xmlWriter.writeEndDocument();
118     }
119 
120     static class TimeZones {
121 
122         private final String ianaVersion;
123         private CountryZones countryZones;
124 
TimeZones(String ianaVersion)125         TimeZones(String ianaVersion) {
126             this.ianaVersion = ianaVersion;
127         }
128 
setCountryZones(CountryZones countryZones)129         void setCountryZones(CountryZones countryZones) {
130             this.countryZones = countryZones;
131         }
132 
writeXml(TimeZones timeZones, XMLStreamWriter writer)133         static void writeXml(TimeZones timeZones, XMLStreamWriter writer)
134                 throws XMLStreamException {
135             writer.writeStartElement(TIMEZONES_ELEMENT);
136             writer.writeAttribute(IANA_VERSION_ATTRIBUTE, timeZones.ianaVersion);
137             CountryZones.writeXml(timeZones.countryZones, writer);
138             writer.writeEndElement();
139         }
140     }
141 
142     static class CountryZones {
143 
144         private final List<Country> countries = new ArrayList<>();
145 
CountryZones()146         CountryZones() {
147         }
148 
writeXml(CountryZones countryZones, XMLStreamWriter writer)149         static void writeXml(CountryZones countryZones, XMLStreamWriter writer)
150                 throws XMLStreamException {
151             writer.writeStartElement(COUNTRY_ZONES_ELEMENT);
152             for (Country country : countryZones.countries) {
153                 Country.writeXml(country, writer);
154             }
155             writer.writeEndElement();
156         }
157 
addCountry(Country country)158         void addCountry(Country country) {
159             countries.add(country);
160         }
161     }
162 
163     static class Country {
164 
165         private final String isoCode;
166         private final String defaultTimeZoneId;
167         private final boolean everUsesUtc;
168         private final List<TimeZoneMapping> timeZoneIds = new ArrayList<>();
169 
Country(String isoCode, String defaultTimeZoneId, boolean everUsesUtc)170         Country(String isoCode, String defaultTimeZoneId, boolean everUsesUtc) {
171             this.defaultTimeZoneId = defaultTimeZoneId;
172             this.isoCode = isoCode;
173             this.everUsesUtc = everUsesUtc;
174         }
175 
addTimeZoneIdentifier(TimeZoneMapping timeZoneId)176         void addTimeZoneIdentifier(TimeZoneMapping timeZoneId) {
177             timeZoneIds.add(timeZoneId);
178         }
179 
writeXml(Country country, XMLStreamWriter writer)180         static void writeXml(Country country, XMLStreamWriter writer)
181                 throws XMLStreamException {
182             writer.writeStartElement(COUNTRY_ELEMENT);
183             writer.writeAttribute(COUNTRY_CODE_ATTRIBUTE, country.isoCode);
184             writer.writeAttribute(DEFAULT_ATTRIBUTE, country.defaultTimeZoneId);
185             writer.writeAttribute(EVER_USES_UTC_ATTRIBUTE, encodeBooleanAttribute(
186                     country.everUsesUtc));
187             for (TimeZoneMapping timeZoneId : country.timeZoneIds) {
188                 TimeZoneMapping.writeXml(timeZoneId, writer);
189             }
190             writer.writeEndElement();
191         }
192     }
193 
encodeBooleanAttribute(boolean value)194     private static String encodeBooleanAttribute(boolean value) {
195         return value ? ATTRIBUTE_TRUE : ATTRIBUTE_FALSE;
196     }
197 
encodeLongAttribute(long epochMillis)198     private static String encodeLongAttribute(long epochMillis) {
199         return Long.toString(epochMillis);
200     }
201 
202     static class TimeZoneMapping {
203 
204         private final String olsonId;
205         private final boolean showInPicker;
206         private final Instant notUsedAfterInclusive;
207 
TimeZoneMapping(String olsonId, boolean showInPicker, Instant notUsedAfterInclusive)208         TimeZoneMapping(String olsonId, boolean showInPicker, Instant notUsedAfterInclusive) {
209             this.olsonId = olsonId;
210             this.showInPicker = showInPicker;
211             this.notUsedAfterInclusive = notUsedAfterInclusive;
212         }
213 
writeXml(TimeZoneMapping timeZoneId, XMLStreamWriter writer)214         static void writeXml(TimeZoneMapping timeZoneId, XMLStreamWriter writer)
215                 throws XMLStreamException {
216             writer.writeStartElement(ZONE_ID_ELEMENT);
217             if (!timeZoneId.showInPicker) {
218                 writer.writeAttribute(ZONE_SHOW_IN_PICKER_ATTRIBUTE, encodeBooleanAttribute(false));
219             }
220             if (timeZoneId.notUsedAfterInclusive != null) {
221                 writer.writeAttribute(ZONE_NOT_USED_AFTER_ATTRIBUTE,
222                         encodeLongAttribute(timeZoneId.notUsedAfterInclusive.toEpochMilli()));
223             }
224             writer.writeCharacters(timeZoneId.olsonId);
225             writer.writeEndElement();
226         }
227     }
228 }
229