¿Cómo convertir ZonedDateTime a Date?

103

Estoy tratando de establecer una fecha y hora independiente del servidor en mi base de datos y creo que la mejor práctica para hacerlo es establecer una fecha y hora UTC. Mi servidor de base de datos es Cassandra y el controlador de base de datos para Java comprende solo el tipo de fecha.

Entonces, asumiendo que en mi código estoy usando el nuevo Java 8 ZonedDateTime para obtener el UTC ahora ( ZonedDateTime.now(ZoneOffset.UTC)), ¿cómo puedo convertir esta instancia de ZonedDateTime en la clase de fecha "heredada"?

Milen Kovachev
fuente
Hay dos clases de "Fecha" integradas en Java java.util.Datey java.sql.Date.
Basil Bourque

Respuestas:

163

Puede convertir ZonedDateTime en un instante, que puede usar directamente con Date.

Date.from(java.time.ZonedDateTime.now().toInstant());
Slim Soltani Dridi
fuente
27
No, será la fecha actual predeterminada del sistema de su zona.
Slim Soltani Dridi
6
@MilenKovachev Su pregunta no tiene sentido: una fecha no tiene una zona horaria, solo representa un instante en el tiempo.
assylias
1
@assylias En realidad, tu declaración no tiene sentido. El formato en el que se almacena la hora implica una zona horaria. La fecha se basa en UTC, desafortunadamente, Java hace algunas cosas estúpidas y no lo trata como tal, y además, considera que la hora es TZ local en lugar de UTC. La forma en que Data, LocalDateTime, ZonedDateTime almacenan datos implica una zona horaria. La forma en que se utilizan es como si las TZ no existieran, lo cual es simplemente incorrecto. java.util.Date tiene una TZ de la JVM predeterminada, implícitamente. El hecho de que la gente lo trate como algo diferente (¡incluidos los documentos java para él!) Es simplemente mala gente.
David
5
@David uh no, a Datees un número de milisegundos desde la época, por lo que está relacionado con UTC. Si lo imprime , se utilizará la zona horaria predeterminada, pero la clase Date no tiene conocimiento de la zona horaria del usuario ... Consulte, por ejemplo, la sección "mapeo" en docs.oracle.com/javase/tutorial/datetime/iso/legacy .html Y LocalDateTime es explícitamente sin referencia a una zona horaria, lo que puede verse como confuso ...
assylias
1
Tu respuesta involucra innecesariamente ZonedDateTime. La java.time.Instantclase es un reemplazo directo de java.util.Date, ambos representan un momento en UTC, aunque Instantusan una resolución más fina de nanosegundos en lugar de milisegundos. Date.from( Instant.now() )debería haber sido tu solución. O para el caso, new Date()que tiene el mismo efecto, capturando el momento actual en UTC.
Basil Bourque
66

tl; dr

java.util.Date.from(  // Transfer the moment in UTC, truncating any microseconds or nanoseconds to milliseconds.
    Instant.now() ;   // Capture current moment in UTC, with resolution as fine as nanoseconds.
)

Aunque no tenía sentido en ese código anterior. Ambos java.util.Datey Instantrepresentan un momento en UTC, siempre en UTC. El código anterior tiene el mismo efecto que:

new java.util.Date()  // Capture current moment in UTC.

No hay beneficio aquí para usar ZonedDateTime. Si ya tiene un ZonedDateTime, ajuste a UTC extrayendo un Instant.

java.util.Date.from(             // Truncates any micros/nanos.
    myZonedDateTime.toInstant()  // Adjust to UTC. Same moment, same point on the timeline, different wall-clock time.
)

Otra respuesta correcta

La respuesta de ssoltanid aborda correctamente su pregunta específica, cómo convertir un objeto java.time ( ZonedDateTime) de la nueva escuela en un objeto de la vieja escuela java.util.Date. Extraiga el Instantde ZonedDateTime y pase a java.util.Date.from().

Pérdida de datos

Tenga en cuenta que sufrirá pérdida de datos , ya que Instantrastrea nanosegundos desde época mientras que java.util.Daterastrea milisegundos desde época.

Diagrama que compara resoluciones de milisegundos, microsegundos y nanosegundos

Su pregunta y comentarios plantean otros problemas.

Mantener los servidores en UTC

Sus servidores deben tener su sistema operativo host configurado en UTC como una mejor práctica en general. La JVM toma esta configuración del sistema operativo host como su zona horaria predeterminada, en las implementaciones de Java que conozco.

Especificar zona horaria

Pero nunca debe confiar en la zona horaria predeterminada actual de la JVM. En lugar de elegir la configuración del host, una bandera que se pasa al iniciar una JVM puede establecer otra zona horaria. Peor aún: ¡Cualquier código en cualquier hilo de cualquier aplicación en cualquier momento puede hacer una llamada a java.util.TimeZone::setDefaultpara cambiar ese valor predeterminado en tiempo de ejecución!

TimestampTipo Cassandra

Cualquier base de datos y controlador decente debería manejar automáticamente el ajuste de una fecha y hora pasada a UTC para el almacenamiento. No uso Cassandra, pero parece tener un soporte rudimentario para la fecha y la hora. La documentación dice que su Timestamptipo es un recuento de milisegundos de la misma época (primer momento de 1970 en UTC).

ISO 8601

Además, Cassandra acepta entradas de cadenas en los formatos estándar ISO 8601 . Afortunadamente, java.time usa formatos ISO 8601 como valores predeterminados para analizar / generar cadenas. La implementación de la Instantclase funcionará toStringbien.

Precisión: milisegundos vs nanosegundos

Pero primero debemos reducir la precisión de nanosegundos de ZonedDateTime a milisegundos. Una forma es crear un Instant fresco usando milisegundos. Afortunadamente, java.time tiene algunos métodos útiles para convertir desde y hacia milisegundos.

Código de ejemplo

Aquí hay un código de ejemplo en Java 8 Update 60.

ZonedDateTime zdt = ZonedDateTime.now( ZoneId.of( "America/Montreal" ) );

Instant instant = zdt.toInstant();
Instant instantTruncatedToMilliseconds = Instant.ofEpochMilli( instant.toEpochMilli() );
String fodderForCassandra = instantTruncatedToMilliseconds.toString();  // Example: 2015-08-18T06:36:40.321Z

O de acuerdo con este documento del controlador de Cassandra Java , puede pasar una java.util.Dateinstancia (que no debe confundirse con java.sqlDate). Entonces podrías hacer un juDate a partir de eso instantTruncatedToMillisecondsen el código anterior.

java.util.Date dateForCassandra = java.util.Date.from( instantTruncatedToMilliseconds );

Si haces esto con frecuencia, podrías hacer una sola línea.

java.util.Date dateForCassandra = java.util.Date.from( zdt.toInstant() );

Pero sería mejor crear un pequeño método de utilidad.

static public java.util.Date toJavaUtilDateFromZonedDateTime ( ZonedDateTime zdt ) {
    Instant instant = zdt.toInstant();
    // Data-loss, going from nanosecond resolution to milliseconds.
    java.util.Date utilDate = java.util.Date.from( instant ) ;
    return utilDate;
}

Note la diferencia en todo este código que en la Pregunta. El código de la pregunta intentaba ajustar la zona horaria de la instancia de ZonedDateTime a UTC. Pero eso no es necesario. Conceptualmente:

ZonedDateTime = Instantáneo + ZoneId

Simplemente extraemos la parte Instant, que ya está en UTC (básicamente en UTC, lea el documento de la clase para obtener detalles precisos).


Tabla de tipos de fecha y hora en Java, tanto moderno como heredado


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.

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

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 .

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.

¿Dónde obtener las clases java.time?

Tabla de qué biblioteca java.time usar con qué versión de Java o Android

El proyecto ThreeTen-Extra extiende java.time con clases adicionales. Este proyecto es un campo de pruebas para posibles adiciones futuras a java.time. Usted puede encontrar algunas clases útiles aquí, como Interval, YearWeek, YearQuarter, y más .

Albahaca Bourque
fuente
Maravillosa explicación, bien detallada. Muchas gracias!
Gianmarco F.
5

Si está usando el backport ThreeTen para Android y no puede usar el más nuevo Date.from(Instant instant)(que requiere un mínimo de API 26), puede usar:

ZonedDateTime zdt = ZonedDateTime.now();
Date date = new Date(zdt.toInstant().toEpochMilli());

o:

Date date = DateTimeUtils.toDate(zdt.toInstant());

Lea también el consejo en la respuesta de Basil Bourque

David Rawson
fuente
1
ThreeTen Backport (y ThreeTenABP) incluyen una DateTimeUtilsclase con los métodos de conversión, por lo que usaría Date date = DateTimeUtils.toDate (zdt.toInstant ()); `. No es de tan bajo nivel.
Ole VV
4

A continuación, se muestra un ejemplo de conversión de la hora actual del sistema a UTC. Implica formatear ZonedDateTime como una cadena y luego el objeto String se analizará en un objeto de fecha utilizando java.text DateFormat.

    ZonedDateTime zdt = ZonedDateTime.now(ZoneOffset.UTC);
    final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd HH:mm:ss");
    final DateFormat FORMATTER_YYYYMMDD_HH_MM_SS = new SimpleDateFormat("yyyyMMdd HH:mm:ss");
    String dateStr = zdt.format(DATETIME_FORMATTER);

    Date utcDate = null;
    try {
        utcDate = FORMATTER_YYYYMMDD_HH_MM_SS.parse(dateStr);
    }catch (ParseException ex){
        ex.printStackTrace();
    }
Asanka Siriwardena
fuente
1

Puede hacer esto usando las clases java.time integradas en Java 8 y posteriores.

ZonedDateTime temporal = ...
long epochSecond = temporal.getLong(INSTANT_SECONDS);
int nanoOfSecond = temporal.get(NANO_OF_SECOND);
Date date = new Date(epochSecond * 1000 + nanoOfSecond / 1000000);
Peter Lawrey
fuente
Lo siento, ¿de dónde vienen las constantes temporales y todas estas constantes?
Milen Kovachev
@MilenKovachev A ZonedDateTime es una instancia de Temporal
Peter Lawrey
Gracias, pero deberías haber mencionado que editaste tu respuesta para que mi comentario no parezca tonto.
Milen Kovachev
2
@MilenKovachev puedes eliminar un comentario, pero no es una pregunta tonta.
Peter Lawrey
1
No puedo entender por qué se rechazó esta respuesta. Instants rastrea segundos y nanosegundos. Dates rastrear milisegundos. Esta conversión de uno a otro es correcta.
scottb
1

Yo uso esto.

public class TimeTools {

    public static Date getTaipeiNowDate() {
        Instant now = Instant.now();
        ZoneId zoneId = ZoneId.of("Asia/Taipei");
        ZonedDateTime dateAndTimeInTai = ZonedDateTime.ofInstant(now, zoneId);
        try {
            return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(dateAndTimeInTai.toString().substring(0, 19).replace("T", " "));
        } catch (ParseException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return null;
    }
}

Porque Date.from(java.time.ZonedDateTime.ofInstant(now, zoneId).toInstant()); no es trabajo !!! Si ejecuta su aplicación en su computadora, no hay problema. Pero si ejecuta en cualquier región de AWS o Docker o GCP, generará un problema. Porque la computadora no es tu zona horaria en la nube. Debes configurar tu zona horaria correctamente en Code. Por ejemplo, Asia / Taipei. Luego se corregirá en AWS o Docker o GCP.

public class App {
    public static void main(String[] args) {
        Instant now = Instant.now();
        ZoneId zoneId = ZoneId.of("Australia/Sydney");
        ZonedDateTime dateAndTimeInLA = ZonedDateTime.ofInstant(now, zoneId);
        try {
            Date ans = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(dateAndTimeInLA.toString().substring(0, 19).replace("T", " "));
            System.out.println("ans="+ans);
        } catch (ParseException e) {
        }
        Date wrongAns = Date.from(java.time.ZonedDateTime.ofInstant(now, zoneId).toInstant());
        System.out.println("wrongAns="+wrongAns);
    }
}
Beehuang
fuente
1
Esta parece ser una de las formas más complejas en cualquiera de las 7 respuestas. ¿Tiene alguna ventaja? Yo pensaría que no. Realmente no hay necesidad de formatear y analizar.
Ole VV
Gracias preguntas. Porque Date wrongAns = Date.from (java.time.ZonedDateTime.ofInstant (ahora, zoneId) .toInstant ()) ;; ¡¡¡¡¡No es trabajo!!!!!
beehuang
Ejecuté el fragmento de segundos justo antes de las 17:08 en mi zona horaria y obtuve ans=Mon Jun 18 01:07:56 CEST 2018, que es incorrecto, y luego wrongAns=Sun Jun 17 17:07:56 CEST 2018, que es correcto.
Ole VV
No tengo ningún problema cuando ejecuto la aplicación en mi computadora. Pero cuando usa Docker, tiene un problema. Porque la zona horaria en la ventana acoplable no es su zona horaria en la computadora. Debes configurar tu zona horaria correctamente en Code. Por ejemplo, Asia / Taipei. Luego se corregirá en AWS, Docker, GCP o cualquier computadora.
beehuang
@ OleV.V. ¿Puedes entender? o alguna pregunta?
beehuang
1

La respuesta aceptada no funcionó para mí. La fecha devuelta es siempre la fecha local y no la fecha de la zona horaria original. Vivo en UTC + 2.

//This did not work for me
Date.from(java.time.ZonedDateTime.now().toInstant()); 

Se me han ocurrido dos formas alternativas de obtener la fecha correcta de un ZonedDateTime.

Digamos que tiene este ZonedDateTime para Hawái

LocalDateTime ldt = LocalDateTime.now();
ZonedDateTime zdt = ldt.atZone(ZoneId.of("US/Hawaii"); // UTC-10

o para UTC como se pidió originalmente

Instant zulu = Instant.now(); // GMT, UTC+0
ZonedDateTime zdt = zulu.atZone(ZoneId.of("UTC"));

Alternativa 1

Podemos usar java.sql.Timestamp. Es simple pero probablemente también hará mella en la integridad de su programación.

Date date1 = Timestamp.valueOf(zdt.toLocalDateTime());

Alternativa 2

Creamos la fecha a partir de milisegundos (respondida aquí anteriormente). Tenga en cuenta que ZoneOffset local es imprescindible.

ZoneOffset localOffset = ZoneOffset.systemDefault().getRules().getOffset(LocalDateTime.now());
long zonedMillis = 1000L * zdt.toLocalDateTime().toEpochSecond(localOffset) + zdt.toLocalDateTime().getNano() / 1000000L;
Date date2 = new Date(zonedMillis);
Avec
fuente
0

Para una aplicación de ventana acoplable como Beehuang comentó, debe establecer su zona horaria.

Alternativamente, puede usar withZoneSameLocal . Por ejemplo:

2014-07-01T00: 00 + 02: 00 [GMT + 02: 00] se convierte por

Date.from(zonedDateTime.withZoneSameLocal(ZoneId.systemDefault()).toInstant())

hasta el martes 01 de julio a las 00:00:00 CEST de 2014 y hasta

Date.from(zonedDateTime.toInstant())

al lun 30 jun 22:00:00 UTC 2014

dwe
fuente
-1

Si solo está interesado en ahora, simplemente use:

Date d = new Date();
Jacob Eckel
fuente
Estoy interesado en ahora pero UTC ahora.
Milen Kovachev
2
Sí, esa será la UTC ahora, la fecha no conoce nada mejor.
Jacob Eckel
3
No, no lo hará. Será Now en la zona horaria del sistema local. La fecha no almacena ninguna información de zona horaria, pero usa la hora actual del sistema e ignora la zona horaria actual del sistema, por lo que nunca la vuelve a convertir a UTC
David
2
@David Date mantiene la hora en relación con la época UTC, por lo que si toma una nueva Date () de un sistema en los EE. UU. Y otro objeto de una computadora en Japón, serán idénticos (mire el tiempo que ambos guardan internamente).
Jacob Eckel
1
@JacobEckel - Sí, pero si pones 9 a.m. en una fecha mientras tu tz es un tz de EE. UU. Y luego cambias a un JP tz y creas una nueva fecha con 9 a.m., el valor interno en Fecha será diferente. Tan pronto como agregue DST a la mezcla, no puede usar Date de manera confiable a menos que su aplicación SIEMPRE se ejecute en UTC específicamente, lo que podría cambiar por varias razones, la mayoría de las cuales giran en torno a un código incorrecto.
David