Comment convertir ZonedDateTime en date?

103

J'essaie de définir une heure de date indépendante du serveur dans ma base de données et je pense que la meilleure pratique pour le faire est de définir une heure UTC. Mon serveur de base de données est Cassandra et le pilote de base de données pour Java ne comprend que le type de date.

Donc, en supposant que dans mon code j'utilise le nouveau Java 8 ZonedDateTime pour obtenir l'UTC now ( ZonedDateTime.now(ZoneOffset.UTC)), comment puis-je convertir cette instance de ZonedDateTime en classe Date "héritée"?

Milen Kovachev
la source
Il existe deux classes "Date" intégrées à Java, java.util.Dateet java.sql.Date.
Basil Bourque

Réponses:

163

Vous pouvez convertir ZonedDateTime en un instant, que vous pouvez utiliser directement avec Date.

Date.from(java.time.ZonedDateTime.now().toInstant());
Slim Soltani Dridi
la source
27
Non, ce sera la date actuelle sur votre système de zone par défaut.
Slim Soltani Dridi
6
@MilenKovachev Votre question n'a pas de sens - une date n'a pas de fuseau horaire - elle ne représente qu'un instant dans le temps.
assylias
1
@assylias En fait, votre déclaration n'a pas de sens. Le format dans lequel l'heure est stockée implique un fuseau horaire. La date est basée sur UTC, malheureusement, Java fait des choses stupides et ne la traite pas comme telle, et en plus, elle considère l'heure comme étant la TZ locale au lieu de UTC. La façon dont Data, LocalDateTime, ZonedDateTime stockent les données implique un fuseau horaire. La façon dont ils sont utilisés est comme si les ZT n'existaient pas, ce qui est tout simplement faux. java.util.Date a une TZ de la JVM par défaut, implicitement. Le fait que les gens le traitent comme quelque chose de différent (y compris la documentation Java pour cela!) Est juste de mauvaises personnes.
David
5
@David euh non - a Dateest un nombre de millisecondes depuis l'époque - donc il est lié à UTC. Si vous l' imprimez , le fuseau horaire par défaut sera utilisé, mais la classe Date n'a pas connaissance du fuseau horaire de l'utilisateur ... Voir par exemple la section "mapping" dans docs.oracle.com/javase/tutorial/datetime/iso/legacy .html Et LocalDateTime est explicitement sans référence à un fuseau horaire - ce qui peut être considéré comme déroutant ...
assylias
1
Votre réponse implique inutilement ZonedDateTime. La java.time.Instantclasse est un remplacement direct pour java.util.Date, les deux représentant un moment en UTC mais Instantutilise une résolution plus fine de nanosecondes au lieu de millisecondes. Date.from( Instant.now() )aurait dû être votre solution. Ou d'ailleurs, ce new Date()qui a le même effet, capturer le moment actuel 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.
)

Bien qu'il n'y ait aucun intérêt dans ce code ci-dessus. Les deux java.util.Dateet Instantreprésentent un moment en UTC, toujours en UTC. Le code ci-dessus a le même effet que:

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

Aucun avantage ici à utiliser ZonedDateTime. Si vous avez déjà un ZonedDateTime, ajustez à UTC en extrayant un fichier 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.
)

Autre réponse correcte

La réponse de ssoltanid répond correctement à votre question spécifique, comment convertir un objet java.time ( ZonedDateTime) de la nouvelle école en un objet de la vieille école java.util.Date. Extrayez le Instantde ZonedDateTime et passez à java.util.Date.from().

Perte de données

Notez que vous subir une perte de données , comme des Instantpistes nanosecondes depuis l' époque en java.util.Datepistes millisecondes depuis l' époque.

diagramme comparant les résolutions de la milliseconde, de la microseconde et de la nanoseconde

Votre question et vos commentaires soulèvent d'autres problèmes.

Gardez les serveurs en UTC

Vos serveurs doivent avoir leur système d'exploitation hôte défini sur UTC comme meilleure pratique en général. La JVM prend ce paramètre du système d'exploitation hôte comme son fuseau horaire par défaut, dans les implémentations Java dont je suis conscient.

Spécifiez le fuseau horaire

Mais vous ne devez jamais vous fier au fuseau horaire par défaut actuel de la JVM. Plutôt que de choisir le paramètre d'hôte, un indicateur passé lors du lancement d'une JVM peut définir un autre fuseau horaire. Pire encore: n'importe quel code dans n'importe quel thread de n'importe quelle application à tout moment peut faire un appel java.util.TimeZone::setDefaultpour changer cette valeur par défaut au moment de l'exécution!

TimestampType de Cassandra

Toute base de données et pilote décents doivent automatiquement gérer l'ajustement d'une date-heure passée à UTC pour le stockage. Je n'utilise pas Cassandra, mais il semble avoir un support rudimentaire pour la date-heure. La documentation indique que son Timestamptype est un décompte de millisecondes de la même époque (premier moment de 1970 en UTC).

ISO 8601

De plus, Cassandra accepte les entrées de chaîne dans les formats standard ISO 8601 . Heureusement, java.time utilise les formats ISO 8601 par défaut pour analyser / générer des chaînes. L' implémentation de Instantla classe toStringfera l'affaire.

Précision: Milliseconde vs Nanosecord

Mais d'abord, nous devons réduire la précision nanoseconde de ZonedDateTime à quelques millisecondes. Une façon consiste à créer un nouvel instantané en millisecondes. Heureusement, java.time propose des méthodes pratiques pour convertir vers et depuis des millisecondes.

Exemple de code

Voici un exemple de code dans 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

Ou selon ce document de pilote Java Cassandra , vous pouvez passer une java.util.Dateinstance (à ne pas confondre avec java.sqlDate). Vous pouvez donc créer un juDate à partir de cela instantTruncatedToMillisecondsdans le code ci-dessus.

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

Si vous faites cela souvent, vous pouvez créer une doublure unique.

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

Mais il serait plus judicieux de créer une petite méthode utilitaire.

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

Notez la différence dans tout ce code que dans la question. Le code de la question essayait d'ajuster le fuseau horaire de l'instance ZonedDateTime à UTC. Mais ce n'est pas nécessaire. Conceptuellement:

ZonedDateTime = Instant + ZoneId

Nous extrayons simplement la partie Instant, qui est déjà en UTC (essentiellement en UTC, lisez la documentation de la classe pour plus de détails).


Tableau des types date-heure en Java, à la fois modernes et hérités


À propos de java.time

Le framework java.time est intégré à Java 8 et versions ultérieures. Ces classes supplantent les anciens gênants hérités des classes date-heure tels que java.util.Date, Calendar, et SimpleDateFormat.

Le projet Joda-Time , désormais en mode maintenance , conseille la migration vers les classes java.time .

Pour en savoir plus, consultez le didacticiel Oracle . Et recherchez Stack Overflow pour de nombreux exemples et explications. La spécification est JSR 310 .

Vous pouvez échanger des objets java.time directement avec votre base de données. Utilisez un pilote JDBC compatible avec JDBC 4.2 ou version ultérieure. Pas besoin de chaînes, pas besoin de java.sql.*classes.

Où obtenir les classes java.time?

Tableau de la bibliothèque java.time à utiliser avec quelle version de Java ou Android

Le projet ThreeTen-Extra étend java.time avec des classes supplémentaires. Ce projet est un terrain d'essai pour d'éventuels futurs ajouts à java.time. Vous trouverez peut - être des classes utiles ici, comme Interval, YearWeek, YearQuarteret plus .

Basil Bourque
la source
Merveilleuse explication, bien détaillée. Merci beaucoup!
Gianmarco F.
4

Voici un exemple de conversion de l'heure système actuelle en UTC. Cela implique le formatage de ZonedDateTime en tant que String, puis l'objet String sera analysé en un objet date à l'aide de 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
la source
4

Si vous utilisez le backport ThreeTen pour Android et que vous ne pouvez pas utiliser le plus récent Date.from(Instant instant)(qui nécessite au minimum l'API 26), vous pouvez utiliser:

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

ou:

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

Veuillez également lire les conseils dans la réponse de Basil Bourque

David Rawson
la source
1
Le ThreeTen Backport (et ThreeTenABP) incluent une DateTimeUtilsclasse avec les méthodes de conversion, donc j'utiliserais Date date = DateTimeUtils.toDate (zdt.toInstant ()); `. Ce n'est pas si bas niveau.
Ole VV
1

Vous pouvez le faire en utilisant les classes java.time intégrées à Java 8 et versions ultérieures.

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
la source
Désolé, d'où viennent le temporel et toutes ces constantes?
Milen Kovachev
@MilenKovachev A ZonedDateTime est une instance de Temporal
Peter Lawrey
Merci, mais vous auriez dû mentionner que vous avez modifié votre réponse pour que mon commentaire ne semble pas idiot.
Milen Kovachev
2
@MilenKovachev vous pouvez supprimer un commentaire, mais ce n'est pas une question idiote.
Peter Lawrey
1
Je ne peux pas comprendre pourquoi cette réponse a été rejetée. Instants piste secondes et nanosecondes. Dates piste en millisecondes. Cette conversion de l'un à l'autre est correcte.
scottb
1

J'utilise ça.

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

Parce que Date.from(java.time.ZonedDateTime.ofInstant(now, zoneId).toInstant()); ça ne marche pas !!! Si vous exécutez votre application sur votre ordinateur, ce n'est pas un problème. Mais si vous exécutez dans une région d'AWS, de Docker ou de GCP, cela générera un problème. Parce que l'ordinateur n'est pas votre fuseau horaire sur le cloud. Vous devez définir votre fuseau horaire correctement dans Code. Par exemple, Asie / Taipei. Ensuite, il corrigera dans AWS ou Docker ou 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
la source
1
Cela semble être l'un des moyens les plus complexes dans l'une des 7 réponses. Y a-t-il des avantages? Je pense que non. Il n'est vraiment pas nécessaire de passer par le formatage et l'analyse.
Ole VV
Merci vous demandez. Parce que Date falseAns = Date.from (java.time.ZonedDateTime.ofInstant (now, zoneId) .toInstant ()) ;; Ça ne marche pas !!!!!
beehuang
J'ai exécuté votre extrait de secondes juste avant 17h08 dans mon fuseau horaire et j'ai obtenu ans=Mon Jun 18 01:07:56 CEST 2018, ce qui est incorrect, puis wrongAns=Sun Jun 17 17:07:56 CEST 2018, ce qui est correct.
Ole VV
Je n'ai aucun problème lors de l'exécution de l'application sur mon ordinateur. Mais lorsque vous utilisez docker, cela pose un problème. Parce que le fuseau horaire dans le docker n'est pas votre fuseau horaire dans l'ordinateur. Vous devez définir votre fuseau horaire correctement dans Code. Par exemple, Asie / Taipei. Ensuite, il corrigera dans AWS, Docker, GCP ou n'importe quel ordinateur.
beehuang
@ OleV.V. Pouvez-vous comprendre? ou des questions?
beehuang
1

La réponse acceptée n'a pas fonctionné pour moi. La date renvoyée est toujours la date locale et non la date du fuseau horaire d'origine. Je vis à UTC + 2.

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

J'ai proposé deux méthodes alternatives pour obtenir la date correcte à partir d'un ZonedDateTime.

Dites que vous avez ce ZonedDateTime pour Hawaii

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

ou pour UTC comme demandé à l'origine

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

Alternative 1

Nous pouvons utiliser java.sql.Timestamp. C'est simple mais cela affectera probablement aussi l'intégrité de votre programmation

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

Alternative 2

Nous créons la date à partir de millis (réponse ici plus tôt). Notez que ZoneOffset locale est un must.

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
la source
0

Pour une application docker comme beehuang commentée, vous devez définir votre fuseau horaire.

Vous pouvez également utiliser withZoneSameLocal . Par exemple:

2014-07-01T00: 00 + 02: 00 [GMT + 02: 00] est converti par

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

au Tue Jul 01 00:00:00 CEST 2014 et par

Date.from(zonedDateTime.toInstant())

au lun.30 juin 22:00:00 UTC 2014

dwe
la source
-1

Si vous n'êtes intéressé que par maintenant, utilisez simplement:

Date d = new Date();
Jacob Eckel
la source
Je suis intéressé maintenant mais UTC maintenant.
Milen Kovachev
2
Oui, ce sera l'heure UTC maintenant, la date ne sait pas mieux.
Jacob Eckel
3
Non, ce ne sera pas le cas. Ce sera maintenant dans le fuseau horaire du système local. La date ne stocke aucune information sur le fuseau horaire, mais elle utilise l'heure système actuelle et ignore le fuseau horaire système actuel, donc il ne la convertit jamais en UTC
David
2
@David Date garde l'heure par rapport à l'époque UTC, donc si vous prenez une nouvelle Date () d'un système aux États-Unis et un autre objet d'un ordinateur au Japon, elles seront identiques (regardez la durée de conservation des deux en interne).
Jacob Eckel
1
@JacobEckel - Oui, mais si vous mettez 9h dans une date alors que votre tz est un tz américain, puis changez en tz JP et créez une nouvelle date à 9h, la valeur interne dans Date sera différente. Dès que vous lancez DST dans le mix, vous ne pouvez pas utiliser de manière fiable Date à moins que votre application ne s'exécute TOUJOURS spécifiquement en UTC, ce qui pourrait changer pour un certain nombre de raisons dont la plupart tournent autour d'un mauvais code.
David