Chuyển đổi giữa java.time.LocalDateTime và java.util.Date


484

Java 8 có API hoàn toàn mới cho ngày và thời gian. Một trong những lớp hữu ích nhất trong API này là LocalDateTime, để giữ giá trị ngày theo thời gian độc lập với múi giờ.

Có lẽ có hàng triệu dòng mã sử dụng lớp kế thừa java.util.Datecho mục đích này. Như vậy, khi giao thoa mã cũ và mã mới sẽ cần phải chuyển đổi giữa hai mã. Vì dường như không có phương pháp trực tiếp để thực hiện điều này, làm thế nào nó có thể được thực hiện?




Câu trả lời:


706

Câu trả lời ngắn:

Date in = new Date();
LocalDateTime ldt = LocalDateTime.ofInstant(in.toInstant(), ZoneId.systemDefault());
Date out = Date.from(ldt.atZone(ZoneId.systemDefault()).toInstant());

Giải thích: (dựa trên câu hỏi này về LocalDate)

Mặc dù tên của nó, java.util.Dateđại diện cho một tức thời trên dòng thời gian, không phải là "ngày". Dữ liệu thực tế được lưu trữ trong đối tượng là một longphần nghìn giây kể từ 1970-01-01T00: 00Z (nửa đêm khi bắt đầu 1970 GMT / UTC).

Lớp tương đương với java.util.Datetrong JSR-310 là Instant, do đó, có các phương thức thuận tiện để cung cấp chuyển đổi sang và chuyển đổi:

Date input = new Date();
Instant instant = input.toInstant();
Date output = Date.from(instant);

Một java.util.Datethể hiện không có khái niệm về múi giờ. Điều này có vẻ lạ nếu bạn gọi toString()trên a java.util.Date, vì toStringnó liên quan đến múi giờ. Tuy nhiên, phương thức đó thực sự sử dụng múi giờ mặc định của Java một cách nhanh chóng để cung cấp chuỗi. Múi giờ không phải là một phần của trạng thái thực tế java.util.Date.

An Instantcũng không chứa bất kỳ thông tin nào về múi giờ. Vì vậy, để chuyển đổi từ Instantngày sang giờ địa phương, cần phải chỉ định múi giờ. Đây có thể là vùng mặc định - ZoneId.systemDefault()- hoặc có thể là múi giờ mà ứng dụng của bạn kiểm soát, chẳng hạn như múi giờ từ tùy chọn của người dùng. LocalDateTimecó một phương thức nhà máy thuận tiện, mất cả thời gian và thời gian:

Date in = new Date();
LocalDateTime ldt = LocalDateTime.ofInstant(in.toInstant(), ZoneId.systemDefault());

Ngược lại, LocalDateTimemúi giờ được chỉ định bằng cách gọi atZone(ZoneId)phương thức. Sau ZonedDateTimeđó có thể được chuyển đổi trực tiếp thành Instant:

LocalDateTime ldt = ...
ZonedDateTime zdt = ldt.atZone(ZoneId.systemDefault());
Date output = Date.from(zdt.toInstant());

Lưu ý rằng việc chuyển đổi từ LocalDateTimesang ZonedDateTimecó khả năng giới thiệu hành vi không mong muốn. Điều này là do không phải mọi ngày giờ địa phương đều tồn tại do Giờ tiết kiệm ánh sáng ban ngày. Vào mùa thu / mùa thu, có một sự trùng lặp trong dòng thời gian địa phương nơi cùng một ngày giờ địa phương xảy ra hai lần. Vào mùa xuân, có một khoảng trống, nơi một giờ biến mất. Xem Javadoc atZone(ZoneId)để biết thêm định nghĩa về việc chuyển đổi sẽ làm gì.

Tóm tắt, nếu bạn đi khứ hồi từ a java.util.Dateđến a LocalDateTimevà quay lại, java.util.Datebạn có thể kết thúc với một tức thì khác do Giờ tiết kiệm ánh sáng ban ngày.

Thông tin bổ sung: Có một sự khác biệt khác sẽ ảnh hưởng đến ngày rất cũ. java.util.Datesử dụng lịch thay đổi vào ngày 15 tháng 10 năm 1582, với ngày trước đó sử dụng lịch Julian thay vì lịch Gregorian. Ngược lại, java.time.*sử dụng hệ thống lịch ISO (tương đương với Gregorian) cho mọi thời đại. Trong hầu hết các trường hợp sử dụng, hệ thống lịch ISO là những gì bạn muốn, nhưng bạn có thể thấy các hiệu ứng kỳ lạ khi so sánh ngày trước năm 1582.


5
Cảm ơn rất nhiều cho lời giải thích rõ ràng. Đặc biệt là tại sao java.util.Datekhông chứa múi giờ, nhưng in nó trong toString(). Tài liệu chính thức của sự kiện không nói rõ điều này khi bạn đăng.
Cherry

Cảnh báo: LocalDateTime.ofInstant(date.toInstant()... không hành xử như người ta mong đợi một cách ngây thơ. Ví dụ new Date(1111-1900,11-1,11,0,0,0);sẽ trở nên 1111-11-17 23:53:28sử dụng phương pháp này. Hãy xem việc thực hiện java.sql.Timestamp#toLocalDateTime()nếu bạn cần kết quả 1111-11-11 00:00:00trong ví dụ trước.
con chó

2
Tôi đã thêm một phần về những ngày rất cũ (trước năm 1582). FWIW, bản sửa lỗi được đề xuất của bạn có thể sai, vì 1111-11-11 trong java.util.Date là cùng một ngày thực tế trong lịch sử là 1111-11-18 trong java.time do các hệ thống lịch khác nhau (chênh lệch 6,5 phút xảy ra ở nhiều múi giờ trước năm 1900)
JodaStephen

2
Cũng đáng lưu ý rằng java.sql.Date#toInstantném một UnsupportedOperationException. Vì vậy, không sử dụng toInstanttrong RowMapper trên java.sql.ResultSet#getDate.
LazerBass

132

Đây là những gì tôi đã đưa ra (và giống như tất cả các câu hỏi hóc búa về Ngày giờ, nó có thể sẽ bị từ chối dựa trên một số điều chỉnh ánh sáng theo múi giờ kỳ lạ: D)

Vượt vòng: Date<< - >>LocalDateTime

Được: Date date = [some date]

(1) LocalDateTime<< Instant<<Date

    Instant instant = Instant.ofEpochMilli(date.getTime());
    LocalDateTime ldt = LocalDateTime.ofInstant(instant, ZoneOffset.UTC);

(2) Date<< Instant<<LocalDateTime

    Instant instant = ldt.toInstant(ZoneOffset.UTC);
    Date date = Date.from(instant);

Thí dụ:

Được:

Date date = new Date();
System.out.println(date + " long: " + date.getTime());

(1) LocalDateTime<< Instant<< Date:

Tạo Instanttừ Date:

Instant instant = Instant.ofEpochMilli(date.getTime());
System.out.println("Instant from Date:\n" + instant);

Tạo Datetừ Instant(không cần thiết, nhưng để minh họa):

date = Date.from(instant);
System.out.println("Date from Instant:\n" + date + " long: " + date.getTime());

Tạo LocalDateTimetừInstant

LocalDateTime ldt = LocalDateTime.ofInstant(instant, ZoneOffset.UTC);
System.out.println("LocalDateTime from Instant:\n" + ldt);

(2) Date<< Instant<<LocalDateTime

Tạo Instanttừ LocalDateTime:

instant = ldt.toInstant(ZoneOffset.UTC);
System.out.println("Instant from LocalDateTime:\n" + instant);

Tạo Datetừ Instant:

date = Date.from(instant);
System.out.println("Date from Instant:\n" + date + " long: " + date.getTime());

Đầu ra là:

Fri Nov 01 07:13:04 PDT 2013 long: 1383315184574

Instant from Date:
2013-11-01T14:13:04.574Z

Date from Instant:
Fri Nov 01 07:13:04 PDT 2013 long: 1383315184574

LocalDateTime from Instant:
2013-11-01T14:13:04.574

Instant from LocalDateTime:
2013-11-01T14:13:04.574Z

Date from Instant:
Fri Nov 01 07:13:04 PDT 2013 long: 1383315184574

2
@scottb bạn đang giải quyết điều đó cho tôi hoặc người đặt ra câu hỏi? Theo suy nghĩ của tôi, thật tốt, thật tuyệt khi chuyển đổi được nêu rõ ràng trong API jdk 8 - Họ ít nhất có thể đã làm điều đó. Trong mọi trường hợp, có rất nhiều thư viện sẽ được cung cấp lại để bao gồm các tính năng Java-8 mới và để biết cách thực hiện nó là điều quan trọng.
Điều phối viên

4
@scottb Tại sao trường hợp bên thứ 3 sẽ không phổ biến? Nó phổ biến chết tiệt. Một ví dụ: JDBC 4 trở xuống (hy vọng không phải 5).
Raman

5
Như một quy tắc JSR-310 chung, không cần phải chuyển đổi giữa các loại bằng epoch-millis. Các lựa chọn thay thế tốt hơn tồn tại bằng cách sử dụng các đối tượng, xem câu trả lời đầy đủ của tôi dưới đây. Câu trả lời ở trên cũng chỉ hoàn toàn hợp lệ nếu sử dụng phần bù vùng như UTC - một số phần của câu trả lời sẽ không hoạt động cho toàn bộ múi giờ như America / New_York.
JodaStephen

2
Thay vì Instant.ofEpochMilli(date.getTime())làmdate.toInstant()

1
@goat có toInstant()vẻ tốt, ngoại trừ thất bại cho java.sql.Date, arggggh! Vì vậy, cuối cùng nó dễ sử dụng hơn Instant.ofEpochMilli(date.getTime()).
vadipp

22

Cách thuận tiện hơn nhiều nếu bạn chắc chắn rằng bạn cần múi giờ mặc định:

Date d = java.sql.Timestamp.valueOf( myLocalDateTime );

25
Chắc chắn, nó dễ dàng hơn, nhưng tôi không thích trộn các thứ liên quan đến jdbc với xử lý Ngày đơn giản, ít nhất là IMHO.
Enrico Giurin

9
Tôi xin lỗi, đây là một giải pháp khủng khiếp cho một vấn đề đơn giản. Giống như xác định 5 bằng với số vòng trong logo Olympic.
Madbreaks

7

những điều sau đây dường như hoạt động khi chuyển đổi từ API LocalDateTime mới sang java.util.date:

Date.from(ZonedDateTime.of({time as LocalDateTime}, ZoneId.systemDefault()).toInstant());

chuyển đổi ngược lại có thể (hy vọng) đạt được cách tương tự ...

hy vọng nó giúp...


5

Mọi thứ đều ở đây: http://blog.progs.be/542/date-to-java-time

Câu trả lời với "vấp ngã" là không chính xác: khi bạn làm

LocalDateTime ldt = LocalDateTime.ofInstant(instant, ZoneOffset.UTC);

nếu múi giờ hệ thống của bạn không phải là UTC / GMT, bạn sẽ thay đổi thời gian!


Chỉ khi bạn làm điều đó trong 1 giờ một năm, vào mùa thu, khi hai lần từ LocalDateTime trùng nhau. Di chuyển về phía trước mùa xuân không gây ra vấn đề. Hầu hết thời gian sẽ chuyển đổi cả hai hướng đúng.
David


3

Tôi không chắc đây là cách đơn giản nhất hay tốt nhất, hoặc nếu có bất kỳ cạm bẫy nào, nhưng nó hoạt động:

static public LocalDateTime toLdt(Date date) {
    GregorianCalendar cal = new GregorianCalendar();
    cal.setTime(date);
    ZonedDateTime zdt = cal.toZonedDateTime();
    return zdt.toLocalDateTime();
}

static public Date fromLdt(LocalDateTime ldt) {
    ZonedDateTime zdt = ZonedDateTime.of(ldt, ZoneId.systemDefault());
    GregorianCalendar cal = GregorianCalendar.from(zdt);
    return cal.getTime();
}

3
Chắc chắn có một cạm bẫy từ LocalDateTimeđến Date. Tại các chuyển đổi tiết kiệm ánh sáng ban ngày, một LocalDateTimecó thể không tồn tại, hoặc xảy ra hai lần. Bạn cần tìm ra những gì bạn muốn xảy ra trong từng trường hợp.
Jon Skeet

1
BTW, GregorianCalendarthuộc API cũ vụng về mà API mới java.timenhắm đến để thay thế
Vadzim

3

Nếu bạn đang dùng Android và sử dụng threetenbp, bạn có thể sử dụng DateTimeUtilsthay thế.

Ví dụ:

Date date = DateTimeUtils.toDate(localDateTime.atZone(ZoneId.systemDefault()).toInstant());

bạn không thể sử dụng Date.fromvì nó chỉ được hỗ trợ trên api 26+

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.