เปรียบเทียบสอง java.util.Dates เพื่อดูว่าพวกเขาอยู่ในวันเดียวกันหรือไม่


250

ฉันต้องเปรียบเทียบสองDate(เช่นdate1และdate2) และเกิดขึ้นกับboolean sameDayที่เป็นจริงของทั้งสองDateร่วมกันในวันเดียวกันและเท็จถ้าพวกเขาไม่

ฉันจะทำสิ่งนี้ได้อย่างไร ดูเหมือนว่าจะมีลมมรสุมสับสนที่นี่ ... และฉันต้องการหลีกเลี่ยงการพึ่งพิงอื่น ๆ นอกเหนือจาก JDK หากเป็นไปได้

เพื่อชี้แจง:ถ้าdate1และdate2แบ่งปันปีเดือนและวันเดียวกันก็sameDayเป็นจริงมิฉะนั้นจะเป็นเท็จ ฉันรู้ว่าต้องใช้ความรู้เกี่ยวกับเขตเวลา ... มันจะดีถ้าผ่านเขตเวลา แต่ฉันสามารถอยู่กับ GMT หรือเวลาท้องถิ่นได้ตราบใดที่ฉันรู้ว่าพฤติกรรมคืออะไร

อีกครั้งเพื่อชี้แจง:

date1 = 2008 Jun 03 12:56:03
date2 = 2008 Jun 03 12:59:44
  => sameDate = true

date1 = 2009 Jun 03 12:56:03
date2 = 2008 Jun 03 12:59:44
  => sameDate = false

date1 = 2008 Aug 03 12:00:00
date2 = 2008 Jun 03 12:00:00
  => sameDate = false

เพื่อชี้แจง - คุณต้องการที่จะรู้ว่าวัตถุวันที่สองอันตกอยู่ในวันเดียวกันของสัปดาห์หรือไม่?
Rob Heiser

คุณต้องการที่จะเปรียบเทียบวันที่เต็ม (วัน, เดือน, ปี) หรือวันเดือนเท่านั้น?
XpiritO

1
@ Rob: ไม่วัน / เดือน / ปีเดียวกัน ... ฉันจะชี้แจง
Jason S

ถ้าอย่างนั้นทำไมคุณไม่ใช้ "เท่ากับ"
XpiritO

7
เพราะมันไม่เท่ากันถ้าชั่วโมง / นาที / วินาทีต่างกัน
Jason S

คำตอบ:


416
Calendar cal1 = Calendar.getInstance();
Calendar cal2 = Calendar.getInstance();
cal1.setTime(date1);
cal2.setTime(date2);
boolean sameDay = cal1.get(Calendar.DAY_OF_YEAR) == cal2.get(Calendar.DAY_OF_YEAR) &&
                  cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR);

โปรดทราบว่า "วันเดียวกัน" นั้นไม่ง่ายอย่างที่คิดเพราะฟังดูเมื่อเขตเวลาแตกต่างกัน รหัสข้างต้นจะคำนวณวันที่สัมพันธ์กับเขตเวลาที่คอมพิวเตอร์ใช้งานอยู่ หากนี่ไม่ใช่สิ่งที่คุณต้องการคุณจะต้องส่งโซนเวลาที่เกี่ยวข้องไปยังการCalendar.getInstance()โทรหลังจากที่คุณได้ตัดสินใจว่าคุณหมายถึงอะไรกับ "วันเดียวกัน"

และใช่ Joda Time LocalDateจะทำให้ทุกอย่างสะอาดและง่ายขึ้น (แม้ว่าจะมีความยากลำบากเหมือนกันเกี่ยวกับเขตเวลา)


ขอบคุณที่ดูเหมือนว่ามันจะทำสิ่งที่ฉันต้องการ ในกรณีของฉันฉันกำลังเปรียบเทียบวันที่ต่อเนื่องกันในซีรีย์ดังนั้นดูเหมือนว่าฉันจะสามารถใช้อินสแตนซ์ปฏิทินแทนอินสแตนซ์ Date ในซีรีย์ของฉันได้
Jason S

1
@ Jason นั่นอาจจะใช่หรือไม่ใช่ความคิดที่ดี ปัญหาหลักของปฏิทินคือมันเป็นรุ่นที่มีน้ำหนักมากและมีสถานะภายในจำนวนมากซึ่งบางส่วนใช้ในการดำเนินการเท่ากับ () หากคุณไม่ระบุวันที่ของคุณเพื่อความเท่าเทียมกันและไม่ใส่ลงใน HashMaps คุณควรจะปรับ
Michael Borgwardt

เจ๋งขอบคุณฉันแค่ใช้ตรรกะเปรียบเทียบกับปัจจุบันและก่อนหน้าวันถามที่นี่ ไม่ควรเรียกใช้ฟังก์ชัน "เท่ากับ" และ "hashcode"
Jason S

1
@UmerHayat: โค้ดเปรียบเทียบวันของปีไม่ใช่วันที่ของเดือน การเปรียบเทียบที่จำเป็นน้อยกว่าหนึ่งรหัสที่สั้นกว่า
Michael Borgwardt

2
คำแนะนำ: เปรียบเทียบ DAY_OF_YEAR ก่อนมันไม่จำเป็นต้องตรวจสอบปี โอเคมันไม่เหมือนการเปรียบเทียบ int แพงมาก แต่ ...
Martin Martin

332

เกี่ยวกับ:

SimpleDateFormat fmt = new SimpleDateFormat("yyyyMMdd");
return fmt.format(date1).equals(fmt.format(date2));

คุณยังสามารถตั้งค่าเขตเวลาเป็น SimpleDateFormat ได้หากต้องการ


2
(ฉันจริงโดยใช้ SimpleDateFormat อยู่แล้วในกรณีของฉันจึงดูเหมือนว่าชนิดของความเหมาะสม.)
เจสัน S

8
ฉันแปลกใจจริงๆที่เห็นสิ่งนี้ แต่ถึงแม้จะมีจุดยืนด้านประสิทธิภาพแล้วSimpleDateFormatวิธีนี้ก็เร็วกว่าวิธีอื่นที่กล่าวถึงในที่Calendarนี้ โดยเฉลี่ยจะใช้เวลาครึ่งหนึ่งเป็นCalendarวิธี (อย่างน้อยในระบบของฉัน) รุ่งโรจน์!
Michael Plautz

ค่าใช้จ่ายส่วนใหญ่ของคำตอบนี้อยู่ที่การสร้าง SimpleDateFormat ใส่ไว้ในเขตข้อมูล threadLocal ใน singleton ถ้าคุณต้องการประสิทธิภาพที่ดียิ่งขึ้น
Thierry

1
ฉันทำสิ่งนี้ใน Swift ด้วย เป็นเรื่องที่น่าอัศจรรย์ว่าสิ่งที่เรียบง่ายเช่นนี้ต้องการแฮ็คเดียวกันในภาษาส่วนใหญ่ C # เป็นข้อยกเว้น - ถ้า (d1.Date == d2.Date) ...
alpsystems.com

2
นี่มันน่าเกลียด ประหลาดใจที่เห็นแฮ็กเช่นนี้ upvoted
Zsolt Safrany

162

ฉันใช้แพ็คเกจ "apache commons lang" เพื่อทำสิ่งนี้ (คือorg.apache.commons.lang.time.DateUtils )

boolean samedate = DateUtils.isSameDay(date1, date2);  //Takes either Calendar or Date objects

2
สิ่งนี้ใช้การพึ่งพาจากภายนอก ... แต่เป็นการดีสำหรับอนาคต
Jason S

20
เพียงคัดลอกแหล่งที่มาและเรียกว่าวัน :)
แอนตัน Kuzmin

แต่มันจะโยนข้อโต้แย้งที่ผิดกฎหมายหากวันใดวันหนึ่งเป็นโมฆะซึ่งไม่เหมาะในบางกรณี
raviraja

1
ตามที่แสดงความคิดเห็นในstackoverflow.com/a/2517824/1665809การแก้ปัญหานี้อาจไม่เหมาะสมหากเกี่ยวข้องกับเขตเวลาที่แตกต่างกัน
mrod

24

คุณสามารถหลีกเลี่ยงการพึ่งพาจากภายนอกและความนิยมในการใช้งานปฏิทินโดยการคำนวณJulian Day Numberสำหรับแต่ละวันที่จากนั้นเปรียบเทียบสิ่งเหล่านี้:

public static boolean isSameDay(Date date1, Date date2) {

    // Strip out the time part of each date.
    long julianDayNumber1 = date1.getTime() / MILLIS_PER_DAY;
    long julianDayNumber2 = date2.getTime() / MILLIS_PER_DAY;

    // If they now are equal then it is the same day.
    return julianDayNumber1 == julianDayNumber2;
}

4
แต่ระวังว่าสิ่งนี้ไม่สามารถพิจารณาการเปลี่ยนแปลงระยะเวลาของวันเนื่องจากการประหยัดเวลากลางวัน แต่ตรงนั้นDateไม่ได้สรุปความคิดของโซนเวลาดังนั้นจึงไม่มีวิธีแก้ไขปัญหานี้โดยไม่มีกฎเกณฑ์
AyeJay

3
นี่เป็นวิธีการแก้ปัญหาที่เร็วที่สุด - ขอบคุณมากสิ่งที่ฉันต้องการ เท่าที่ฉันมีการตรวจสอบเป็นจำนวนมากวันที่ ...
Ridcully

2
Nitpick: date1.getTime () ไม่ส่งคืน Julian Day Number แต่เป็นมิลลิวินาทีนับตั้งแต่ 1970-01-01 ความแตกต่างอย่างมาก
ต่อ Lindberg

2
สิ่งนี้ดำเนินการเปรียบเทียบในโซนเวลา UTC หากคุณต้องการโซนเวลาท้องถิ่นของคุณหรือที่อื่น ๆ บนโลกใบนี้คุณไม่สามารถรู้ได้ว่าผลลัพธ์ที่คุณได้รับนั้นถูกต้องหรือไม่
Ole VV

20

Joda เวลา

สำหรับการเพิ่มการพึ่งพาฉันเกรงว่า java.util.Date & .Calendar นั้นแย่มากที่สิ่งแรกที่ฉันทำกับโปรเจ็กต์ใหม่คือการเพิ่มห้องสมุด Joda-Time ใน Java 8 คุณสามารถใช้แพ็คเกจ java.time ใหม่ซึ่งได้แรงบันดาลใจจาก Joda-Time

แก่นแท้ของ Joda-Time คือDateTimeคลาส ซึ่งแตกต่างจาก java.util.Date จะเข้าใจเขตเวลาที่กำหนด ( DateTimeZone) เมื่อแปลงจาก juDate ให้กำหนดโซน

DateTimeZone zone = DateTimeZone.forID( "America/Montreal" );
DateTime dateTimeQuébec = new DateTime( date , zone );

LocalDate

วิธีหนึ่งในการตรวจสอบว่าที่ดินลงวันที่สองครั้งในวันเดียวกันคือการแปลงเป็นLocalDateวัตถุ

การแปลงนั้นขึ้นอยู่กับเขตเวลาที่กำหนด เพื่อเปรียบเทียบLocalDateวัตถุพวกเขาจะต้องถูกแปลงด้วยโซนเดียวกัน

นี่คือวิธีการอรรถประโยชน์เล็กน้อย

static public Boolean sameDate ( DateTime dt1 , DateTime dt2 )
{
    LocalDate ld1 = new LocalDate( dt1 );
    // LocalDate determination depends on the time zone.
    // So be sure the date-time values are adjusted to the same time zone.
    LocalDate ld2 = new LocalDate( dt2.withZone( dt1.getZone() ) );
    Boolean match = ld1.equals( ld2 );
    return match;
}

ดีกว่าจะเป็นอาร์กิวเมนต์อื่นโดยระบุเขตเวลาแทนการสมมติว่าควรใช้เขตเวลาของวัตถุ DateTime เป็นครั้งแรก

static public Boolean sameDate ( DateTimeZone zone , DateTime dt1 , DateTime dt2 )
{
    LocalDate ld1 = new LocalDate( dt1.withZone( zone ) );
    // LocalDate determination depends on the time zone.
    // So be sure the date-time values are adjusted to the same time zone.
    LocalDate ld2 = new LocalDate( dt2.withZone( zone ) );
    return ld1.equals( ld2 );
}

การเป็นตัวแทนสตริง

อีกวิธีหนึ่งคือการสร้างการแสดงสตริงของส่วนวันที่ของแต่ละวันที่แล้วเปรียบเทียบสตริง

อีกครั้งเขตเวลาที่กำหนดเป็นสิ่งสำคัญ

DateTimeFormatter formatter = ISODateTimeFormat.date();  // Static method.
String s1 = formatter.print( dateTime1 );
String s2 = formatter.print( dateTime2.withZone( dt1.getZone() )  );
Boolean match = s1.equals( s2 );
return match;

ช่วงเวลา

โซลูชันทั่วไปคือการกำหนดช่วงเวลาจากนั้นถามว่าช่วงเวลามีเป้าหมายของคุณหรือไม่ โค้ดตัวอย่างนี้อยู่ใน Joda-Time 2.4 โปรดทราบว่าคลาสที่เกี่ยวข้องกับ "เที่ยงคืน" จะถูกคัดค้าน ใช้withTimeAtStartOfDayวิธีการแทน Joda-Time เสนอสามคลาสเพื่อแสดงช่วงเวลาในรูปแบบต่างๆ: ช่วงเวลาช่วงเวลาและระยะเวลา

ใช้วิธี "Half-Open" โดยที่จุดเริ่มต้นของการรวมรวมและสิ้นสุดเฉพาะ

เขตเวลาของเป้าหมายอาจแตกต่างจากเขตเวลาของช่วงเวลา

DateTimeZone timeZone = DateTimeZone.forID( "Europe/Paris" );
DateTime target = new DateTime( 2012, 3, 4, 5, 6, 7, timeZone );
DateTime start = DateTime.now( timeZone ).withTimeAtStartOfDay();
DateTime stop = start.plusDays( 1 ).withTimeAtStartOfDay();
Interval interval = new Interval( start, stop );
boolean containsTarget = interval.contains( target );

java.time

Java 8 และใหม่กว่ามาพร้อมกับเฟรมเวิร์กjava.time แรงบันดาลใจจาก Joda-Time ที่กำหนดโดย JSR 310 และขยายโดยโครงการ ThreeTen-Extra ดูการสอน

ผู้สร้าง Joda-Time ได้สั่งให้พวกเราทุกคนย้ายไปที่ java.time ทันทีที่สะดวก ในขณะเดียวกัน Joda-Time ยังคงดำเนินต่อไปในฐานะโครงการที่ได้รับการบำรุงรักษาอย่างแข็งขัน แต่คาดว่างานในอนาคตจะเกิดขึ้นเฉพาะใน java.time และ ThreeTen-Extra มากกว่า Joda-Time

เพื่อสรุป java.time โดยย่อ… Instantเป็นช่วงเวลาหนึ่งในไทม์ไลน์ใน UTC ใช้เขตเวลา ( ZoneId) เพื่อรับZonedDateTimeวัตถุ ที่จะย้ายออกจากระยะเวลาที่จะได้รับความคิดที่ไม่แน่นอนคลุมเครือของวันที่เวลาใช้เรียน "ท้องถิ่น": LocalDateTime, ,LocalDateLocalTime

ตรรกะที่กล่าวถึงในส่วน Joda-Time ของคำตอบนี้ใช้กับ java.time

คลาส java.util.Date เก่ามีtoInstantวิธีการใหม่สำหรับการแปลงเป็น java.time

Instant instant = yourJavaUtilDate.toInstant(); // Convert into java.time type.

การกำหนดวันที่ต้องใช้เขตเวลา

ZoneId zoneId = ZoneId.of( "America/Montreal" );

เราใช้ว่าวัตถุโซนเวลาที่จะได้รับInstant ZonedDateTimeจากนั้นเราแยกค่าวันที่อย่างเดียว (a LocalDate) ตามเป้าหมายของเราคือการเปรียบเทียบวันที่ (ไม่ใช่ชั่วโมงนาที ฯลฯ )

ZonedDateTime zdt1 = ZonedDateTime.ofInstant( instant , zoneId );
LocalDate localDate1 = LocalDate.from( zdt1 );

ทำสิ่งเดียวกันกับjava.util.Dateวัตถุที่สองที่เราต้องการเปรียบเทียบ ฉันจะใช้ช่วงเวลาปัจจุบันแทน

ZonedDateTime zdt2 = ZonedDateTime.now( zoneId );
LocalDate localDate2 = LocalDate.from( zdt2 );

ใช้isEqualวิธีพิเศษเพื่อทดสอบค่าวันที่เดียวกัน

Boolean sameDate = localDate1.isEqual( localDate2 );

java.time: หรือคุณสามารถทำได้LocalDate localDate = LocalDate.fromDateFields(yourJavaUtilDate);
CyberMew

1
@CyberMew Er? ไม่มีfromDateFields()ในของฉันLocalDateระดับ
Ole VV

1
ขออภัยฉันควรชัดเจนขึ้น ความคิดเห็นมีความเกี่ยวข้องกับคลาสJoda-Time LocalDate
CyberMew

7

แปลงวันที่เพื่อ Java 8 java.time.LocalDate เท่าที่เห็นนี่

LocalDate localDate1 = date1.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
LocalDate localDate2 = date2.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();

// compare dates
assertTrue("Not on the same day", localDate1.equals(localDate2));

นี่คือคำตอบที่ฉันโปรดปราน หากคุณต้องการควบคุมเขตเวลาที่ใช้แทนการใช้การตั้งค่าของคอมพิวเตอร์คุณควรใส่ตัวอย่างเช่นZoneId.of("America/Phoenix") หรือZoneId.of("Europe/Budapest")แทนที่จะเป็นค่าเริ่มต้นของระบบ
Ole VV

6

Java 8

หากคุณใช้ Java 8 ในโครงการและเปรียบเทียบjava.sql.Timestampคุณสามารถใช้LocalDateคลาส:

sameDate = date1.toLocalDateTime().toLocalDate().equals(date2.toLocalDateTime().toLocalDate());

หากคุณกำลังใช้java.util.Dateให้ดูที่คำตอบ Istvan ซึ่งไม่ชัดเจน


การใช้ Java 8 เป็นความคิดที่ดี คุณคิดdate1และdate2เป็นjava.sql.Timestampอย่างไร ตามคำถามที่พวกเขามีผมจะเข้าใจDate java.util.Dateรหัสของคุณใช้การตั้งค่าเขตเวลาของคอมพิวเตอร์ด้วยซึ่งจะใช้งานได้หลากหลายตามวัตถุประสงค์ ยังฉันต้องการที่จะทำให้ข้อเท็จจริงนี้ชัดเจนในรหัส
Ole VV

@ OleV.V java.sql.Timestampขอขอบคุณสำหรับความคิดเห็นของคุณคำตอบเดิมของฉันเป็นจริงเกี่ยวกับ ดังที่คุณกล่าวว่านี่เป็นการดีกว่าที่จะกำหนดเขตเวลาอย่างชัดเจน
amanteaux

5
private boolean isSameDay(Date date1, Date date2) {
        Calendar calendar1 = Calendar.getInstance();
        calendar1.setTime(date1);
        Calendar calendar2 = Calendar.getInstance();
        calendar2.setTime(date2);
        boolean sameYear = calendar1.get(Calendar.YEAR) == calendar2.get(Calendar.YEAR);
        boolean sameMonth = calendar1.get(Calendar.MONTH) == calendar2.get(Calendar.MONTH);
        boolean sameDay = calendar1.get(Calendar.DAY_OF_MONTH) == calendar2.get(Calendar.DAY_OF_MONTH);
        return (sameDay && sameMonth && sameYear);
    }

5

สำหรับผู้ใช้ Android:

คุณสามารถใช้DateUtils.isToday(dateMilliseconds)เพื่อตรวจสอบว่าวันที่กำหนดเป็นวันที่ปัจจุบันหรือไม่

การอ้างอิง API: https://developer.android.com/reference/android/text/format/DateUtils.html#isToday(long)


1
วิธีนี้ใช้เวลาวัตถุซึ่งเลิกใช้แล้วตั้งแต่ API 22: developer.android.com/reference/android/text/format/Time.html
Oleksandr Bodashko

2
+1 สำหรับนักพัฒนา Android วิธีนี้ใช้แบบดั้งเดิมตามพารามิเตอร์เพียงใช้DateUtils.isToday(x.getTime())(x เป็นตัวอย่าง java.util.date) จะทำ
jackycflau

1

นอกเหนือจากวิธีการแก้ปัญหา Binil Thomas

public static boolean isOnSameDay(Timestamp... dates) {
    SimpleDateFormat fmt = new SimpleDateFormat("yyyyMMdd");
    String date1 = fmt.format(dates[0]);
    for (Timestamp date : dates) {
        if (!fmt.format(date).equals(date1)) {
            return false;
        }
    }
    return true;
}

การใช้

    isOnSameDay(date1,date2,date3 ...);
//or 
    isOnSameDay(mydates);

1

สำหรับ Kotlin devs นี่เป็นรุ่นที่มีการเปรียบเทียบวิธีการจัดรูปแบบสตริง:

val sdf = SimpleDateFormat("yyMMdd")
if (sdf.format(date1) == sdf.format(date2)) {
    // same day
}

มันไม่ใช่วิธีที่ดีที่สุด แต่สั้นและใช้งานได้


1
SimpleDateFormatชั้นเรียนถูกยกเลิกเมื่อหลายปีก่อนโดยjava.time.DateTimeFormatterชั้นเรียนสมัยใหม่ที่กำหนดไว้ใน JSR 310 การแนะนำให้ใช้ในปี 2019 เป็นคำแนะนำที่ไม่ดี
Basil Bourque

2
รหัสนี้จะไม่สนใจปัญหาสำคัญของเขตเวลา สำหรับช่วงเวลาใด ๆ วันที่แตกต่างกันไปทั่วโลกตามโซน
Basil Bourque

-4

คุณสามารถใช้ตรรกะเดียวกันกับโซลูชัน SimpleDateFormat โดยไม่ต้องพึ่งพา SimpleDateFormat

date1.getFullYear()*10000 + date1.getMonth()*100 + date1.getDate() == 
date2.getFullYear()*10000 + date2.getMonth()*100 + date2.getDate()

6
วิธีการเหล่านี้ถูกคัดค้านใน java.util.Date คุณควรใช้ปฏิทินเพื่อทำสิ่งนี้ นอกจากนี้ยังได้รับปีไม่ได้รับเต็มปี
กริช
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.