You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by gg...@apache.org on 2025/08/22 21:24:06 UTC

(commons-lang) 02/02: Add org.apache.commons.lang3.time.DateUtils.toLocalDateTime(Date[, TimeZone]) #1385

This is an automated email from the ASF dual-hosted git repository.

ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-lang.git

commit 0c6a5c10a7a0f0073b6577e5c4898ceb42428b55
Author: Gary D. Gregory <ga...@gmail.com>
AuthorDate: Fri Aug 22 17:23:57 2025 -0400

    Add org.apache.commons.lang3.time.DateUtils.toLocalDateTime(Date[,
    TimeZone]) #1385
---
 src/changes/changes.xml                            |  1 +
 .../org/apache/commons/lang3/time/DateUtils.java   | 24 ++++++++
 .../apache/commons/lang3/time/DateUtilsTest.java   | 71 ++++++++++++++++++++++
 3 files changed, 96 insertions(+)

diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 9b48506f0..6fac9d6d2 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -68,6 +68,7 @@ The <action> type attribute can be add,update,fix,remove.
     <action                   type="add" dev="ggregory" due-to="Gary Gregory">Add documentation to site for CVE-2025-48924 ClassUtils.getClass(...) can throw a StackOverflowError on very long inputs.</action>
     <action                   type="add" dev="ggregory" due-to="Gary Gregory">Add org.apache.commons.lang3.StringUtils.indexOfAny(CharSequence, int, char...).</action>
     <action                   type="add" dev="ggregory" due-to="Gary Gregory">Add org.apache.commons.lang3.concurrent.ConcurrentException.ConcurrentException(String).</action>
+    <action                   type="add" dev="ggregory" due-to="Finger, Gary Gregory, Piotr P. Karwasz">Add org.apache.commons.lang3.time.DateUtils.toLocalDateTime(Date[, TimeZone]) #1385.</action>
     <!-- UPDATE -->
     <action type="update" dev="ggregory" due-to="Gary Gregory">[test] Bump org.apache.commons:commons-text from 1.13.1 to 1.14.0.</action>
   </release>
diff --git a/src/main/java/org/apache/commons/lang3/time/DateUtils.java b/src/main/java/org/apache/commons/lang3/time/DateUtils.java
index 23ab00e10..061ab2aa3 100644
--- a/src/main/java/org/apache/commons/lang3/time/DateUtils.java
+++ b/src/main/java/org/apache/commons/lang3/time/DateUtils.java
@@ -18,6 +18,7 @@
 
 import java.text.ParseException;
 import java.text.ParsePosition;
+import java.time.LocalDateTime;
 import java.util.Calendar;
 import java.util.Date;
 import java.util.Iterator;
@@ -1626,6 +1627,29 @@ public static Calendar toCalendar(final Date date, final TimeZone tz) {
         return c;
     }
 
+    /**
+     * Converts a {@link Date} to a {@link LocalDateTime}.
+     *
+     * @param date the Date to convert, not null.
+     * @return a new LocalDateTime.
+     * @since 3.19.0
+     */
+    public static LocalDateTime toLocalDateTime(final Date date) {
+        return toLocalDateTime(date, TimeZone.getDefault());
+    }
+
+    /**
+     * Converts a {@link Date} to a {@link LocalDateTime}.
+     *
+     * @param date     the Date to convert to a LocalDateTime, not null.
+     * @param timeZone the time zone, null maps to to the default time zone.
+     * @return a new LocalDateTime.
+     * @since 3.19.0
+     */
+    public static LocalDateTime toLocalDateTime(final Date date, final TimeZone timeZone) {
+        return LocalDateTime.ofInstant(date.toInstant(), TimeZones.toTimeZone(timeZone).toZoneId());
+    }
+
     /**
      * Truncates a date, leaving the field specified as the most
      * significant field.
diff --git a/src/test/java/org/apache/commons/lang3/time/DateUtilsTest.java b/src/test/java/org/apache/commons/lang3/time/DateUtilsTest.java
index e06d71934..3fbfaadfc 100644
--- a/src/test/java/org/apache/commons/lang3/time/DateUtilsTest.java
+++ b/src/test/java/org/apache/commons/lang3/time/DateUtilsTest.java
@@ -30,6 +30,9 @@
 import java.text.DateFormat;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
 import java.util.Calendar;
 import java.util.Date;
 import java.util.GregorianCalendar;
@@ -37,12 +40,16 @@
 import java.util.Locale;
 import java.util.NoSuchElementException;
 import java.util.TimeZone;
+import java.util.stream.Stream;
 
 import org.apache.commons.lang3.AbstractLangTest;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
 import org.junitpioneer.jupiter.DefaultLocale;
 import org.junitpioneer.jupiter.ReadsDefaultLocale;
 import org.junitpioneer.jupiter.WritesDefaultLocale;
@@ -58,6 +65,70 @@ class DateUtilsTest extends AbstractLangTest {
     private static final TimeZone TIME_ZONE_MET = TimeZone.getTimeZone("MET");
     private static Date BASE_DATE;
 
+    private static Stream<Arguments> testToLocalDateTimeTimeZone() {
+        // @formatter:off
+        return Stream.of(
+                Arguments.of(
+                        LocalDateTime.ofInstant(
+                                java.sql.Timestamp.valueOf("2000-01-01 12:30:45").toInstant(),
+                                TimeZone.getTimeZone("America/New_York").toZoneId()
+                        ),
+                        java.sql.Timestamp.valueOf("2000-01-01 12:30:45"),
+                        TimeZone.getTimeZone("America/New_York")
+                ),
+                Arguments.of(
+                        LocalDateTime.ofInstant(
+                                java.sql.Timestamp.valueOf("2023-03-12 02:30:00").toInstant(),
+                                TimeZone.getTimeZone("America/New_York").toZoneId()
+                        ),
+                        java.sql.Timestamp.valueOf("2023-03-12 02:30:00"),
+                        TimeZone.getTimeZone("America/New_York")
+                ),
+                Arguments.of(
+                        LocalDateTime.ofInstant(
+                                java.sql.Timestamp.valueOf("2023-03-12 02:30:00").toInstant(),
+                                TimeZone.getDefault().toZoneId()
+                        ),
+                        java.sql.Timestamp.valueOf("2023-03-12 02:30:00"),
+                        null
+                ),
+                Arguments.of(
+                        LocalDateTime.of(2022, 12, 31, 19, 0),
+                        Date.from(LocalDateTime.of(2023, 1, 1, 0, 0)
+                                .atOffset(ZoneOffset.UTC)
+                                .toInstant()),
+                        TimeZone.getTimeZone("America/New_York")
+                ),
+                Arguments.of(
+                        LocalDateTime.of(2023, 3, 12, 3, 0),
+                        Date.from(LocalDateTime.of(2023, 3, 12, 7, 0)
+                                .atOffset(ZoneOffset.UTC)
+                                .toInstant()),
+                        TimeZone.getTimeZone("America/New_York")
+                ),
+                Arguments.of(
+                        LocalDateTime.of(2023, 1, 1, 14, 0),
+                        Date.from(LocalDateTime.of(2023, 1, 1, 0, 0)
+                                .atOffset(ZoneOffset.UTC)
+                                .toInstant()),
+                        TimeZone.getTimeZone("Pacific/Kiritimati")
+                )
+        );
+        // @formatter:on
+    }
+
+    @ParameterizedTest
+    @MethodSource
+    void testToLocalDateTimeTimeZone(final LocalDateTime expected, final Date date, final TimeZone timeZone) {
+        assertEquals(expected, DateUtils.toLocalDateTime(date, timeZone));
+    }
+
+    @Test
+    void testToLocalDateTime() {
+        final Date date = new Date();
+        assertEquals(LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()), DateUtils.toLocalDateTime(date));
+    }
+
     /**
      * Used to check that Calendar objects are close enough
      * delta is in milliseconds