web-dev-qa-db-fra.com

Conversion de chaîne conforme à ISO 8601 en Java.util.Date

J'essaye de convertir un ISO 8601 chaîne formatée en un Java.util.Date.

J'ai trouvé que le modèle yyyy-MM-dd'T'HH:mm:ssZ était conforme à ISO8601 s'il était utilisé avec un paramètre régional (exemple de comparaison).

Cependant, avec le Java.text.SimpleDateFormat, je ne peux pas convertir la chaîne correctement formatée 2010-01-01T12:00:00+01:00. Je dois d'abord le convertir en 2010-01-01T12:00:00+0100, sans les deux points.

Donc, la solution actuelle est

SimpleDateFormat ISO8601DATEFORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.GERMANY);
String date = "2010-01-01T12:00:00+01:00".replaceAll("\\+0([0-9]){1}\\:00", "+0$100");
System.out.println(ISO8601DATEFORMAT.parse(date));

ce qui n'est évidemment pas Nice. Est-ce que je manque quelque chose ou y a-t-il une meilleure solution?


Réponse

Grâce au commentaire de JuanZe, j'ai trouvé la magie Joda-Time , c'est aussi décrit ici .

Donc, la solution est

DateTimeFormatter parser2 = ISODateTimeFormat.dateTimeNoMillis();
String jtdate = "2010-01-01T12:00:00+01:00";
System.out.println(parser2.parseDateTime(jtdate));

Ou plus simplement, utilisez l'analyseur par défaut via le constructeur:

DateTime dt = new DateTime( "2010-01-01T12:00:00+01:00" ) ;

Pour moi, c'est Nice.

613
Ice09

Malheureusement, les formats de fuseau horaire disponibles pour SimpleDateFormat (Java 6 et versions antérieures) ne sont pas ISO 8601 . SimpleDateFormat comprend les chaînes de fuseau horaire telles que "GMT + 01: 00" ou "+0100", ce dernier étant conforme à RFC # 822 .

Même si Java 7 a pris en charge les descripteurs de fuseau horaire conformément à ISO 8601, SimpleDateFormat n'est toujours pas en mesure d'analyser correctement une chaîne de date complète, car il ne prend pas en charge les éléments facultatifs.

Reformater votre chaîne d'entrée à l'aide de regexp est certainement une possibilité, mais les règles de remplacement ne sont pas aussi simples que dans votre question:

  • Certains fuseaux horaires ne sont pas des heures complètes UTC , la chaîne ne se termine donc pas nécessairement par ": 00".
  • ISO8601 autorise uniquement l'inclusion du nombre d'heures dans le fuseau horaire. "+01" équivaut à "+01: 00".
  • ISO8601 autorise l'utilisation de "Z" pour indiquer l'UTC au lieu de "+00: 00".

La solution la plus simple consiste éventuellement à utiliser le convertisseur de type de données dans JAXB, car JAXB doit pouvoir analyser la chaîne de date ISO8601 conformément à la spécification de schéma XML. javax.xml.bind.DatatypeConverter.parseDateTime("2010-01-01T12:00:00Z") vous donnera un objet Calendar sur lequel vous pouvez simplement utiliser getTime (), si vous avez besoin d'un objet Date.

Vous pourriez probablement utiliser Joda-Time également, mais je ne sais pas pourquoi vous devriez vous en préoccuper.

447
jarnbjo

La façon dont cela est béni par Java 7 documentation :

DateFormat df1 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
String string1 = "2001-07-04T12:08:56.235-0700";
Date result1 = df1.parse(string1);

DateFormat df2 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
String string2 = "2001-07-04T12:08:56.235-07:00";
Date result2 = df2.parse(string2);

Vous pouvez trouver plus d'exemples dans la section . Exemples dans SimpleDateFormat javadoc .

202
Antonio

D'accord, cette question a déjà reçu une réponse, mais je vais laisser tomber ma réponse quand même. Cela pourrait aider quelqu'un.

Je cherchais une solution pour Android (API 7).

  • Joda était hors de question - il est énorme et souffre d'une initialisation lente. Cela paraissait également excessif à cette fin.
  • Les réponses impliquant javax.xml ne fonctionneront pas sur Android API 7.

Nous avons fini par implémenter cette classe simple. Il couvre uniquement la forme la plus courante des chaînes ISO 8601, mais cela devrait suffire dans certains cas (lorsque vous êtes certain que l'entrée sera dans ce format).

import Java.text.ParseException;
import Java.text.SimpleDateFormat;
import Java.util.Calendar;
import Java.util.Date;
import Java.util.GregorianCalendar;

/**
 * Helper class for handling a most common subset of ISO 8601 strings
 * (in the following format: "2008-03-01T13:00:00+01:00"). It supports
 * parsing the "Z" timezone, but many other less-used features are
 * missing.
 */
public final class ISO8601 {
    /** Transform Calendar to ISO 8601 string. */
    public static String fromCalendar(final Calendar calendar) {
        Date date = calendar.getTime();
        String formatted = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
            .format(date);
        return formatted.substring(0, 22) + ":" + formatted.substring(22);
    }

    /** Get current date and time formatted as ISO 8601 string. */
    public static String now() {
        return fromCalendar(GregorianCalendar.getInstance());
    }

    /** Transform ISO 8601 string to Calendar. */
    public static Calendar toCalendar(final String iso8601string)
            throws ParseException {
        Calendar calendar = GregorianCalendar.getInstance();
        String s = iso8601string.replace("Z", "+00:00");
        try {
            s = s.substring(0, 22) + s.substring(23);  // to get rid of the ":"
        } catch (IndexOutOfBoundsException e) {
            throw new ParseException("Invalid length", 0);
        }
        Date date = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ").parse(s);
        calendar.setTime(date);
        return calendar;
    }
}

Note de performance: J'instancie un nouveau SimpleDateFormat chaque fois afin d'éviter n bogue dans Android 2.1. Si vous êtes aussi étonné que moi, voyez cette énigme . Pour les autres moteurs Java, vous pouvez mettre l'instance en cache dans un champ statique privé (à l'aide de ThreadLocal, pour être thread-safe).

198
wrygiel

Java.time

L'API Java.time (intégrée à Java 8 et versions ultérieures) facilite un peu la tâche.

Si vous savez que l'entrée est dans UTC , telle que le Z (pour le zoulou) à la fin, le Instant classe peut analyser.

_Java.util.Date date = Date.from( Instant.parse( "2014-12-12T10:39:40Z" ));
_

Si votre entrée peut être une autre valeur décalée par rapport à UTC plutôt que UTC indiqué par Z (Zulu) à la fin, utilisez la classe OffsetDateTime à analyser.

_OffsetDateTime odt = OffsetDateTime.parse( "2010-01-01T12:00:00+01:00" );
_

Extrayez ensuite un Instant et convertissez-le en un Java.util.Date en appelant from .

_Instant instant = odt.toInstant();  // Instant is always in UTC.
Java.util.Date date = Java.util.Date.from( instant );
_
92
Adam

La bibliothèque Jackson-databind a aussi classe ISO8601DateFormat qui le fait (implémentation réelle dans ISO8601Utils .

ISO8601DateFormat df = new ISO8601DateFormat();
Date d = df.parse("2010-07-28T22:25:51Z");
67
david_p

tl; dr

_OffsetDateTime.parse ( "2010-01-01T12:00:00+01:00" )
_

Utiliser Java.time

Le nouveau package [Java.time] dans Java 8 et versions ultérieures a été inspiré par Joda-Time.

La classe OffsetDateTime représente un moment sur la timeline avec un offset-à-UTC mais pas un fuseau horaire.

_OffsetDateTime odt = OffsetDateTime.parse ( "2010-01-01T12:00:00+01:00" );
_

L'appel de toString génère une chaîne au format standard ISO 8601:

2010-01-01T12: 00 + 01: 00

Pour voir la même valeur à travers l'objectif de l'UTC, extrayez un Instant ou ajustez le décalage de _+01:00_ à _00:00_.

_Instant instant = odt.toInstant();  
_

…ou…

_OffsetDateTime odtUtc = odt.withOffsetSameInstant( ZoneOffset.UTC );
_

Ajustez dans un fuseau horaire si vous le souhaitez. Un fuseau horaire est un historique des valeurs offset-à-UTC d'une région, avec un ensemble de règles de traitement des anomalies telles que l'heure d'été (DST). Donc, appliquez un fuseau horaire plutôt qu'un simple décalage chaque fois que cela est possible.

_ZonedDateTime zonedDateTimeMontréal = odt.atZoneSameInstant( ZoneId.of( "America/Montreal" ) );
_

À propos de Java.time

Le cadre Java.time est intégré à Java 8 et versions ultérieures. Ces classes supplantent les anciennes classes gênantes legacy date-heure telles que Java.util.Date , Calendar , & SimpleDateFormat =.

Le projet Joda-Time , actuellement en mode maintenance , conseille la migration vers les classes Java.time .

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

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

Où obtenir les classes Java.time?

Le projet ThreeTen-Extra étend Java.time avec des classes supplémentaires. Ce projet est un terrain d’essai pour d’éventuels ajouts à Java.time. Vous pouvez trouver ici quelques classes utiles telles que Interval , YearWeek , YearQuarter , et plus .


44
Basil Bourque

Pour Java version 7

Vous pouvez suivre la documentation Oracle: http://docs.Oracle.com/javase/7/docs/api/Java/text/SimpleDateFormat.html

X - est utilisé pour le fuseau horaire ISO 8601

TimeZone tz = TimeZone.getTimeZone("UTC");
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX");
df.setTimeZone(tz);
String nowAsISO = df.format(new Date());

System.out.println(nowAsISO);

DateFormat df1 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX");
//nowAsISO = "2013-05-31T00:00:00Z";
Date finalResult = df1.parse(nowAsISO);

System.out.println(finalResult);
27
d.danailov

La solution DatatypeConverter ne fonctionne pas sur tous les ordinateurs virtuels. Ce qui suit fonctionne pour moi:

javax.xml.datatype.DatatypeFactory.newInstance().newXMLGregorianCalendar("2011-01-01Z").toGregorianCalendar().getTime()

J'ai constaté que joda ne fonctionnait pas immédiatement (particulièrement pour l'exemple que j'ai donné ci-dessus avec le fuseau horaire à une date qui devrait être valide)

20
James Scriven

Je pense que nous devrions utiliser

DateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'")

pour Date 2010-01-01T12:00:00Z

15
Toby

Un autre moyen très simple d’analyser les horodatages ISO8601 consiste à utiliser org.Apache.commons.lang.time.DateUtils:

import static org.junit.Assert.assertEquals;

import Java.text.ParseException;
import Java.util.Date;
import org.Apache.commons.lang.time.DateUtils;
import org.junit.Test;

public class ISO8601TimestampFormatTest {
  @Test
  public void parse() throws ParseException {
    Date date = DateUtils.parseDate("2010-01-01T12:00:00+01:00", new String[]{ "yyyy-MM-dd'T'HH:mm:ssZZ" });
    assertEquals("Fri Jan 01 12:00:00 CET 2010", date.toString());
  }
}
9
tmandry

La solution de contournement pour Java 7+ utilise SimpleDateFormat:
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX", Locale.US);

Ce code peut analyser le format ISO8601 comme:

  • 2017-05-17T06:01:43.785Z
  • 2017-05-13T02:58:21.391+01:00

Mais sur Java6, SimpleDateFormat ne comprend pas le caractère X et jettera
IllegalArgumentException: Unknown pattern character 'X'
Nous devons normaliser la date ISO8601 au format lisible dans Java 6 avec SimpleDateFormat.

public static Date iso8601Format(String formattedDate) throws ParseException {
    try {
        DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX", Locale.US);
        return df.parse(formattedDate);
    } catch (IllegalArgumentException ex) {
        // error happen in Java 6: Unknown pattern character 'X'
        if (formattedDate.endsWith("Z")) formattedDate = formattedDate.replace("Z", "+0000");
        else formattedDate = formattedDate.replaceAll("([+-]\\d\\d):(\\d\\d)\\s*$", "$1$2");
        DateFormat df1 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale.US);
        return df1.parse(formattedDate);
    }
}

Méthode ci-dessus pour remplacer [Z par +0000] ou [+01:00 par +0100] en cas d'erreur dans Java 6 (vous pouvez détecter Java version et remplacez try/catch par une instruction if).

6
Khang .NT

Java.time

Notez que dans Java 8, vous pouvez utiliser la classe Java.time.ZonedDateTime et sa méthode statique parse(CharSequence text).

6
Martin Rust

J'ai fait face au problème même et je l'ai résolu par le code suivant.

 public static Calendar getCalendarFromISO(String datestring) {
    Calendar calendar = Calendar.getInstance(TimeZone.getDefault(), Locale.getDefault()) ;
    SimpleDateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault());
    try {
        Date date = dateformat.parse(datestring);
        date.setHours(date.getHours() - 1);
        calendar.setTime(date);

        String test = dateformat.format(calendar.getTime());
        Log.e("TEST_TIME", test);

    } catch (ParseException e) {
        e.printStackTrace();
    }

    return calendar;
}

J'utilisais auparavant SimpleDateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale.getDefault());

Mais plus tard, j'ai trouvé que la cause principale de l'exception était le yyyy-MM-dd'T'HH:mm:ss.SSSZ,

Donc j'ai utilisé

SimpleDateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault());

Cela a bien fonctionné pour moi.

4
Abhay Kumar

Aussi, vous pouvez utiliser la classe suivante -

org.springframework.extensions.surf.util.ISO8601DateFormat


Date date = ISO8601DateFormat.parse("date in iso8601");

Lien vers le Java Doc - Hiérarchie du package org.springframework.extensions.surf.maven.plugin.util

4
Sergey Palyukh

Comme d'autres l'ont mentionné, Android ne dispose pas d'un moyen efficace de prendre en charge l'analyse/le formatage des dates ISO 8601 à l'aide de classes incluses dans le SDK. Comme j'ai écrit ce code plusieurs fois, j'ai finalement créé un Gist qui comprend une classe DateUtils qui prend en charge le formatage et l'analyse des dates ISO 8601 et RFC 1123. The Gist inclut également un scénario de test montrant ce qu’il prend en charge.

https://Gist.github.com/mraccola/702330625fad8eebe7d

3
Matt Accola

Apache Jackrabbit utilise le format ISO 8601 pour les dates persistantes, et une classe auxiliaire permet de les analyser:

org.Apache.jackrabbit.util.ISO8601

Livré avec jackrabbit-jcr-commons .

3

Java a une douzaine de façons différentes d’analyser une date/heure, comme le montrent les excellentes réponses fournies ici. Mais quelque peu étonnant, aucune des classes de temps de Java n'implémente pleinement l'ISO 8601!

Avec Java 8, je recommanderais:

ZonedDateTime zp = ZonedDateTime.parse(string);
Date date = Date.from(zp.toInstant());

Cela traitera les exemples à la fois en UTC et avec un décalage, comme "2017-09-13T10: 36: 40Z" ou "2017-09-13T10: 36: 40 + 01: 00". Cela conviendra pour la plupart des cas d'utilisation.

Mais il ne gérera pas les exemples tels que "2017-09-13T10: 36: 40 + 01", qui est une date/heure ISO 8601 valide.
Il ne gérera pas uniquement la date, par ex. "2017-09-13".

Si vous devez gérer cela, je suggérerais d'utiliser d'abord une expression régulière pour renifler la syntaxe.

Il existe une belle liste d'exemples ISO 8601 avec de nombreux cas délicats: https://www.myintervals.com/blog/2009/05/20/iso-8601-date-validation-that-doesnt-suck / Je ne connais aucune classe Java capable de les gérer toutes.

2
Daniel Winterstein

SimpleDateFormat pour Java 1.7 a un modèle cool pour le format ISO 8601.

Classe SimpleDateFormat

Voici ce que j'ai fait:

Date d = new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ss.SSSZ",
         Locale.ENGLISH).format(System.currentTimeMillis());
2
Eesha

Fais-le comme ça:

public static void main(String[] args) throws ParseException {

    String dateStr = "2016-10-19T14:15:36+08:00";
    Date date = javax.xml.bind.DatatypeConverter.parseDateTime(dateStr).getTime();

    System.out.println(date);

}

Voici la sortie:

Mer. 19 oct. 15:15:36 CST 2016

1
yinhaomin

Utilisez une chaîne comme LocalDate.parse(((String) data.get("d_iso8601")),DateTimeFormatter.ISO_DATE)

1
Stepan

Je suis surpris de constater que même une bibliothèque Java ne prend pas en charge tous les formats de date ISO 8601 conformément à https://en.wikipedia.org/wiki/ISO_8601 . Joda DateTime prenait en charge la plupart d’entre eux, mais pas tous et c’est pourquoi j’ai ajouté une logique personnalisée permettant de les gérer tous. Voici ma mise en œuvre.

import Java.text.ParseException;
import Java.util.Date;

import org.Apache.commons.lang3.time.DateUtils;
import org.joda.time.DateTime;

public class ISO8601DateUtils {
        
        /**
         * It parses all the date time formats from https://en.wikipedia.org/wiki/ISO_8601 and returns Joda DateTime.
         * Zoda DateTime does not support dates of format 20190531T160233Z, and hence added custom logic to handle this using SimpleDateFormat.
         * @param dateTimeString ISO 8601 date time string
         * @return
         */
        public static DateTime parse(String dateTimeString) {
                try {
                        return new DateTime( dateTimeString );
                } catch(Exception e) {
                        try {
                                Date dateTime = DateUtils.parseDate(dateTimeString, JODA_NOT_SUPPORTED_ISO_DATES);
                                return new DateTime(dateTime.getTime());
                        } catch (ParseException e1) {
                                throw new RuntimeException(String.format("Date %s could not be parsed to ISO date", dateTimeString));
                        }
                }
        }
  
        private static String[] JODA_NOT_SUPPORTED_ISO_DATES = new String[] {
                        // upto millis
                        "yyyyMMdd'T'HHmmssSSS'Z'",
                        "yyyyMMdd'T'HHmmssSSSZ",
                        "yyyyMMdd'T'HHmmssSSSXXX",
                        
                        "yyyy-MM-dd'T'HHmmssSSS'Z'",
                        "yyyy-MM-dd'T'HHmmssSSSZ",
                        "yyyy-MM-dd'T'HHmmssSSSXXX",
                        
                        // upto seconds
                        "yyyyMMdd'T'HHmmss'Z'",
                        "yyyyMMdd'T'HHmmssZ",
                        "yyyyMMdd'T'HHmmssXXX",
                        
                        "yyyy-MM-dd'T'HHmmss'Z'", 
                        "yyyy-MM-dd'T'HHmmssZ",
                        "yyyy-MM-dd'T'HHmmssXXX",
                        
                        // upto minutes
                        "yyyyMMdd'T'HHmm'Z'",
                        "yyyyMMdd'T'HHmmZ",
                        "yyyyMMdd'T'HHmmXXX",

                        "yyyy-MM-dd'T'HHmm'Z'",
                        "yyyy-MM-dd'T'HHmmZ",
                        "yyyy-MM-dd'T'HHmmXXX",
                        
                        //upto hours is already supported by Joda DateTime
        };
}
0
raok1997

J'avais un besoin similaire: je devais être capable d'analyser n'importe quelle date conforme à la norme ISO8601 sans connaître le format exact à l'avance, et je voulais une solution légère qui fonctionnerait également sur Android.

Lorsque j'ai recherché mes besoins dans Google, je suis tombé sur cette question et j'ai constaté qu'AFAIU, aucune réponse ne correspondait parfaitement à mes besoins. Alors j'ai développé jISO8601 et l'ai poussé sur maven central.

Ajoutez juste en vous pom.xml:

<dependency>
  <groupId>fr.turri</groupId>
  <artifactId>jISO8601</artifactId>
  <version>0.2</version>
</dependency>

et puis vous êtes prêt à partir:

import fr.turri.jiso8601.*;
...
Calendar cal = Iso8601Deserializer.toCalendar("1985-03-04");
Date date = Iso8601Deserializer.toDate("1985-03-04T12:34:56Z");

Espère que ça aide.

0
gturri

Pour formater simplement une date comme celle-ci, les éléments suivants ont fonctionné pour moi dans une application basée sur Java 6. Il y a une DateFormat classe JacksonThymeleafISO8601DateFormat dans le projet thymeleaf qui insère le colon manquant:

https://github.com/thymeleaf/thymeleaf/blob/40d27f44df7b52eda47d1bc6f1b3012add6098b3/src/main/Java/org/thymeleaf/standard/serializer/StandardJavaScriptSerializer.Java

Je l'ai utilisé pour la compatibilité du format de date ECMAScript.

0
ekip

Un petit test qui montre comment analyser une date dans ISO8601 et que LocalDateTime ne gère pas les fichiers DST.

 @Test
    public void shouldHandleDaylightSavingTimes() throws ParseException {

        //ISO8601 UTC date format
        SimpleDateFormat utcFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");

        // 1 hour of difference between 2 dates in UTC happening at the Daylight Saving Time
        Date d1 = utcFormat.parse("2019-10-27T00:30:00.000Z");
        Date d2 = utcFormat.parse("2019-10-27T01:30:00.000Z");

        //Date 2 is before date 2
        Assert.assertTrue(d1.getTime() < d2.getTime());
        // And there is 1 hour difference between the 2 dates
        Assert.assertEquals(1000*60*60, d2.getTime() - d1.getTime());

        //Print the dates in local time
        SimpleDateFormat localFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm z Z", Locale.forLanguageTag("fr_CH"));
        localFormat.setTimeZone(TimeZone.getTimeZone("Europe/Zurich"));

        //Both dates are at 02h30 local time (because of DST), but one is CEST +0200 and the other CET +0100 (clock goes backwards)
        Assert.assertEquals("2019-10-27 02:30 CEST +0200", localFormat.format(d1));
        Assert.assertEquals("2019-10-27 02:30 CET +0100", localFormat.format(d2));

        //Small test that shows that LocalDateTime does not handle DST (and should not be used for storing timeseries data)
        LocalDateTime ld1 = LocalDateTime.ofInstant(d1.toInstant(), ZoneId.of("Europe/Zurich"));
        LocalDateTime ld2 = LocalDateTime.ofInstant(d2.toInstant(), ZoneId.of("Europe/Zurich"));

        //Note that a localdatetime does not handle DST, therefore the 2 dates are the same
        Assert.assertEquals(ld1, ld2);

        //They both have the following local values
        Assert.assertEquals(2019, ld1.getYear());
        Assert.assertEquals(27, ld1.getDayOfMonth());
        Assert.assertEquals(10, ld1.getMonthValue());
        Assert.assertEquals(2, ld1.getHour());
        Assert.assertEquals(30, ld1.getMinute());
        Assert.assertEquals(0, ld1.getSecond());

    }
0
ddtxra