From cd7d144601f2ea1f298adc5adbf70874a51c143a Mon Sep 17 00:00:00 2001 From: Kamilkime Date: Mon, 25 May 2026 18:22:44 +0200 Subject: [PATCH 1/4] NIFI-15973 Fix issues caused by the use of deprecated GregorianCalendar --- .../field/ObjectLocalDateFieldConverter.java | 6 +- .../ObjectLocalDateTimeFieldConverter.java | 5 +- .../field/ObjectStringFieldConverter.java | 8 +-- .../field/ObjectTimestampFieldConverter.java | 6 +- .../record/util/DataTypeUtils.java | 4 +- .../ObjectLocalDateFieldConverterTest.java | 67 +++++++++++++++++++ .../field/ObjectStringFieldConverterTest.java | 15 +++++ .../ObjectTimestampFieldConverterTest.java | 20 ++++++ ...TestObjectLocalDateTimeFieldConverter.java | 20 ++++++ .../org/apache/nifi/avro/AvroTypeUtil.java | 10 +-- 10 files changed, 147 insertions(+), 14 deletions(-) create mode 100644 nifi-commons/nifi-record/src/test/java/org/apache/nifi/serialization/record/field/ObjectLocalDateFieldConverterTest.java diff --git a/nifi-commons/nifi-record/src/main/java/org/apache/nifi/serialization/record/field/ObjectLocalDateFieldConverter.java b/nifi-commons/nifi-record/src/main/java/org/apache/nifi/serialization/record/field/ObjectLocalDateFieldConverter.java index 63f2633998fd..fccff92e74e4 100644 --- a/nifi-commons/nifi-record/src/main/java/org/apache/nifi/serialization/record/field/ObjectLocalDateFieldConverter.java +++ b/nifi-commons/nifi-record/src/main/java/org/apache/nifi/serialization/record/field/ObjectLocalDateFieldConverter.java @@ -49,7 +49,11 @@ public LocalDate convertField(final Object field, final Optional pattern return localDate; } case Date date -> { - return date.toLocalDate(); + // Avoid java.sql.Date#toLocalDate which uses deprecated getYear/getMonth/getDate + // accessors backed by GregorianCalendar, applying Julian calendar semantics for years + // before 1582 and shifting pre-1582 dates by approximately two days. java.sql.Date + // does not implement toInstant, so the epoch milliseconds are read explicitly. + return ofInstant(Instant.ofEpochMilli(date.getTime())); } case java.util.Date date -> { final Instant instant = date.toInstant(); diff --git a/nifi-commons/nifi-record/src/main/java/org/apache/nifi/serialization/record/field/ObjectLocalDateTimeFieldConverter.java b/nifi-commons/nifi-record/src/main/java/org/apache/nifi/serialization/record/field/ObjectLocalDateTimeFieldConverter.java index 3c84ad8d3a6d..e06572b571e2 100644 --- a/nifi-commons/nifi-record/src/main/java/org/apache/nifi/serialization/record/field/ObjectLocalDateTimeFieldConverter.java +++ b/nifi-commons/nifi-record/src/main/java/org/apache/nifi/serialization/record/field/ObjectLocalDateTimeFieldConverter.java @@ -64,7 +64,10 @@ public LocalDateTime convertField(final Object field, final Optional pat return localDateTime; } case Timestamp timestamp -> { - return timestamp.toLocalDateTime(); + // Avoid Timestamp#toLocalDateTime which routes through deprecated GregorianCalendar + // and applies Julian calendar semantics for years before 1582, shifting pre-1582 + // timestamps by approximately two days. + return ofInstant(timestamp.toInstant()); } case Date date -> { // java.sql.Date and java.sql.Time do not support the toInstant() method so using getTime() is required diff --git a/nifi-commons/nifi-record/src/main/java/org/apache/nifi/serialization/record/field/ObjectStringFieldConverter.java b/nifi-commons/nifi-record/src/main/java/org/apache/nifi/serialization/record/field/ObjectStringFieldConverter.java index 65b9c92d34b2..4ffcccb26b35 100644 --- a/nifi-commons/nifi-record/src/main/java/org/apache/nifi/serialization/record/field/ObjectStringFieldConverter.java +++ b/nifi-commons/nifi-record/src/main/java/org/apache/nifi/serialization/record/field/ObjectStringFieldConverter.java @@ -23,7 +23,6 @@ import java.sql.Clob; import java.sql.Timestamp; import java.time.Instant; -import java.time.LocalDateTime; import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; @@ -58,10 +57,9 @@ public String convertField(final Object field, final Optional pattern, f return Long.toString(timestamp.getTime()); } final DateTimeFormatter formatter = DateTimeFormatterRegistry.getDateTimeFormatter(pattern.get()); - final LocalDateTime localDateTime = timestamp.toLocalDateTime(); - - // Convert LocalDateTime to ZonedDateTime using system default zone to support offsets in Date Time Formatter - final ZonedDateTime dateTime = ZonedDateTime.of(localDateTime, ZoneId.systemDefault()); + // Avoid Timestamp#toLocalDateTime which routes through deprecated GregorianCalendar + // and applies Julian calendar semantics for years before 1582. + final ZonedDateTime dateTime = timestamp.toInstant().atZone(ZoneId.systemDefault()); return formatter.format(dateTime); } case Date date -> { diff --git a/nifi-commons/nifi-record/src/main/java/org/apache/nifi/serialization/record/field/ObjectTimestampFieldConverter.java b/nifi-commons/nifi-record/src/main/java/org/apache/nifi/serialization/record/field/ObjectTimestampFieldConverter.java index aa8f5b48b230..981f650970bf 100644 --- a/nifi-commons/nifi-record/src/main/java/org/apache/nifi/serialization/record/field/ObjectTimestampFieldConverter.java +++ b/nifi-commons/nifi-record/src/main/java/org/apache/nifi/serialization/record/field/ObjectTimestampFieldConverter.java @@ -20,6 +20,7 @@ import java.sql.Timestamp; import java.time.LocalDateTime; +import java.time.ZoneId; import java.util.Optional; /** @@ -40,6 +41,9 @@ class ObjectTimestampFieldConverter implements FieldConverter @Override public Timestamp convertField(final Object field, final Optional pattern, final String name) { final LocalDateTime localDateTime = CONVERTER.convertField(field, pattern, name); - return localDateTime == null ? null : Timestamp.valueOf(localDateTime); + // Avoid Timestamp.valueOf(LocalDateTime) which routes through deprecated GregorianCalendar + // and applies Julian calendar semantics for years before 1582, shifting pre-1582 timestamps + // by approximately two days. + return localDateTime == null ? null : Timestamp.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant()); } } diff --git a/nifi-commons/nifi-record/src/main/java/org/apache/nifi/serialization/record/util/DataTypeUtils.java b/nifi-commons/nifi-record/src/main/java/org/apache/nifi/serialization/record/util/DataTypeUtils.java index 8e02b4a1d1e5..2ae785d576b9 100644 --- a/nifi-commons/nifi-record/src/main/java/org/apache/nifi/serialization/record/util/DataTypeUtils.java +++ b/nifi-commons/nifi-record/src/main/java/org/apache/nifi/serialization/record/util/DataTypeUtils.java @@ -200,7 +200,9 @@ public static Object convertType( case DATE: final FieldConverter localDateConverter = StandardFieldConverterRegistry.getRegistry().getFieldConverter(LocalDate.class); final LocalDate localDate = localDateConverter.convertField(value, dateFormat, fieldName); - return localDate == null ? null : Date.valueOf(localDate); + // Avoid Date.valueOf(LocalDate) which routes through deprecated GregorianCalendar + // and applies Julian calendar semantics for years before 1582. + return localDate == null ? null : new Date(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant().toEpochMilli()); case DECIMAL: return toBigDecimal(value, fieldName); case DOUBLE: diff --git a/nifi-commons/nifi-record/src/test/java/org/apache/nifi/serialization/record/field/ObjectLocalDateFieldConverterTest.java b/nifi-commons/nifi-record/src/test/java/org/apache/nifi/serialization/record/field/ObjectLocalDateFieldConverterTest.java new file mode 100644 index 000000000000..b451a6902444 --- /dev/null +++ b/nifi-commons/nifi-record/src/test/java/org/apache/nifi/serialization/record/field/ObjectLocalDateFieldConverterTest.java @@ -0,0 +1,67 @@ +/* + * 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 + * + * http://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.nifi.serialization.record.field; + +import org.junit.jupiter.api.Test; + +import java.sql.Date; +import java.time.LocalDate; +import java.time.ZoneId; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +class ObjectLocalDateFieldConverterTest { + private static final ObjectLocalDateFieldConverter CONVERTER = new ObjectLocalDateFieldConverter(); + + private static final String FIELD_NAME = LocalDate.class.getSimpleName(); + + @Test + void testConvertFieldNull() { + assertNull(CONVERTER.convertField(null, Optional.empty(), FIELD_NAME)); + } + + @Test + void testConvertFieldLocalDate() { + final LocalDate input = LocalDate.of(2025, 5, 25); + assertEquals(input, CONVERTER.convertField(input, Optional.empty(), FIELD_NAME)); + } + + @Test + void testConvertFieldSqlDateModernYear() { + final LocalDate localDate = LocalDate.of(2025, 5, 25); + final Date sqlDate = new Date(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant().toEpochMilli()); + + final LocalDate result = CONVERTER.convertField(sqlDate, Optional.empty(), FIELD_NAME); + + assertEquals(localDate, result); + } + + @Test + void testConvertFieldSqlDateYearOneIsProlepticGregorian() { + // A java.sql.Date built from a proleptic-Gregorian year-1 epoch must round-trip back + // to year 1, not be shifted to year 0 by Julian calendar semantics in + // java.sql.Date#toLocalDate (which uses deprecated getYear/getMonth/getDate). + final LocalDate yearOne = LocalDate.of(1, 1, 1); + final Date sqlDate = new Date(yearOne.atStartOfDay(ZoneId.systemDefault()).toInstant().toEpochMilli()); + + final LocalDate result = CONVERTER.convertField(sqlDate, Optional.empty(), FIELD_NAME); + + assertEquals(yearOne, result); + } +} diff --git a/nifi-commons/nifi-record/src/test/java/org/apache/nifi/serialization/record/field/ObjectStringFieldConverterTest.java b/nifi-commons/nifi-record/src/test/java/org/apache/nifi/serialization/record/field/ObjectStringFieldConverterTest.java index e73cc490f4ac..b496ec79d0e7 100644 --- a/nifi-commons/nifi-record/src/test/java/org/apache/nifi/serialization/record/field/ObjectStringFieldConverterTest.java +++ b/nifi-commons/nifi-record/src/test/java/org/apache/nifi/serialization/record/field/ObjectStringFieldConverterTest.java @@ -24,8 +24,10 @@ import java.time.LocalDateTime; import java.time.ZoneId; import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; import java.time.zone.ZoneRules; import java.util.Date; +import java.util.Locale; import java.util.Optional; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -158,6 +160,19 @@ void testConvertFieldObjectArrayEmpty() { assertEquals(EMPTY_ARRAY_STRING, string); } + @Test + void testConvertFieldTimestampYearOneIsProlepticGregorian() { + // A Timestamp built from a proleptic-Gregorian year-1 Instant must format with year 1 + // (not "0000-12-30" produced when Timestamp#toLocalDateTime applies Julian semantics). + final LocalDateTime yearOne = LocalDateTime.of(1, 1, 1, 12, 0, 0); + final Timestamp timestamp = Timestamp.from(yearOne.atZone(ZoneId.systemDefault()).toInstant()); + + final String formatted = CONVERTER.convertField(timestamp, Optional.of(DEFAULT_PATTERN), FIELD_NAME); + + final String expected = DateTimeFormatter.ofPattern(DEFAULT_PATTERN, Locale.ROOT).format(yearOne); + assertEquals(expected, formatted); + } + private String getDateTimeZoneOffset() { final Timestamp inputTimestamp = Timestamp.valueOf(DATE_TIME_DEFAULT); final LocalDateTime inputLocalDateTime = inputTimestamp.toLocalDateTime(); diff --git a/nifi-commons/nifi-record/src/test/java/org/apache/nifi/serialization/record/field/ObjectTimestampFieldConverterTest.java b/nifi-commons/nifi-record/src/test/java/org/apache/nifi/serialization/record/field/ObjectTimestampFieldConverterTest.java index 2d92724c2bd9..c152335ad5e3 100644 --- a/nifi-commons/nifi-record/src/test/java/org/apache/nifi/serialization/record/field/ObjectTimestampFieldConverterTest.java +++ b/nifi-commons/nifi-record/src/test/java/org/apache/nifi/serialization/record/field/ObjectTimestampFieldConverterTest.java @@ -162,6 +162,26 @@ public void testConvertFieldStringFormatCustomZoneOffsetCoordinatedUniversalTime assertEquals(expected, timestamp); } + @Test + public void testConvertFieldStringYearOneIsProlepticGregorian() { + // Verify that year 1 in the source string is interpreted via proleptic Gregorian + // (matching java.time semantics) rather than the Julian calendar that would route + // through the deprecated GregorianCalendar in Timestamp.valueOf and shift the value + // by approximately two days. + final String yearOne = "0001-01-01 12:00:00"; + final Timestamp timestamp = CONVERTER.convertField(yearOne, DEFAULT_PATTERN, FIELD_NAME); + final Instant expected = LocalDateTime.of(1, 1, 1, 12, 0, 0).atZone(ZoneId.systemDefault()).toInstant(); + assertEquals(expected, timestamp.toInstant()); + } + + @Test + public void testConvertFieldLocalDateTimeYearOneIsProlepticGregorian() { + final LocalDateTime yearOne = LocalDateTime.of(1, 1, 1, 12, 0, 0); + final Timestamp timestamp = CONVERTER.convertField(yearOne, DEFAULT_PATTERN, FIELD_NAME); + final Instant expected = yearOne.atZone(ZoneId.systemDefault()).toInstant(); + assertEquals(expected, timestamp.toInstant()); + } + private Timestamp getDateTimeCoordinatedUniversalTime() { final Timestamp dateTime = Timestamp.valueOf(DATE_TIME_DEFAULT); final LocalDateTime localDateTime = dateTime.toLocalDateTime(); diff --git a/nifi-commons/nifi-record/src/test/java/org/apache/nifi/serialization/record/field/TestObjectLocalDateTimeFieldConverter.java b/nifi-commons/nifi-record/src/test/java/org/apache/nifi/serialization/record/field/TestObjectLocalDateTimeFieldConverter.java index 1aeabfc527f0..34269d3030e3 100644 --- a/nifi-commons/nifi-record/src/test/java/org/apache/nifi/serialization/record/field/TestObjectLocalDateTimeFieldConverter.java +++ b/nifi-commons/nifi-record/src/test/java/org/apache/nifi/serialization/record/field/TestObjectLocalDateTimeFieldConverter.java @@ -19,6 +19,7 @@ import org.junit.jupiter.api.Test; +import java.sql.Timestamp; import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneId; @@ -100,4 +101,23 @@ public void testWithDateFormatMicrosecondPrecision() { final LocalDateTime result = converter.convertField(MICROS_TIMESTAMP_LONG, Optional.of("yyyy-MM-dd'T'HH:mm:ss.SSSSSS"), FIELD_NAME); assertEquals(LOCAL_DATE_TIME_MICROS_PRECISION, result); } + + @Test + public void testConvertTimestampYearOneIsProlepticGregorian() { + // A Timestamp constructed directly from a proleptic-Gregorian year-1 Instant must + // round-trip back to year 1, not be shifted to year 0 by Julian calendar semantics + // in Timestamp#toLocalDateTime. + final LocalDateTime yearOne = LocalDateTime.of(1, 1, 1, 12, 0, 0); + final Timestamp timestamp = Timestamp.from(yearOne.atZone(ZoneId.systemDefault()).toInstant()); + + final LocalDateTime result = converter.convertField(timestamp, Optional.empty(), FIELD_NAME); + + assertEquals(yearOne, result); + } + + @Test + public void testConvertStringYearOneIsProlepticGregorian() { + final LocalDateTime result = converter.convertField("0001-01-01 12:00:00", Optional.of("yyyy-MM-dd HH:mm:ss"), FIELD_NAME); + assertEquals(LocalDateTime.of(1, 1, 1, 12, 0, 0), result); + } } diff --git a/nifi-extension-bundles/nifi-extension-utils/nifi-record-utils/nifi-avro-record-utils/src/main/java/org/apache/nifi/avro/AvroTypeUtil.java b/nifi-extension-bundles/nifi-extension-utils/nifi-record-utils/nifi-avro-record-utils/src/main/java/org/apache/nifi/avro/AvroTypeUtil.java index eeed128c7a63..e4ffde51e347 100644 --- a/nifi-extension-bundles/nifi-extension-utils/nifi-record-utils/nifi-avro-record-utils/src/main/java/org/apache/nifi/avro/AvroTypeUtil.java +++ b/nifi-extension-bundles/nifi-extension-utils/nifi-record-utils/nifi-avro-record-utils/src/main/java/org/apache/nifi/avro/AvroTypeUtil.java @@ -1153,11 +1153,11 @@ private static Object normalizeValue(final Object value, final Schema avroSchema final String logicalName = logicalType.getName(); if (LOGICAL_TYPE_DATE.equals(logicalName)) { // date logical name means that the value is number of days since Jan 1, 1970 - // Handle both Integer (legacy) and LocalDate (newer Avro libraries) - if (value instanceof LocalDate localDate) { - return java.sql.Date.valueOf(localDate); - } - return java.sql.Date.valueOf(LocalDate.ofEpochDay((int) value)); + // Handle both Integer (legacy) and LocalDate (newer Avro libraries). + // Avoid Date.valueOf(LocalDate) which routes through deprecated GregorianCalendar + // and applies Julian calendar semantics for years before 1582. + final LocalDate localDate = (value instanceof LocalDate ld) ? ld : LocalDate.ofEpochDay((int) value); + return new java.sql.Date(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant().toEpochMilli()); } else if (LOGICAL_TYPE_TIME_MILLIS.equals(logicalName)) { // time-millis logical name means that the value is number of milliseconds since midnight. // Handle both Integer (legacy) and LocalTime (newer Avro libraries) From d7a238fe021e797ad38f12b1d5ccd5e81ab22437 Mon Sep 17 00:00:00 2001 From: Kamilkime Date: Mon, 25 May 2026 18:37:14 +0200 Subject: [PATCH 2/4] NIFI-15973 Remove comments --- .../record/field/ObjectLocalDateFieldConverter.java | 4 ---- .../record/field/ObjectLocalDateTimeFieldConverter.java | 3 --- .../record/field/ObjectStringFieldConverter.java | 2 -- .../record/field/ObjectTimestampFieldConverter.java | 3 --- .../nifi/serialization/record/util/DataTypeUtils.java | 2 -- .../record/field/ObjectLocalDateFieldConverterTest.java | 9 +-------- .../record/field/ObjectStringFieldConverterTest.java | 4 ---- .../record/field/ObjectTimestampFieldConverterTest.java | 4 ---- .../field/TestObjectLocalDateTimeFieldConverter.java | 5 ----- .../src/main/java/org/apache/nifi/avro/AvroTypeUtil.java | 2 -- 10 files changed, 1 insertion(+), 37 deletions(-) diff --git a/nifi-commons/nifi-record/src/main/java/org/apache/nifi/serialization/record/field/ObjectLocalDateFieldConverter.java b/nifi-commons/nifi-record/src/main/java/org/apache/nifi/serialization/record/field/ObjectLocalDateFieldConverter.java index fccff92e74e4..40b8ae191bc6 100644 --- a/nifi-commons/nifi-record/src/main/java/org/apache/nifi/serialization/record/field/ObjectLocalDateFieldConverter.java +++ b/nifi-commons/nifi-record/src/main/java/org/apache/nifi/serialization/record/field/ObjectLocalDateFieldConverter.java @@ -49,10 +49,6 @@ public LocalDate convertField(final Object field, final Optional pattern return localDate; } case Date date -> { - // Avoid java.sql.Date#toLocalDate which uses deprecated getYear/getMonth/getDate - // accessors backed by GregorianCalendar, applying Julian calendar semantics for years - // before 1582 and shifting pre-1582 dates by approximately two days. java.sql.Date - // does not implement toInstant, so the epoch milliseconds are read explicitly. return ofInstant(Instant.ofEpochMilli(date.getTime())); } case java.util.Date date -> { diff --git a/nifi-commons/nifi-record/src/main/java/org/apache/nifi/serialization/record/field/ObjectLocalDateTimeFieldConverter.java b/nifi-commons/nifi-record/src/main/java/org/apache/nifi/serialization/record/field/ObjectLocalDateTimeFieldConverter.java index e06572b571e2..cf8aa118d40d 100644 --- a/nifi-commons/nifi-record/src/main/java/org/apache/nifi/serialization/record/field/ObjectLocalDateTimeFieldConverter.java +++ b/nifi-commons/nifi-record/src/main/java/org/apache/nifi/serialization/record/field/ObjectLocalDateTimeFieldConverter.java @@ -64,9 +64,6 @@ public LocalDateTime convertField(final Object field, final Optional pat return localDateTime; } case Timestamp timestamp -> { - // Avoid Timestamp#toLocalDateTime which routes through deprecated GregorianCalendar - // and applies Julian calendar semantics for years before 1582, shifting pre-1582 - // timestamps by approximately two days. return ofInstant(timestamp.toInstant()); } case Date date -> { diff --git a/nifi-commons/nifi-record/src/main/java/org/apache/nifi/serialization/record/field/ObjectStringFieldConverter.java b/nifi-commons/nifi-record/src/main/java/org/apache/nifi/serialization/record/field/ObjectStringFieldConverter.java index 4ffcccb26b35..7e0be549cb10 100644 --- a/nifi-commons/nifi-record/src/main/java/org/apache/nifi/serialization/record/field/ObjectStringFieldConverter.java +++ b/nifi-commons/nifi-record/src/main/java/org/apache/nifi/serialization/record/field/ObjectStringFieldConverter.java @@ -57,8 +57,6 @@ public String convertField(final Object field, final Optional pattern, f return Long.toString(timestamp.getTime()); } final DateTimeFormatter formatter = DateTimeFormatterRegistry.getDateTimeFormatter(pattern.get()); - // Avoid Timestamp#toLocalDateTime which routes through deprecated GregorianCalendar - // and applies Julian calendar semantics for years before 1582. final ZonedDateTime dateTime = timestamp.toInstant().atZone(ZoneId.systemDefault()); return formatter.format(dateTime); } diff --git a/nifi-commons/nifi-record/src/main/java/org/apache/nifi/serialization/record/field/ObjectTimestampFieldConverter.java b/nifi-commons/nifi-record/src/main/java/org/apache/nifi/serialization/record/field/ObjectTimestampFieldConverter.java index 981f650970bf..94668551d2b8 100644 --- a/nifi-commons/nifi-record/src/main/java/org/apache/nifi/serialization/record/field/ObjectTimestampFieldConverter.java +++ b/nifi-commons/nifi-record/src/main/java/org/apache/nifi/serialization/record/field/ObjectTimestampFieldConverter.java @@ -41,9 +41,6 @@ class ObjectTimestampFieldConverter implements FieldConverter @Override public Timestamp convertField(final Object field, final Optional pattern, final String name) { final LocalDateTime localDateTime = CONVERTER.convertField(field, pattern, name); - // Avoid Timestamp.valueOf(LocalDateTime) which routes through deprecated GregorianCalendar - // and applies Julian calendar semantics for years before 1582, shifting pre-1582 timestamps - // by approximately two days. return localDateTime == null ? null : Timestamp.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant()); } } diff --git a/nifi-commons/nifi-record/src/main/java/org/apache/nifi/serialization/record/util/DataTypeUtils.java b/nifi-commons/nifi-record/src/main/java/org/apache/nifi/serialization/record/util/DataTypeUtils.java index 2ae785d576b9..5a64a6d94c98 100644 --- a/nifi-commons/nifi-record/src/main/java/org/apache/nifi/serialization/record/util/DataTypeUtils.java +++ b/nifi-commons/nifi-record/src/main/java/org/apache/nifi/serialization/record/util/DataTypeUtils.java @@ -200,8 +200,6 @@ public static Object convertType( case DATE: final FieldConverter localDateConverter = StandardFieldConverterRegistry.getRegistry().getFieldConverter(LocalDate.class); final LocalDate localDate = localDateConverter.convertField(value, dateFormat, fieldName); - // Avoid Date.valueOf(LocalDate) which routes through deprecated GregorianCalendar - // and applies Julian calendar semantics for years before 1582. return localDate == null ? null : new Date(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant().toEpochMilli()); case DECIMAL: return toBigDecimal(value, fieldName); diff --git a/nifi-commons/nifi-record/src/test/java/org/apache/nifi/serialization/record/field/ObjectLocalDateFieldConverterTest.java b/nifi-commons/nifi-record/src/test/java/org/apache/nifi/serialization/record/field/ObjectLocalDateFieldConverterTest.java index b451a6902444..aadd7352a0a9 100644 --- a/nifi-commons/nifi-record/src/test/java/org/apache/nifi/serialization/record/field/ObjectLocalDateFieldConverterTest.java +++ b/nifi-commons/nifi-record/src/test/java/org/apache/nifi/serialization/record/field/ObjectLocalDateFieldConverterTest.java @@ -27,8 +27,8 @@ import static org.junit.jupiter.api.Assertions.assertNull; class ObjectLocalDateFieldConverterTest { - private static final ObjectLocalDateFieldConverter CONVERTER = new ObjectLocalDateFieldConverter(); + private static final ObjectLocalDateFieldConverter CONVERTER = new ObjectLocalDateFieldConverter(); private static final String FIELD_NAME = LocalDate.class.getSimpleName(); @Test @@ -46,22 +46,15 @@ void testConvertFieldLocalDate() { void testConvertFieldSqlDateModernYear() { final LocalDate localDate = LocalDate.of(2025, 5, 25); final Date sqlDate = new Date(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant().toEpochMilli()); - final LocalDate result = CONVERTER.convertField(sqlDate, Optional.empty(), FIELD_NAME); - assertEquals(localDate, result); } @Test void testConvertFieldSqlDateYearOneIsProlepticGregorian() { - // A java.sql.Date built from a proleptic-Gregorian year-1 epoch must round-trip back - // to year 1, not be shifted to year 0 by Julian calendar semantics in - // java.sql.Date#toLocalDate (which uses deprecated getYear/getMonth/getDate). final LocalDate yearOne = LocalDate.of(1, 1, 1); final Date sqlDate = new Date(yearOne.atStartOfDay(ZoneId.systemDefault()).toInstant().toEpochMilli()); - final LocalDate result = CONVERTER.convertField(sqlDate, Optional.empty(), FIELD_NAME); - assertEquals(yearOne, result); } } diff --git a/nifi-commons/nifi-record/src/test/java/org/apache/nifi/serialization/record/field/ObjectStringFieldConverterTest.java b/nifi-commons/nifi-record/src/test/java/org/apache/nifi/serialization/record/field/ObjectStringFieldConverterTest.java index b496ec79d0e7..b7d96c6fe8f9 100644 --- a/nifi-commons/nifi-record/src/test/java/org/apache/nifi/serialization/record/field/ObjectStringFieldConverterTest.java +++ b/nifi-commons/nifi-record/src/test/java/org/apache/nifi/serialization/record/field/ObjectStringFieldConverterTest.java @@ -162,13 +162,9 @@ void testConvertFieldObjectArrayEmpty() { @Test void testConvertFieldTimestampYearOneIsProlepticGregorian() { - // A Timestamp built from a proleptic-Gregorian year-1 Instant must format with year 1 - // (not "0000-12-30" produced when Timestamp#toLocalDateTime applies Julian semantics). final LocalDateTime yearOne = LocalDateTime.of(1, 1, 1, 12, 0, 0); final Timestamp timestamp = Timestamp.from(yearOne.atZone(ZoneId.systemDefault()).toInstant()); - final String formatted = CONVERTER.convertField(timestamp, Optional.of(DEFAULT_PATTERN), FIELD_NAME); - final String expected = DateTimeFormatter.ofPattern(DEFAULT_PATTERN, Locale.ROOT).format(yearOne); assertEquals(expected, formatted); } diff --git a/nifi-commons/nifi-record/src/test/java/org/apache/nifi/serialization/record/field/ObjectTimestampFieldConverterTest.java b/nifi-commons/nifi-record/src/test/java/org/apache/nifi/serialization/record/field/ObjectTimestampFieldConverterTest.java index c152335ad5e3..cdce7acf4723 100644 --- a/nifi-commons/nifi-record/src/test/java/org/apache/nifi/serialization/record/field/ObjectTimestampFieldConverterTest.java +++ b/nifi-commons/nifi-record/src/test/java/org/apache/nifi/serialization/record/field/ObjectTimestampFieldConverterTest.java @@ -164,10 +164,6 @@ public void testConvertFieldStringFormatCustomZoneOffsetCoordinatedUniversalTime @Test public void testConvertFieldStringYearOneIsProlepticGregorian() { - // Verify that year 1 in the source string is interpreted via proleptic Gregorian - // (matching java.time semantics) rather than the Julian calendar that would route - // through the deprecated GregorianCalendar in Timestamp.valueOf and shift the value - // by approximately two days. final String yearOne = "0001-01-01 12:00:00"; final Timestamp timestamp = CONVERTER.convertField(yearOne, DEFAULT_PATTERN, FIELD_NAME); final Instant expected = LocalDateTime.of(1, 1, 1, 12, 0, 0).atZone(ZoneId.systemDefault()).toInstant(); diff --git a/nifi-commons/nifi-record/src/test/java/org/apache/nifi/serialization/record/field/TestObjectLocalDateTimeFieldConverter.java b/nifi-commons/nifi-record/src/test/java/org/apache/nifi/serialization/record/field/TestObjectLocalDateTimeFieldConverter.java index 34269d3030e3..dd51f8084139 100644 --- a/nifi-commons/nifi-record/src/test/java/org/apache/nifi/serialization/record/field/TestObjectLocalDateTimeFieldConverter.java +++ b/nifi-commons/nifi-record/src/test/java/org/apache/nifi/serialization/record/field/TestObjectLocalDateTimeFieldConverter.java @@ -104,14 +104,9 @@ public void testWithDateFormatMicrosecondPrecision() { @Test public void testConvertTimestampYearOneIsProlepticGregorian() { - // A Timestamp constructed directly from a proleptic-Gregorian year-1 Instant must - // round-trip back to year 1, not be shifted to year 0 by Julian calendar semantics - // in Timestamp#toLocalDateTime. final LocalDateTime yearOne = LocalDateTime.of(1, 1, 1, 12, 0, 0); final Timestamp timestamp = Timestamp.from(yearOne.atZone(ZoneId.systemDefault()).toInstant()); - final LocalDateTime result = converter.convertField(timestamp, Optional.empty(), FIELD_NAME); - assertEquals(yearOne, result); } diff --git a/nifi-extension-bundles/nifi-extension-utils/nifi-record-utils/nifi-avro-record-utils/src/main/java/org/apache/nifi/avro/AvroTypeUtil.java b/nifi-extension-bundles/nifi-extension-utils/nifi-record-utils/nifi-avro-record-utils/src/main/java/org/apache/nifi/avro/AvroTypeUtil.java index e4ffde51e347..c60800f28e7c 100644 --- a/nifi-extension-bundles/nifi-extension-utils/nifi-record-utils/nifi-avro-record-utils/src/main/java/org/apache/nifi/avro/AvroTypeUtil.java +++ b/nifi-extension-bundles/nifi-extension-utils/nifi-record-utils/nifi-avro-record-utils/src/main/java/org/apache/nifi/avro/AvroTypeUtil.java @@ -1154,8 +1154,6 @@ private static Object normalizeValue(final Object value, final Schema avroSchema if (LOGICAL_TYPE_DATE.equals(logicalName)) { // date logical name means that the value is number of days since Jan 1, 1970 // Handle both Integer (legacy) and LocalDate (newer Avro libraries). - // Avoid Date.valueOf(LocalDate) which routes through deprecated GregorianCalendar - // and applies Julian calendar semantics for years before 1582. final LocalDate localDate = (value instanceof LocalDate ld) ? ld : LocalDate.ofEpochDay((int) value); return new java.sql.Date(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant().toEpochMilli()); } else if (LOGICAL_TYPE_TIME_MILLIS.equals(logicalName)) { From f00faa81e68e8f13fc18eeb99b6b33c84f49dca1 Mon Sep 17 00:00:00 2001 From: Kamilkime Date: Tue, 26 May 2026 13:52:34 +0200 Subject: [PATCH 3/4] NIFI-15973 Address review comments --- .../field/ObjectTimestampFieldConverter.java | 8 +++++++- .../serialization/record/util/DataTypeUtils.java | 7 ++++++- .../field/ObjectLocalDateFieldConverterTest.java | 9 +++++++-- .../field/ObjectStringFieldConverterTest.java | 5 ++++- .../field/ObjectTimestampFieldConverterTest.java | 16 ++++++++++------ .../TestObjectLocalDateTimeFieldConverter.java | 8 ++++++-- .../java/org/apache/nifi/avro/AvroTypeUtil.java | 12 ++++++++++-- 7 files changed, 50 insertions(+), 15 deletions(-) diff --git a/nifi-commons/nifi-record/src/main/java/org/apache/nifi/serialization/record/field/ObjectTimestampFieldConverter.java b/nifi-commons/nifi-record/src/main/java/org/apache/nifi/serialization/record/field/ObjectTimestampFieldConverter.java index 94668551d2b8..6bd7e013bba3 100644 --- a/nifi-commons/nifi-record/src/main/java/org/apache/nifi/serialization/record/field/ObjectTimestampFieldConverter.java +++ b/nifi-commons/nifi-record/src/main/java/org/apache/nifi/serialization/record/field/ObjectTimestampFieldConverter.java @@ -21,6 +21,7 @@ import java.sql.Timestamp; import java.time.LocalDateTime; import java.time.ZoneId; +import java.time.ZonedDateTime; import java.util.Optional; /** @@ -41,6 +42,11 @@ class ObjectTimestampFieldConverter implements FieldConverter @Override public Timestamp convertField(final Object field, final Optional pattern, final String name) { final LocalDateTime localDateTime = CONVERTER.convertField(field, pattern, name); - return localDateTime == null ? null : Timestamp.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant()); + if (localDateTime == null) { + return null; + } + + final ZonedDateTime zonedDateTime = localDateTime.atZone(ZoneId.systemDefault()); + return Timestamp.from(zonedDateTime.toInstant()); } } diff --git a/nifi-commons/nifi-record/src/main/java/org/apache/nifi/serialization/record/util/DataTypeUtils.java b/nifi-commons/nifi-record/src/main/java/org/apache/nifi/serialization/record/util/DataTypeUtils.java index 5a64a6d94c98..4ccbf2fbf511 100644 --- a/nifi-commons/nifi-record/src/main/java/org/apache/nifi/serialization/record/util/DataTypeUtils.java +++ b/nifi-commons/nifi-record/src/main/java/org/apache/nifi/serialization/record/util/DataTypeUtils.java @@ -200,7 +200,12 @@ public static Object convertType( case DATE: final FieldConverter localDateConverter = StandardFieldConverterRegistry.getRegistry().getFieldConverter(LocalDate.class); final LocalDate localDate = localDateConverter.convertField(value, dateFormat, fieldName); - return localDate == null ? null : new Date(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant().toEpochMilli()); + if (localDate == null) { + return null; + } + + final ZonedDateTime zonedDate = localDate.atStartOfDay(ZoneId.systemDefault()); + return new Date(zonedDate.toInstant().toEpochMilli()); case DECIMAL: return toBigDecimal(value, fieldName); case DOUBLE: diff --git a/nifi-commons/nifi-record/src/test/java/org/apache/nifi/serialization/record/field/ObjectLocalDateFieldConverterTest.java b/nifi-commons/nifi-record/src/test/java/org/apache/nifi/serialization/record/field/ObjectLocalDateFieldConverterTest.java index aadd7352a0a9..03ff0b661ae8 100644 --- a/nifi-commons/nifi-record/src/test/java/org/apache/nifi/serialization/record/field/ObjectLocalDateFieldConverterTest.java +++ b/nifi-commons/nifi-record/src/test/java/org/apache/nifi/serialization/record/field/ObjectLocalDateFieldConverterTest.java @@ -21,6 +21,7 @@ import java.sql.Date; import java.time.LocalDate; import java.time.ZoneId; +import java.time.ZonedDateTime; import java.util.Optional; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -45,16 +46,20 @@ void testConvertFieldLocalDate() { @Test void testConvertFieldSqlDateModernYear() { final LocalDate localDate = LocalDate.of(2025, 5, 25); - final Date sqlDate = new Date(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant().toEpochMilli()); + final ZonedDateTime zonedDate = localDate.atStartOfDay(ZoneId.systemDefault()); + final Date sqlDate = new Date(zonedDate.toInstant().toEpochMilli()); final LocalDate result = CONVERTER.convertField(sqlDate, Optional.empty(), FIELD_NAME); + assertEquals(localDate, result); } @Test void testConvertFieldSqlDateYearOneIsProlepticGregorian() { final LocalDate yearOne = LocalDate.of(1, 1, 1); - final Date sqlDate = new Date(yearOne.atStartOfDay(ZoneId.systemDefault()).toInstant().toEpochMilli()); + final ZonedDateTime zonedDate = yearOne.atStartOfDay(ZoneId.systemDefault()); + final Date sqlDate = new Date(zonedDate.toInstant().toEpochMilli()); final LocalDate result = CONVERTER.convertField(sqlDate, Optional.empty(), FIELD_NAME); + assertEquals(yearOne, result); } } diff --git a/nifi-commons/nifi-record/src/test/java/org/apache/nifi/serialization/record/field/ObjectStringFieldConverterTest.java b/nifi-commons/nifi-record/src/test/java/org/apache/nifi/serialization/record/field/ObjectStringFieldConverterTest.java index b7d96c6fe8f9..bc1732832c3a 100644 --- a/nifi-commons/nifi-record/src/test/java/org/apache/nifi/serialization/record/field/ObjectStringFieldConverterTest.java +++ b/nifi-commons/nifi-record/src/test/java/org/apache/nifi/serialization/record/field/ObjectStringFieldConverterTest.java @@ -24,6 +24,7 @@ import java.time.LocalDateTime; import java.time.ZoneId; import java.time.ZoneOffset; +import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.time.zone.ZoneRules; import java.util.Date; @@ -163,9 +164,11 @@ void testConvertFieldObjectArrayEmpty() { @Test void testConvertFieldTimestampYearOneIsProlepticGregorian() { final LocalDateTime yearOne = LocalDateTime.of(1, 1, 1, 12, 0, 0); - final Timestamp timestamp = Timestamp.from(yearOne.atZone(ZoneId.systemDefault()).toInstant()); + final ZonedDateTime zonedYearOne = yearOne.atZone(ZoneId.systemDefault()); + final Timestamp timestamp = Timestamp.from(zonedYearOne.toInstant()); final String formatted = CONVERTER.convertField(timestamp, Optional.of(DEFAULT_PATTERN), FIELD_NAME); final String expected = DateTimeFormatter.ofPattern(DEFAULT_PATTERN, Locale.ROOT).format(yearOne); + assertEquals(expected, formatted); } diff --git a/nifi-commons/nifi-record/src/test/java/org/apache/nifi/serialization/record/field/ObjectTimestampFieldConverterTest.java b/nifi-commons/nifi-record/src/test/java/org/apache/nifi/serialization/record/field/ObjectTimestampFieldConverterTest.java index cdce7acf4723..2628aabd3c82 100644 --- a/nifi-commons/nifi-record/src/test/java/org/apache/nifi/serialization/record/field/ObjectTimestampFieldConverterTest.java +++ b/nifi-commons/nifi-record/src/test/java/org/apache/nifi/serialization/record/field/ObjectTimestampFieldConverterTest.java @@ -164,18 +164,22 @@ public void testConvertFieldStringFormatCustomZoneOffsetCoordinatedUniversalTime @Test public void testConvertFieldStringYearOneIsProlepticGregorian() { - final String yearOne = "0001-01-01 12:00:00"; - final Timestamp timestamp = CONVERTER.convertField(yearOne, DEFAULT_PATTERN, FIELD_NAME); - final Instant expected = LocalDateTime.of(1, 1, 1, 12, 0, 0).atZone(ZoneId.systemDefault()).toInstant(); - assertEquals(expected, timestamp.toInstant()); + final LocalDateTime yearOne = LocalDateTime.of(1, 1, 1, 12, 0, 0); + final ZonedDateTime zonedYearOne = yearOne.atZone(ZoneId.systemDefault()); + final Timestamp timestamp = CONVERTER.convertField("0001-01-01 12:00:00", DEFAULT_PATTERN, FIELD_NAME); + final Timestamp expected = Timestamp.from(zonedYearOne.toInstant()); + + assertEquals(expected, timestamp); } @Test public void testConvertFieldLocalDateTimeYearOneIsProlepticGregorian() { final LocalDateTime yearOne = LocalDateTime.of(1, 1, 1, 12, 0, 0); + final ZonedDateTime zonedYearOne = yearOne.atZone(ZoneId.systemDefault()); final Timestamp timestamp = CONVERTER.convertField(yearOne, DEFAULT_PATTERN, FIELD_NAME); - final Instant expected = yearOne.atZone(ZoneId.systemDefault()).toInstant(); - assertEquals(expected, timestamp.toInstant()); + final Timestamp expected = Timestamp.from(zonedYearOne.toInstant()); + + assertEquals(expected, timestamp); } private Timestamp getDateTimeCoordinatedUniversalTime() { diff --git a/nifi-commons/nifi-record/src/test/java/org/apache/nifi/serialization/record/field/TestObjectLocalDateTimeFieldConverter.java b/nifi-commons/nifi-record/src/test/java/org/apache/nifi/serialization/record/field/TestObjectLocalDateTimeFieldConverter.java index dd51f8084139..b923c9d8771e 100644 --- a/nifi-commons/nifi-record/src/test/java/org/apache/nifi/serialization/record/field/TestObjectLocalDateTimeFieldConverter.java +++ b/nifi-commons/nifi-record/src/test/java/org/apache/nifi/serialization/record/field/TestObjectLocalDateTimeFieldConverter.java @@ -23,6 +23,7 @@ import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneId; +import java.time.ZonedDateTime; import java.time.temporal.ChronoUnit; import java.util.Optional; @@ -105,14 +106,17 @@ public void testWithDateFormatMicrosecondPrecision() { @Test public void testConvertTimestampYearOneIsProlepticGregorian() { final LocalDateTime yearOne = LocalDateTime.of(1, 1, 1, 12, 0, 0); - final Timestamp timestamp = Timestamp.from(yearOne.atZone(ZoneId.systemDefault()).toInstant()); + final ZonedDateTime zonedYearOne = yearOne.atZone(ZoneId.systemDefault()); + final Timestamp timestamp = Timestamp.from(zonedYearOne.toInstant()); final LocalDateTime result = converter.convertField(timestamp, Optional.empty(), FIELD_NAME); + assertEquals(yearOne, result); } @Test public void testConvertStringYearOneIsProlepticGregorian() { final LocalDateTime result = converter.convertField("0001-01-01 12:00:00", Optional.of("yyyy-MM-dd HH:mm:ss"), FIELD_NAME); - assertEquals(LocalDateTime.of(1, 1, 1, 12, 0, 0), result); + final LocalDateTime expected = LocalDateTime.of(1, 1, 1, 12, 0, 0); + assertEquals(expected, result); } } diff --git a/nifi-extension-bundles/nifi-extension-utils/nifi-record-utils/nifi-avro-record-utils/src/main/java/org/apache/nifi/avro/AvroTypeUtil.java b/nifi-extension-bundles/nifi-extension-utils/nifi-record-utils/nifi-avro-record-utils/src/main/java/org/apache/nifi/avro/AvroTypeUtil.java index c60800f28e7c..1ddf4a757574 100644 --- a/nifi-extension-bundles/nifi-extension-utils/nifi-record-utils/nifi-avro-record-utils/src/main/java/org/apache/nifi/avro/AvroTypeUtil.java +++ b/nifi-extension-bundles/nifi-extension-utils/nifi-record-utils/nifi-avro-record-utils/src/main/java/org/apache/nifi/avro/AvroTypeUtil.java @@ -68,6 +68,7 @@ import java.time.Duration; import java.time.Instant; import java.time.LocalDate; +import java.time.LocalDateTime; import java.time.LocalTime; import java.time.ZoneId; import java.time.ZonedDateTime; @@ -1154,8 +1155,15 @@ private static Object normalizeValue(final Object value, final Schema avroSchema if (LOGICAL_TYPE_DATE.equals(logicalName)) { // date logical name means that the value is number of days since Jan 1, 1970 // Handle both Integer (legacy) and LocalDate (newer Avro libraries). - final LocalDate localDate = (value instanceof LocalDate ld) ? ld : LocalDate.ofEpochDay((int) value); - return new java.sql.Date(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant().toEpochMilli()); + final LocalDate localDate; + if (value instanceof LocalDate ld) { + localDate = ld; + } else { + localDate = LocalDate.ofEpochDay((int) value); + } + + final ZonedDateTime zonedDate = localDate.atStartOfDay(ZoneId.systemDefault()); + return new java.sql.Date(zonedDate.toInstant().toEpochMilli()); } else if (LOGICAL_TYPE_TIME_MILLIS.equals(logicalName)) { // time-millis logical name means that the value is number of milliseconds since midnight. // Handle both Integer (legacy) and LocalTime (newer Avro libraries) From 22174a948e4d8c6ad4fad5fc3776cc21367db4d4 Mon Sep 17 00:00:00 2001 From: Kamilkime Date: Tue, 26 May 2026 14:25:51 +0200 Subject: [PATCH 4/4] NIFI-15973 Import sql.Date, remove unused import --- .../src/main/java/org/apache/nifi/avro/AvroTypeUtil.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nifi-extension-bundles/nifi-extension-utils/nifi-record-utils/nifi-avro-record-utils/src/main/java/org/apache/nifi/avro/AvroTypeUtil.java b/nifi-extension-bundles/nifi-extension-utils/nifi-record-utils/nifi-avro-record-utils/src/main/java/org/apache/nifi/avro/AvroTypeUtil.java index 1ddf4a757574..4a453cfc6f42 100644 --- a/nifi-extension-bundles/nifi-extension-utils/nifi-record-utils/nifi-avro-record-utils/src/main/java/org/apache/nifi/avro/AvroTypeUtil.java +++ b/nifi-extension-bundles/nifi-extension-utils/nifi-record-utils/nifi-avro-record-utils/src/main/java/org/apache/nifi/avro/AvroTypeUtil.java @@ -63,12 +63,12 @@ import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.sql.Blob; +import java.sql.Date; import java.sql.Time; import java.sql.Timestamp; import java.time.Duration; import java.time.Instant; import java.time.LocalDate; -import java.time.LocalDateTime; import java.time.LocalTime; import java.time.ZoneId; import java.time.ZonedDateTime; @@ -1163,7 +1163,7 @@ private static Object normalizeValue(final Object value, final Schema avroSchema } final ZonedDateTime zonedDate = localDate.atStartOfDay(ZoneId.systemDefault()); - return new java.sql.Date(zonedDate.toInstant().toEpochMilli()); + return new Date(zonedDate.toInstant().toEpochMilli()); } else if (LOGICAL_TYPE_TIME_MILLIS.equals(logicalName)) { // time-millis logical name means that the value is number of milliseconds since midnight. // Handle both Integer (legacy) and LocalTime (newer Avro libraries)