Java: Wie überprüfe ich, ob ein Datum innerhalb eines bestimmten Bereichs liegt?

77

Ich habe eine Reihe von Bereichen mit Start- und Enddaten. Ich möchte überprüfen, ob ein Datum innerhalb dieses Bereichs liegt.

Date.before () und Date.after () scheinen etwas umständlich zu sein. Was ich wirklich brauche, ist so etwas wie dieser Pseudocode:

boolean isWithinRange(Date testDate) {
    return testDate >= startDate && testDate <= endDate;
}

Ich bin mir nicht sicher, ob es relevant ist, aber die Daten, die ich aus der Datenbank ziehe, haben Zeitstempel.

Mike Sickler
quelle
1
Ähnliche Frage mit mehreren Antworten: Wie kann ich feststellen, ob ein Datum in Java zwischen zwei Daten liegt?
Basil Bourque
1
Zu Ihrer Information , die lästigen alten Datums- / Uhrzeitklassen wie java.util.Dateund java.util.Calendarsind jetzt Legacy , ersetzt durch die Klassen java.time . Siehe Tutorial von Oracle .
Basil Bourque

Antworten:

141
boolean isWithinRange(Date testDate) {
   return !(testDate.before(startDate) || testDate.after(endDate));
}

Kommt mir nicht so unangenehm vor. Beachten Sie, dass ich es so geschrieben habe, anstatt

return testDate.after(startDate) && testDate.before(endDate);

es würde also auch funktionieren, wenn testDate genau einem der Endfälle entspricht.

Paul Tomblin
quelle
4
Mit diesem Code ist Folgendes ungültig: date is 01-01-2014 and the range is from 01-01-2014 to 31-01-2014Wie können Sie ihn gültig machen?
Shahe
4
@Shahe das erste, was ich tun würde, ist die Zeitabschnitte Ihrer Daten zu überprüfen. Die Chancen stehen gut, dass Sie datefrüh am Morgen sind und startDatedanach sind.
Paul Tomblin
Dieser Trick mit Negieren oder ist nach sieben Jahren immer noch schön. :)
Tosz
Es funktioniert nicht, wenn testDate das aktuelle Datum von heute ist. Wie wird mit diesem Szenario in der oben vorgeschlagenen Lösung umgegangen?
Swift
@Swift Dateenthält Zeit (Stunden, Minuten, Sekunden und Millisekunden). Wenn Sie also testen möchten, ob etwas "heute" ist, müssen Sie ein Datumsobjekt erstellen, das zum Zeitpunkt 0: 00: 00.000 "heute" und zum Zeitpunkt 0: 00: 00.000 "morgen" ist. Ich benutze die CalendarKlasse dafür, aber ich bin sicher, dass es einen besseren Weg gibt, wenn Sie JodaTime oder die neueren Basis-Java-Klassen verwenden.
Paul Tomblin
26

tl; dr

ZoneId z = ZoneId.of( "America/Montreal" );  // A date only has meaning within a specific time zone. At any given moment, the date varies around the globe by zone.
LocalDate ld = 
    givenJavaUtilDate.toInstant()  // Convert from legacy class `Date` to modern class `Instant` using new methods added to old classes.
                     .atZone( z )  // Adjust into the time zone in order to determine date.
                     .toLocalDate();  // Extract date-only value.

LocalDate today = LocalDate.now( z );  // Get today’s date for specific time zone.
LocalDate kwanzaaStart = today.withMonth( Month.DECEMBER ).withDayOfMonth( 26 );  // Kwanzaa starts on Boxing Day, day after Christmas.
LocalDate kwanzaaStop = kwanzaaStart.plusWeeks( 1 );  // Kwanzaa lasts one week.
Boolean isDateInKwanzaaThisYear = (
    ( ! today.isBefore( kwanzaaStart ) ) // Short way to say "is equal to or is after".
    &&
    today.isBefore( kwanzaaStop )  // Half-Open span of time, beginning inclusive, ending is *exclusive*.
)

Halb offen

Bei der Datums- und Uhrzeitarbeit wird üblicherweise der "Half-Open" -Ansatz verwendet, um eine Zeitspanne zu definieren. Der Anfang ist inklusive, während das Ende exklusiv ist . Eine Woche, die an einem Montag beginnt, läuft also bis zum folgenden Montag, schließt dies jedoch nicht ein.

java.time

Java 8 und höher wird mit dem integrierten java.timeFramework geliefert. Unterstützt die alten lästigen Klassen einschließlich java.util.Date/.Calendarund SimpleDateFormat. Inspiriert von der erfolgreichen Joda-Time-Bibliothek. Definiert durch JSR 310. Erweitert durch das ThreeTen-Extra-Projekt.

Ein InstantMoment auf der Zeitachse in UTC mit einer Auflösung von Nanosekunden.

Instant

Konvertieren Sie Ihre java.util.DateObjekte in Sofortobjekte.

Instant start = myJUDateStart.toInstant();
Instant stop = …

Wenn Sie java.sql.TimestampObjekte über JDBC aus einer Datenbank abrufen , konvertieren Sie sie auf ähnliche Weise in java.time.Instant . A java.sql.Timestampist bereits in UTC, sodass Sie sich keine Gedanken über Zeitzonen machen müssen.

Instant start = mySqlTimestamp.toInstant() ;
Instant stop = …

Holen Sie sich den aktuellen Moment zum Vergleich.

Instant now = Instant.now();

Vergleichen Sie mit den Methoden isBefore, isAfter und equals.

Boolean containsNow = ( ! now.isBefore( start ) ) && ( now.isBefore( stop ) ) ;

LocalDate

Vielleicht möchten Sie nur mit dem Datum arbeiten, nicht mit der Tageszeit.

Die LocalDateKlasse repräsentiert einen Nur-Datum-Wert ohne Tageszeit und ohne Zeitzone.

LocalDate start = LocalDate.of( 2016 , 1 , 1 ) ;
LocalDate stop = LocalDate.of( 2016 , 1 , 23 ) ;

Geben Sie eine Zeitzone an, um das aktuelle Datum abzurufen. Das heutige Datum variiert zu jedem Zeitpunkt je nach Zeitzone. Zum Beispiel bricht in Paris früher ein neuer Tag an als in Montréal.

LocalDate today = LocalDate.now( ZoneId.of( "America/Montreal" ) );

Wir können die Verwendung isEqual, isBeforeund isAfterMethoden zu vergleichen. In der Datums- und Zeitarbeit verwenden wir üblicherweise den Half-Open-Ansatz, bei dem der Beginn einer Zeitspanne inklusive und das Ende exklusiv ist .

Boolean containsToday = ( ! today.isBefore( start ) ) && ( today.isBefore( stop ) ) ;

Interval

Wenn Sie die ThreeTen-Extra- Bibliothek zu Ihrem Projekt hinzufügen möchten , können Sie mit der IntervalKlasse eine Zeitspanne definieren. Diese Klasse bietet Methoden zum Testen, ob das Intervall andere Datums- / Zeitangaben / Intervalle enthält , aneinander grenzt, einschließt oder überlappt .

Die IntervalKlasse arbeitet an InstantObjekten. Die InstantKlasse repräsentiert einen Moment auf der Zeitachse in UTC mit einer Auflösung von Nanosekunden (bis zu neun (9) Stellen eines Dezimalbruchs).

Wir können den LocalDateMoment auf einen bestimmten Moment einstellen , den ersten Moment des Tages, indem wir eine Zeitzone angeben, um einen zu erhalten ZonedDateTime. Von dort können wir durch Extrahieren von a zu UTC zurückkehren Instant.

ZoneId z = ZoneId.of( "America/Montreal" );
Interval interval = 
    Interval.of( 
        start.atStartOfDay( z ).toInstant() , 
        stop.atStartOfDay( z ).toInstant() );
Instant now = Instant.now();
Boolean containsNow = interval.contains( now );

Über java.time

Das java.time- Framework ist in Java 8 und höher integriert. Diese Klassen verdrängen die lästigen alten Legacy - Datum-Zeit - Klassen wie java.util.Date, Calendar, & SimpleDateFormat.

Das Joda-Time- Projekt, das sich jetzt im Wartungsmodus befindet , empfiehlt die Migration zu den java.timeKlassen.

Weitere Informationen finden Sie im Oracle-Lernprogramm . Suchen Sie im Stapelüberlauf nach vielen Beispielen und Erklärungen. Die Spezifikation ist JSR 310 .

Wo bekomme ich die java.timeKlassen?

Das ThreeTen-Extra- Projekt wird java.timeum zusätzliche Klassen erweitert. Dieses Projekt ist ein Testfeld für mögliche zukünftige Ergänzungen java.time. Sie können einige nützliche Klassen hier wie finden Interval, YearWeek, YearQuarter, und mehr .

Basil Bourque
quelle
16

Das ist der richtige Weg. Kalender funktionieren genauso. Das Beste, was ich Ihnen (basierend auf Ihrem Beispiel) anbieten kann, ist Folgendes:

boolean isWithinRange(Date testDate) {
    return testDate.getTime() >= startDate.getTime() &&
             testDate.getTime() <= endDate.getTime();
}

Date.getTime () gibt die Anzahl der Millisekunden seit dem 1.1.1970 00:00:00 GMT zurück und ist lang, sodass es leicht vergleichbar ist.

MBCook
quelle
9

Erwägen Sie die Verwendung von Joda Time . Ich liebe diese Bibliothek und wünschte, sie würde das derzeitige schreckliche Durcheinander ersetzen, das die vorhandenen Java-Datums- und Kalenderklassen sind. Die Datumsabwicklung ist richtig gemacht.

EDIT: Es ist nicht mehr 2009 und Java 8 ist schon seit Ewigkeiten draußen. Verwenden Sie die in java.time integrierten Java 8-Klassen, die auf Joda Time basieren, wie Basil Bourque oben erwähnt. In diesem Fall möchten Sie die Period-Klasse und hier das Oracle-Tutorial zur Verwendung.

Ben Hardy
quelle
3
Glauben Sie nicht, dass die Einführung von Joda Time in ein Projekt im Laufe der Zeit ist, wenn Sie nur einen einfachen Vergleich
anstellen möchten,
3
Ich nehme an, du meinst Overkill. Bei den meisten Bibliothekserweiterungen würde ich Ihnen zustimmen. Joda Time ist jedoch ziemlich leicht und hilft Ihnen, dank der Unveränderlichkeit seiner Darstellungen einen korrekteren Code für die Datumsverarbeitung zu schreiben. Daher ist es meiner Meinung nach keine schlechte Sache, dies einzuführen.
Ben Hardy
25
Und vergessen wir nicht die StackOverflow-Regel Nr. 46: Wenn jemand Daten oder Zeiten in Java erwähnt, muss jemand vorschlagen, auf Joda Time umzusteigen. Glaubst du mir nicht? Versuchen Sie, eine Frage zu finden, die dies nicht tut.
Paul Tomblin
2
Sogar Sun / Oracle gab die alten Datums- / Uhrzeitklassen auf, die mit den frühesten Versionen von Java gebündelt waren, und stimmte zu, die alten Klassen durch das Java.time- Framework (inspiriert von Joda-Time) zu ersetzen . Diese alten Klassen waren schlecht gestaltet und hatten sich als mühsam und verwirrend erwiesen.
Basil Bourque
4

Eine einfache Möglichkeit besteht darin, die Daten nach dem 1. Januar 1970 in Millisekunden umzuwandeln (verwenden Sie Date.getTime ()) und diese Werte dann zu vergleichen.

Rahul
quelle
1

Um meinen Fall abzudecken -> Ich habe ein Start- und Enddatum für den Bereich sowie eine Datumsliste, die so teilweise im bereitgestellten Bereich wie vollständig (überlappend) liegen kann.

Mit Tests abgedeckte Lösung:

/**
 * Check has any of quote work days in provided range.
 *
 * @param startDate inclusively
 * @param endDate   inclusively
 *
 * @return true if any in provided range inclusively
 */
public boolean hasAnyWorkdaysInRange(LocalDate startDate, LocalDate endDate) {
    if (CollectionUtils.isEmpty(workdays)) {
        return false;
    }

    LocalDate firstWorkDay = getFirstWorkDay().getDate();
    LocalDate lastWorkDay = getLastWorkDay().getDate();

    return (firstWorkDay.isBefore(endDate) || firstWorkDay.equals(endDate))
            && (lastWorkDay.isAfter(startDate) || lastWorkDay.equals(startDate));
}
Alex Efimov
quelle
1

Schon seit Java 8

HINWEIS: Alle DateKonstruktoren bis auf einen sind veraltet. Die meisten DateMethoden sind veraltet. REF: Datum: Veraltete Methoden . Alle außer Date::from(Instant)statischen Methoden sind veraltet.

Da Java 8 die Verwendung eines Instant (unveränderlichen, threadsicheren, Schaltsekunden-fähigen) Typs anstelle von in Betracht ziehtDate . REF: Sofort

  static final LocalTime MARKETS_OPEN = LocalTime.of(07, 00);
  static final LocalTime MARKETS_CLOSE = LocalTime.of(20, 00);

    // Instant utcTime = testDate.toInstant();
    var bigAppleTime = ZonedDateTime.ofInstant(utcTime, ZoneId.of("America/New_York"));

im Bereich INCLUSIVE:

    return !bigAppleTime.toLocalTime().isBefore(MARKETS_OPEN)
        && !bigAppleTime.toLocalTime().isAfter(MARKETS_CLOSE);

im Bereich EXKLUSIV:

    return bigAppleTime.toLocalTime().isAfter(MARKETS_OPEN)
        && bigAppleTime.toLocalTime().isBefore(MARKETS_CLOSE);
Epox
quelle
0

Es ist egal, welche Datumsgrenze welche ist.

Math.abs(date1.getTime() - date2.getTime()) == 
    Math.abs(date1.getTime() - dateBetween.getTime()) + Math.abs(dateBetween.getTime() - date2.getTime());
Radu
quelle
0

Sie können so verwenden

Interval interval = new Interval(date1.getTime(),date2.getTime());
Interval interval2 = new Interval(date3.getTime(), date4.getTime());
Interval overlap = interval.overlap(interval2);
boolean isOverlap = overlap == null ? false : true
Koray Peker
quelle
0

Das war mir klarer,

// declare calendar outside the scope of isWithinRange() so that we initialize it only once
private Calendar calendar = Calendar.getInstance();

public boolean isWithinRange(Date date, Date startDate, Date endDate) {

    calendar.setTime(startDate);
    int startDayOfYear = calendar.get(Calendar.DAY_OF_YEAR); // first day is 1, last day is 365
    int startYear = calendar.get(Calendar.YEAR);

    calendar.setTime(endDate);
    int endDayOfYear = calendar.get(Calendar.DAY_OF_YEAR);
    int endYear = calendar.get(Calendar.YEAR);

    calendar.setTime(date);
    int dayOfYear = calendar.get(Calendar.DAY_OF_YEAR);
    int year = calendar.get(Calendar.YEAR);

    return (year > startYear && year < endYear) // year is within the range
            || (year == startYear && dayOfYear >= startDayOfYear) // year is same as start year, check day as well
            || (year == endYear && dayOfYear < endDayOfYear); // year is same as end year, check day as well

}
Dhunju_likes_to_Learn
quelle
2
(A) CalendarKlasse ist nicht threadsicher. Das Verwalten einer Instanz, auf die möglicherweise von mehr als einem Thread zugegriffen wird, ist daher riskant. (B) Die lästigen alten Klassen wie java.util.Dateund java.util.Calendarsind jetzt Legacy , ersetzt durch die java.time- Klassen. Siehe Oracle Tutorial .
Basil Bourque
@BasilBourque, danke, dass Sie auf die Probleme hingewiesen haben. Es sieht so aus, als ob Android die neue DateTime-Klasse noch nicht unterstützt. : /
Dhunju_likes_to_Learn
1
Ein großer Teil der java.time Funktionalität zurückportiert zu Java 6 und 7 in ThreeTen-Backport und weiter angepasst Android in ThreeTenABP (siehe So verwenden ... ). Bei dieser Frage geht es nicht um Android.
Basil Bourque
0
  public class TestDate {

    public static void main(String[] args) {
    // TODO Auto-generated method stub

    String fromDate = "18-FEB-2018";
    String toDate = "20-FEB-2018";

    String requestDate = "19/02/2018";  
    System.out.println(checkBetween(requestDate,fromDate, toDate));
}

public static boolean checkBetween(String dateToCheck, String startDate, String endDate) {
    boolean res = false;
    SimpleDateFormat fmt1 = new SimpleDateFormat("dd-MMM-yyyy"); //22-05-2013
    SimpleDateFormat fmt2 = new SimpleDateFormat("dd/MM/yyyy"); //22-05-2013
    try {
     Date requestDate = fmt2.parse(dateToCheck);
     Date fromDate = fmt1.parse(startDate);
     Date toDate = fmt1.parse(endDate);
     res = requestDate.compareTo(fromDate) >= 0 && requestDate.compareTo(toDate) <=0;
    }catch(ParseException pex){
        pex.printStackTrace();
    }
    return res;
   }
 }
rashedmedisys
quelle
2
Bitte fügen Sie einige Wörter hinzu, um kurz zu beschreiben / erklären, warum und wie dieser Code die Frage beantwortet.
Yannis
1
Schön, dass Sie dazu beitragen möchten, danke. (1) Bitte bringen Sie den Jungen nicht bei, die lange veraltete und notorisch problematische SimpleDateFormatKlasse zu benutzen . Zumindest nicht als erste Option. Und das nicht ohne Vorbehalt. Heute haben wir java.timedie moderne Java-API für Datum und Uhrzeit und ihre DateTimeFormatter. (2) Nur-Code-Antworten sind sehr selten hilfreich. Aus der begleitenden Erklärung lernen wir alle.
Ole VV
1
Es ist ein schlechter Rat, 2018 die Verwendung dieser furchtbar problematischen Klassen vorzuschlagen, die vor Jahren verdrängt wurden. Darüber hinaus ignoriert Ihr Code das entscheidende Problem der Zeitzone.
Basil Bourque
-1

Ihre Logik würde gut funktionieren. Wie bereits erwähnt, befinden sich die Daten, die Sie aus der Datenbank erhalten, im Zeitstempel. Sie müssen nur zuerst den Zeitstempel in das Datum konvertieren und dann diese Logik verwenden.

Vergessen Sie auch nicht, nach Nulldaten zu suchen.

Hier teile ich ein bisschen, um von Zeitstempel auf Datum zu konvertieren.

public static Date convertTimeStamptoDate (String val) löst eine Ausnahme aus {

    DateFormat df = null;
    Date date = null;

    try {
        df = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        date = df.parse(val);
        // System.out.println("Date Converted..");
        return date;
    } catch (Exception ex) {
        System.out.println(ex);
        return convertDate2(val);
    } finally {
        df = null;
        date = null;
    }
}
Naveen Yadav
quelle