Java: Tại sao hàm tạo ngày không được dùng nữa và thay vào đó tôi sử dụng cái gì?


211

Tôi đến từ thế giới C #, vì vậy chưa có kinh nghiệm với Java. Tôi vừa được nói bởi Eclipse Dateđã bị phản đối:

Person p = new Person();
p.setDateOfBirth(new Date(1985, 1, 1));

Tại sao? Và những gì (đặc biệt là trong các trường hợp như trên) nên được sử dụng thay thế?


37
Tôi đang trải qua một quá trình học tập tương tự, cũng đi từ C # sang Java. Một điều khác mà bit tôi là tháng trong năm là một hệ thống dựa trên 0 (0 đến 11 trong đó tháng 1 = 0 và 12 tháng 12 = 11) nhưng các ngày trong tháng là dựa trên 1 (1 đến 31). Đứng lên trên cái đó!
Paul Sasik

2
@Paul Sasik, vâng, nhưng có Lịch. Chẳng hạn, hằng số cho mỗi tháng
Diogo

5
@PaulSasik lol. Vâng, Java ngu ngốc. Phải chuyển từ C # sang Java và OMG nỗi đau và khổ sở.
cbmeek


8
Những nhận xét "lol" lén lút về Java từ những người C # khiến tôi bật cười vì .Net có thư viện thời gian đúng giờ ( Noda Time ) từ một cổng của thư viện Java xuất sắc Joda-Time .
Basil Bourque

Câu trả lời:


97

Các Dateconstructor cụ thể không được dùng nữa và Calendarnên được sử dụng thay thế. Các JavaDoccho ngày mô tả mà nhà xây dựng đang bị phản đối và làm thế nào để thay thế chúng bằng Calendar.


25
Lịch yêu cầu một đối tượng bổ sung và thích thêm 8 dòng mã để thực hiện điều tương tự là tạo đối tượng Ngày từ những gì tôi có thể nói. Thật khó hiểu và dường như không cần thiết khi bạn chỉ cần một Ngày và không phải là biến điều chỉnh múi giờ.
G_V

5
FYI, người già lớp ngày thời gian khủng khiếp phiền hà như java.util.Date, java.util.Calendarjava.text.SimpleDateFormatbây giờ là di sản , thay thế bởi sự java.time class built vào Java 8 và sau đó. Xem Hướng dẫn của Oracle .
Basil Bourque

250

Các java.util.Datelớp được không thực sự phản đối, chỉ constructor đó, cùng với một vài nhà thầu khác / phương pháp đang bị phản đối. Nó không được chấp nhận vì cách sử dụng đó không hoạt động tốt với quốc tế hóa. Các Calendarlớp nên được sử dụng thay vì:

Calendar cal = Calendar.getInstance();
cal.set(Calendar.YEAR, 1988);
cal.set(Calendar.MONTH, Calendar.JANUARY);
cal.set(Calendar.DAY_OF_MONTH, 1);
Date dateRepresentation = cal.getTime();

Hãy xem ngày Javadoc:

http://doad.oracle.com/javase/6/docs/api/java/util/Date.html


2
Điều này nên được trả lời tốt nhất. Cảm ơn.
jordaniac89

Bằng cách nói "không hoạt động tốt với quốc tế hóa", bạn có nghĩa là đối với Ngày, bạn không thể gán TimeZone cho nó? Cảm ơn
DiveInto

Ngày nào đã phân tích một Chuỗi, vì vậy thay vào đó chúng ta phải phân chuỗi một Chuỗi chứa năm, tháng và ngày? Có vẻ như rất nhiều rắc rối cho một cái gì đó mà trong hầu hết các trường hợp không cần logic và phương pháp phức tạp như vậy được thêm vào nó.
G_V

1
Chỉ cần thêm, điều này sẽ xem xét múi giờ mặc định. Nếu chúng tôi muốn chỉ định bất kỳ múi giờ nào khác, chúng tôi có thể sử dụngCalendar cal = Calendar.getInstance(TimeZone.getTimeZone(<timezone id>));
Vikas Prasad

1
"Thay vào đó nên sử dụng lớp Lịch" - Đối với Java 8 trở lên, các java.time.*lớp là tùy chọn tốt hơn ... nếu bạn đang thay đổi mã của mình.
Stephen C

73

tl; dr

LocalDate.of( 1985 , 1 , 1 )

…hoặc là…

LocalDate.of( 1985 , Month.JANUARY , 1 )

Chi tiết

Các java.util.Date, java.util.Calendarjava.text.SimpleDateFormatcác lớp đã được vội vàng quá nhanh khi Java lần đầu tiên ra mắt và phát triển. Các lớp học không được thiết kế hoặc thực hiện tốt. Những cải tiến đã được thử, do đó, sự phản đối mà bạn đã tìm thấy. Thật không may, những nỗ lực cải thiện phần lớn đã thất bại. Bạn nên tránh các lớp này hoàn toàn. Chúng được thay thế trong Java 8 bởi các lớp mới.

Các vấn đề trong mã của bạn

Một java.util.Date có cả ngày và phần thời gian. Bạn đã bỏ qua phần thời gian trong mã của bạn. Vì vậy, lớp Date sẽ bắt đầu ngày như được xác định bởi múi giờ mặc định của JVM của bạn và áp dụng thời gian đó cho đối tượng Date. Vì vậy, kết quả mã của bạn sẽ thay đổi tùy thuộc vào máy nào chạy hoặc múi giờ nào được đặt. Có lẽ không phải là những gì bạn muốn.

Nếu bạn chỉ muốn ngày, không có phần thời gian, chẳng hạn như ngày sinh, bạn có thể không muốn sử dụng một Dateđối tượng. Bạn có thể muốn lưu trữ chỉ một chuỗi ngày, ở định dạng ISO 8601YYYY-MM-DD . Hoặc sử dụng một LocalDateđối tượng từ Joda-Time (xem bên dưới).

Joda-Thời gian

Điều đầu tiên cần học trong Java: Tránh các lớp java.util.Date & java.util.CalWiki nổi tiếng rắc rối đi kèm với Java.

Như được ghi chú chính xác trong câu trả lời của user3277382 , hãy sử dụng Joda-Time hoặc mới gói java.time. * Mới trong Java 8.

Mã ví dụ trong Joda-Time 2.3

DateTimeZone timeZoneNorway = DateTimeZone.forID( "Europe/Oslo" );
DateTime birthDateTime_InNorway = new DateTime( 1985, 1, 1, 3, 2, 1, timeZoneNorway );

DateTimeZone timeZoneNewYork = DateTimeZone.forID( "America/New_York" );
DateTime birthDateTime_InNewYork = birthDateTime_InNorway.toDateTime( timeZoneNewYork ); 

DateTime birthDateTime_UtcGmt = birthDateTime_InNorway.toDateTime( DateTimeZone.UTC );

LocalDate birthDate = new LocalDate( 1985, 1, 1 );

Kết xuất giao diện điều khiển

System.out.println( "birthDateTime_InNorway: " + birthDateTime_InNorway );
System.out.println( "birthDateTime_InNewYork: " + birthDateTime_InNewYork );
System.out.println( "birthDateTime_UtcGmt: " + birthDateTime_UtcGmt );
System.out.println( "birthDate: " + birthDate );

Khi chạy đào

birthDateTime_InNorway: 1985-01-01T03:02:01.000+01:00
birthDateTime_InNewYork: 1984-12-31T21:02:01.000-05:00
birthDateTime_UtcGmt: 1985-01-01T02:02:01.000Z
birthDate: 1985-01-01

java.time

Trong trường hợp này, mã cho java.time gần giống với mã của Joda-Time .

Chúng ta có một múi giờ ( ZoneId) và xây dựng một đối tượng thời gian được gán cho múi giờ đó ( ZonedDateTime). Sau đó, sử dụng mẫu Đối tượng không thay đổi , chúng tôi tạo thời gian ngày mới dựa trên cùng một đối tượng cũ (tính số nano giây kể từ epoch ) nhưng được gán múi giờ khác. Cuối cùng, chúng tôi nhận được một LocalDatecái không có múi giờ cũng như múi giờ mặc dù chú ý múi giờ được áp dụng khi xác định ngày đó ( ví dụ một ngày mới bắt đầu sớm hơn ở Oslo so với ở New York ).

ZoneId zoneId_Norway = ZoneId.of( "Europe/Oslo" );
ZonedDateTime zdt_Norway = ZonedDateTime.of( 1985 , 1 , 1 , 3 , 2 , 1 , 0 , zoneId_Norway );

ZoneId zoneId_NewYork = ZonedId.of( "America/New_York" );
ZonedDateTime zdt_NewYork = zdt_Norway.withZoneSameInstant( zoneId_NewYork );

ZonedDateTime zdt_Utc = zdt_Norway.withZoneSameInstant( ZoneOffset.UTC );  // Or, next line is similar.
Instant instant = zdt_Norway.toInstant();  // Instant is always in UTC.

LocalDate localDate_Norway = zdt_Norway.toLocalDate();

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 di cư đến java.time lớp.

Để tìm hiểu thêm, xem Hướng dẫn Oracle . Và tìm kiếm Stack Overflow cho 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 bạn. 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ầnjava.sql.* lớp.

Nơi để có được các lớp java.time?

  • Java SE 8 , Java SE 9 , Java SE 10 và mới hơn
    • Được xây dựng trong.
    • Một phần của API Java tiêu chuẩn với việc triển khai đi kèm.
    • Java 9 thêm một số tính năng nhỏ và sửa lỗi.
  • Java SE 6 Java SE 7
    • Phần lớn chức năng java.time được chuyển ngược lại sang Java 6 & 7 trong ThreeTen-Backport .
  • Android
    • Các phiên bản sau của triển khai gói Android của các lớp java.time.
    • Đối với Android trước đó (<26), các ThreeTenABP dự án thích nghi ThreeTen-backport (nêu trên). Xem Cách sử dụng ThreeTenABP .

Các ThreeTen-Extra dự án mở rộng java.time với các lớp bổ sung. Dự án này là một nền tảng chứng minh cho các bổ sung có thể 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 .


5
Đạo cụ để thực sự đề cập đến nguyên nhân gốc rễ và cung cấp bảo hiểm của tất cả các lựa chọn thay thế có sẵn.
mabi

Đây là câu trả lời đúng và lớp Lịch nên tránh. ThreeTen là lựa chọn tốt nhất
ant2009

11

Một lý do khiến nhà xây dựng không được chấp nhận là ý nghĩa của tham số năm không phải là điều bạn mong đợi. Javadoc nói:

Kể từ phiên bản JDK 1.1, được thay thế bằng Calendar.set(year + 1900, month, date).

Lưu ý rằng trường năm là số năm kể từ 1900 , vì vậy mã mẫu của bạn rất có thể sẽ không làm những gì bạn mong đợi. Và đó là điểm chính.

Nói chung, DateAPI chỉ hỗ trợ lịch phương tây hiện đại, có các thành phần được chỉ định riêng và hoạt động không nhất quán nếu bạn đặt trường.

Các API CalendarGregorianCalendartốt hơn Date, và API thời gian Joda của bên thứ 3 thường được cho là tốt nhất. Trong Java 8, họ đã giới thiệu các java.timegói và đây là các gói được đề xuất thay thế.


Giải pháp này hoạt động rất tốt, ngoại trừ việc nó không sử dụng thời gian 0 cho giờ-phút-giây-mili giây như Ngày mới (năm, tháng, ngày) đã làm. Vì vậy, chúng ta cần một cái gì đó như: Lịch cal = Calendar.getInstance (); cal.clear (); cal.set (năm + 1900, tháng, ngày); Ngày dateRepftimeation = cal.getTime ();
Joel Richard Koett

11

Tôi đã bắt gặp câu hỏi này như là một bản sao của một câu hỏi mới hơn , trong đó hỏi cách thức không được phản đối để có được một Datenăm, tháng và ngày cụ thể là gì.

Các câu trả lời ở đây cho đến nay nói rằng hãy sử dụng Calendarlớp và điều đó là đúng cho đến khi Java 8 xuất hiện. Nhưng kể từ Java 8, cách tiêu chuẩn để làm điều này là:

LocalDate localDate = LocalDate.of(1985, 1, 1);

Và sau đó nếu bạn thực sự cần một java.util.Date, bạn có thể sử dụng các gợi ý trong câu hỏi này .

Để biết thêm thông tin, hãy xem API hoặc các hướng dẫn cho Java 8.


7

Xin lưu ý rằng Calendar.getTime()không đặc biệt theo nghĩa là ban ngày phần mặc định với thời gian hiện tại.

Để sao chép, hãy thử chạy mã sau một vài lần:

Calendar c = Calendar.getInstance();
c.set(2010, 2, 7); // NB: 2 means March, not February!
System.err.println(c.getTime());

Đầu ra, ví dụ:

Sun Mar 07 10:46:21 CET 2010

Chạy mã chính xác một vài phút sau mang lại:

Sun Mar 07 10:57:51 CET 2010

Vì vậy, trong khi set()buộc các trường tương ứng phải sửa các giá trị, nó sẽ rò rỉ thời gian hệ thống cho các trường khác. (Đã thử nghiệm ở trên với Sun jdk6 & jdk7)




2

Vì hàm tạo ngày không được dùng nữa, bạn có thể thử mã này.

import java.util.Calendar;

  Calendar calendar = Calendar.getInstance();
     calendar.set(Calendar.HOUR_OF_DAY, 6);// for 6 hour
     calendar.set(Calendar.MINUTE, 0);// for 0 min
     calendar.set(Calendar.SECOND, 0);// for 0 sec
     calendar.set(1996,0,26);// for Date [year,month(0 to 11), date]

    Date date = new Date(calendar.getTimeInMillis());// calendar gives long value

    String mConvertedDate = date.toString();// Fri Jan 26 06:00:00 GMT+05:30 1996

1
Các lớp khủng khiếp này hiện đang là di sản, đã được thay thế từ nhiều năm trước bởi các lớp java.time được định nghĩa trong JSR 310. Đề xuất DateCalendarnăm 2019 là lời khuyên tồi.
Basil Bourque

@BasilBourque cảm ơn vì lời đề nghị, tôi rất mong được cập nhật sớm.
Divyanshu Kumar


1

Bạn có thể tạo một phương thức giống như new Date(year,month,date)trong mã của bạn bằng cách sử dụng Calendarlớp.

private Date getDate(int year,int month,int date){
    Calendar cal = Calendar.getInstance();
    cal.set(Calendar.YEAR, year);
    cal.set(Calendar.MONTH, month-1);
    cal.set(Calendar.DAY_OF_MONTH, day);
    return cal.getTime();
}

Nó sẽ hoạt động giống như các nhà xây dựng không dùng nữa Date


1
Các lớp rắc rối khủng khiếp này đã được thay thế từ nhiều năm trước bởi các lớp java.time hiện đại , cụ thể InstantZonedDateTime.
Basil Bourque

Giải pháp ở trên nên thêm 1900 vào năm và KHÔNG nên trừ 1 từ tháng và cũng nên chạy cal.clear () sau dòng Calendar.getInstance (), để giờ / phút / giây / mili giây bị giảm (như họ làm trong hàm tạo ngày).
Joel Richard Koett

1

Hàm tạo ngày dự kiến ​​năm theo định dạng năm kể từ năm 1900, tháng dựa trên số không, ngày dựa trên một lần và đặt giờ / phút / giây / mili giây thành không.

Date result = new Date(year, month, day);

Vì vậy, bằng cách sử dụng thay thế Lịch (năm dựa trên số không, tháng dựa trên số không, ngày dựa trên một lần) cho nhà xây dựng Ngày không dùng nữa, chúng tôi cần một cái gì đó như:

Calendar calendar = Calendar.getInstance();
calendar.clear(); // Sets hours/minutes/seconds/milliseconds to zero
calendar.set(year + 1900, month, day);
Date result = calendar.getTime();

Hoặc sử dụng Java 1.8 (có năm dựa trên số không và tháng và ngày dựa trên một lần):

Date result = Date.from(LocalDate.of(year + 1900, month + 1, day).atStartOfDay(ZoneId.systemDefault()).toInstant());

Dưới đây là các phiên bản bằng nhau của Ngày, Lịch và Java 1.8:

int year = 1985; // 1985
int month = 1; // January
int day = 1; // 1st

// Original, 1900-based year, zero-based month, one-based day
Date date1 = new Date(year - 1900, month - 1, day);

// Calendar, zero-based year, zero-based month, one-based day
Calendar calendar = Calendar.getInstance();
calendar.clear(); // Sets hours/minutes/seconds/milliseconds to zero
calendar.set(year, month - 1, day);
Date date2 = calendar.getTime();

// Java-time back to Date, zero-based year, one-based month, one-based day
Date date3 = Date.from(LocalDate.of(year, month, day).atStartOfDay(ZoneId.systemDefault()).toInstant());

SimpleDateFormat format = new SimpleDateFormat("yyyy-MMM-dd HH:mm:ss.SSS");

// All 3 print "1985-Jan-01 00:00:00.000"
System.out.println(format.format(date1));
System.out.println(format.format(date2));
System.out.println(format.format(date3));

1
Cả hai lớp khủng khiếp này đã được thay thế từ nhiều năm trước bởi các lớp java.time được định nghĩa trong JSR 310.
Basil Bourque

Câu trả lời được cập nhật để hiển thị các phiên bản Ngày, Lịch và Java 1.8.
Joel Richard Koett

0
new GregorianCalendar(1985, Calendar.JANUARY, 1).getTime();

(cách tiền Java-8)


Đây có lẽ là điều tốt nhất bạn có thể làm mà không cần java.time. Tuy nhiên, trước Java 8 bạn cũng có thể sử dụng java.time, có một backport: thư viện ThreeTen Backport .
Ole VV
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.