Java: เหตุใดตัวสร้าง Date จึงเลิกใช้แล้วและฉันจะใช้อะไรแทน


212

ฉันมาจากโลก C # ดังนั้นยังไม่เคยมีประสบการณ์กับ Java ฉันเพิ่งบอกโดย Eclipse ที่Dateเลิกใช้แล้ว:

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

ทำไม? และควรใช้อะไร (โดยเฉพาะในกรณีเช่นด้านบน) แทน?


37
ฉันกำลังประสบกับเส้นโค้งการเรียนรู้ที่คล้ายกันซึ่งเริ่มจาก C # ถึง Java อีกสิ่งหนึ่งที่บิตฉันคือเดือนของปีเป็นระบบที่ใช้ 0 (0 ถึง 11 โดยที่ Jan. = 0 และ Dec. = 11) แต่วันของเดือนคือ 1-based (1 ถึง 31) มุ่งหน้าไปที่หนึ่ง!
Paul Sasik

2
@ Paul Sasik ใช่ แต่มี Calendar.JANUARY เป็นค่าคงที่เช่นกันและอีกหนึ่งค่าต่อเดือน
Diogo

5
@ PaulSasik lol ใช่จาวาโง่ ๆ ต้องเปลี่ยนจาก C # เป็น Java และ OMG ความเจ็บปวดและความทุกข์ยาก
cbmeeks

2
มีความเป็นไปได้ที่ซ้ำกันของทำไมเมธอด java.util.Date ส่วนใหญ่จึงเลิกใช้แล้ว
โทมัส

8
ต่อว่าต่อขาน "ฮ่า ๆ" หมายเหตุเกี่ยวกับ Java จาก C # คนทำให้ฉันหัวเราะเพราะสุทธิได้ดีวันเวลาของห้องสมุด ( Noda เวลา ) จากพอร์ตของดี Java ห้องสมุดJoda เวลา
Basil Bourque

คำตอบ:


98

ตัวDateสร้างเฉพาะเลิกใช้แล้วและCalendarควรใช้แทน JavaDocสำหรับวันที่Calendarจะอธิบายถึงการก่อสร้างจะเลิกใช้และวิธีการแทนที่พวกเขาใช้


25
ปฏิทินต้องการวัตถุพิเศษและต้องการรหัสมากกว่า 8 บรรทัดเพื่อทำสิ่งเดียวกันซึ่งสร้างวัตถุวันที่จากสิ่งที่ฉันสามารถบอกได้ มันดูสับสนและไม่จำเป็นเมื่อคุณต้องการ Date และไม่ใช่ตัวแปรที่ปรับเขตเวลา
G_V

5
FYI, ลำบากชะมัดเก่าชั้นเรียนวันที่เวลาเช่นjava.util.Date, java.util.Calendarและjava.text.SimpleDateFormatตอนนี้มรดกแทนที่โดยjava.timeชั้นเรียนที่สร้างขึ้นใน Java 8 และต่อมา ดูการสอนโดยออราเคิล
Basil Bourque

250

java.util.Dateชั้นไม่ได้เลิกใช้จริงเพียงแค่สร้างที่พร้อมกับคู่อื่น ๆ ก่อสร้าง / วิธีการถูกปฏิเสธ มันเลิกใช้แล้วเนื่องจากการใช้งานประเภทนั้นใช้งานไม่ได้กับระบบสากล Calendarชั้นควรจะใช้แทน:

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();

ดูวันที่ Javadoc:

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


2
นี่ควรเป็นคำตอบที่ดีที่สุด ขอบคุณ
jordaniac89

ด้วยการพูดว่า "ใช้งานไม่ได้กับความเป็นสากล" คุณหมายถึงว่าสำหรับ Date คุณไม่สามารถกำหนด TimeZone ให้กับมันได้หรือไม่ ขอบคุณ
DiveInto

วันที่ใดที่แยกสตริงดังนั้นตอนนี้เราจึงต้องซับสตริงที่มีปีเดือนและวันแทน ดูเหมือนจะเป็นเรื่องยุ่งยากเป็นพิเศษสำหรับบางสิ่งซึ่งในกรณีส่วนใหญ่ไม่ต้องการตรรกะที่ซับซ้อนและวิธีการที่เพิ่มเข้ามา
G_V

1
เพียงเพิ่มนี้จะใช้เวลาเขตเวลาเริ่มต้นในการพิจารณา หากเราต้องการระบุเขตเวลาอื่นเราสามารถใช้Calendar cal = Calendar.getInstance(TimeZone.getTimeZone(<timezone id>));
Vikas Prasad

1
"ควรใช้คลาสปฏิทินแทน" - สำหรับ Java 8 และใหม่กว่าjava.time.*คลาสนั้นเป็นตัวเลือกที่ดีกว่า ... หากคุณเปลี่ยนรหัส
Stephen C

73

TL; DR

LocalDate.of( 1985 , 1 , 1 )

…หรือ…

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

รายละเอียด

The java.util.Date, java.util.Calendarและjava.text.SimpleDateFormatคลาสต่างรีบเร่งเร็วเกินไปเมื่อ Java เปิดตัวและพัฒนาครั้งแรก ชั้นเรียนไม่ได้รับการออกแบบหรือใช้งานอย่างดี มีการพยายามปรับปรุงทำให้เกิดความไม่พอใจที่คุณพบ น่าเสียดายที่ความพยายามในการปรับปรุงล้มเหลวเป็นส่วนใหญ่ คุณควรหลีกเลี่ยงคลาสเหล่านี้ทั้งหมด พวกมันถูกแทนที่ด้วย Java 8 โดยคลาสใหม่

ปัญหาในรหัสของคุณ

java.util.Date มีทั้งวันที่และเวลา คุณละเว้นส่วนเวลาในรหัสของคุณ ดังนั้นคลาส Date จะเริ่มต้นวันตามที่กำหนดโดยเขตเวลาเริ่มต้นของ JVM ของคุณและใช้เวลานั้นกับวัตถุ Date ดังนั้นผลลัพธ์ของรหัสของคุณจะแตกต่างกันไปขึ้นอยู่กับเครื่องที่มันทำงานหรือตั้งค่าเขตเวลา อาจไม่ใช่สิ่งที่คุณต้องการ

หากคุณต้องการเพียงแค่วันที่โดยไม่มีส่วนเวลาเช่นวันเกิดคุณอาจไม่ต้องการใช้Dateวัตถุ คุณอาจต้องการที่จะเก็บเพียงสายของวันที่ในการรับรองมาตรฐาน ISO 8601YYYY-MM-DDรูปแบบของ หรือใช้LocalDateวัตถุจาก Joda-Time (ดูด้านล่าง)

Joda เวลา

สิ่งแรกที่ต้องเรียนรู้ใน Java: หลีกเลี่ยงคลาส java.util.Date ที่มีปัญหาอย่างฉาวโฉ่และ java.util.Calendar ที่มาพร้อมกับ Java

ตามที่ระบุไว้อย่างถูกต้องในคำตอบโดย user3277382ให้ใช้แพ็คเกจJoda-Timeหรือjava.time. *ใหม่ใน Java 8

โค้ดตัวอย่างใน 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 );

ถ่ายโอนข้อมูลไปยังคอนโซล ...

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 );

เมื่อวิ่ง ...

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

ในกรณีนี้รหัสสำหรับjava.timeเกือบจะเหมือนกับที่Joda เวลา

เราได้รับเขตเวลา ( ZoneId) และสร้างวัตถุวันที่ที่กำหนดให้กับเขตเวลานั้น ( ZonedDateTime) แล้วใช้ไม่เปลี่ยนรูปวัตถุรูปแบบที่เราสร้างวันครั้งใหม่บนพื้นฐานทันทีเดียวกันวัตถุเก่า (นับของนาโนวินาทีตั้งแต่ยุค ) แต่ได้รับมอบหมายโซนเวลาอื่น ๆ สุดท้ายเราได้รับLocalDateซึ่งไม่มีเวลาของวันหรือโซนเวลาแม้ว่าจะแจ้งให้ทราบล่วงหน้าเขตเวลาที่ใช้ในการกำหนดวันที่ (เป็นอรุณรุ่งวันใหม่ก่อนหน้านี้ในออสโลกว่าในนิวยอร์กตัวอย่าง)

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();

เกี่ยวกับjava.time

java.timeกรอบถูกสร้างขึ้นใน Java 8 และต่อมา ชั้นเรียนเหล่านี้แย่งลำบากเก่ามรดกเรียนวันที่เวลาเช่นjava.util.Date, และCalendarSimpleDateFormat

Joda เวลาโครงการขณะนี้อยู่ในโหมดการบำรุงรักษาให้คำแนะนำแก่การโยกย้ายไปยังjava.timeชั้นเรียน

ต้องการเรียนรู้เพิ่มเติมโปรดดูที่ออราเคิลกวดวิชา และค้นหา Stack Overflow สำหรับตัวอย่างและคำอธิบายมากมาย สเปกJSR 310

คุณสามารถแลกเปลี่ยนวัตถุjava.timeโดยตรงกับฐานข้อมูลของคุณ ใช้ไดรเวอร์ JDBC ที่สอดคล้องกับJDBC 4.2หรือใหม่กว่า ไม่จำเป็นต้องใช้สตริงไม่จำเป็นต้องมีjava.sql.*คลาส

จะรับคลาส java.time ได้ที่ไหน?

  • Java SE 8 , Java SE 9 , Java SE 10และใหม่กว่า
    • Built-in
    • เป็นส่วนหนึ่งของ Java API มาตรฐานที่มีการใช้งานแบบรวม
    • Java 9 เพิ่มคุณสมบัติและการแก้ไขเล็กน้อย
  • Java SE 6และ Java SE 7
    • มากของการทำงาน java.time จะกลับรังเพลิง Java 6 และ 7 ในThreeTen-ย้ายกลับ
  • Android
    • การใช้งานชุดบันเดิล Android รุ่นต่อมาของคลาส java.time
    • สำหรับก่อนหน้านี้ Android (<26) ที่ThreeTenABPโครงการปรับThreeTen-ย้ายกลับ (ดังกล่าวข้างต้น) ดูวิธีการใช้ ThreeTenABP … .

โครงการThreeTen-Extraขยาย java.time ด้วยคลาสเพิ่มเติม โครงการนี้เป็นพื้นฐานที่พิสูจน์ได้สำหรับการเพิ่มเติมในอนาคตไปยัง java.time คุณอาจพบว่าการเรียนที่มีประโยชน์บางอย่างที่นี่เช่นInterval, YearWeek, YearQuarterและอื่น ๆ อีกมากมาย


5
อุปกรณ์ประกอบฉากสำหรับพูดถึงสาเหตุที่แท้จริงและให้ความคุ้มครองของทางเลือกที่มีอยู่ทั้งหมด
mabi

นี่คือคำตอบที่ถูกต้องและควรหลีกเลี่ยงคลาสปฏิทิน ThreeTen เป็นตัวเลือกที่ดีที่สุด
ant2009

11

เหตุผลหนึ่งที่ตัวสร้างถูกคัดค้านคือความหมายของพารามิเตอร์ปีไม่ใช่สิ่งที่คุณคาดหวัง javadoc พูดว่า:

ในฐานะของ JDK รุ่น 1.1 Calendar.set(year + 1900, month, date)แทนที่ด้วย

โปรดสังเกตว่าฟิลด์ปีคือจำนวนปี1900ดังนั้นรหัสตัวอย่างของคุณน่าจะไม่ทำตามที่คุณคาดหวัง และนั่นคือประเด็น

โดยทั่วไปแล้วDateAPI สนับสนุนเฉพาะปฏิทินตะวันตกที่ทันสมัยมีองค์ประกอบที่ระบุเฉพาะและมีพฤติกรรมไม่สอดคล้องกันหากคุณตั้งค่าฟิลด์

CalendarและGregorianCalendarAPIs จะดีกว่าDateและบุคคลที่ 3 APIs Joda เวลาที่ถูกคิดโดยทั่วไปจะดีที่สุด ใน Java 8 พวกเขาแนะนำjava.timeแพ็คเกจและตอนนี้เป็นทางเลือกที่แนะนำ


โซลูชันนี้ใช้งานได้ดียกเว้นว่าจะไม่ใช้ zero-time สำหรับชั่วโมงนาทีนาทีวินาทีเป็นมิลลิวินาทีเหมือนวันที่ใหม่ (ปีเดือนวัน) ดังนั้นเราต้องการบางสิ่งเช่นปฏิทิน cal = Calendar.getInstance (); cal.clear (); cal.set (ปี + 1900, เดือน, วันที่) วันที่ dateRepresentation = cal.getTime ();
Joel Richard Koett

11

ฉันเจอคำถามนี้เป็นคำถามที่ใหม่กว่าซึ่งถามว่าวิธีที่ไม่ได้รับการปฏิเสธในการรับDateปีเดือนและวันที่ระบุ

คำตอบที่นี่จนถึงตอนนี้บอกว่าจะใช้Calendarคลาสและนั่นเป็นความจริงจนกระทั่ง Java 8 ออกมา แต่เป็นของ Java 8 วิธีมาตรฐานในการทำเช่นนี้คือ:

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

และถ้าคุณต้องการจริงๆjava.util.Dateคุณสามารถใช้คำแนะนำในคำถามนี้

สำหรับข้อมูลเพิ่มเติมตรวจสอบ APIหรือบทช่วยสอนสำหรับ Java 8


7

โปรดทราบว่าCalendar.getTime()nondeterministic ในแง่ที่ว่าเวลากลางวันส่วนเริ่มต้นเป็นเวลาปัจจุบัน

หากต้องการทำซ้ำให้ลองเรียกใช้รหัสต่อไปนี้สองสามครั้ง:

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

ผลลัพธ์เช่น:

Sun Mar 07 10:46:21 CET 2010

ใช้รหัสเดียวกันแน่นอนสองสามนาทีต่อมาอัตราผลตอบแทน:

Sun Mar 07 10:57:51 CET 2010

ดังนั้นในขณะที่set()บังคับให้เขตข้อมูลที่เกี่ยวข้องเพื่อแก้ไขค่ามันรั่วเวลาของระบบสำหรับเขตข้อมูลอื่น (ทดสอบข้างต้นด้วย Sun jdk6 & jdk7)



5

นักพัฒนา Java ส่วนใหญ่ใช้แพ็คเกจJoda-Timeของบุคคลที่สามในปัจจุบัน Jodaมันได้รับการยอมรับอย่างกว้างขวางว่าเป็นการใช้งานที่ดีกว่ามาก

อย่างไรก็ตามJava 8จะมีแพ็คเกจ java.time. *ใหม่ ดูบทความนี้แนะนำวันและเวลาใหม่ API สำหรับ JDK 8


2

เนื่องจากตัวสร้าง Date เลิกใช้แล้วคุณสามารถลองใช้รหัสนี้ได้

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
คลาสที่น่ากลัวเหล่านี้เป็นมรดกตกทอดมาแล้วเมื่อหลายปีก่อนโดยคลาสjava.time ที่กำหนดไว้ใน JSR 310 คำแนะนำDateและCalendarในปี 2019 เป็นคำแนะนำที่ไม่ดี
Basil Bourque

@BasilBourque ขอบคุณสำหรับคำแนะนำฉันรอคอยที่จะอัปเดตในไม่ช้า
Divyanshu Kumar


1

คุณสามารถสร้างวิธีการเหมือนnew Date(year,month,date)ในรหัสของคุณโดยใช้Calendarคลาส

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();
}

มันจะทำงานเหมือนกับตัวสร้างที่ไม่สนับสนุนของ Date


1
เรียนลำบากชะมัดเหล่านี้ถูกแทนที่ด้วยปีที่ผ่านมาโดยในปัจจุบันjava.timeเรียนเฉพาะและInstant ZonedDateTime
Basil Bourque

โซลูชันด้านบนควรเพิ่ม 1900 เข้ากับปีและไม่ควรลบ 1 จากเดือนและควรรัน cal.clear () หลังจากบรรทัด Calendar.getInstance () เพื่อให้ชั่วโมง / นาที / วินาที / มิลลิวินาทีได้รับ zeroed (เช่น พวกเขาทำในตัวสร้างวันที่)
Joel Richard Koett

1

ตัวสร้างวันที่คาดว่าปีที่ผ่านมาในรูปแบบของปีตั้งแต่ 1900, เดือนที่เป็นศูนย์, หนึ่งวันที่ใช้และตั้งค่าชั่วโมง / นาที / วินาที / มิลลิวินาทีเป็นศูนย์

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

ดังนั้นการใช้การแทนที่ปฏิทิน (ปีที่เป็นศูนย์, เดือนที่เป็นศูนย์, หนึ่งวันที่ใช้) สำหรับตัวสร้าง Date ที่เลิกใช้แล้วเราต้องการบางสิ่งดังนี้:

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

หรือใช้ Java 1.8 (ซึ่งมีปี zero-based และหนึ่งเดือนและวัน):

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

นี่คือ Date, Calendar และ 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
ทั้งสองเรียนที่น่ากลัวเหล่านี้ถูกแทนที่ด้วยปีที่ผ่านมาโดยเรียนjava.time ที่กำหนดไว้ใน JSR 310
Basil Bourque

อัปเดตคำตอบเพื่อแสดงเวอร์ชันวันที่ปฏิทินและ Java 1.8
Joel Richard Koett

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

(วิธี pre-Java-8)


มันอาจดีที่สุดที่คุณสามารถทำได้โดยไม่ต้อง java.time อย่างไรก็ตามยัง pre-Java 8 คุณสามารถใช้ java.time มีการย้ายกลับ: ห้องสมุด ThreeTen ย้ายกลับ
Ole VV
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.