Come ottenere l'ora di inizio e l'ora di fine di una giornata?


121

Come ottenere l'ora di inizio e l'ora di fine di una giornata?

codice come questo non è accurato:

 private Date getStartOfDay(Date date) {
    Calendar calendar = Calendar.getInstance();
    int year = calendar.get(Calendar.YEAR);
    int month = calendar.get(Calendar.MONTH);
    int day = calendar.get(Calendar.DATE);
    calendar.set(year, month, day, 0, 0, 0);
    return calendar.getTime();
}

private Date getEndOfDay(Date date) {
    Calendar calendar = Calendar.getInstance();
    int year = calendar.get(Calendar.YEAR);
    int month = calendar.get(Calendar.MONTH);
    int day = calendar.get(Calendar.DATE);
    calendar.set(year, month, day, 23, 59, 59);
    return calendar.getTime();
}

Non è preciso al millisecondo.


8
Cosa intendi per "non accurato"?
Kirk Woll

Cosa c'è di sbagliato nel codice? Cosa ottieni che non ti aspetti? In base a quale fuso orario vuoi che inizino e finiscano i tuoi giorni?
Mark Reed

4
Il codice precedente non utilizza nemmeno la data passata nel metodo. Dovrebbe essere: calendar.setTime (data) dopo la creazione del calendario.
Jerry Destremps

2
Questa domanda presuppone che i valori di data e ora abbiano una risoluzione di millisecondi. Vero per java.util.Date e Joda-Time. Ma False per il pacchetto java.time in Java 8 (nanosecondi), alcuni database come Postgres (microsecondi), librerie unix (secondi interi) e forse altri. Vedi la mia risposta per un approccio più sicuro usando Half-Open.
Basil Bourque

2
In realtà dovresti ottenere l'inizio del giorno successivo e quando iterando gli elementi in quel giorno (assumendo che tu voglia farlo) specificherai il limite superiore usando <invece di <=. Ciò escluderebbe il giorno successivo ma includerebbe tutto il giorno precedente fino al nanosecondo.
Daniel F

Risposte:


115

tl; dr

LocalDate                       // Represents an entire day, without time-of-day and without time zone.
.now(                           // Capture the current date.
    ZoneId.of( "Asia/Tokyo" )   // Returns a `ZoneId` object.
)                               // Returns a `LocalDate` object.
.atStartOfDay(                  // Determines the first moment of the day as seen on that date in that time zone. Not all days start at 00:00!
    ZoneId.of( "Asia/Tokyo" ) 
)                               // Returns a `ZonedDateTime` object.

Inizio della giornata

Ottieni l'intera durata della giornata odierna come si vede in un fuso orario.

Utilizzando l'approccio Half-Open, dove è l'inizio inclusivo mentre il finale è esclusivo . Questo approccio risolve il difetto nel codice che non riesce a tenere conto dell'ultimo secondo della giornata.

ZoneId zoneId = ZoneId.of( "Africa/Tunis" ) ;
LocalDate today = LocalDate.now( zoneId  ) ;

ZonedDateTime zdtStart = today.atStartOfDay( zoneId ) ;
ZonedDateTime zdtStop = today.plusDays( 1 ).atStartOfDay( zoneId ) ;

zdtStart.toString () = 2020-01-30T00: 00 + 01: 00 [Africa / Tunisi]

zdtStop.toString () = 2020-01-31T00: 00 + 01: 00 [Africa / Tunisi]

Guarda gli stessi momenti in UTC.

Instant start = zdtStart.toInstant() ;
Instant stop = zdtStop.toInstant() ;

start.toString () = 2020-01-29T23: 00: 00Z

stop.toString () = 2020-01-30T23: 00: 00Z

Se desideri visualizzare l'intero giorno di una data in UTC anziché in un fuso orario, utilizza OffsetDateTime.

LocalDate today = LocalDate.now( ZoneOffset.UTC  ) ;

OffsetDateTime odtStart = today.atTime( OffsetTime.MIN ) ;
OffsetDateTime odtStop = today.plusDays( 1 ).atTime( OffsetTime.MIN ) ;

odtStart.toString () = 2020-01-30T00: 00 + 18: 00

odtStop.toString () = 2020-01-31T00: 00 + 18: 00

Questi OffsetDateTime oggetti saranno già in UTC, ma puoi chiamare toInstantse hai bisogno di tali oggetti che sono sempre in UTC per definizione.

Instant start = odtStart.toInstant() ;
Instant stop = odtStop.toInstant() ;

start.toString () = 2020-01-29T06: 00: 00Z

stop.toString () = 2020-01-30T06: 00: 00Z

Suggerimento: potresti essere interessato ad aggiungere la libreria ThreeTen-Extra al tuo progetto per utilizzare la sua Intervalclasse per rappresentare questa coppia di Instantoggetti. Questo offre classe metodi utili per il confronto, come abuts, overlaps, contains, e altro ancora.

Interval interval = Interval.of( start , stop ) ;

interval.toString () = 2020-01-29T06: 00: 00Z / 2020-01-30T06: 00: 00Z

Mezzo aperto

La risposta di mprivat è corretta. Il suo punto è non cercare di ottenere la fine di una giornata, ma piuttosto confrontarla con "prima dell'inizio del giorno successivo". La sua idea è nota come approccio "semiaperto" in cui un arco di tempo ha un inizio inclusivo mentre il finale è esclusivo .

  • Gli attuali framework data-ora di Java (java.util.Date/Calendar e Joda-Time ) utilizzano entrambi i millisecondi dell'epoca . Ma in Java 8, le nuove classi java.time. * JSR 310 utilizzano una risoluzione in nanosecondi. Qualsiasi codice scritto in base alla forzatura del conteggio dei millisecondi dell'ultimo momento della giornata non sarebbe corretto se passato alle nuove classi.
  • Il confronto dei dati da altre fonti diventa errato se utilizzano altre risoluzioni. Ad esempio, le librerie Unix in genere impiegano interi secondi e database come Postgres risolvono la data e l'ora in microsecondi.
  • Alcuni cambiamenti dell'ora legale avvengono dopo mezzanotte, il che potrebbe confondere ulteriormente le cose.

inserisci qui la descrizione dell'immagine

Joda-Time 2.3 offre un metodo per questo scopo, per ottenere primo momento della giornata: withTimeAtStartOfDay(). Allo stesso modo in java.time,LocalDate::atStartOfDay .

Cerca in StackOverflow "joda half-open" per vedere ulteriori discussioni ed esempi.

Vedi questo post, Intervalli di tempo e altri intervalli dovrebbero essere semiaperti , di Bill Schneider.

Evita le classi data-ora precedenti

Le classi java.util.Date e .Calendar sono notoriamente problematiche. Evitali.

Usa le classi java.time . Il framework java.time è il successore ufficiale della libreria di grande successo Joda-Time .

java.time

Il framework java.time è integrato in Java 8 e versioni successive. Backport su Java 6 e 7 nel progetto ThreeTen-Backport , ulteriormente adattato ad Android nel ThreeTenABP progetto .

Un Instantè un momento sulla timeline in UTC con una risoluzione di nanosecondi .

Instant instant = Instant.now();

Applica un fuso orario per ottenere l' ora dell'orologio da parete per alcune località.

ZoneId zoneId = ZoneId.of( "America/Montreal" );
ZonedDateTime zdt = ZonedDateTime.ofInstant( instant , zoneId );

Per ottenere il primo momento della giornata, ripassa la LocalDatelezione e il suo atStartOfDaymetodo.

ZonedDateTime zdtStart = zdt.toLocalDate().atStartOfDay( zoneId );

Usando l'approccio Half-Open, ottieni il primo momento del giorno successivo.

ZonedDateTime zdtTomorrowStart = zdtStart.plusDays( 1 );

Tabella di tutti i tipi di data e ora in Java, sia moderni che legacy

Attualmente il framework java.time manca di una Intervalclasse come descritto di seguito per Joda-Time. Tuttavia, il progetto ThreeTen-Extra estende java.time con classi aggiuntive. Questo progetto è il banco di prova per possibili future aggiunte a java.time. Tra le sue classi c'è Interval. Costruisci un Intervalpassando un paio di Instantoggetti. Possiamo estrarre un Instantdai nostri ZonedDateTimeoggetti.

Interval today = Interval.of( zdtStart.toInstant() , zdtTomorrowStart.toInstant() );

Informazioni su java.time

Il framework java.time è integrato in Java 8 e versioni successive. Queste classi soppiantare la vecchia fastidiosi legacy classi data-time come java.util.Date, Calendar, e SimpleDateFormat.

Per saperne di più, vedere il tutorial Oracle . E cerca Stack Overflow per molti esempi e spiegazioni. La specifica è JSR 310 .

Il progetto Joda-Time , ora in modalità di manutenzione , consiglia la migrazione alle classi java.time .

Puoi scambiare oggetti java.time direttamente con il tuo database. Utilizza un driver JDBC compatibile con JDBC 4.2 o successivo. Non c'è bisogno di stringhe, non c'è bisogno di java.sql.*classi. Hibernate 5 e JPA 2.2 supportano java.time .

Dove ottenere le classi java.time?


Joda-Time

AGGIORNAMENTO: Il progetto Joda-Time è ora in modalità di manutenzione e consiglia la migrazione alle classi java.time . Lascio questa sezione intatta per la storia.

Joda Time ha tre classi per rappresentare un arco di tempo in vari modi: Interval, Period, e Duration. An Intervalha un inizio e una fine specifici sulla linea temporale dell'Universo. Ciò corrisponde alla nostra esigenza di rappresentare "un giorno".

Chiamiamo il metodo withTimeAtStartOfDaypiuttosto che impostare l'ora del giorno su zero. A causa dell'ora legale e di altre anomalie, il primo momento della giornata potrebbe non esserlo 00:00:00.

Codice di esempio utilizzando Joda-Time 2.3.

DateTimeZone timeZone = DateTimeZone.forID( "America/Montreal" );
DateTime now = DateTime.now( timeZone );
DateTime todayStart = now.withTimeAtStartOfDay();
DateTime tomorrowStart = now.plusDays( 1 ).withTimeAtStartOfDay();
Interval today = new Interval( todayStart, tomorrowStart );

Se è necessario, è possibile convertire in java.util.Date.

java.util.Date date = todayStart.toDate();

Devo usareLocalDateTime
user924

170

Java 8


public static Date atStartOfDay(Date date) {
    LocalDateTime localDateTime = dateToLocalDateTime(date);
    LocalDateTime startOfDay = localDateTime.with(LocalTime.MIN);
    return localDateTimeToDate(startOfDay);
}

public static Date atEndOfDay(Date date) {
    LocalDateTime localDateTime = dateToLocalDateTime(date);
    LocalDateTime endOfDay = localDateTime.with(LocalTime.MAX);
    return localDateTimeToDate(endOfDay);
}

private static LocalDateTime dateToLocalDateTime(Date date) {
    return LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
}

private static Date localDateTimeToDate(LocalDateTime localDateTime) {
    return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
}

Aggiornamento : ho aggiunto questi 2 metodi alle mie classi di utilità Java qui

Si trova nel Maven Central Repository all'indirizzo:

<dependency>
  <groupId>com.github.rkumsher</groupId>
  <artifactId>utils</artifactId>
  <version>1.3</version>
</dependency>

Java 7 e versioni precedenti


Con Apache Commons

public static Date atEndOfDay(Date date) {
    return DateUtils.addMilliseconds(DateUtils.ceiling(date, Calendar.DATE), -1);
}

public static Date atStartOfDay(Date date) {
    return DateUtils.truncate(date, Calendar.DATE);
}

Senza Apache Commons

public Date atEndOfDay(Date date) {
    Calendar calendar = Calendar.getInstance();
    calendar.setTime(date);
    calendar.set(Calendar.HOUR_OF_DAY, 23);
    calendar.set(Calendar.MINUTE, 59);
    calendar.set(Calendar.SECOND, 59);
    calendar.set(Calendar.MILLISECOND, 999);
    return calendar.getTime();
}

public Date atStartOfDay(Date date) {
    Calendar calendar = Calendar.getInstance();
    calendar.setTime(date);
    calendar.set(Calendar.HOUR_OF_DAY, 0);
    calendar.set(Calendar.MINUTE, 0);
    calendar.set(Calendar.SECOND, 0);
    calendar.set(Calendar.MILLISECOND, 0);
    return calendar.getTime();
}

5
Raccomando questo approccio a causa dei problemi di casi limite che incontrerai con i fusi orari, l'ora legale, ecc. Che una libreria di orari come Joda si prenderà cura di te.
Edward Anderson

7
Penso che getStartOfDaydovrebbe essere: LocalDateTime startOfDay = localDateTime.with(LocalTime.MIN);Ie LocalTime, non LocalDateTime.
Konstantin Kulagin

Non pensare a quelli getEndOfDay(Date date). getStartOfDay(Date date)i metodi nella sezione Java 8 sono corretti, vale a dire LocalDateTime.MAX e .MIN ti mettono alle prime date possibili nel passato e nel futuro, non all'inizio e alla fine di un giorno. Suggerirei invece .atStartOfDay () per inizio e .plusDays (1) .atStartOfDay (). MinusNanos (1) per fine.
Glen Mazza

Il metodo localDateTimeToDate(LocalDateTime startOfDay)genera
un'eccezione

1
@GlenMazza la soluzione Java 8 utilizza LocalTime .MAX , non LocalDateTime.MAX . La soluzione è corretta.
Frederico Pantuzza

28

in getEndOfDay, puoi aggiungere:

calendar.set(Calendar.MILLISECOND, 999);

Sebbene matematicamente parlando, non è possibile specificare la fine di un giorno se non dicendo che è "prima dell'inizio del giorno successivo".

Così, invece di dire, if(date >= getStartOfDay(today) && date <= getEndOfDay(today))si dovrebbe dire: if(date >= getStartOfDay(today) && date < getStartOfDay(tomorrow)). Questa è una definizione molto più solida (e non devi preoccuparti della precisione del millisecondo).


1
La seconda metà di questa risposta è il modo migliore. Questo approccio è noto come "Half-Open". Spiego ulteriormente nella mia risposta .
Basil Bourque

14

java.time

Utilizzo del java.timeframework integrato in Java 8.

import java.time.LocalTime;
import java.time.LocalDateTime;

LocalDateTime now = LocalDateTime.now(); // 2015-11-19T19:42:19.224
// start of a day
now.with(LocalTime.MIN); // 2015-11-19T00:00
now.with(LocalTime.MIDNIGHT); // 2015-11-19T00:00
// end of a day
now.with(LocalTime.MAX); // 2015-11-19T23:59:59.999999999

3
Questa risposta ignora le anomalie come l'ora legale (DST). Le Local…classi non hanno il concetto di fusi orari e regolazione per anomalie. Ad esempio, alcuni fusi orari hanno un cambio di ora legale a mezzanotte, quindi il primo momento della giornata è l'ora 01:00:00.0(1 AM) piuttosto che il 00:00:00.0valore prodotto da questo codice. Usa ZonedDateTimeinvece.
Basil Bourque

4

Un altro modo per trovare l'inizio della giornata con java8 java.time.ZonedDateTime invece di passare attraverso LocalDateTimeè semplicemente troncare l'input ZonedDateTime a DAYS:

zonedDateTimeInstance.truncatedTo( ChronoUnit.DAYS );

2

Per java 8 funzionano le seguenti istruzioni a riga singola. In questo esempio utilizzo il fuso orario UTC. Considera la possibilità di modificare il fuso orario attualmente utilizzato.

System.out.println(new Date());

final LocalDateTime endOfDay       = LocalDateTime.of(LocalDate.now(), LocalTime.MAX);
final Date          endOfDayAsDate = Date.from(endOfDay.toInstant(ZoneOffset.UTC));

System.out.println(endOfDayAsDate);

final LocalDateTime startOfDay       = LocalDateTime.of(LocalDate.now(), LocalTime.MIN);
final Date          startOfDayAsDate = Date.from(startOfDay.toInstant(ZoneOffset.UTC));

System.out.println(startOfDayAsDate);

Se nessuna differenza di orario con l'uscita. Provare:ZoneOffset.ofHours(0)


1
In che modo questo aggiunge valore oltre a quello delle Risposte esistenti? Inoltre, ignori la questione cruciale del fuso orario. E passare la costante UTC a toInstantè una sintassi illegale e concettualmente ridondante.
Basil Bourque

@BasilBourque "In che modo questo valore aggiunto oltre a quello delle risposte esistenti?" Ecco come funziona stackoverflow. BTW questo è solo un modello. Le persone possono cambiare come vogliono. passare UTC a toInstant è totalmente legale, puoi esaminare gli output.
Fırat KÜÇÜK

2

Java 8 o ThreeTenABP

ZonedDateTime

ZonedDateTime curDate = ZonedDateTime.now();

public ZonedDateTime startOfDay() {
    return curDate
    .toLocalDate()
    .atStartOfDay()
    .atZone(curDate.getZone())
    .withEarlierOffsetAtOverlap();
}

public ZonedDateTime endOfDay() {

    ZonedDateTime startOfTomorrow =
        curDate
        .toLocalDate()
        .plusDays(1)
        .atStartOfDay()
        .atZone(curDate.getZone())
        .withEarlierOffsetAtOverlap();

    return startOfTomorrow.minusSeconds(1);
}

// based on https://stackoverflow.com/a/29145886/1658268

LocalDateTime

LocalDateTime curDate = LocalDateTime.now();

public LocalDateTime startOfDay() {
    return curDate.atStartOfDay();
}

public LocalDateTime endOfDay() {
    return startOfTomorrow.atTime(LocalTime.MAX);  //23:59:59.999999999;
}

// based on https://stackoverflow.com/a/36408726/1658268

Spero che aiuti qualcuno.


Il metodo "atTime" è un ottimo approccio insieme alla costante "LocalTime.MAX" per la fine della giornata. Bello!
Mr. Anderson

2

Ho provato questo codice e funziona bene!

final ZonedDateTime now = ZonedDateTime.now(ZoneOffset.UTC);
final ZonedDateTime startofDay =
    now.toLocalDate().atStartOfDay(ZoneOffset.UTC);
final ZonedDateTime endOfDay =
    now.toLocalDate().atTime(LocalTime.MAX).atZone(ZoneOffset.UTC);

Se ce l'hai ZonedDateTimeea Instanttua disposizione non ce n'è mai bisogno java.sql.Timestamp. Quella classe è una delle terribili vecchie classi legacy ora soppiantate dalle classi java.time . A partire da JDBC 4.2, possiamo scambiare direttamente oggetti java.time con il database. Il tuo mix di eredità e classi moderne non ha senso per me.
Basil Bourque

1

Un'altra soluzione che non dipende da alcun framework è:

static public Date getStartOfADay(Date day) {
    final long oneDayInMillis = 24 * 60 * 60 * 1000;
    return new Date(day.getTime() / oneDayInMillis * oneDayInMillis);
}

static public Date getEndOfADay(Date day) {
    final long oneDayInMillis = 24 * 60 * 60 * 1000;
    return new Date((day.getTime() / oneDayInMillis + 1) * oneDayInMillis - 1);
}

Notare che restituisce l'ora basata su UTC


1
private Date getStartOfDay(Date date) {
    Calendar calendar = Calendar.getInstance();
    int year = calendar.get(Calendar.YEAR);
    int month = calendar.get(Calendar.MONTH);
    int day = calendar.get(Calendar.DATE);
    calendar.setTimeInMillis(0);
    calendar.set(year, month, day, 0, 0, 0);
    return calendar.getTime();
    }

private Date getEndOfDay(Date date) {
    Calendar calendar = Calendar.getInstance();
    int year = calendar.get(Calendar.YEAR);
    int month = calendar.get(Calendar.MONTH);
    int day = calendar.get(Calendar.DATE);
    calendar.setTimeInMillis(0);
    calendar.set(year, month, day, 23, 59, 59);
    return calendar.getTime();
    }

calendar.setTimeInMillis (0); ti dà una precisione fino a millisecondi


1
Queste terribili classi data-ora sono state anni fa soppiantate dalle moderne classi java.time definite in JSR 310.
Basil Bourque,

@BasilBourque il pacchetto java.time è apparso solo in Java 8 che è disponibile solo da Android N (API 26)
ivan8m8

@ ivan8m8 La maggior parte delle funzionalità java.time è sottoposta a back port su Java 6 e 7 nella libreria ThreeTen-Backport . Ulteriormente adattato per i primi Android (<26) nella libreria ThreeTenABP .
Basil Bourque,

@BasilBourque ma il ThreeTen utilizza un meccanismo estremamente inefficiente su Android .
ivan8m8

@ ivan8m8 Potresti pensare al predecessore di java.time , Joda-Time . Di nuovo, guarda il progetto ThreeTenABP . Non ho sentito parlare di tale inefficienza su Android utilizzando quella libreria.
Basil Bourque,

0

So che è un po 'tardi, ma nel caso di Java 8, se stai usando OffsetDateTime (che offre molti vantaggi, come TimeZone, Nanoseconds, ecc.), Puoi usare il seguente codice:

OffsetDateTime reallyEndOfDay = someDay.withHour(23).withMinute(59).withSecond(59).withNano(999999999);
// output: 2019-01-10T23:59:59.999999999Z

0

Ho avuto diversi inconvenienti con tutte le soluzioni perché avevo bisogno del tipo di variabile Istantanea e il fuso orario interferiva sempre cambiando tutto, quindi combinando le soluzioni ho visto che questa è una buona opzione.

        LocalDate today = LocalDate.now();
        Instant startDate = Instant.parse(today.toString()+"T00:00:00Z");
        Instant endDate = Instant.parse(today.toString()+"T23:59:59Z");

e abbiamo come risultato

        startDate = 2020-01-30T00:00:00Z
        endDate = 2020-01-30T23:59:59Z

Spero ti aiuti


La giornata si conclude infatti all'arrivo del primo momento del giorno successivo . Il codice salta nell'ultimo secondo intero di una giornata, un miliardo di nanosecondi che non vengono mai segnalati. Vedere la mia risposta per conoscere l'approccio semiaperto alla definizione di un intervallo di tempo.
Basil Bourque il

La manipolazione delle stringhe per gestire i valori di data e ora è generalmente un approccio scadente. Usa oggetti intelligenti piuttosto che stringhe stupide per questo lavoro. Le classi java.time forniscono tutte le funzionalità necessarie per questo problema senza ricorrere alla manipolazione delle stringhe.
Basil Bourque,

Ho appena aggiunto una sezione tl; dr alla mia risposta che mostra come ottenere l'intervallo di un giorno come coppia di Instantoggetti. Suggerimento: potresti essere interessato ad aggiungere la libreria ThreeTen-Extra al tuo progetto per utilizzare la sua Intervalclasse.
Basil Bourque il

0

Penso che il più semplice sarebbe qualcosa del tipo:

// Joda Time

DateTime dateTime=new DateTime(); 

StartOfDayMillis = dateTime.withMillis(System.currentTimeMillis()).withTimeAtStartOfDay().getMillis();
EndOfDayMillis = dateTime.withMillis(StartOfDayMillis).plusDays(1).minusSeconds(1).getMillis();

Questi millisecondi possono essere quindi convertiti in Calendar, Instant o LocalDate secondo le tue esigenze con Joda Time.


-2
public static Date beginOfDay(Date date) {
    Calendar cal = Calendar.getInstance();
    cal.setTime(date);
    cal.set(Calendar.HOUR_OF_DAY, 0);
    cal.set(Calendar.MINUTE, 0);
    cal.set(Calendar.SECOND, 0);
    cal.set(Calendar.MILLISECOND, 0);

    return cal.getTime();
}

public static Date endOfDay(Date date) {
    Calendar cal = Calendar.getInstance();
    cal.setTime(date);
    cal.set(Calendar.HOUR_OF_DAY, 23);
    cal.set(Calendar.MINUTE, 59);
    cal.set(Calendar.SECOND, 59);
    cal.set(Calendar.MILLISECOND, 999);

    return cal.getTime();
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.