Skip to content

Commit ab8f5ea

Browse files
authored
Forbid trappy methods from java.time (#28476)
ava.time has the functionality needed to deal with timezones with varying offsets correctly, but it also has a bunch of methods that silently let you forget about the hard cases, which raises the risk that we'll quietly do the wrong thing at some point in the future. This change adds the trappy methods to the list of forbidden methods to try and help stop this from happening. It also fixes the only use of these methods in the codebase so far: IngestDocument#deepCopy() used ZonedDateTime.of() which may alter the offset of the given time in cases where the offset is ambiguous.
1 parent 3ddea8d commit ab8f5ea

File tree

3 files changed

+73
-4
lines changed

3 files changed

+73
-4
lines changed

buildSrc/src/main/resources/forbidden/es-server-signatures.txt

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,56 @@ org.joda.time.DateTime#<init>(int, int, int, int, int, int)
8282
org.joda.time.DateTime#<init>(int, int, int, int, int, int, int)
8383
org.joda.time.DateTime#now()
8484
org.joda.time.DateTimeZone#getDefault()
85+
86+
@defaultMessage Local times may be ambiguous or nonexistent in a specific time zones. Use ZoneRules#getValidOffsets() instead.
87+
java.time.LocalDateTime#atZone(java.time.ZoneId)
88+
java.time.ZonedDateTime#of(int, int, int, int, int, int, int, java.time.ZoneId)
89+
java.time.ZonedDateTime#of(java.time.LocalDate, java.time.LocalTime, java.time.ZoneId)
90+
java.time.ZonedDateTime#of(java.time.LocalDateTime, java.time.ZoneId)
91+
java.time.ZonedDateTime#truncatedTo(java.time.temporal.TemporalUnit)
92+
java.time.ZonedDateTime#of(int, int, int, int, int, int, int, java.time.ZoneId)
93+
java.time.ZonedDateTime#of(java.time.LocalDate, java.time.LocalTime, java.time.ZoneId)
94+
java.time.ZonedDateTime#of(java.time.LocalDateTime, java.time.ZoneId)
95+
java.time.ZonedDateTime#ofLocal(java.time.LocalDateTime, java.time.ZoneId, java.time.ZoneOffset)
96+
java.time.OffsetDateTime#atZoneSimilarLocal(java.time.ZoneId)
97+
java.time.zone.ZoneRules#getOffset(java.time.LocalDateTime)
98+
99+
@defaultMessage Manipulation of an OffsetDateTime may yield a time that is not valid in the desired time zone. Use ZonedDateTime instead.
100+
java.time.OffsetDateTime#minus(long, java.time.temporal.TemporalUnit)
101+
java.time.OffsetDateTime#minus(long, java.time.temporal.TemporalUnit)
102+
java.time.OffsetDateTime#minus(java.time.temporal.TemporalAmount)
103+
java.time.OffsetDateTime#minusDays(long)
104+
java.time.OffsetDateTime#minusHours(long)
105+
java.time.OffsetDateTime#minusMinutes(long)
106+
java.time.OffsetDateTime#minusMonths(long)
107+
java.time.OffsetDateTime#minusNanos(long)
108+
java.time.OffsetDateTime#minusSeconds(long)
109+
java.time.OffsetDateTime#minusWeeks(long)
110+
java.time.OffsetDateTime#minusYears(long)
111+
java.time.OffsetDateTime#plus(long, java.time.temporal.TemporalUnit)
112+
java.time.OffsetDateTime#plus(java.time.temporal.TemporalAmount)
113+
java.time.OffsetDateTime#plusDays(long)
114+
java.time.OffsetDateTime#plusHours(long)
115+
java.time.OffsetDateTime#plusMinutes(long)
116+
java.time.OffsetDateTime#plusMonths(long)
117+
java.time.OffsetDateTime#plusNanos(long)
118+
java.time.OffsetDateTime#plusSeconds(long)
119+
java.time.OffsetDateTime#plusWeeks(long)
120+
java.time.OffsetDateTime#plusYears(long)
121+
java.time.OffsetDateTime#with(java.time.temporal.TemporalAdjuster)
122+
java.time.OffsetDateTime#with(java.time.temporal.TemporalField, long)
123+
java.time.OffsetDateTime#withDayOfMonth(int)
124+
java.time.OffsetDateTime#withDayOfYear(int)
125+
java.time.OffsetDateTime#withHour(int)
126+
java.time.OffsetDateTime#withMinute(int)
127+
java.time.OffsetDateTime#withMonth(int)
128+
java.time.OffsetDateTime#withNano(int)
129+
java.time.OffsetDateTime#withOffsetSameInstant(java.time.ZoneOffset)
130+
java.time.OffsetDateTime#withOffsetSameLocal(java.time.ZoneOffset)
131+
java.time.OffsetDateTime#withSecond(int)
132+
java.time.OffsetDateTime#withYear(int)
133+
134+
@defaultMessage Daylight saving is not the only reason for a change in timezone offset.
135+
java.time.zone.ZoneRules#getStandardOffset(java.time.Instant)
136+
java.time.zone.ZoneRules#getDaylightSavings(java.time.Instant)
137+
java.time.zone.ZoneRules#isDaylightSavings(java.time.Instant)

server/src/main/java/org/elasticsearch/ingest/IngestDocument.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -609,13 +609,11 @@ private static Object deepCopy(Object value) {
609609
return Arrays.copyOf(bytes, bytes.length);
610610
} else if (value == null || value instanceof String || value instanceof Integer ||
611611
value instanceof Long || value instanceof Float ||
612-
value instanceof Double || value instanceof Boolean) {
612+
value instanceof Double || value instanceof Boolean ||
613+
value instanceof ZonedDateTime) {
613614
return value;
614615
} else if (value instanceof Date) {
615616
return ((Date) value).clone();
616-
} else if (value instanceof ZonedDateTime) {
617-
ZonedDateTime zonedDateTime = (ZonedDateTime) value;
618-
return ZonedDateTime.of(zonedDateTime.toLocalDate(), zonedDateTime.toLocalTime(), zonedDateTime.getZone());
619617
} else {
620618
throw new IllegalArgumentException("unexpected value type [" + value.getClass() + "]");
621619
}

server/src/test/java/org/elasticsearch/ingest/IngestDocumentTests.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
import org.elasticsearch.test.ESTestCase;
2323
import org.junit.Before;
2424

25+
import java.time.Instant;
26+
import java.time.ZoneId;
2527
import java.time.ZoneOffset;
2628
import java.time.ZonedDateTime;
2729
import java.util.ArrayList;
@@ -986,6 +988,22 @@ public void testCopyConstructor() {
986988
assertIngestDocument(ingestDocument, copy);
987989
}
988990

991+
public void testCopyConstructorWithZonedDateTime() {
992+
ZoneId timezone = ZoneId.of("Europe/London");
993+
994+
Map<String, Object> sourceAndMetadata = new HashMap<>();
995+
sourceAndMetadata.put("beforeClockChange", ZonedDateTime.ofInstant(Instant.ofEpochSecond(1509237000), timezone));
996+
sourceAndMetadata.put("afterClockChange", ZonedDateTime.ofInstant(Instant.ofEpochSecond(1509240600), timezone));
997+
998+
IngestDocument original = new IngestDocument(sourceAndMetadata, new HashMap<>());
999+
IngestDocument copy = new IngestDocument(original);
1000+
1001+
assertThat(copy.getSourceAndMetadata().get("beforeClockChange"),
1002+
equalTo(original.getSourceAndMetadata().get("beforeClockChange")));
1003+
assertThat(copy.getSourceAndMetadata().get("afterClockChange"),
1004+
equalTo(original.getSourceAndMetadata().get("afterClockChange")));
1005+
}
1006+
9891007
public void testSetInvalidSourceField() throws Exception {
9901008
Map<String, Object> document = new HashMap<>();
9911009
Object randomObject = randomFrom(new ArrayList<>(), new HashMap<>(), 12, 12.34);

0 commit comments

Comments
 (0)