¿Cómo obtener la hora de inicio y finalización de un día?

121

¿Cómo obtener la hora de inicio y finalización de un día?

código como este no es exacto:

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

No tiene una precisión de milisegundos.

kalman03
fuente
8
¿Qué quiere decir que "no es exacto"?
Kirk Woll
¿Qué pasa con el código? ¿Qué obtienes que no esperas? ¿En qué zona horaria desea que comiencen y terminen sus días?
Mark Reed
4
El código anterior ni siquiera usa la fecha pasada al método. Debería estar haciendo: calendar.setTime (fecha) después de que se crea el calendario.
Jerry Destremps
2
Esta pregunta asume que los valores de fecha y hora tienen una resolución de milisegundos. Verdadero para java.util.Date y Joda-Time. Pero False para el paquete java.time en Java 8 (nanosegundos), algunas bases de datos como Postgres (microsegundos), bibliotecas de Unix (segundos completos) y quizás otras. Vea mi respuesta para un enfoque más seguro usando Half-Open.
Basil Bourque
2
En realidad, debería obtener el comienzo del día siguiente y al iterar los elementos de ese día (suponiendo que quiera hacer eso) especificará el límite superior utilizando <en lugar de <=. Eso excluiría el día siguiente pero incluiría todo el día anterior hasta el nanosegundo.
Daniel F

Respuestas:

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.

Inicio del día

Obtenga la longitud completa de hoy como se ve en una zona horaria.

Utilizando el enfoque Half-Open, donde el principio es inclusivo mientras que el final es exclusivo . Este enfoque resuelve la falla en su código que no tiene en cuenta el último segundo del día.

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 [África / Túnez]

zdtStop.toString () = 2020-01-31T00: 00 + 01: 00 [África / Túnez]

Ver los mismos momentos en UTC.

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

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

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

Si desea el día completo de una fecha como se ve en UTC en lugar de en una zona horaria, use 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

Estos OffsetDateTime objetos ya estarán en UTC, pero puede llamar toInstantsi los necesita, que siempre están en UTC por definición.

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

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

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

Sugerencia: Puede que le interese agregar la biblioteca ThreeTen-Extra a su proyecto para usar su Intervalclase para representar este par de Instantobjetos. Esto ofrece la clase métodos útiles para la comparación como abuts, overlaps, contains, y mucho más.

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

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

Medio abierto

La respuesta de mprivat es correcta. Su punto es no tratar de obtener el final del día, sino compararlo con "antes del comienzo del día siguiente". Su idea se conoce como el enfoque "Half-Open" donde un lapso de tiempo tiene un comienzo que es inclusivo mientras que el final es exclusivo .

  • Los marcos actuales de fecha y hora de Java (java.util.Date/Calendar y Joda-Time ) usan milisegundos de la época . Pero en Java 8, las nuevas clases java.time. * JSR 310 utilizan una resolución de nanosegundos. Cualquier código que haya escrito basado en forzar la cuenta de milisegundos del último momento del día sería incorrecto si se cambiara a las nuevas clases.
  • La comparación de datos de otras fuentes se vuelve defectuosa si emplean otras resoluciones. Por ejemplo, las bibliotecas de Unix suelen emplear segundos enteros y las bases de datos como Postgres resuelven la fecha y la hora en microsegundos.
  • Algunos cambios en el horario de verano ocurren durante la medianoche, lo que podría confundir aún más las cosas.

ingrese la descripción de la imagen aquí

Joda-Time 2.3 ofrece un método para este mismo propósito, para obtener un primer momento del día: withTimeAtStartOfDay(). Del mismo modo, en java.time, LocalDate::atStartOfDay.

Busque en StackOverflow "joda medio abierto" para ver más debates y ejemplos.

Vea esta publicación, Los intervalos de tiempo y otros rangos deben estar medio abiertos , por Bill Schneider.

Evite las clases de fecha y hora heredadas

Las clases java.util.Date y .Calendar son notoriamente problemáticas. Evítales.

Utilice clases de java.time . El marco java.time es el sucesor oficial de la exitosa biblioteca Joda-Time .

java.time

El marco java.time está integrado en Java 8 y versiones posteriores. Adaptado a Java 6 y 7 en el proyecto ThreeTen-Backport , más adaptado a Android en el proyecto ThreeTenABP .

Un Instantes un momento en la línea de tiempo en UTC con una resolución de nanosegundos .

Instant instant = Instant.now();

Aplique una zona horaria para obtener la hora del reloj de pared de alguna localidad.

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

Para obtener el primer momento del día, repase la LocalDateclase y su atStartOfDaymétodo.

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

Usando el enfoque Half-Open, obtenga el primer momento del día siguiente.

ZonedDateTime zdtTomorrowStart = zdtStart.plusDays( 1 );

Tabla de todos los tipos de fecha y hora en Java, tanto modernos como heredados

Actualmente, el marco java.time carece de una Intervalclase como se describe a continuación para Joda-Time. Sin embargo, el proyecto ThreeTen-Extra extiende java.time con clases adicionales. Este proyecto es el campo de pruebas para posibles adiciones futuras a java.time. Entre sus clases se encuentra Interval. Construye un Intervalpasando un par de Instantobjetos. Podemos extraer un Instantde nuestros ZonedDateTimeobjetos.

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

Acerca de java.time

El marco java.time está integrado en Java 8 y versiones posteriores. Estas clases suplantar la vieja problemáticos heredados clases de fecha y hora como java.util.Date, Calendar, y SimpleDateFormat.

Para obtener más información, consulte el tutorial de Oracle . Y busque Stack Overflow para obtener muchos ejemplos y explicaciones. La especificación es JSR 310 .

El proyecto Joda-Time , ahora en modo de mantenimiento , aconseja la migración a las clases java.time .

Puede intercambiar objetos java.time directamente con su base de datos. Utilice un controlador JDBC compatible con JDBC 4.2 o posterior. No se necesitan cuerdas, no se necesitan java.sql.*clases. Hibernate 5 y JPA 2.2 admiten java.time .

¿Dónde obtener las clases java.time?


Joda-Time

ACTUALIZACIÓN: El proyecto Joda-Time ahora está en modo de mantenimiento y recomienda la migración a las clases java.time . Dejo esta sección intacta para la historia.

Joda-Time tiene tres clases para representar un espacio de tiempo de varias maneras: Interval, Period, y Duration. An Intervaltiene un comienzo y un final específicos en la línea de tiempo del Universo. Esto se ajusta a nuestra necesidad de representar "un día".

Llamamos al método en withTimeAtStartOfDaylugar de establecer la hora del día en ceros. Debido al horario de verano y otras anomalías, el primer momento del día puede no serlo 00:00:00.

Código de ejemplo usando 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 );

Si es necesario, puede convertirlo a java.util.Date.

java.util.Date date = todayStart.toDate();
Albahaca Bourque
fuente
Necesito usarLocalDateTime
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());
}

Actualización : he agregado estos 2 métodos a mis clases de utilidad de Java aquí

Está en el repositorio central de Maven en:

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

Java 7 y versiones anteriores


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

Sin 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();
}
RKumsher
fuente
5
No recomiendo este enfoque debido a los problemas de casos extremos con los que se encontrará con las zonas horarias, el horario de verano, etc., que una biblioteca de tiempo como Joda se encargará por usted.
Edward Anderson
7
Creo que getStartOfDaydebería ser: LocalDateTime startOfDay = localDateTime.with(LocalTime.MIN);es decir, LocalTime, no LocalDateTime.
Konstantin Kulagin
No pienses en esos getEndOfDay(Date date). getStartOfDay(Date date)Los métodos en la sección de Java 8 son correctos, a saber, LocalDateTime.MAX y .MIN lo ubican en las fechas más tempranas posibles en el pasado y en el futuro, no el comienzo y el final de un día. En su lugar, sugeriría .atStartOfDay () para el inicio y .plusDays (1) .atStartOfDay (). MinusNanos (1) para el final.
Glen Mazza
El método localDateTimeToDate(LocalDateTime startOfDay)arroja una excepción - java.lang.IllegalArgumentException: java.lang.ArithmeticException: overflow largo
sanluck
1
@GlenMazza la solución Java 8 usa LocalTime .MAX , no LocalDateTime.MAX . La solucion es correcta.
Frederico Pantuzza
28

en getEndOfDay, puede agregar:

calendar.set(Calendar.MILLISECOND, 999);

Aunque matemáticamente hablando, no puede especificar el final de un día más que diciendo que es "antes del comienzo del día siguiente".

Así que en lugar de decir if(date >= getStartOfDay(today) && date <= getEndOfDay(today)), debe decir: if(date >= getStartOfDay(today) && date < getStartOfDay(tomorrow)). Esa es una definición mucho más sólida (y no tiene que preocuparse por la precisión de milisegundos).

mprivat
fuente
1
La segunda mitad de esta respuesta es la mejor manera. Este enfoque se conoce como "medio abierto". Explico más en mi respuesta .
Basil Bourque
14

java.time

Usando el java.timemarco integrado en 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
Przemek
fuente
3
Esta respuesta ignora anomalías como el horario de verano (DST). Las Local…clases no tienen concepto de zonas horarias y se ajustan a anomalías. Por ejemplo, algunas zonas horarias tienen un cambio de horario de verano a la medianoche, por lo que el primer momento del día es a la hora 01:00:00.0(1 am) en lugar del 00:00:00.0valor producido por este código. Úselo en su ZonedDateTimelugar.
Basil Bourque
4

Una forma adicional de encontrar el inicio del día con java8 java.time.ZonedDateTime en lugar de pasar LocalDateTimees simplemente truncar la entrada ZonedDateTime a DAYS:

zonedDateTimeInstance.truncatedTo( ChronoUnit.DAYS );
laur
fuente
2

Para java 8, las siguientes declaraciones de una sola línea están funcionando. En este ejemplo utilizo la zona horaria UTC. Considere cambiar la zona horaria que utiliza actualmente.

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

Si no hay diferencia horaria con la salida. Tratar:ZoneOffset.ofHours(0)

Fırat KÜÇÜK
fuente
1
¿Cómo agrega valor esto más allá del de las Respuestas existentes? Además, ignora la cuestión crucial de la zona horaria. Y pasar la constante UTC a toInstantes una sintaxis ilegal y conceptualmente redundante.
Basil Bourque
@BasilBourque "¿Cómo agrega valor esto más allá del de las Respuestas existentes?" Así es como funciona stackoverflow. Por cierto, esto es solo una plantilla. Las personas pueden cambiar de la forma que quieran. pasar UTC a toInstant es totalmente legal, puede examinar las salidas.
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

Espero que ayude a alguien.

SudoPlz
fuente
El método "atTime" es un muy buen enfoque junto con la constante "LocalTime.MAX" para el final del día. ¡Agradable!
Sr. Anderson
2

¡Probé este código y funciona bien!

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);
Siddhey Sankhe
fuente
Si lo tiene ZonedDateTimeya Instantsu disposición no es necesario que lo utilice nunca java.sql.Timestamp. Esa clase es una de las terribles clases antiguas heredadas ahora suplantadas por las clases java.time . A partir de JDBC 4.2, podemos intercambiar directamente objetos java.time con la base de datos. Su mezcla de clases tradicionales y modernas no tiene sentido para mí.
Basil Bourque
1

Otra solución que no depende de ningún marco es:

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

Tenga en cuenta que devuelve la hora basada en UTC

Alexander Chingarev
fuente
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); le da una precisión de hasta milisegundos

manik venkat
fuente
1
Estas terribles clases de fecha y hora fueron suplantadas hace años por las modernas clases java.time definidas en JSR 310.
Basil Bourque
@BasilBourque el paquete java.time solo apareció en Java 8, que está disponible solo desde Android N (API 26)
ivan8m8
@ ivan8m8 La mayor parte de la funcionalidad de java.time está adaptada a Java 6 y 7 en la biblioteca ThreeTen-Backport . Más adaptado para los primeros Android (<26) en la biblioteca ThreeTenABP .
Basil Bourque
@BasilBourque pero ThreeTen usa un mecanismo extremadamente ineficiente en Android .
ivan8m8
@ ivan8m8 Puede que estés pensando en el predecesor de java.time , Joda-Time . Nuevamente, vea el proyecto ThreeTenABP . No he oído hablar de tal ineficiencia en Android al usar esa biblioteca.
Basil Bourque
0

Sé que es un poco tarde, pero en el caso de Java 8, si está usando OffsetDateTime (que ofrece muchas ventajas, como TimeZone, Nanoseconds, etc.), puede usar el siguiente código:

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

Tuve varios inconvenientes con todas las soluciones porque necesitaba el tipo de variable Instant y la Zona horaria siempre interfería cambiando todo, luego combinando soluciones vi que esta es una buena opción.

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

y tenemos como resultado

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

Espero que te ayude

yOshi
fuente
En realidad, el día termina con la llegada del primer momento del día siguiente . Su código salta el último segundo completo de un día, mil millones de nanosegundos que nunca se informan. Vea mi respuesta para aprender sobre el enfoque semiabierto para definir un lapso de tiempo.
Basil Bourque
Manipular cadenas como una forma de manejar valores de fecha y hora es generalmente un enfoque deficiente. Utilice objetos inteligentes en lugar de cuerdas tontas para este trabajo. Las clases java.time brindan toda la funcionalidad que necesita para este problema sin recurrir a la manipulación de cadenas.
Basil Bourque
Acabo de agregar una sección tl; dr a mi Respuesta que muestra cómo obtener el rango de un día como un par de Instantobjetos. Sugerencia: puede que le interese agregar la biblioteca ThreeTen-Extra a su proyecto para usar su Intervalclase.
Basil Bourque
0

Creo que lo más fácil sería algo como:

// Joda Time

DateTime dateTime=new DateTime(); 

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

Estos milis se pueden convertir en Calendar, Instant o LocalDate según sus requisitos con Joda Time.

happyvirus
fuente
-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();
}
jack jin
fuente