1 /*
   2  * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 /*
  25  * This file is available under and governed by the GNU General Public
  26  * License version 2 only, as published by the Free Software Foundation.
  27  * However, the following notice accompanied the original version of this
  28  * file:
  29  *
  30  * Copyright (c) 2008-2012, Stephen Colebourne & Michael Nascimento Santos
  31  *
  32  * All rights reserved.
  33  *
  34  * Redistribution and use in source and binary forms, with or without
  35  * modification, are permitted provided that the following conditions are met:
  36  *
  37  *  * Redistributions of source code must retain the above copyright notice,
  38  *    this list of conditions and the following disclaimer.
  39  *
  40  *  * Redistributions in binary form must reproduce the above copyright notice,
  41  *    this list of conditions and the following disclaimer in the documentation
  42  *    and/or other materials provided with the distribution.
  43  *
  44  *  * Neither the name of JSR-310 nor the names of its contributors
  45  *    may be used to endorse or promote products derived from this software
  46  *    without specific prior written permission.
  47  *
  48  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  49  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  50  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  51  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  52  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  53  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  54  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  55  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  56  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  57  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  58  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  59  */
  60 package tck.java.time;
  61 
  62 import static org.testng.Assert.assertEquals;
  63 import static org.testng.Assert.fail;
  64 
  65 import java.io.ByteArrayInputStream;
  66 import java.io.ByteArrayOutputStream;
  67 import java.io.DataInputStream;
  68 import java.io.DataOutputStream;
  69 import java.io.IOException;
  70 import java.io.ObjectInputStream;
  71 import java.io.ObjectStreamConstants;
  72 import java.lang.reflect.Field;
  73 import java.time.DateTimeException;
  74 import java.time.LocalTime;
  75 import java.time.ZoneId;
  76 import java.time.ZoneOffset;
  77 import java.time.temporal.Queries;
  78 import java.time.temporal.TemporalAccessor;
  79 import java.time.temporal.TemporalField;
  80 import java.time.temporal.TemporalQuery;
  81 import java.time.zone.ZoneRulesException;
  82 import java.util.HashMap;
  83 import java.util.Map;
  84 
  85 import org.testng.annotations.DataProvider;
  86 import org.testng.annotations.Test;
  87 
  88 /**
  89  * Test ZoneId.
  90  */
  91 @Test
  92 public class TCKZoneId extends AbstractTCKTest {
  93 
  94     //-----------------------------------------------------------------------
  95     @Test
  96     public void test_serialization() throws Exception {
  97         assertSerializable(ZoneId.of("Europe/London"));
  98         assertSerializable(ZoneId.of("America/Chicago"));
  99     }
 100 
 101     @Test
 102     public void test_serialization_format() throws Exception {
 103         ByteArrayOutputStream baos = new ByteArrayOutputStream();
 104         try (DataOutputStream dos = new DataOutputStream(baos) ) {
 105             dos.writeByte(7);
 106             dos.writeUTF("Europe/London");
 107         }
 108         byte[] bytes = baos.toByteArray();
 109         assertSerializedBySer(ZoneId.of("Europe/London"), bytes);
 110     }
 111 
 112     @Test
 113     public void test_deserialization_lenient_characters() throws Exception {
 114         // an ID can be loaded without validation during deserialization
 115         String id = "QWERTYUIOPASDFGHJKLZXCVBNM~/._+-";
 116         ZoneId deser = deserialize(id);
 117         // getting the ID and string are OK
 118         assertEquals(deser.getId(), id);
 119         assertEquals(deser.toString(), id);
 120         // getting the rules is not
 121         try {
 122             deser.getRules();
 123             fail();
 124         } catch (ZoneRulesException ex) {
 125             // expected
 126         }
 127     }
 128 
 129     @Test(expectedExceptions=DateTimeException.class)
 130     public void test_deserialization_lenient_badCharacters() throws Exception {
 131         // an ID can be loaded without validation during deserialization
 132         // but there is a check to ensure the ID format is valid
 133         deserialize("|!?");
 134     }
 135 
 136     @Test(dataProvider="offsetBasedValid", expectedExceptions=DateTimeException.class)
 137     public void test_deserialization_lenient_offsetNotAllowed_noPrefix(String input, String resolvedId) throws Exception {
 138         // an ID can be loaded without validation during deserialization
 139         // but there is a check to ensure the ID format is valid
 140         deserialize(input);
 141     }
 142 
 143     @Test(dataProvider="offsetBasedValid", expectedExceptions=DateTimeException.class)
 144     public void test_deserialization_lenient_offsetNotAllowed_prefixUTC(String input, String resolvedId) throws Exception {
 145         // an ID can be loaded without validation during deserialization
 146         // but there is a check to ensure the ID format is valid
 147         deserialize("UTC" + input);
 148     }
 149 
 150     @Test(dataProvider="offsetBasedValid", expectedExceptions=DateTimeException.class)
 151     public void test_deserialization_lenient_offsetNotAllowed_prefixGMT(String input, String resolvedId) throws Exception {
 152         // an ID can be loaded without validation during deserialization
 153         // but there is a check to ensure the ID format is valid
 154         deserialize("GMT" + input);
 155     }
 156 
 157     @Test(dataProvider="offsetBasedValid", expectedExceptions=DateTimeException.class)
 158     public void test_deserialization_lenient_offsetNotAllowed_prefixUT(String input, String resolvedId) throws Exception {
 159         // an ID can be loaded without validation during deserialization
 160         // but there is a check to ensure the ID format is valid
 161         deserialize("UT" + input);
 162     }
 163 
 164     private ZoneId deserialize(String id) throws Exception {
 165         String serClass = ZoneId.class.getPackage().getName() + ".Ser";
 166         Class<?> serCls = Class.forName(serClass);
 167         Field field = serCls.getDeclaredField("serialVersionUID");
 168         field.setAccessible(true);
 169         long serVer = (Long) field.get(null);
 170         ByteArrayOutputStream baos = new ByteArrayOutputStream();
 171         try (DataOutputStream dos = new DataOutputStream(baos)) {
 172             dos.writeShort(ObjectStreamConstants.STREAM_MAGIC);
 173             dos.writeShort(ObjectStreamConstants.STREAM_VERSION);
 174             dos.writeByte(ObjectStreamConstants.TC_OBJECT);
 175             dos.writeByte(ObjectStreamConstants.TC_CLASSDESC);
 176             dos.writeUTF(serClass);
 177             dos.writeLong(serVer);
 178             dos.writeByte(ObjectStreamConstants.SC_EXTERNALIZABLE | ObjectStreamConstants.SC_BLOCK_DATA);
 179             dos.writeShort(0);  // number of fields
 180             dos.writeByte(ObjectStreamConstants.TC_ENDBLOCKDATA);  // end of classdesc
 181             dos.writeByte(ObjectStreamConstants.TC_NULL);  // no superclasses
 182             dos.writeByte(ObjectStreamConstants.TC_BLOCKDATA);
 183             dos.writeByte(1 + 2 + id.length());  // length of data (1 byte + 2 bytes UTF length + 32 bytes UTF)
 184             dos.writeByte(7);  // ZoneId
 185             dos.writeUTF(id);
 186             dos.writeByte(ObjectStreamConstants.TC_ENDBLOCKDATA);  // end of blockdata
 187         }
 188         ZoneId deser = null;
 189         try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()))) {
 190             deser = (ZoneId) ois.readObject();
 191         }
 192         return deser;
 193     }
 194 
 195     //-----------------------------------------------------------------------
 196     // OLD_IDS_PRE_2005
 197     //-----------------------------------------------------------------------
 198     public void test_constant_OLD_IDS_PRE_2005() {
 199         Map<String, String> ids = ZoneId.OLD_IDS_PRE_2005;
 200         assertEquals(ids.get("EST"), "America/New_York");
 201         assertEquals(ids.get("MST"), "America/Denver");
 202         assertEquals(ids.get("HST"), "Pacific/Honolulu");
 203         assertEquals(ids.get("ACT"), "Australia/Darwin");
 204         assertEquals(ids.get("AET"), "Australia/Sydney");
 205         assertEquals(ids.get("AGT"), "America/Argentina/Buenos_Aires");
 206         assertEquals(ids.get("ART"), "Africa/Cairo");
 207         assertEquals(ids.get("AST"), "America/Anchorage");
 208         assertEquals(ids.get("BET"), "America/Sao_Paulo");
 209         assertEquals(ids.get("BST"), "Asia/Dhaka");
 210         assertEquals(ids.get("CAT"), "Africa/Harare");
 211         assertEquals(ids.get("CNT"), "America/St_Johns");
 212         assertEquals(ids.get("CST"), "America/Chicago");
 213         assertEquals(ids.get("CTT"), "Asia/Shanghai");
 214         assertEquals(ids.get("EAT"), "Africa/Addis_Ababa");
 215         assertEquals(ids.get("ECT"), "Europe/Paris");
 216         assertEquals(ids.get("IET"), "America/Indiana/Indianapolis");
 217         assertEquals(ids.get("IST"), "Asia/Kolkata");
 218         assertEquals(ids.get("JST"), "Asia/Tokyo");
 219         assertEquals(ids.get("MIT"), "Pacific/Apia");
 220         assertEquals(ids.get("NET"), "Asia/Yerevan");
 221         assertEquals(ids.get("NST"), "Pacific/Auckland");
 222         assertEquals(ids.get("PLT"), "Asia/Karachi");
 223         assertEquals(ids.get("PNT"), "America/Phoenix");
 224         assertEquals(ids.get("PRT"), "America/Puerto_Rico");
 225         assertEquals(ids.get("PST"), "America/Los_Angeles");
 226         assertEquals(ids.get("SST"), "Pacific/Guadalcanal");
 227         assertEquals(ids.get("VST"), "Asia/Ho_Chi_Minh");
 228     }
 229 
 230     @Test(expectedExceptions=UnsupportedOperationException.class)
 231     public void test_constant_OLD_IDS_PRE_2005_immutable() {
 232         Map<String, String> ids = ZoneId.OLD_IDS_PRE_2005;
 233         ids.clear();
 234     }
 235 
 236     //-----------------------------------------------------------------------
 237     // OLD_IDS_POST_2005
 238     //-----------------------------------------------------------------------
 239     public void test_constant_OLD_IDS_POST_2005() {
 240         Map<String, String> ids = ZoneId.OLD_IDS_POST_2005;
 241         assertEquals(ids.get("EST"), "-05:00");
 242         assertEquals(ids.get("MST"), "-07:00");
 243         assertEquals(ids.get("HST"), "-10:00");
 244         assertEquals(ids.get("ACT"), "Australia/Darwin");
 245         assertEquals(ids.get("AET"), "Australia/Sydney");
 246         assertEquals(ids.get("AGT"), "America/Argentina/Buenos_Aires");
 247         assertEquals(ids.get("ART"), "Africa/Cairo");
 248         assertEquals(ids.get("AST"), "America/Anchorage");
 249         assertEquals(ids.get("BET"), "America/Sao_Paulo");
 250         assertEquals(ids.get("BST"), "Asia/Dhaka");
 251         assertEquals(ids.get("CAT"), "Africa/Harare");
 252         assertEquals(ids.get("CNT"), "America/St_Johns");
 253         assertEquals(ids.get("CST"), "America/Chicago");
 254         assertEquals(ids.get("CTT"), "Asia/Shanghai");
 255         assertEquals(ids.get("EAT"), "Africa/Addis_Ababa");
 256         assertEquals(ids.get("ECT"), "Europe/Paris");
 257         assertEquals(ids.get("IET"), "America/Indiana/Indianapolis");
 258         assertEquals(ids.get("IST"), "Asia/Kolkata");
 259         assertEquals(ids.get("JST"), "Asia/Tokyo");
 260         assertEquals(ids.get("MIT"), "Pacific/Apia");
 261         assertEquals(ids.get("NET"), "Asia/Yerevan");
 262         assertEquals(ids.get("NST"), "Pacific/Auckland");
 263         assertEquals(ids.get("PLT"), "Asia/Karachi");
 264         assertEquals(ids.get("PNT"), "America/Phoenix");
 265         assertEquals(ids.get("PRT"), "America/Puerto_Rico");
 266         assertEquals(ids.get("PST"), "America/Los_Angeles");
 267         assertEquals(ids.get("SST"), "Pacific/Guadalcanal");
 268         assertEquals(ids.get("VST"), "Asia/Ho_Chi_Minh");
 269     }
 270 
 271     @Test(expectedExceptions=UnsupportedOperationException.class)
 272     public void test_constant_OLD_IDS_POST_2005_immutable() {
 273         Map<String, String> ids = ZoneId.OLD_IDS_POST_2005;
 274         ids.clear();
 275     }
 276 
 277     //-----------------------------------------------------------------------
 278     // mapped factory
 279     //-----------------------------------------------------------------------
 280     @Test
 281     public void test_of_string_Map() {
 282         Map<String, String> map = new HashMap<>();
 283         map.put("LONDON", "Europe/London");
 284         map.put("PARIS", "Europe/Paris");
 285         ZoneId test = ZoneId.of("LONDON", map);
 286         assertEquals(test.getId(), "Europe/London");
 287     }
 288 
 289     @Test
 290     public void test_of_string_Map_lookThrough() {
 291         Map<String, String> map = new HashMap<>();
 292         map.put("LONDON", "Europe/London");
 293         map.put("PARIS", "Europe/Paris");
 294         ZoneId test = ZoneId.of("Europe/Madrid", map);
 295         assertEquals(test.getId(), "Europe/Madrid");
 296     }
 297 
 298     @Test
 299     public void test_of_string_Map_emptyMap() {
 300         Map<String, String> map = new HashMap<>();
 301         ZoneId test = ZoneId.of("Europe/Madrid", map);
 302         assertEquals(test.getId(), "Europe/Madrid");
 303     }
 304 
 305     @Test(expectedExceptions=DateTimeException.class)
 306     public void test_of_string_Map_badFormat() {
 307         Map<String, String> map = new HashMap<>();
 308         ZoneId.of("Not known", map);
 309     }
 310 
 311     @Test(expectedExceptions=ZoneRulesException.class)
 312     public void test_of_string_Map_unknown() {
 313         Map<String, String> map = new HashMap<>();
 314         ZoneId.of("Unknown", map);
 315     }
 316 
 317     //-----------------------------------------------------------------------
 318     // regular factory
 319     //-----------------------------------------------------------------------
 320     @DataProvider(name="offsetBasedZero")
 321     Object[][] data_offsetBasedZero() {
 322         return new Object[][] {
 323                 {""}, {"0"},
 324                 {"+00"},{"+0000"},{"+00:00"},{"+000000"},{"+00:00:00"},
 325                 {"-00"},{"-0000"},{"-00:00"},{"-000000"},{"-00:00:00"},
 326         };
 327     }
 328 
 329     @Test(dataProvider="offsetBasedZero")
 330     public void factory_of_String_offsetBasedZero_noPrefix(String id) {
 331         if (id.length() > 0 && id.equals("0") == false) {
 332             ZoneId test = ZoneId.of(id);
 333             assertEquals(test, ZoneOffset.UTC);
 334         }
 335     }
 336 
 337     @Test(dataProvider="offsetBasedZero")
 338     public void factory_of_String_offsetBasedZero_prefixUTC(String id) {
 339         ZoneId test = ZoneId.of("UTC" + id);
 340         assertEquals(test, ZoneOffset.UTC);
 341     }
 342 
 343     @Test(dataProvider="offsetBasedZero")
 344     public void factory_of_String_offsetBasedZero_prefixGMT(String id) {
 345         ZoneId test = ZoneId.of("GMT" + id);
 346         assertEquals(test, ZoneOffset.UTC);
 347     }
 348 
 349     @Test(dataProvider="offsetBasedZero")
 350     public void factory_of_String_offsetBasedZero_prefixUT(String id) {
 351         ZoneId test = ZoneId.of("UT" + id);
 352         assertEquals(test, ZoneOffset.UTC);
 353     }
 354 
 355     @Test
 356     public void factory_of_String_offsetBasedZero_z() {
 357         ZoneId test = ZoneId.of("Z");
 358         assertEquals(test, ZoneOffset.UTC);
 359     }
 360 
 361     //-----------------------------------------------------------------------
 362     @DataProvider(name="offsetBasedValid")
 363     Object[][] data_offsetBasedValid() {
 364         return new Object[][] {
 365                 {"+0", "Z"},
 366                 {"+5", "+05:00"},
 367                 {"+01", "+01:00"},
 368                 {"+0100", "+01:00"},{"+01:00", "+01:00"},
 369                 {"+010000", "+01:00"},{"+01:00:00", "+01:00"},
 370                 {"+12", "+12:00"},
 371                 {"+1234", "+12:34"},{"+12:34", "+12:34"},
 372                 {"+123456", "+12:34:56"},{"+12:34:56", "+12:34:56"},
 373                 {"-02", "-02:00"},
 374                 {"-5", "-05:00"},
 375                 {"-0200", "-02:00"},{"-02:00", "-02:00"},
 376                 {"-020000", "-02:00"},{"-02:00:00", "-02:00"},
 377         };
 378     }
 379 
 380     @Test(dataProvider="offsetBasedValid")
 381     public void factory_of_String_offsetBasedValid_noPrefix(String input, String id) {
 382         ZoneId test = ZoneId.of(input);
 383         assertEquals(test.getId(), id);
 384         assertEquals(test, ZoneOffset.of(id));
 385     }
 386 
 387     @Test(dataProvider="offsetBasedValid")
 388     public void factory_of_String_offsetBasedValid_prefixUTC(String input, String id) {
 389         ZoneId test = ZoneId.of("UTC" + input);
 390         assertEquals(test.getId(), id);
 391         assertEquals(test, ZoneOffset.of(id));
 392     }
 393 
 394     @Test(dataProvider="offsetBasedValid")
 395     public void factory_of_String_offsetBasedValid_prefixGMT(String input, String id) {
 396         ZoneId test = ZoneId.of("GMT" + input);
 397         assertEquals(test.getId(), id);
 398         assertEquals(test, ZoneOffset.of(id));
 399     }
 400 
 401     @Test(dataProvider="offsetBasedValid")
 402     public void factory_of_String_offsetBasedValid_prefixUT(String input, String id) {
 403         ZoneId test = ZoneId.of("UT" + input);
 404         assertEquals(test.getId(), id);
 405         assertEquals(test, ZoneOffset.of(id));
 406     }
 407 
 408     //-----------------------------------------------------------------------
 409     @DataProvider(name="offsetBasedInvalid")
 410     Object[][] data_offsetBasedInvalid() {
 411         return new Object[][] {
 412                 {"A"}, {"B"}, {"C"}, {"D"}, {"E"}, {"F"}, {"G"}, {"H"}, {"I"}, {"J"}, {"K"}, {"L"}, {"M"},
 413                 {"N"}, {"O"}, {"P"}, {"Q"}, {"R"}, {"S"}, {"T"}, {"U"}, {"V"}, {"W"}, {"X"}, {"Y"}, {"Z"},
 414                 {"+0:00"}, {"+00:0"}, {"+0:0"},
 415                 {"+000"}, {"+00000"},
 416                 {"+0:00:00"}, {"+00:0:00"}, {"+00:00:0"}, {"+0:0:0"}, {"+0:0:00"}, {"+00:0:0"}, {"+0:00:0"},
 417                 {"+01_00"}, {"+01;00"}, {"+01@00"}, {"+01:AA"},
 418                 {"+19"}, {"+19:00"}, {"+18:01"}, {"+18:00:01"}, {"+1801"}, {"+180001"},
 419                 {"-0:00"}, {"-00:0"}, {"-0:0"},
 420                 {"-000"}, {"-00000"},
 421                 {"-0:00:00"}, {"-00:0:00"}, {"-00:00:0"}, {"-0:0:0"}, {"-0:0:00"}, {"-00:0:0"}, {"-0:00:0"},
 422                 {"-19"}, {"-19:00"}, {"-18:01"}, {"-18:00:01"}, {"-1801"}, {"-180001"},
 423                 {"-01_00"}, {"-01;00"}, {"-01@00"}, {"-01:AA"},
 424                 {"@01:00"},
 425         };
 426     }
 427 
 428     @Test(dataProvider="offsetBasedInvalid", expectedExceptions=DateTimeException.class)
 429     public void factory_of_String_offsetBasedInvalid_noPrefix(String id) {
 430         if (id.equals("Z")) {
 431             throw new DateTimeException("Fake exception: Z alone is valid, not invalid");
 432         }
 433         ZoneId.of(id);
 434     }
 435 
 436     @Test(dataProvider="offsetBasedInvalid", expectedExceptions=DateTimeException.class)
 437     public void factory_of_String_offsetBasedInvalid_prefixUTC(String id) {
 438         ZoneId.of("UTC" + id);
 439     }
 440 
 441     @Test(dataProvider="offsetBasedInvalid", expectedExceptions=DateTimeException.class)
 442     public void factory_of_String_offsetBasedInvalid_prefixGMT(String id) {
 443         ZoneId.of("GMT" + id);
 444     }
 445 
 446     @Test(dataProvider="offsetBasedInvalid", expectedExceptions=DateTimeException.class)
 447     public void factory_of_String_offsetBasedInvalid_prefixUT(String id) {
 448         if (id.equals("C")) {
 449             throw new DateTimeException("Fake exception: UT + C = UTC, thus it is valid, not invalid");
 450         }
 451         ZoneId.of("UT" + id);
 452     }
 453 
 454     //-----------------------------------------------------------------------
 455     @DataProvider(name="regionBasedInvalid")
 456     Object[][] data_regionBasedInvalid() {
 457         // \u00ef is a random unicode character
 458         return new Object[][] {
 459                 {""}, {":"}, {"#"},
 460                 {"\u00ef"}, {"`"}, {"!"}, {"\""}, {"\u00ef"}, {"$"}, {"^"}, {"&"}, {"*"}, {"("}, {")"}, {"="},
 461                 {"\\"}, {"|"}, {","}, {"<"}, {">"}, {"?"}, {";"}, {"'"}, {"["}, {"]"}, {"{"}, {"}"},
 462                 {"\u00ef:A"}, {"`:A"}, {"!:A"}, {"\":A"}, {"\u00ef:A"}, {"$:A"}, {"^:A"}, {"&:A"}, {"*:A"}, {"(:A"}, {"):A"}, {"=:A"}, {"+:A"},
 463                 {"\\:A"}, {"|:A"}, {",:A"}, {"<:A"}, {">:A"}, {"?:A"}, {";:A"}, {"::A"}, {"':A"}, {"@:A"}, {"~:A"}, {"[:A"}, {"]:A"}, {"{:A"}, {"}:A"},
 464                 {"A:B#\u00ef"}, {"A:B#`"}, {"A:B#!"}, {"A:B#\""}, {"A:B#\u00ef"}, {"A:B#$"}, {"A:B#^"}, {"A:B#&"}, {"A:B#*"},
 465                 {"A:B#("}, {"A:B#)"}, {"A:B#="}, {"A:B#+"},
 466                 {"A:B#\\"}, {"A:B#|"}, {"A:B#,"}, {"A:B#<"}, {"A:B#>"}, {"A:B#?"}, {"A:B#;"}, {"A:B#:"},
 467                 {"A:B#'"}, {"A:B#@"}, {"A:B#~"}, {"A:B#["}, {"A:B#]"}, {"A:B#{"}, {"A:B#}"},
 468         };
 469     }
 470 
 471     @Test(dataProvider="regionBasedInvalid", expectedExceptions=DateTimeException.class)
 472     public void factory_of_String_regionBasedInvalid(String id) {
 473         ZoneId.of(id);
 474     }
 475 
 476     //-----------------------------------------------------------------------
 477     @Test
 478     public void factory_of_String_region_EuropeLondon() {
 479         ZoneId test = ZoneId.of("Europe/London");
 480         assertEquals(test.getId(), "Europe/London");
 481         assertEquals(test.getRules().isFixedOffset(), false);
 482     }
 483 
 484     //-----------------------------------------------------------------------
 485     @Test(expectedExceptions=NullPointerException.class)
 486     public void factory_of_String_null() {
 487         ZoneId.of(null);
 488     }
 489 
 490     @Test(expectedExceptions=DateTimeException.class)
 491     public void factory_of_String_badFormat() {
 492         ZoneId.of("Unknown rule");
 493     }
 494 
 495     @Test(expectedExceptions=ZoneRulesException.class)
 496     public void factory_of_String_unknown() {
 497         ZoneId.of("Unknown");
 498     }
 499 
 500     //-----------------------------------------------------------------------
 501     // from(TemporalAccessor)
 502     //-----------------------------------------------------------------------
 503     @Test
 504     public void factory_from_TemporalAccessor_zoneId() {
 505         TemporalAccessor mock = new TemporalAccessor() {
 506             @Override
 507             public boolean isSupported(TemporalField field) {
 508                 return false;
 509             }
 510             @Override
 511             public long getLong(TemporalField field) {
 512                 throw new DateTimeException("Mock");
 513             }
 514             @SuppressWarnings("unchecked")
 515             @Override
 516             public <R> R query(TemporalQuery<R> query) {
 517                 if (query == Queries.zoneId()) {
 518                     return (R) ZoneId.of("Europe/Paris");
 519                 }
 520                 return TemporalAccessor.super.query(query);
 521             }
 522         };
 523         assertEquals(ZoneId.from(mock),  ZoneId.of("Europe/Paris"));
 524     }
 525 
 526     @Test
 527     public void factory_from_TemporalAccessor_offset() {
 528         ZoneOffset offset = ZoneOffset.ofHours(1);
 529         assertEquals(ZoneId.from(offset), offset);
 530     }
 531 
 532     @Test(expectedExceptions=DateTimeException.class)
 533     public void factory_from_TemporalAccessor_invalid_noDerive() {
 534         ZoneId.from(LocalTime.of(12, 30));
 535     }
 536 
 537     @Test(expectedExceptions=NullPointerException.class)
 538     public void factory_from_TemporalAccessor_null() {
 539         ZoneId.from(null);
 540     }
 541 
 542     //-----------------------------------------------------------------------
 543     // equals() / hashCode()
 544     //-----------------------------------------------------------------------
 545     @Test
 546     public void test_equals() {
 547         ZoneId test1 = ZoneId.of("Europe/London");
 548         ZoneId test2 = ZoneId.of("Europe/Paris");
 549         ZoneId test2b = ZoneId.of("Europe/Paris");
 550         assertEquals(test1.equals(test2), false);
 551         assertEquals(test2.equals(test1), false);
 552 
 553         assertEquals(test1.equals(test1), true);
 554         assertEquals(test2.equals(test2), true);
 555         assertEquals(test2.equals(test2b), true);
 556 
 557         assertEquals(test1.hashCode() == test1.hashCode(), true);
 558         assertEquals(test2.hashCode() == test2.hashCode(), true);
 559         assertEquals(test2.hashCode() == test2b.hashCode(), true);
 560     }
 561 
 562     @Test
 563     public void test_equals_null() {
 564         assertEquals(ZoneId.of("Europe/London").equals(null), false);
 565     }
 566 
 567     @Test
 568     public void test_equals_notEqualWrongType() {
 569         assertEquals(ZoneId.of("Europe/London").equals("Europe/London"), false);
 570     }
 571 
 572     //-----------------------------------------------------------------------
 573     // toString()
 574     //-----------------------------------------------------------------------
 575     @DataProvider(name="toString")
 576     Object[][] data_toString() {
 577         return new Object[][] {
 578                 {"Europe/London", "Europe/London"},
 579                 {"Europe/Paris", "Europe/Paris"},
 580                 {"Europe/Berlin", "Europe/Berlin"},
 581                 {"UTC", "Z"},
 582                 {"UTC+01:00", "+01:00"},
 583         };
 584     }
 585 
 586     @Test(dataProvider="toString")
 587     public void test_toString(String id, String expected) {
 588         ZoneId test = ZoneId.of(id);
 589         assertEquals(test.toString(), expected);
 590     }
 591 
 592 }