93 /** |
93 /** |
94 * Serialization version. |
94 * Serialization version. |
95 */ |
95 */ |
96 private static final long serialVersionUID = 8386373296231747096L; |
96 private static final long serialVersionUID = 8386373296231747096L; |
97 /** |
97 /** |
98 * The regex pattern for region IDs. |
|
99 */ |
|
100 private static final Pattern PATTERN = Pattern.compile("[A-Za-z][A-Za-z0-9~/._+-]+"); |
|
101 |
|
102 /** |
|
103 * The time-zone ID, not null. |
98 * The time-zone ID, not null. |
104 */ |
99 */ |
105 private final String id; |
100 private final String id; |
106 /** |
101 /** |
107 * The time-zone rules, null if zone ID was loaded leniently. |
102 * The time-zone rules, null if zone ID was loaded leniently. |
108 */ |
103 */ |
109 private final transient ZoneRules rules; |
104 private final transient ZoneRules rules; |
110 |
|
111 /** |
|
112 * Obtains an instance of {@code ZoneRegion} from an identifier without checking |
|
113 * if the time-zone has available rules. |
|
114 * <p> |
|
115 * This method parses the ID and applies any appropriate normalization. |
|
116 * It does not validate the ID against the known set of IDs for which rules are available. |
|
117 * <p> |
|
118 * This method is intended for advanced use cases. |
|
119 * For example, consider a system that always retrieves time-zone rules from a remote server. |
|
120 * Using this factory would allow a {@code ZoneRegion}, and thus a {@code ZonedDateTime}, |
|
121 * to be created without loading the rules from the remote server. |
|
122 * |
|
123 * @param zoneId the time-zone ID, not null |
|
124 * @return the zone ID, not null |
|
125 * @throws DateTimeException if the ID format is invalid |
|
126 */ |
|
127 private static ZoneRegion ofLenient(String zoneId) { |
|
128 return ofId(zoneId, false); |
|
129 } |
|
130 |
105 |
131 /** |
106 /** |
132 * Obtains an instance of {@code ZoneId} from an identifier. |
107 * Obtains an instance of {@code ZoneId} from an identifier. |
133 * |
108 * |
134 * @param zoneId the time-zone ID, not null |
109 * @param zoneId the time-zone ID, not null |
137 * @throws DateTimeException if the ID format is invalid |
112 * @throws DateTimeException if the ID format is invalid |
138 * @throws ZoneRulesException if checking availability and the ID cannot be found |
113 * @throws ZoneRulesException if checking availability and the ID cannot be found |
139 */ |
114 */ |
140 static ZoneRegion ofId(String zoneId, boolean checkAvailable) { |
115 static ZoneRegion ofId(String zoneId, boolean checkAvailable) { |
141 Objects.requireNonNull(zoneId, "zoneId"); |
116 Objects.requireNonNull(zoneId, "zoneId"); |
142 if (zoneId.length() < 2 || |
117 checkName(zoneId); |
143 zoneId.startsWith("UT") || // includes UTC |
|
144 zoneId.startsWith("GMT") || |
|
145 (PATTERN.matcher(zoneId).matches() == false)) { |
|
146 throw new DateTimeException("Invalid ID for region-based ZoneId, invalid format: " + zoneId); |
|
147 } |
|
148 ZoneRules rules = null; |
118 ZoneRules rules = null; |
149 try { |
119 try { |
150 // always attempt load for better behavior after deserialization |
120 // always attempt load for better behavior after deserialization |
151 rules = ZoneRulesProvider.getRules(zoneId, true); |
121 rules = ZoneRulesProvider.getRules(zoneId, true); |
152 } catch (ZoneRulesException ex) { |
122 } catch (ZoneRulesException ex) { |
155 } |
125 } |
156 } |
126 } |
157 return new ZoneRegion(zoneId, rules); |
127 return new ZoneRegion(zoneId, rules); |
158 } |
128 } |
159 |
129 |
|
130 /** |
|
131 * Checks that the given string is a legal ZondId name. |
|
132 * |
|
133 * @param zoneId the time-zone ID, not null |
|
134 * @throws DateTimeException if the ID format is invalid |
|
135 */ |
|
136 private static void checkName(String zoneId) { |
|
137 int n = zoneId.length(); |
|
138 if (n < 2) { |
|
139 throw new DateTimeException("Invalid ID for region-based ZoneId, invalid format: " + zoneId); |
|
140 } |
|
141 for (int i = 0; i < n; i++) { |
|
142 char c = zoneId.charAt(i); |
|
143 if (c >= 'a' && c <= 'z') continue; |
|
144 if (c >= 'A' && c <= 'Z') continue; |
|
145 if (c == '/' && i != 0) continue; |
|
146 if (c >= '0' && c <= '9' && i != 0) continue; |
|
147 if (c == '~' && i != 0) continue; |
|
148 if (c == '.' && i != 0) continue; |
|
149 if (c == '_' && i != 0) continue; |
|
150 if (c == '+' && i != 0) continue; |
|
151 if (c == '-' && i != 0) continue; |
|
152 throw new DateTimeException("Invalid ID for region-based ZoneId, invalid format: " + zoneId); |
|
153 } |
|
154 } |
|
155 |
|
156 /** |
|
157 * Obtains an instance of {@code ZoneId} wrapping an offset. |
|
158 * <p> |
|
159 * For example, zone IDs like 'UTC', 'GMT', 'UT' and 'UTC+01:30' will be setup here. |
|
160 * |
|
161 * @param zoneId the time-zone ID, not null |
|
162 * @param offset the offset, not null |
|
163 * @return the zone ID, not null |
|
164 */ |
|
165 static ZoneRegion ofPrefixedOffset(String zoneId, ZoneOffset offset) { |
|
166 return new ZoneRegion(zoneId, offset.getRules()); |
|
167 } |
|
168 |
160 //------------------------------------------------------------------------- |
169 //------------------------------------------------------------------------- |
161 /** |
170 /** |
162 * Constructor. |
171 * Constructor. |
163 * |
172 * |
164 * @param id the time-zone ID, not null |
173 * @param id the time-zone ID, not null |