TimeZonesGmtTest.java
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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
*
* https://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 org.apache.commons.lang3.time;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
import java.util.TimeZone;
import org.apache.commons.lang3.SerializationUtils;
import org.junit.jupiter.api.Test;
/**
* Tests {@link ImmutableTimeZone} and {@link TimeZones#GMT}.
* <p>
* TimeZones.GMT is a public static final mutable TimeZone. Callers can call setRawOffset() / setID() to globally corrupt time zone behavior.
* </p>
* <p>
* Pre-patch: setRawOffset() / setID() succeed silently and mutate the shared singleton.
* </p>
* <p>
* Post-patch: setRawOffset() / setID() throw UnsupportedOperationException.
* </p>
*/
public class TimeZonesGmtTest {
@Test
public void testCloneReturnsSelfForImmutableSingleton() {
// For an immutable singleton, clone() should return the same instance,
// and there must be no need to defensively copy the value.
final Object copy = TimeZones.GMT.clone();
assertSame(TimeZones.GMT, copy, "clone() of an immutable singleton should return the same instance");
}
@Test
public void testGmtIdRemainsGmtAfterAttemptedMutation() {
assertThrows(UnsupportedOperationException.class, () -> TimeZones.GMT.setID("Etc/UTC"));
assertEquals("GMT", TimeZones.GMT.getID());
}
@Test
public void testGmtOffsetRemainsZeroAfterAttemptedMutation() {
assertThrows(UnsupportedOperationException.class, () -> TimeZones.GMT.setRawOffset(3600000));
assertEquals(0, TimeZones.GMT.getRawOffset());
}
@Test
public void testGmtTimeZoneIsImmutableSetId() {
assertThrows(UnsupportedOperationException.class, () -> TimeZones.GMT.setID("Etc/UTC"));
}
@Test
public void testGmtTimeZoneIsImmutableSetRawOffset() {
assertThrows(UnsupportedOperationException.class, () -> TimeZones.GMT.setRawOffset(3600000));
}
@Test
public void testSerializationRoundTripPreservesId() throws Exception {
// Round-trip via Serializable to confirm the type is wire-format friendly.
final TimeZone roundtrip = SerializationUtils.roundtrip(TimeZones.GMT);
assertNotNull(roundtrip, "Round-tripped TimeZone must not be null");
assertEquals("GMT", roundtrip.getID(), "Round-tripped TimeZone id must remain \"GMT\"");
assertEquals(0, roundtrip.getRawOffset(), "Round-tripped TimeZone offset must remain 0");
}
@Test
public void testSerializedFormUsesNamedClass() throws Exception {
// Wire-format hazard guard: ensure the implementation class is a named static
// nested type, not an anonymous inner class whose synthetic name (TimeZones$1)
// is brittle across non-functional code reorderings.
assertEquals(ImmutableTimeZone.class.getName(), TimeZones.GMT.getClass().getName());
}
}