| /* |
| * Copyright (C) 2017 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.libcore.timezone.tzlookup; |
| |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.OutputStreamWriter; |
| import java.io.StringReader; |
| import java.io.StringWriter; |
| import java.io.Writer; |
| import java.nio.charset.StandardCharsets; |
| import java.time.Instant; |
| import java.util.ArrayList; |
| import java.util.List; |
| import javax.xml.stream.XMLOutputFactory; |
| import javax.xml.stream.XMLStreamException; |
| import javax.xml.stream.XMLStreamWriter; |
| import javax.xml.transform.OutputKeys; |
| import javax.xml.transform.Transformer; |
| import javax.xml.transform.TransformerException; |
| import javax.xml.transform.TransformerFactory; |
| import javax.xml.transform.stream.StreamResult; |
| import javax.xml.transform.stream.StreamSource; |
| |
| /** |
| * A class that knows about the structure of the tzlookup.xml file. |
| */ |
| final class TzLookupFile { |
| |
| // <timezones ianaversion="2017b"> |
| private static final String TIMEZONES_ELEMENT = "timezones"; |
| private static final String IANA_VERSION_ATTRIBUTE = "ianaversion"; |
| |
| // <countryzones> |
| private static final String COUNTRY_ZONES_ELEMENT = "countryzones"; |
| |
| // <country code="iso_code" default="olson_id" [defaultBoost="n|y"] everutc="n|y"> |
| private static final String COUNTRY_ELEMENT = "country"; |
| private static final String COUNTRY_CODE_ATTRIBUTE = "code"; |
| private static final String DEFAULT_ATTRIBUTE = "default"; |
| private static final String DEFAULT_BOOST_ATTRIBUTE = "defaultBoost"; |
| private static final String EVER_USES_UTC_ATTRIBUTE = "everutc"; |
| |
| // <id [picker="n|y"]> |
| private static final String ZONE_ID_ELEMENT = "id"; |
| // Default when unspecified is "y" / true. |
| private static final String ZONE_SHOW_IN_PICKER_ATTRIBUTE = "picker"; |
| // The time when the zone stops being distinct from another of the country's zones (inclusive). |
| private static final String ZONE_NOT_USED_AFTER_ATTRIBUTE = "notafter"; |
| |
| |
| // Short encodings for boolean attributes. |
| private static final String ATTRIBUTE_FALSE = "n"; |
| private static final String ATTRIBUTE_TRUE = "y"; |
| |
| static void write(TimeZones timeZones, String outputFile) |
| throws XMLStreamException, IOException { |
| /* |
| * The required XML structure is: |
| * <timezones ianaversion="2017b"> |
| * <countryzones> |
| * <country code="us" default="America/New_York" everutc="n"> |
| * <!-- -5:00 --> |
| * <id notafter="1234">America/New_York"</id> |
| * ... |
| * <!-- -8:00 --> |
| * <id picker="n">America/Los_Angeles</id> |
| * ... |
| * </country> |
| * <country code="gb" default="Europe/London" defaultBoost="y" everutc="y"> |
| * <!-- 0:00 --> |
| * <id>Europe/London</id> |
| * </country> |
| * </countryzones> |
| * </timezones> |
| */ |
| |
| StringWriter writer = new StringWriter(); |
| writeRaw(timeZones, writer); |
| String rawXml = writer.getBuffer().toString(); |
| |
| TransformerFactory factory = TransformerFactory.newInstance(); |
| try (Writer fileWriter = new OutputStreamWriter( |
| new FileOutputStream(outputFile), StandardCharsets.UTF_8)) { |
| |
| // Transform the XML with the identity transform but with indenting |
| // so it's more human-readable. |
| Transformer transformer = factory.newTransformer(); |
| transformer.setOutputProperty(OutputKeys.INDENT, "yes"); |
| transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "1"); |
| transformer.transform( |
| new StreamSource(new StringReader(rawXml)), new StreamResult(fileWriter)); |
| } catch (TransformerException e) { |
| throw new XMLStreamException(e); |
| } |
| } |
| |
| private static void writeRaw(TimeZones timeZones, Writer fileWriter) |
| throws XMLStreamException { |
| XMLOutputFactory xmlOutputFactory = XMLOutputFactory.newFactory(); |
| XMLStreamWriter xmlWriter = xmlOutputFactory.createXMLStreamWriter(fileWriter); |
| xmlWriter.writeStartDocument(); |
| xmlWriter.writeComment("\n\n **** Autogenerated file - DO NOT EDIT ****\n\n"); |
| TimeZones.writeXml(timeZones, xmlWriter); |
| xmlWriter.writeEndDocument(); |
| } |
| |
| static class TimeZones { |
| |
| private final String ianaVersion; |
| private CountryZones countryZones; |
| |
| TimeZones(String ianaVersion) { |
| this.ianaVersion = ianaVersion; |
| } |
| |
| void setCountryZones(CountryZones countryZones) { |
| this.countryZones = countryZones; |
| } |
| |
| static void writeXml(TimeZones timeZones, XMLStreamWriter writer) |
| throws XMLStreamException { |
| writer.writeStartElement(TIMEZONES_ELEMENT); |
| writer.writeAttribute(IANA_VERSION_ATTRIBUTE, timeZones.ianaVersion); |
| CountryZones.writeXml(timeZones.countryZones, writer); |
| writer.writeEndElement(); |
| } |
| } |
| |
| static class CountryZones { |
| |
| private final List<Country> countries = new ArrayList<>(); |
| |
| CountryZones() { |
| } |
| |
| static void writeXml(CountryZones countryZones, XMLStreamWriter writer) |
| throws XMLStreamException { |
| writer.writeStartElement(COUNTRY_ZONES_ELEMENT); |
| for (Country country : countryZones.countries) { |
| Country.writeXml(country, writer); |
| } |
| writer.writeEndElement(); |
| } |
| |
| void addCountry(Country country) { |
| countries.add(country); |
| } |
| } |
| |
| static class Country { |
| |
| private final String isoCode; |
| private final String defaultTimeZoneId; |
| private final boolean defaultTimeZoneBoost; |
| private final boolean everUsesUtc; |
| private final List<TimeZoneMapping> timeZoneIds = new ArrayList<>(); |
| |
| Country(String isoCode, String defaultTimeZoneId, boolean defaultTimeZoneBoost, |
| boolean everUsesUtc) { |
| this.defaultTimeZoneId = defaultTimeZoneId; |
| this.isoCode = isoCode; |
| this.defaultTimeZoneBoost = defaultTimeZoneBoost; |
| this.everUsesUtc = everUsesUtc; |
| } |
| |
| void addTimeZoneIdentifier(TimeZoneMapping timeZoneId) { |
| timeZoneIds.add(timeZoneId); |
| } |
| |
| static void writeXml(Country country, XMLStreamWriter writer) |
| throws XMLStreamException { |
| writer.writeStartElement(COUNTRY_ELEMENT); |
| writer.writeAttribute(COUNTRY_CODE_ATTRIBUTE, country.isoCode); |
| writer.writeAttribute(DEFAULT_ATTRIBUTE, country.defaultTimeZoneId); |
| if (country.defaultTimeZoneBoost) { |
| writer.writeAttribute(DEFAULT_BOOST_ATTRIBUTE, |
| encodeBooleanAttribute(country.defaultTimeZoneBoost)); |
| } |
| writer.writeAttribute(EVER_USES_UTC_ATTRIBUTE, encodeBooleanAttribute( |
| country.everUsesUtc)); |
| for (TimeZoneMapping timeZoneId : country.timeZoneIds) { |
| TimeZoneMapping.writeXml(timeZoneId, writer); |
| } |
| writer.writeEndElement(); |
| } |
| } |
| |
| private static String encodeBooleanAttribute(boolean value) { |
| return value ? ATTRIBUTE_TRUE : ATTRIBUTE_FALSE; |
| } |
| |
| private static String encodeLongAttribute(long epochMillis) { |
| return Long.toString(epochMillis); |
| } |
| |
| static class TimeZoneMapping { |
| |
| private final String olsonId; |
| private final boolean showInPicker; |
| private final Instant notUsedAfterInclusive; |
| |
| TimeZoneMapping(String olsonId, boolean showInPicker, Instant notUsedAfterInclusive) { |
| this.olsonId = olsonId; |
| this.showInPicker = showInPicker; |
| this.notUsedAfterInclusive = notUsedAfterInclusive; |
| } |
| |
| static void writeXml(TimeZoneMapping timeZoneId, XMLStreamWriter writer) |
| throws XMLStreamException { |
| writer.writeStartElement(ZONE_ID_ELEMENT); |
| if (!timeZoneId.showInPicker) { |
| writer.writeAttribute(ZONE_SHOW_IN_PICKER_ATTRIBUTE, encodeBooleanAttribute(false)); |
| } |
| if (timeZoneId.notUsedAfterInclusive != null) { |
| writer.writeAttribute(ZONE_NOT_USED_AFTER_ATTRIBUTE, |
| encodeLongAttribute(timeZoneId.notUsedAfterInclusive.toEpochMilli())); |
| } |
| writer.writeCharacters(timeZoneId.olsonId); |
| writer.writeEndElement(); |
| } |
| } |
| } |