Làm thế nào để chuyển đổi ZonedDateTime thành Date?


101

Tôi đang cố gắng đặt thời gian ngày tháng không xác định của máy chủ trong cơ sở dữ liệu của mình và tôi tin rằng cách tốt nhất để làm như vậy là đặt Ngày giờ UTC. Máy chủ db của tôi là Cassandra và trình điều khiển db cho Java chỉ hiểu kiểu Ngày.

Vì vậy, giả sử rằng trong mã của tôi, tôi đang sử dụng Java 8 ZonedDateTime mới để lấy UTC now ( ZonedDateTime.now(ZoneOffset.UTC)), làm cách nào tôi có thể chuyển đổi phiên bản ZonedDateTime này thành lớp Date "kế thừa"?


Có hai lớp "Ngày" được tích hợp trong Java java.util.Datejava.sql.Date.
Basil Bourque

Câu trả lời:


161

Bạn có thể chuyển đổi ZonedDateTime thành tức thì mà bạn có thể sử dụng trực tiếp với Date.

Date.from(java.time.ZonedDateTime.now().toInstant());

27
Không, nó sẽ là Ngày hiện tại trên hệ thống vùng của bạn mặc định.
Slim Soltani Dridi

6
@MilenKovachev Câu hỏi của bạn không có ý nghĩa - Ngày không có múi giờ - nó chỉ đại diện cho thời gian tức thì.
assylias

1
@assylias Thực ra, câu nói của bạn không có ý nghĩa. Định dạng lưu trữ thời gian ngụ ý múi giờ. Ngày dựa trên UTC, thật không may, Java thực hiện một số điều ngu ngốc và không coi nó như vậy, và trên hết, coi thời gian là TZ cục bộ thay vì UTC. Cách Data, LocalDateTime, ZonedDateTime lưu trữ dữ liệu ngụ ý một múi giờ. Cách họ sử dụng như thể TZ không tồn tại, điều này đơn giản là sai. java.util.Date có TZ mặc định của JVM, ngầm định. Việc mọi người coi nó như bất cứ thứ gì khác biệt (kể cả tài liệu java cho nó!) Chỉ là những người xấu.
David

5
@David uh no - a Datelà một số mili giây kể từ kỷ nguyên - vì vậy nó có liên quan đến UTC. Nếu bạn in nó, múi giờ mặc định sẽ được sử dụng, nhưng lớp Ngày không biết về múi giờ của người dùng ... Ví dụ: xem phần "ánh xạ" trong docs.oracle.com/javase/tutorial/datetime/iso/legacy .html Và LocalDateTime là một cách rõ ràng mà không cần tham chiếu đến một múi giờ - có thể được coi là khó hiểu ...
assylias

1
Câu trả lời của bạn không cần liên quan đến ZonedDateTime. Các java.time.Instantlớp là một sự thay thế trực tiếp cho java.util.Date, cả hai đại diện cho một thời điểm trong UTC mặc dù Instantsử dụng độ phân giải tốt hơn của nano giây thay vì mili giây. Date.from( Instant.now() )lẽ ra phải là giải pháp của bạn. Hoặc đối với vấn đề đó, chỉ new Date()có tác dụng tương tự, ghi lại khoảnh khắc hiện tại theo giờ UTC.
Basil Bourque

64

tl; dr

java.util.Date.from(  // Transfer the moment in UTC, truncating any microseconds or nanoseconds to milliseconds.
    Instant.now() ;   // Capture current moment in UTC, with resolution as fine as nanoseconds.
)

Mặc dù không có điểm nào trong đoạn mã trên. Cả hai java.util.DateInstantđại diện cho một thời điểm trong UTC, luôn luôn ở UTC. Mã trên có tác dụng tương tự như:

new java.util.Date()  // Capture current moment in UTC.

Không có lợi ích ở đây để sử dụng ZonedDateTime. Nếu bạn đã có a ZonedDateTime, hãy điều chỉnh thành UTC bằng cách giải nén a Instant.

java.util.Date.from(             // Truncates any micros/nanos.
    myZonedDateTime.toInstant()  // Adjust to UTC. Same moment, same point on the timeline, different wall-clock time.
)

Câu trả lời khác đúng

Câu trả lời của ssoltanid giải quyết chính xác câu hỏi cụ thể của bạn, làm thế nào để chuyển đổi một đối tượng java.time mới ( ZonedDateTime) thành một java.util.Dateđối tượng cũ . Trích xuất Instanttừ ZonedDateTime và chuyển đến java.util.Date.from().

Mất dữ liệu

Lưu ý rằng bạn sẽ bị mất dữ liệu , vì Instanttheo dõi nano giây kể từ kỷ nguyên trong khi java.util.Datetheo dõi mili giây kể từ kỷ nguyên.

biểu đồ so sánh độ phân giải của mili giây, micro giây và nano giây

Câu hỏi và nhận xét của bạn nêu ra các vấn đề khác.

Giữ máy chủ ở UTC

Máy chủ của bạn nên đặt hệ điều hành chủ của chúng thành UTC như một phương pháp hay nhất nói chung. JVM chọn cài đặt HĐH máy chủ này làm múi giờ mặc định của nó, trong các triển khai Java mà tôi biết.

Chỉ định múi giờ

Nhưng bạn không nên dựa vào múi giờ mặc định hiện tại của JVM. Thay vì chọn cài đặt máy chủ, một cờ được thông qua khi khởi chạy JVM có thể đặt múi giờ khác. Thậm chí tệ hơn: Bất kỳ mã nào trong bất kỳ chuỗi nào của bất kỳ ứng dụng nào vào bất kỳ lúc nào đều có thể thực hiện lệnh gọi java.util.TimeZone::setDefaultđể thay đổi mặc định đó trong thời gian chạy!

TimestampLoại Cassandra

Bất kỳ cơ sở dữ liệu và trình điều khiển tốt nào sẽ tự động xử lý việc điều chỉnh ngày-giờ đã qua thành UTC để lưu trữ. Tôi không sử dụng Cassandra, nhưng nó dường như có một số hỗ trợ thô sơ cho ngày-giờ. Tài liệu cho biết Timestamploại của nó là số phần nghìn giây từ cùng một kỷ nguyên (thời điểm đầu tiên của năm 1970 theo giờ UTC).

ISO 8601

Hơn nữa, Cassandra chấp nhận đầu vào chuỗi ở các định dạng tiêu chuẩn ISO 8601 . May mắn thay, java.time sử dụng các định dạng ISO 8601 làm định dạng mặc định để phân tích cú pháp / tạo chuỗi. Việc triển khai Instantlớp học toStringsẽ hoạt động tốt.

Độ chính xác: Mili giây so với Nanosecord

Nhưng trước tiên, chúng ta cần giảm độ chính xác nano giây của ZonedDateTime xuống mili giây. Một cách là tạo Instant mới bằng cách sử dụng mili giây. May mắn thay, java.time có một số phương pháp tiện dụng để chuyển đổi sang và từ mili giây.

Mã mẫu

Đây là một số mã ví dụ trong Java 8 Update 60.

ZonedDateTime zdt = ZonedDateTime.now( ZoneId.of( "America/Montreal" ) );

Instant instant = zdt.toInstant();
Instant instantTruncatedToMilliseconds = Instant.ofEpochMilli( instant.toEpochMilli() );
String fodderForCassandra = instantTruncatedToMilliseconds.toString();  // Example: 2015-08-18T06:36:40.321Z

Hoặc theo tài liệu trình điều khiển Java Cassandra này , bạn có thể vượt qua một java.util.Datephiên bản (không nên nhầm lẫn với java.sqlDate). Vì vậy, bạn có thể tạo ngày tháng năm từ đó instantTruncatedToMillisecondstrong đoạn mã trên.

java.util.Date dateForCassandra = java.util.Date.from( instantTruncatedToMilliseconds );

Nếu làm điều này thường xuyên, bạn có thể tạo một lớp lót.

java.util.Date dateForCassandra = java.util.Date.from( zdt.toInstant() );

Nhưng sẽ gọn gàng hơn nếu tạo ra một phương thức tiện ích nhỏ.

static public java.util.Date toJavaUtilDateFromZonedDateTime ( ZonedDateTime zdt ) {
    Instant instant = zdt.toInstant();
    // Data-loss, going from nanosecond resolution to milliseconds.
    java.util.Date utilDate = java.util.Date.from( instant ) ;
    return utilDate;
}

Lưu ý sự khác biệt trong tất cả mã này so với trong Câu hỏi. Mã của Câu hỏi đang cố gắng điều chỉnh múi giờ của phiên bản ZonedDateTime thành UTC. Nhưng điều đó là không cần thiết. Về mặt khái niệm:

ZonedDateTime = Instant + ZoneId

Chúng tôi chỉ trích xuất phần Instant, phần này đã có trong UTC (về cơ bản là UTC, hãy đọc tài liệu lớp để biết chi tiết chính xác).


Bảng các kiểu ngày-giờ trong Java, cả hiện đại và kế thừa


Giới thiệu về java.time

Các java.time khung được xây dựng vào Java 8 và sau đó. Những lớp học thay thế cái cũ phiền hà di sản lớp học ngày thời gian như java.util.Date, Calendar, & SimpleDateFormat.

Các Joda thời gian dự án, bây giờ trong chế độ bảo trì , khuyên chuyển đổi sang các java.time lớp.

Để tìm hiểu thêm, hãy xem Hướng dẫn Oracle . Và tìm kiếm Stack Overflow để có nhiều ví dụ và giải thích. Đặc điểm kỹ thuật là JSR 310 .

Bạn có thể trao đổi các đối tượng java.time trực tiếp với cơ sở dữ liệu của mình. Sử dụng trình điều khiển JDBC tương thích với JDBC 4.2 trở lên. Không cần chuỗi, không cần java.sql.*lớp.

Lấy các lớp java.time ở đâu?

Bảng sử dụng thư viện java.time với phiên bản Java hoặc Android nào

Các ThreeTen-Extra dự án mở rộng java.time với các lớp bổ sung. Dự án này là cơ sở chứng minh cho những bổ sung có thể có trong tương lai cho java.time. Bạn có thể tìm thấy một số các lớp học hữu ích ở đây chẳng hạn như Interval, YearWeek, YearQuarter, và nhiều hơn nữa .


Giải thích tuyệt vời, cũng chi tiết. Cảm ơn bạn rất nhiều!
Gianmarco F.

4

Đây là một ví dụ chuyển đổi thời gian hệ thống hiện tại sang UTC. Nó liên quan đến việc định dạng ZonedDateTime dưới dạng một Chuỗi và sau đó đối tượng Chuỗi sẽ được phân tích cú pháp thành một đối tượng ngày tháng bằng cách sử dụng java.text DateFormat.

    ZonedDateTime zdt = ZonedDateTime.now(ZoneOffset.UTC);
    final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd HH:mm:ss");
    final DateFormat FORMATTER_YYYYMMDD_HH_MM_SS = new SimpleDateFormat("yyyyMMdd HH:mm:ss");
    String dateStr = zdt.format(DATETIME_FORMATTER);

    Date utcDate = null;
    try {
        utcDate = FORMATTER_YYYYMMDD_HH_MM_SS.parse(dateStr);
    }catch (ParseException ex){
        ex.printStackTrace();
    }

4

Nếu bạn đang sử dụng cổng nền ThreeTen cho Android và không thể sử dụng phiên bản mới hơn Date.from(Instant instant)(yêu cầu tối thiểu API 26), bạn có thể sử dụng:

ZonedDateTime zdt = ZonedDateTime.now();
Date date = new Date(zdt.toInstant().toEpochMilli());

hoặc là:

Date date = DateTimeUtils.toDate(zdt.toInstant());

Hãy cũng đọc lời khuyên trong câu trả lời của Basil Bourque


1
ThreeTen Backport (và ThreeTenABP) bao gồm một DateTimeUtilslớp với các phương thức chuyển đổi, vì vậy tôi sẽ sử dụng Date date = DateTimeUtils.toDate (zdt.toInstant ()); `. Nó không quá thấp.
Ole VV

1

Bạn có thể thực hiện việc này bằng cách sử dụng các lớp java.time được tích hợp trong Java 8 trở lên.

ZonedDateTime temporal = ...
long epochSecond = temporal.getLong(INSTANT_SECONDS);
int nanoOfSecond = temporal.get(NANO_OF_SECOND);
Date date = new Date(epochSecond * 1000 + nanoOfSecond / 1000000);

Xin lỗi, thời gian và tất cả các hằng số này đến từ đâu?
Milen Kovachev

@MilenKovachev A ZonedDateTime là một phiên bản của Temporal
Peter Lawrey

Cảm ơn, nhưng đáng lẽ bạn nên đề cập rằng bạn đã chỉnh sửa câu trả lời của mình để nhận xét của tôi không có vẻ ngớ ngẩn.
Milen Kovachev

2
@MilenKovachev bạn có thể xóa nhận xét, nhưng đó không phải là một câu hỏi ngớ ngẩn.
Peter Lawrey

1
Tôi không thể hiểu tại sao câu trả lời này bị từ chối. Instants theo dõi giây và nano giây. Dates theo dõi mili giây. Việc chuyển đổi từ cái này sang cái khác là đúng.
scottb

1

Tôi sử dụng cái này.

public class TimeTools {

    public static Date getTaipeiNowDate() {
        Instant now = Instant.now();
        ZoneId zoneId = ZoneId.of("Asia/Taipei");
        ZonedDateTime dateAndTimeInTai = ZonedDateTime.ofInstant(now, zoneId);
        try {
            return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(dateAndTimeInTai.toString().substring(0, 19).replace("T", " "));
        } catch (ParseException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return null;
    }
}

Bởi vì Date.from(java.time.ZonedDateTime.ofInstant(now, zoneId).toInstant()); nó không hoạt động !!! Nếu bạn chạy ứng dụng của bạn trong máy tính của bạn, nó không có vấn đề. Nhưng nếu bạn chạy ở bất kỳ vùng nào của AWS hoặc Docker hoặc GCP, nó sẽ tạo ra sự cố. Vì máy tính không phải là múi giờ của bạn trên Đám mây. Bạn nên đặt múi giờ chính xác của mình trong Code. Ví dụ: Châu Á / Đài Bắc. Sau đó, nó sẽ chính xác trong AWS hoặc Docker hoặc GCP.

public class App {
    public static void main(String[] args) {
        Instant now = Instant.now();
        ZoneId zoneId = ZoneId.of("Australia/Sydney");
        ZonedDateTime dateAndTimeInLA = ZonedDateTime.ofInstant(now, zoneId);
        try {
            Date ans = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(dateAndTimeInLA.toString().substring(0, 19).replace("T", " "));
            System.out.println("ans="+ans);
        } catch (ParseException e) {
        }
        Date wrongAns = Date.from(java.time.ZonedDateTime.ofInstant(now, zoneId).toInstant());
        System.out.println("wrongAns="+wrongAns);
    }
}

1
Đây có vẻ là một trong những cách phức tạp nhất trong bất kỳ câu trả lời nào trong 7 câu trả lời. Nó có bất kỳ lợi thế? Tôi sẽ nghĩ là không. Thực sự không cần phải trải qua quá trình định dạng và phân tích cú pháp.
Ole VV

Cảm ơn bạn hỏi. Vì Date saiAns = Date.from (java.time.ZonedDateTime.ofInstant (now, zoneId) .toInstant ()) ;; Nó không làm việc!!!!!
beehuang

Tôi đã cho bạn chạy đoạn mã giây ngay trước 17:08 theo múi giờ của tôi và nhận được ans=Mon Jun 18 01:07:56 CEST 2018, không chính xác, và sau đó wrongAns=Sun Jun 17 17:07:56 CEST 2018, đúng.
Ole VV

Tôi không gặp vấn đề gì khi chạy ứng dụng trong máy tính của mình. Nhưng khi sử dụng docker, nó có vấn đề. Vì múi giờ trong docker không phải là múi giờ của bạn trong máy tính. Bạn nên đặt múi giờ chính xác của mình trong Code. Ví dụ: Châu Á / Đài Bắc. Sau đó, nó sẽ chính xác trong AWS, Docker, GCP hoặc bất kỳ máy tính nào.
beehuang

@ OleV.V. Bạn có thể hiểu không ?? hoặc bất kỳ câu hỏi?
beehuang

1

Câu trả lời được chấp nhận không phù hợp với tôi. Ngày trả về luôn là Ngày cục bộ chứ không phải Ngày cho Múi giờ ban đầu. Tôi sống ở UTC + 2.

//This did not work for me
Date.from(java.time.ZonedDateTime.now().toInstant()); 

Tôi đã nghĩ ra hai cách thay thế để lấy Ngày chính xác từ ZonedDateTime.

Giả sử bạn có ZonedDateTime này cho Hawaii

LocalDateTime ldt = LocalDateTime.now();
ZonedDateTime zdt = ldt.atZone(ZoneId.of("US/Hawaii"); // UTC-10

hoặc cho UTC như đã hỏi ban đầu

Instant zulu = Instant.now(); // GMT, UTC+0
ZonedDateTime zdt = zulu.atZone(ZoneId.of("UTC"));

Phương án 1

Chúng ta có thể sử dụng java.sql.Timestamp. Nó đơn giản nhưng nó có thể sẽ làm giảm tính toàn vẹn trong lập trình của bạn

Date date1 = Timestamp.valueOf(zdt.toLocalDateTime());

Phương án 2

Chúng tôi tạo Ngày từ mili (đã trả lời ở đây trước đó). Lưu ý rằng ZoneOffset cục bộ là phải.

ZoneOffset localOffset = ZoneOffset.systemDefault().getRules().getOffset(LocalDateTime.now());
long zonedMillis = 1000L * zdt.toLocalDateTime().toEpochSecond(localOffset) + zdt.toLocalDateTime().getNano() / 1000000L;
Date date2 = new Date(zonedMillis);

0

Đối với một ứng dụng docker như beehuang đã nhận xét, bạn nên đặt múi giờ của mình.

Ngoài ra, bạn có thể sử dụng vớiZoneSameLocal . Ví dụ:

2014-07-01T00: 00 + 02: 00 [GMT + 02: 00] được chuyển đổi bởi

Date.from(zonedDateTime.withZoneSameLocal(ZoneId.systemDefault()).toInstant())

đến Thứ Ba, 01 00:00:00 CEST 2014muộn hơn

Date.from(zonedDateTime.toInstant())

đến Thứ Hai, ngày 30 tháng 6 22:00:00 UTC 2014


-1

Nếu bạn chỉ quan tâm đến bây giờ, thì chỉ cần sử dụng:

Date d = new Date();

Tôi quan tâm đến bây giờ nhưng giờ UTC.
Milen Kovachev

2
Vâng, đó sẽ là UTC bây giờ, Ngày không biết rõ hơn.
Jacob Eckel

3
Không, nó sẽ không. Nó sẽ là Bây giờ trong múi giờ hệ thống địa phương. Ngày không lưu trữ bất kỳ thông tin múi giờ nào, nhưng nó sử dụng thời gian hệ thống hiện tại và bỏ qua múi giờ hệ thống hiện tại, do đó, không bao giờ chuyển đổi nó trở lại UTC
David

2
@David Date lưu giữ thời gian so với kỷ nguyên UTC, vì vậy nếu bạn lấy một Ngày () mới từ một hệ thống ở Hoa Kỳ và một đối tượng khác tạo thành một máy tính ở Nhật Bản, chúng sẽ giống hệt nhau (hãy xem xét thời gian cả hai đều giữ trong nội bộ).
Jacob Eckel

1
@JacobEckel - Có, nhưng nếu bạn đặt 9 giờ sáng vào một ngày trong khi tz của bạn là tz của Hoa Kỳ và sau đó đổi sang tz JP và tạo một ngày mới với 9 giờ sáng, giá trị nội bộ trong Ngày sẽ khác. Ngay sau khi bạn ném DST vào hỗn hợp, bạn không thể sử dụng Date một cách đáng tin cậy trừ khi ứng dụng của bạn LUÔN LUÔN chạy theo giờ UTC cụ thể, điều này có thể thay đổi vì bất kỳ lý do nào mà hầu hết đều xoay quanh mã xấu.
David
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.