admin管理员组

文章数量:1122846

Let's get this over with.

How do you convert a LocalDateTime to a java.util.Date without any "magic tricks" pulled by Java (which are becoming less and less amusing to me)?

The year, month, and day are correct (January 1, 1900), but the time is for some reason 0:29:43 (instead of 00:00:00, as I intended).

Your time, I figure, may be different, for example because it may depend on time zone.

import java.util.Date;
import java.time.LocalDateTime;
import java.time.Instant;
import java.time.ZoneId;

public class DateDemo {
    public static void main(String[] args) {
        Date date = toDate(1900, 1, 1, 0, 0); // must be: January 1, 1900. 00:00:00
        System.out.println(ZoneId.systemDefault()); // Europe/Moscow
        System.out.println(date); // actual: Mon Jan 01 00:29:43 MSK 1900
    }

    static Date toDate(int year, int month, int day, int hours, int minutes) {
        // LocalDateTime appeared a convenient medium, what with its handy factory methods
        LocalDateTime localDateTime = LocalDateTime.of(year, month, day, hours, minutes);
        Instant instant = localDateTime.atZone(ZoneId.systemDefault()).toInstant();
        return Date.from(instant);
    }
}

If for some reason it's impossible, you may change LocalDateTime to some other class from java.util.time. The point is I have year, month, day, hours, minutes, seconds and need to make a Date instance from that concisely and readably (preferably, without using any deprecated API). LocalDateTime factory methods appeared handy.

This works but has one deprecated call (should be zero), generates two warnings in total and is ugly AF. Thus suboptimal.

new Date(localDateTime.getYear() - 1900, localDateTime.getMonth().getValue(), localDateTime.getDayOfMonth(), localDateTime.getHour(), localDateTime.getMinute(), localDateTime.getSecond())

I have to use java.util.Date. We got a legacy project. Our APIs expect Date instances.

I examined these before posting (some of them suggest the same, non-working, solution): 1, 2, 3, 4.

Java 8

Let's get this over with.

How do you convert a LocalDateTime to a java.util.Date without any "magic tricks" pulled by Java (which are becoming less and less amusing to me)?

The year, month, and day are correct (January 1, 1900), but the time is for some reason 0:29:43 (instead of 00:00:00, as I intended).

Your time, I figure, may be different, for example because it may depend on time zone.

import java.util.Date;
import java.time.LocalDateTime;
import java.time.Instant;
import java.time.ZoneId;

public class DateDemo {
    public static void main(String[] args) {
        Date date = toDate(1900, 1, 1, 0, 0); // must be: January 1, 1900. 00:00:00
        System.out.println(ZoneId.systemDefault()); // Europe/Moscow
        System.out.println(date); // actual: Mon Jan 01 00:29:43 MSK 1900
    }

    static Date toDate(int year, int month, int day, int hours, int minutes) {
        // LocalDateTime appeared a convenient medium, what with its handy factory methods
        LocalDateTime localDateTime = LocalDateTime.of(year, month, day, hours, minutes);
        Instant instant = localDateTime.atZone(ZoneId.systemDefault()).toInstant();
        return Date.from(instant);
    }
}

If for some reason it's impossible, you may change LocalDateTime to some other class from java.util.time. The point is I have year, month, day, hours, minutes, seconds and need to make a Date instance from that concisely and readably (preferably, without using any deprecated API). LocalDateTime factory methods appeared handy.

This works but has one deprecated call (should be zero), generates two warnings in total and is ugly AF. Thus suboptimal.

new Date(localDateTime.getYear() - 1900, localDateTime.getMonth().getValue(), localDateTime.getDayOfMonth(), localDateTime.getHour(), localDateTime.getMinute(), localDateTime.getSecond())

I have to use java.util.Date. We got a legacy project. Our APIs expect Date instances.

I examined these before posting (some of them suggest the same, non-working, solution): 1, 2, 3, 4.

Java 8

Share Improve this question edited Nov 27, 2024 at 5:53 Cagepi asked Nov 26, 2024 at 13:33 CagepiCagepi 1951 silver badge7 bronze badges 32
  • 8 Why do you want to use the legacy Date library? It's a very bad idea to use it in 2024+. – Youcef LAIDANI Commented Nov 26, 2024 at 13:37
  • 5 As explained in 3 and 4, Date is like Instant. The instant represented by the Date you got is the same as the instant represented by localDateTime.atZone(ZoneId.systemDefault()).toInstant() . It's just that its toString method (or most of the other formatting methods in the legacy API) does not format it correctly. – Sweeper Commented Nov 26, 2024 at 13:41
  • 2 "I have year, month, day, hours, minutes, seconds and need to make a Date instance" and what time zone is the time given? How is the date being evaluated - printed, saved in database, ... - that is, how you got the 0:29:43? And what is the default TimeZone (ZoneId)? – user85421 Commented Nov 26, 2024 at 17:24
  • 2 The Date object holds the correct time, it just prints wrong That's often the way with that class ;) – g00se Commented Nov 27, 2024 at 22:40
  • 2 @Anonymous I’m fine with calling it a bug. I only pointed out that the behavior is reproducible and does not come out of thin air. Using today’s offset +3:00 explains the numbers but that does not imply that Date’s toString() method should use that offset. – Holger Commented Nov 28, 2024 at 12:28
 |  Show 27 more comments

2 Answers 2

Reset to default 2

tl;dr

the time is for some reason 0:29:43

Your result is unreproducible. See your code run at Ideone.com. Result: Mon Jan 01 00:00:00 GMT 1900. Your JVM’s current default time zone may produce other results, but you neglected to post that time zone. Run: System.out.println( ZoneId.systemDefault() ) ;.

Read on for a better approach in getting to a java.util.Date from a LocalDateTime object. Time zone is crucial.

java.util.Date                           // Legacy class. Terribly designed; use only if you must. 
.from (                                  // New conversion method added to the old class. 
    LocalDateTime                        // Represents a date with time-of-day but lacking the context of a time zone or offset-from-UTC. 
    .of (
        LocalDate.of ( y , m , d ) ,     // Date-only. 
        LocalTime.of ( h , m , s )       // Time-only. 
    )                                    // Returns a `LocalDateTime` object. 
    .atZone ( 
        ZoneId.of( "America/Edmonton" )  // Returns a time zone, a `ZoneId` object. 
    )                                    // Returns a `ZonedDateTime` object. 
    .toInstant()                         // Returns a `Instant` object. 
)

For example, using an offset-from-UTC of zero hours-minutes-seconds:

String output =
        java.util.Date
                .from (
                        LocalDateTime
                                .of (
                                        LocalDate.of ( 1900 , 1 , 1 ) ,
                                        LocalTime.MIDNIGHT
                                )
                                .atZone (
                                       ZoneOffset.UTC
                                )
                                .toInstant ( )
                )
                .toString ( );  // This method lies to you! The JVM’s current default time zone is dynamically applied, while the object actually represents a moment as seen with an offset-from-UTC of zero hours-minutes-seconds. Very confusing anti-feature.

System.out.println ( "output = " + output );

When run on a JVM with a default time zone of UTC (offset of zero):

output = Mon Jan 01 00:00:00 UTC 1900

For fun, in that code above change that time zone from ZoneOffset.UTC to ZoneId.of ( "Asia/Tokyo" ):

output = Sun Dec 31 15:00:00 UTC 1899

Time Zone

The source of your confusion is time zone, on two counts.

Count # 1 — Determine a moment

The legacy class java.util.Date represents a moment, a specific point on the timeline. A moment is made up of a date, a time of day, and a time zone.

Moment = Date + Time + Zone

For any given moment, the time and the date both vary around the globe by time zone. Noon on January 23 of 2025 in Tokyo Japan is hours earlier than noon in Toulouse France, which is hours earlier than Toledo Ohio US. In this example, we have three different moments, several hours apart.

If you have a date and a time in hand, and want to determine a moment, you need to know in which time zone is that date and time meant to be seen.

If you do not know the intended time zone, then you have reached an impasse and cannot continue. You absolutely cannot guess which zone, you need to know with certainty. And absolutely should not pick a zone randomly as your code is doing with a call to ZoneId.systemDefault which accesses the JVM’s current default time zone. That default can be changed at any moment by any code in any app within that JVM.

If you know the intended zone, then proceed. A real time zone is named in format of Continent/Region, not 2-4 letter codes like CST or IST.

ZoneId z = ZoneId.of( "Asia/Tokyo" ) ;

Apply that zone to your LocalDateTime object to determine a moment, a point on the timeline. The LocalDateTime object has two parts: date and time. Adding the third part, time zone, makes a moment. In Java we represent this moment as a ZonedDateTime object.

ZonedDateTime zdt = myLocalDateTime.atZone( z ) ;  // Voilà, a moment. 

We can adjust that moment to be seen from an offset of zero hours-minutes-seconds from UTC by extracting an Instant. Same moment, different wall-clock/calendar.

Instant instant = zdt.toInstant() ;

Convert from that modern class to its legacy counterpart, java.util.Date.

java.util.Date myJavaUtilDate = java.util.Date.from( instant ) ;

Count # 2 — Date#toString lies

Also very confusing is the fact that java.util.Date#toString lies to you. In generating its text, the method dynamically applies the JVM‘s current default time zone. The result is not the semantics of what is stored within the Date object.

The stored value is a moment as seen in UTC, not as seen in the JVM’s current default time zone.

This lie is a well-intentioned feature (anti-feature?) but terribly poor design choice. This is but one of many reasons to avoid the legacy date-time classes. Always do your work in java.time, then use the new conversion methods added to the old legacy classes as needed to interface with the parts of your own code not yet updated to java.time.

Converting java.util.Date to Instant

To examine a java.util.Date object, you can switch back to java.time. With the java.time.Instant class, your can generate text in standard ISO 8601 format.

Instant instant = myJavaUtilDate.toInstant() ;
String output = instant.toString() ;  // Generate text in standard ISO 8601 format. 

2024-11-26T16:18:22.123456Z

The Z on the end is a standard abbreviation for an offset of zero, +00:00.

Note the fractional second. The legacy class java.util.Date is limited to mere milliseconds resolution. In contrast, the modern java.time.Instant class resolves to nanoseconds. So calling java.util.Date#from( instant ) may involve data-loss, truncating any microseconds and nanoseconds.

Count from epoch reference

Another way to verify the value of a java.util.Date is to extract its count-from-epoch. Both java.util.Date & java.time.Instant are built on a count from the same epoch reference of first moment of 1970 in UTC, 1970-01-01T00:00Z. The java.util.Date holds a count of milliseconds, while java.time.Instant holds a count of whole seconds plus a number of nanoseconds.

long millisecondsOfJavaUtilDate = myJavaUtilDate.getTime() ;
long millisecondsOfInstant = instant.toEpochMilli() ;

Again, be aware that the Instant object may hold microseconds or nanoseconds. Calling Instant#toEpochMilli() ignores that info.

A solution (aka hack) is to get the different components of the LocalDateTime and plug them into a Date object. It might work or not depending on your needs.

    public static Date convertToDate(LocalDateTime localDateTime) {
        int year = localDateTime.getYear();
        int month = localDateTime.getMonthValue();
        int day = localDateTime.getDayOfMonth();
        int hour = localDateTime.getHour();
        int minute = localDateTime.getMinute();
        int second = localDateTime.getSecond();

        @SuppressWarnings("deprecation")
        Date date = new Date(year - 1900, month - 1, day, hour, minute, second);
        
        return date;
    }

Bear in mind this returns a different point in time compared to the original LocalDateTime, but it sounds from your question that's what you want.

And caveat emptor: please add plenty of tests for whatever you are doing, including when there are daylight savings changes.

本文标签: