1 /* 2 * Copyright (c) 2007-present, Stephen Colebourne & Michael Nascimento Santos 3 * 4 * All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions are met: 8 * 9 * * Redistributions of source code must retain the above copyright notice, 10 * this list of conditions and the following disclaimer. 11 * 12 * * Redistributions in binary form must reproduce the above copyright notice, 13 * this list of conditions and the following disclaimer in the documentation 14 * and/or other materials provided with the distribution. 15 * 16 * * Neither the name of JSR-310 nor the names of its contributors 17 * may be used to endorse or promote products derived from this software 18 * without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 28 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 29 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 */ 32 package org.threeten.bp; 33 34 import java.io.DataInput; 35 import java.io.DataOutput; 36 import java.io.IOException; 37 import java.io.InvalidObjectException; 38 import java.io.ObjectStreamException; 39 import java.io.Serializable; 40 import java.util.regex.Pattern; 41 42 import org.threeten.bp.jdk8.Jdk8Methods; 43 import org.threeten.bp.zone.ZoneRules; 44 import org.threeten.bp.zone.ZoneRulesException; 45 import org.threeten.bp.zone.ZoneRulesProvider; 46 47 /** 48 * A geographical region where the same time-zone rules apply. 49 * <p> 50 * Time-zone information is categorized as a set of rules defining when and 51 * how the offset from UTC/Greenwich changes. These rules are accessed using 52 * identifiers based on geographical regions, such as countries or states. 53 * The most common region classification is the Time Zone Database (TZDB), 54 * which defines regions such as 'Europe/Paris' and 'Asia/Tokyo'. 55 * <p> 56 * The region identifier, modeled by this class, is distinct from the 57 * underlying rules, modeled by {@link ZoneRules}. 58 * The rules are defined by governments and change frequently. 59 * By contrast, the region identifier is well-defined and long-lived. 60 * This separation also allows rules to be shared between regions if appropriate. 61 * 62 * <h3>Specification for implementors</h3> 63 * This class is immutable and thread-safe. 64 */ 65 final class ZoneRegion extends ZoneId implements Serializable { 66 67 /** 68 * Serialization version. 69 */ 70 private static final long serialVersionUID = 8386373296231747096L; 71 /** 72 * The regex pattern for region IDs. 73 */ 74 private static final Pattern PATTERN = Pattern.compile("[A-Za-z][A-Za-z0-9~/._+-]+"); 75 76 /** 77 * The time-zone ID, not null. 78 */ 79 private final String id; 80 /** 81 * The time-zone rules, null if zone ID was loaded leniently. 82 */ 83 private final transient ZoneRules rules; 84 85 /** 86 * Obtains an instance of {@code ZoneRegion} from an identifier without checking 87 * if the time-zone has available rules. 88 * <p> 89 * This method parses the ID and applies any appropriate normalization. 90 * It does not validate the ID against the known set of IDsfor which rules are available. 91 * <p> 92 * This method is intended for advanced use cases. 93 * For example, consider a system that always retrieves time-zone rules from a remote server. 94 * Using this factory would allow a {@code ZoneRegion}, and thus a {@code ZonedDateTime}, 95 * to be created without loading the rules from the remote server. 96 * 97 * @param zoneId the time-zone ID, not null 98 * @return the zone ID, not null 99 * @throws DateTimeException if the ID format is invalid 100 */ ofLenient(String zoneId)101 private static ZoneRegion ofLenient(String zoneId) { 102 if (zoneId.equals("Z") || zoneId.startsWith("+") || zoneId.startsWith("-")) { 103 throw new DateTimeException("Invalid ID for region-based ZoneId, invalid format: " + zoneId); 104 } 105 if (zoneId.equals("UTC") || zoneId.equals("GMT") || zoneId.equals("UT")) { 106 return new ZoneRegion(zoneId, ZoneOffset.UTC.getRules()); 107 } 108 if (zoneId.startsWith("UTC+") || zoneId.startsWith("GMT+") || 109 zoneId.startsWith("UTC-") || zoneId.startsWith("GMT-")) { 110 ZoneOffset offset = ZoneOffset.of(zoneId.substring(3)); 111 if (offset.getTotalSeconds() == 0) { 112 return new ZoneRegion(zoneId.substring(0, 3), offset.getRules()); 113 } 114 return new ZoneRegion(zoneId.substring(0, 3) + offset.getId(), offset.getRules()); 115 } 116 if (zoneId.startsWith("UT+") || zoneId.startsWith("UT-")) { 117 ZoneOffset offset = ZoneOffset.of(zoneId.substring(2)); 118 if (offset.getTotalSeconds() == 0) { 119 return new ZoneRegion("UT", offset.getRules()); 120 } 121 return new ZoneRegion("UT" + offset.getId(), offset.getRules()); 122 } 123 return ofId(zoneId, false); 124 } 125 126 /** 127 * Obtains an instance of {@code ZoneId} from an identifier. 128 * 129 * @param zoneId the time-zone ID, not null 130 * @param checkAvailable whether to check if the zone ID is available 131 * @return the zone ID, not null 132 * @throws DateTimeException if the ID format is invalid 133 * @throws DateTimeException if checking availability and the ID cannot be found 134 */ ofId(String zoneId, boolean checkAvailable)135 static ZoneRegion ofId(String zoneId, boolean checkAvailable) { 136 Jdk8Methods.requireNonNull(zoneId, "zoneId"); 137 if (zoneId.length() < 2 || PATTERN.matcher(zoneId).matches() == false) { 138 throw new DateTimeException("Invalid ID for region-based ZoneId, invalid format: " + zoneId); 139 } 140 ZoneRules rules = null; 141 try { 142 // always attempt load for better behavior after deserialization 143 rules = ZoneRulesProvider.getRules(zoneId, true); 144 } catch (ZoneRulesException ex) { 145 // special case as removed from data file 146 if (zoneId.equals("GMT0")) { 147 rules = ZoneOffset.UTC.getRules(); 148 } else if (checkAvailable) { 149 throw ex; 150 } 151 } 152 return new ZoneRegion(zoneId, rules); 153 } 154 155 //------------------------------------------------------------------------- 156 /** 157 * Constructor. 158 * 159 * @param id the time-zone ID, not null 160 * @param rules the rules, null for lazy lookup 161 */ ZoneRegion(String id, ZoneRules rules)162 ZoneRegion(String id, ZoneRules rules) { 163 this.id = id; 164 this.rules = rules; 165 } 166 167 //----------------------------------------------------------------------- 168 @Override getId()169 public String getId() { 170 return id; 171 } 172 173 @Override getRules()174 public ZoneRules getRules() { 175 // additional query for group provider when null allows for possibility 176 // that the provider was added after the ZoneId was created 177 return (rules != null ? rules : ZoneRulesProvider.getRules(id, false)); 178 } 179 180 //----------------------------------------------------------------------- writeReplace()181 private Object writeReplace() { 182 return new Ser(Ser.ZONE_REGION_TYPE, this); 183 } 184 185 /** 186 * Defend against malicious streams. 187 * @return never 188 * @throws InvalidObjectException always 189 */ readResolve()190 private Object readResolve() throws ObjectStreamException { 191 throw new InvalidObjectException("Deserialization via serialization delegate"); 192 } 193 194 @Override write(DataOutput out)195 void write(DataOutput out) throws IOException { 196 out.writeByte(Ser.ZONE_REGION_TYPE); 197 writeExternal(out); 198 } 199 writeExternal(DataOutput out)200 void writeExternal(DataOutput out) throws IOException { 201 out.writeUTF(id); 202 } 203 readExternal(DataInput in)204 static ZoneId readExternal(DataInput in) throws IOException { 205 String id = in.readUTF(); 206 return ofLenient(id); 207 } 208 209 } 210