L’analisi di SimpleDateFormat perde il fuso orario

Codice:

SimpleDateFormat sdf = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss z"); sdf.setTimeZone(TimeZone.getTimeZone("GMT")); System.out.println(new Date()); try { String d = sdf.format(new Date()); System.out.println(d); System.out.println(sdf.parse(d)); } catch (Exception e) { e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. } 

Produzione:

 Thu Aug 08 17:26:32 GMT+08:00 2013 2013.08.08 09:26:32 GMT Thu Aug 08 17:26:32 GMT+08:00 2013 

Nota che format() formatta correttamente la Date su GMT, ma parse() perde i dettagli GMT. So che posso usare la substring() e aggirare questo problema, ma qual è la ragione alla base di questo fenomeno?

Ecco una domanda duplicata che non ha alcuna risposta.

Modifica: Permettimi di porre la domanda in un altro modo, qual è il modo per recuperare un object Date in modo che sia sempre in GMT?

Tutto ciò di cui avevo bisogno era questo:

 SimpleDateFormat sdf = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss"); sdf.setTimeZone(TimeZone.getTimeZone("GMT")); SimpleDateFormat sdfLocal = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss"); try { String d = sdf.format(new Date()); System.out.println(d); System.out.println(sdfLocal.parse(d)); } catch (Exception e) { e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. } 

Risultato: leggermente dubbio, ma voglio che la data sia coerente

 2013.08.08 11:01:08 Thu Aug 08 11:01:08 GMT+08:00 2013 

La soluzione dell’OP al suo problema, come dice, ha un risultato dubbio. Quel codice mostra ancora confusione sulle rappresentazioni del tempo. Per chiarire questa confusione e creare codice che non porti a tempi sbagliati, considera questa estensione di ciò che ha fatto:

 public static void _testDateFormatting() { SimpleDateFormat sdfGMT1 = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss"); sdfGMT1.setTimeZone(TimeZone.getTimeZone("GMT")); SimpleDateFormat sdfGMT2 = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss z"); sdfGMT2.setTimeZone(TimeZone.getTimeZone("GMT")); SimpleDateFormat sdfLocal1 = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss"); SimpleDateFormat sdfLocal2 = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss z"); try { Date d = new Date(); String s1 = d.toString(); String s2 = sdfLocal1.format(d); // Store s3 or s4 in database. String s3 = sdfGMT1.format(d); String s4 = sdfGMT2.format(d); // Retrieve s3 or s4 from database, using LOCAL sdf. String s5 = sdfLocal1.parse(s3).toString(); //EXCEPTION String s6 = sdfLocal2.parse(s3).toString(); String s7 = sdfLocal1.parse(s4).toString(); String s8 = sdfLocal2.parse(s4).toString(); // Retrieve s3 from database, using GMT sdf. // Note that this is the SAME sdf that created s3. Date d2 = sdfGMT1.parse(s3); String s9 = d2.toString(); String s10 = sdfGMT1.format(d2); String s11 = sdfLocal2.format(d2); } catch (Exception e) { e.printStackTrace(); } } 

esaminare i valori in un debugger:

 s1 "Mon Sep 07 06:11:53 EDT 2015" (id=831698113128) s2 "2015.09.07 06:11:53" (id=831698114048) s3 "2015.09.07 10:11:53" (id=831698114968) s4 "2015.09.07 10:11:53 GMT+00:00" (id=831698116112) s5 "Mon Sep 07 10:11:53 EDT 2015" (id=831698116944) s6 -- omitted, gave parse exception s7 "Mon Sep 07 10:11:53 EDT 2015" (id=831698118680) s8 "Mon Sep 07 06:11:53 EDT 2015" (id=831698119584) s9 "Mon Sep 07 06:11:53 EDT 2015" (id=831698120392) s10 "2015.09.07 10:11:53" (id=831698121312) s11 "2015.09.07 06:11:53 EDT" (id=831698122256) 

sdf2 e sdfLocal2 includono il fuso orario, quindi possiamo vedere cosa sta realmente accadendo. s1 e s2 sono alle 06:11:53 nella zona EDT. s3 e s4 sono alle 10:11:53 nella zona GMT – equivalenti all’orario originale EDT. Immaginiamo di salvare s3 o s4 in una base di dati, dove usiamo il GMT per coerenza, così possiamo avere orari da qualsiasi parte del mondo, senza memorizzare fusi orari diversi.

s5 analizza l’ora GMT, ma la tratta come ora locale. Quindi dice “10:11:53” – l’ora GMT – ma pensa che siano le 10:11:53 in ora locale . Non bene.

s7 analizza l’ora GMT, ma ignora il GMT nella stringa, quindi lo tratta sempre come ora locale.

s8 funziona, perché ora includiamo GMT nella stringa e il parser della zona locale lo usa per convertire da un fuso orario a un altro.

Supponiamo ora di non voler memorizzare la zona, di essere in grado di analizzare s3, ma di visualizzarla come ora locale. La risposta è analizzare utilizzando lo stesso fuso orario in cui è stato memorizzato , quindi usa lo stesso sdf di quello in cui è stato creato, sdfGMT1. s9, s10 e s11 sono tutte rappresentazioni dell’orario originale. Sono tutti “corretti”. Cioè, d2 == d1. Quindi è solo una questione su come lo si vuole VISUALIZZARE. Se si desidera visualizzare ciò che è memorizzato in DB – GMT time – allora è necessario formattarlo usando un GMT sdf. Questo è s10.

Quindi ecco la soluzione finale, se non si desidera memorizzare esplicitamente con “GMT” nella stringa, e si desidera visualizzare in formato GMT:

 public static void _testDateFormatting() { SimpleDateFormat sdfGMT1 = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss"); sdfGMT1.setTimeZone(TimeZone.getTimeZone("GMT")); try { Date d = new Date(); String s3 = sdfGMT1.format(d); // Store s3 in DB. // ... // Retrieve s3 from database, using GMT sdf. Date d2 = sdfGMT1.parse(s3); String s10 = sdfGMT1.format(d2); } catch (Exception e) { e.printStackTrace(); } } 

tl; dr

qual è il modo per recuperare un object Date in modo che sia sempre in GMT?

 Instant.now() 

Dettagli

Stai usando fastidiose vecchie lezioni di data-ora che sono ora soppiantate dalle classi java.time.

Instant = UTC

La class Instant rappresenta un momento sulla timeline in UTC con una risoluzione di nanosecondi (fino a nove (9) cifre di una frazione decimale).

 Instant instant = Instant.now() ; // Current moment in UTC. 

ISO 8601

Per scambiare questi dati come testo, utilizzare esclusivamente i formati ISO 8601 standard . Questi formati sono sensibilmente progettati per essere non ambigui, facili da elaborare a macchina e facili da leggere in molte culture da persone.

Le classi java.time utilizzano i formati standard per impostazione predefinita durante l’analisi e la generazione di stringhe.

 String output = instant.toString() ; 

2017-01-23T12: 34: 56.123456789Z

Fuso orario

Se si desidera vedere lo stesso momento presentato nell’ora di wall-wall di una particolare regione, applicare un ZoneId per ottenere un ZonedDateTime .

Specificare un nome di fuso orario appropriato nel formato di continent/region , ad esempio America/Montreal , Africa/Casablanca o Pacific/Auckland . Non utilizzare mai l’abbreviazione di 3-4 lettere come EST o IST quanto non sono veri fusi orari, non standardizzati e nemmeno unici (!).

 ZoneId z = ZoneId.of( "Asia/Singapore" ) ; ZonedDateTime zdt = instant.atZone( z ) ; // Same simultaneous moment, same point on the timeline. 

Vedi questo codice dal vivo su IdeOne.com .

Notare la differenza di otto ore, poiché il fuso orario di Asia/Singapore ha attualmente un offset da UTC di +08: 00. Stesso momento, diverso tempo di orologio a muro.

instant.toString (): 2017-01-23T12: 34: 56.123456789Z

zdt.toString (): 2017-01-23T20: 34: 56.123456789 + 08: 00 [Asia / Singapore]

Convertire

Evita la class java.util.Date precedente. Ma se devi, puoi convertire. Guarda ai nuovi metodi aggiunti alle vecchie classi.

 java.util.Date date = Date.fromInstant( instant ) ; 

… andando dall’altra parte …

 Instant instant = myJavaUtilDate.toInstant() ; 

Data-only

Per data solo, utilizzare LocalDate .

 LocalDate ld = zdt.toLocalDate() ; 

A proposito di java.time

Il framework java.time è integrato in Java 8 e versioni successive. Queste classi soppiantano le fastidiose vecchie lezioni di data e ora come java.util.Date , Calendar e SimpleDateFormat .

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

Per saperne di più, consultare l’ esercitazione Oracle . E cerca Stack Overflow per molti esempi e spiegazioni. La specifica è JSR 310 .

Dove ottenere le classi java.time?

  • Java SE 8 , Java SE 9 e versioni successive
    • Built-in.
    • Parte dell’API Java standard con un’implementazione in bundle.
    • Java 9 aggiunge alcune funzionalità e correzioni minori.
  • Java SE 6 e Java SE 7
    • Gran parte della funzionalità java.time è back-porting su Java 6 e 7 in ThreeTen-Backport .
  • android
    • Il progetto ThreeTenABP adatta ThreeTen-Backport (menzionato sopra) per Android in particolare.
    • Vedi Come usare ThreeTenABP ….

Il progetto ThreeTen-Extra estende java.time con classi aggiuntive. Questo progetto è un terreno di prova per possibili aggiunte future a java.time. Puoi trovare alcune classi utili come Interval , YearWeek , YearQuarter e altro .