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.zone; 33 34 import java.util.Collections; 35 import java.util.NavigableMap; 36 import java.util.Set; 37 import java.util.concurrent.ConcurrentHashMap; 38 import java.util.concurrent.ConcurrentMap; 39 import java.util.concurrent.CopyOnWriteArrayList; 40 41 import org.threeten.bp.DateTimeException; 42 import org.threeten.bp.ZoneId; 43 import org.threeten.bp.ZonedDateTime; 44 import org.threeten.bp.jdk8.Jdk8Methods; 45 46 /** 47 * Provider of time-zone rules to the system. 48 * <p> 49 * This class manages the configuration of time-zone rules. 50 * The static methods provide the public API that can be used to manage the providers. 51 * The abstract methods provide the SPI that allows rules to be provided. 52 * <p> 53 * Rules are looked up primarily by zone ID, as used by {@link ZoneId}. 54 * Only zone region IDs may be used, zone offset IDs are not used here. 55 * <p> 56 * Time-zone rules are political, thus the data can change at any time. 57 * Each provider will provide the latest rules for each zone ID, but they 58 * may also provide the history of how the rules changed. 59 * 60 * <h3>Specification for implementors</h3> 61 * This interface is a service provider that can be called by multiple threads. 62 * Implementations must be immutable and thread-safe. 63 * <p> 64 * Providers must ensure that once a rule has been seen by the application, the 65 * rule must continue to be available. 66 * <p> 67 * Many systems would like to update time-zone rules dynamically without stopping the JVM. 68 * When examined in detail, this is a complex problem. 69 * Providers may choose to handle dynamic updates, however the default provider does not. 70 */ 71 public abstract class ZoneRulesProvider { 72 73 /** 74 * The set of loaded providers. 75 */ 76 private static final CopyOnWriteArrayList<ZoneRulesProvider> PROVIDERS = new CopyOnWriteArrayList<ZoneRulesProvider>(); 77 /** 78 * The lookup from zone region ID to provider. 79 */ 80 private static final ConcurrentMap<String, ZoneRulesProvider> ZONES = new ConcurrentHashMap<String, ZoneRulesProvider>(512, 0.75f, 2); 81 static { ZoneRulesInitializer.initialize()82 ZoneRulesInitializer.initialize(); 83 } 84 85 //------------------------------------------------------------------------- 86 /** 87 * Gets the set of available zone IDs. 88 * <p> 89 * These zone IDs are loaded and available for use by {@code ZoneId}. 90 * 91 * @return the unmodifiable set of zone IDs, not null 92 */ getAvailableZoneIds()93 public static Set<String> getAvailableZoneIds() { 94 return Collections.unmodifiableSet(ZONES.keySet()); 95 } 96 97 /** 98 * Gets the rules for the zone ID. 99 * <p> 100 * This returns the latest available rules for the zone ID. 101 * <p> 102 * This method relies on time-zone data provider files that are configured. 103 * These are loaded using a {@code ServiceLoader}. 104 * <p> 105 * The caching flag is designed to allow provider implementations to 106 * prevent the rules being cached in {@code ZoneId}. 107 * Under normal circumstances, the caching of zone rules is highly desirable 108 * as it will provide greater performance. However, there is a use case where 109 * the caching would not be desirable, see {@link #provideRules}. 110 * 111 * @param zoneId the zone ID as defined by {@code ZoneId}, not null 112 * @param forCaching whether the rules are being queried for caching, 113 * true if the returned rules will be cached by {@code ZoneId}, 114 * false if they will be returned to the user without being cached in {@code ZoneId} 115 * @return the rules, null if {@code forCaching} is true and this 116 * is a dynamic provider that wants to prevent caching in {@code ZoneId}, 117 * otherwise not null 118 * @throws ZoneRulesException if rules cannot be obtained for the zone ID 119 */ getRules(String zoneId, boolean forCaching)120 public static ZoneRules getRules(String zoneId, boolean forCaching) { 121 Jdk8Methods.requireNonNull(zoneId, "zoneId"); 122 return getProvider(zoneId).provideRules(zoneId, forCaching); 123 } 124 125 /** 126 * Gets the history of rules for the zone ID. 127 * <p> 128 * Time-zones are defined by governments and change frequently. 129 * This method allows applications to find the history of changes to the 130 * rules for a single zone ID. The map is keyed by a string, which is the 131 * version string associated with the rules. 132 * <p> 133 * The exact meaning and format of the version is provider specific. 134 * The version must follow lexicographical order, thus the returned map will 135 * be order from the oldest known rules to the newest available rules. 136 * The default 'TZDB' group uses version numbering consisting of the year 137 * followed by a letter, such as '2009e' or '2012f'. 138 * <p> 139 * Implementations must provide a result for each valid zone ID, however 140 * they do not have to provide a history of rules. 141 * Thus the map will always contain one element, and will only contain more 142 * than one element if historical rule information is available. 143 * 144 * @param zoneId the zone region ID as used by {@code ZoneId}, not null 145 * @return a modifiable copy of the history of the rules for the ID, sorted 146 * from oldest to newest, not null 147 * @throws ZoneRulesException if history cannot be obtained for the zone ID 148 */ getVersions(String zoneId)149 public static NavigableMap<String, ZoneRules> getVersions(String zoneId) { 150 Jdk8Methods.requireNonNull(zoneId, "zoneId"); 151 return getProvider(zoneId).provideVersions(zoneId); 152 } 153 154 /** 155 * Gets the provider for the zone ID. 156 * 157 * @param zoneId the zone region ID as used by {@code ZoneId}, not null 158 * @return the provider, not null 159 * @throws ZoneRulesException if the zone ID is unknown 160 */ getProvider(String zoneId)161 private static ZoneRulesProvider getProvider(String zoneId) { 162 ZoneRulesProvider provider = ZONES.get(zoneId); 163 if (provider == null) { 164 if (ZONES.isEmpty()) { 165 throw new ZoneRulesException("No time-zone data files registered"); 166 } 167 throw new ZoneRulesException("Unknown time-zone ID: " + zoneId); 168 } 169 return provider; 170 } 171 172 //------------------------------------------------------------------------- 173 /** 174 * Registers a zone rules provider. 175 * <p> 176 * This adds a new provider to those currently available. 177 * A provider supplies rules for one or more zone IDs. 178 * A provider cannot be registered if it supplies a zone ID that has already been 179 * registered. See the notes on time-zone IDs in {@link ZoneId}, especially 180 * the section on using the concept of a "group" to make IDs unique. 181 * <p> 182 * To ensure the integrity of time-zones already created, there is no way 183 * to deregister providers. 184 * 185 * @param provider the provider to register, not null 186 * @throws ZoneRulesException if a region is already registered 187 */ registerProvider(ZoneRulesProvider provider)188 public static void registerProvider(ZoneRulesProvider provider) { 189 Jdk8Methods.requireNonNull(provider, "provider"); 190 registerProvider0(provider); 191 PROVIDERS.add(provider); 192 } 193 194 /** 195 * Registers the provider. 196 * 197 * @param provider the provider to register, not null 198 * @throws ZoneRulesException if unable to complete the registration 199 */ registerProvider0(ZoneRulesProvider provider)200 private static void registerProvider0(ZoneRulesProvider provider) { 201 for (String zoneId : provider.provideZoneIds()) { 202 Jdk8Methods.requireNonNull(zoneId, "zoneId"); 203 ZoneRulesProvider old = ZONES.putIfAbsent(zoneId, provider); 204 if (old != null) { 205 throw new ZoneRulesException( 206 "Unable to register zone as one already registered with that ID: " + zoneId + 207 ", currently loading from provider: " + provider); 208 } 209 } 210 } 211 212 //------------------------------------------------------------------------- 213 /** 214 * Refreshes the rules from the underlying data provider. 215 * <p> 216 * This method is an extension point that allows providers to refresh their 217 * rules dynamically at a time of the applications choosing. 218 * After calling this method, the offset stored in any {@link ZonedDateTime} 219 * may be invalid for the zone ID. 220 * <p> 221 * Dynamic behavior is entirely optional and most providers, including the 222 * default provider, do not support it. 223 * 224 * @return true if the rules were updated 225 * @throws ZoneRulesException if an error occurs during the refresh 226 */ refresh()227 public static boolean refresh() { 228 boolean changed = false; 229 for (ZoneRulesProvider provider : PROVIDERS) { 230 changed |= provider.provideRefresh(); 231 } 232 return changed; 233 } 234 235 //----------------------------------------------------------------------- 236 /** 237 * Constructor. 238 */ ZoneRulesProvider()239 protected ZoneRulesProvider() { 240 } 241 242 //----------------------------------------------------------------------- 243 /** 244 * SPI method to get the available zone IDs. 245 * <p> 246 * This obtains the IDs that this {@code ZoneRulesProvider} provides. 247 * A provider should provide data for at least one region. 248 * <p> 249 * The returned regions remain available and valid for the lifetime of the application. 250 * A dynamic provider may increase the set of regions as more data becomes available. 251 * 252 * @return the unmodifiable set of region IDs being provided, not null 253 * @throws ZoneRulesException if a problem occurs while providing the IDs 254 */ provideZoneIds()255 protected abstract Set<String> provideZoneIds(); 256 257 /** 258 * SPI method to get the rules for the zone ID. 259 * <p> 260 * This loads the rules for the region and version specified. 261 * The version may be null to indicate the "latest" version. 262 * 263 * @param regionId the time-zone region ID, not null 264 * @return the rules, not null 265 * @throws DateTimeException if rules cannot be obtained 266 */ provideRules(String regionId, boolean forCaching)267 protected abstract ZoneRules provideRules(String regionId, boolean forCaching); 268 269 /** 270 * SPI method to get the history of rules for the zone ID. 271 * <p> 272 * This returns a map of historical rules keyed by a version string. 273 * The exact meaning and format of the version is provider specific. 274 * The version must follow lexicographical order, thus the returned map will 275 * be order from the oldest known rules to the newest available rules. 276 * The default 'TZDB' group uses version numbering consisting of the year 277 * followed by a letter, such as '2009e' or '2012f'. 278 * <p> 279 * Implementations must provide a result for each valid zone ID, however 280 * they do not have to provide a history of rules. 281 * Thus the map will always contain one element, and will only contain more 282 * than one element if historical rule information is available. 283 * <p> 284 * The returned versions remain available and valid for the lifetime of the application. 285 * A dynamic provider may increase the set of versions as more data becomes available. 286 * 287 * @param zoneId the zone region ID as used by {@code ZoneId}, not null 288 * @return a modifiable copy of the history of the rules for the ID, sorted 289 * from oldest to newest, not null 290 * @throws ZoneRulesException if history cannot be obtained for the zone ID 291 */ provideVersions(String zoneId)292 protected abstract NavigableMap<String, ZoneRules> provideVersions(String zoneId); 293 294 /** 295 * SPI method to refresh the rules from the underlying data provider. 296 * <p> 297 * This method provides the opportunity for a provider to dynamically 298 * recheck the underlying data provider to find the latest rules. 299 * This could be used to load new rules without stopping the JVM. 300 * Dynamic behavior is entirely optional and most providers do not support it. 301 * <p> 302 * This implementation returns false. 303 * 304 * @return true if the rules were updated 305 * @throws DateTimeException if an error occurs during the refresh 306 */ provideRefresh()307 protected boolean provideRefresh() { 308 return false; 309 } 310 311 } 312