Comment définir le fuseau horaire d'un java.util.Date?

225

J'ai analysé un à java.util.Datepartir d'un Stringmais il définit le fuseau horaire local comme fuseau horaire de l' dateobjet.

Le fuseau horaire n'est pas spécifié dans celui à Stringpartir duquel Dateest analysé. Je veux définir un fuseau horaire spécifique de l' dateobjet.

Comment puis je faire ça?

Yatendra Goel
la source
8
Bien que ce ne soit pas vraiment une réponse à votre question, j'ai utilisé Joda Time après l'avoir vu mentionné plusieurs fois ici. Cela me semble plus rationnel que les API standard et peut faire ce genre de chose assez facilement.
clstrfsck
2
@msandiford De nos jours, utilisez les classes java.time plutôt que Joda-Time. Le projet Joda-Time est maintenant en mode maintenance , l'équipe conseillant la migration vers les classes java.time . Voir Tutoriel par Oracle .
Basil Bourque

Réponses:

315

Utilisez DateFormat. Par exemple,

SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
isoFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
Date date = isoFormat.parse("2010-05-23T09:01:02");
Codeur ZZ
la source
6
si la date est créée à partir de la classe Calendar, vous pouvez définir le fuseau horaire pour Calendar.
lwpro2
19
@ lwpro2 cette déclaration est trompeuse; Vous pouvez définir le fuseau horaire d'un objet Calendar, mais en obtenir un objet Date à l'aide de la méthode getTime () renverra un objet Date avec le fuseau horaire de l'ordinateur hôte.
BrDaHa
J'ai du mal à analyser le 20/02/15 14:44. Quelqu'un peut-il aider
MS
@BrDaHa est correct. Vous devrez le faire TimeZone.setDefault()avant d'appeler getTime()pour que le nouvel objet date soit dans le fuseau horaire de votre choix. Dans JDK 1.8, les Calendar.getTime()appels return new Date(getTimeInMillis());.
jpllosa
182

Sachez que les java.util.Dateobjets ne contiennent aucune information de fuseau horaire par eux-mêmes - vous ne pouvez pas définir le fuseau horaire sur un Dateobjet. La seule chose qu'un Dateobjet contient est un nombre de millisecondes depuis "l'époque" - 1er janvier 1970, 00:00:00 UTC.

Comme le montre le codeur ZZ, vous définissez le fuseau horaire sur l' DateFormatobjet, pour lui indiquer dans quel fuseau horaire vous souhaitez afficher la date et l'heure.

Jesper
la source
77
Après une recherche sur Google, une expérimentation et une exploration, j'ai réalisé que c'est un ajout précis et utile à la réponse - et mérite d'être souligné: la date ne contient que la valeur en millisecondes . Si vous regardez la source, il y a à peu près juste un longchamp appelé fastTime. Date.toString()utilise en fait un Calendarpour interpréter cette milliseconde fois. L'impression d'un Datefichier semble donc avoir un fuseau horaire (par défaut), ce qui conduit à des questions compréhensibles sur la façon de définir ce fuseau horaire.
David Carboni
3
il y a des informations de fuseau horaire à l'intérieur des objets Date. Mais c'est peut-être vrai, vous ne pouvez pas le changer.
lwpro2
3
@ Iwpro2, Jesper affirme (et je suis d'accord) que l'objet Date ne stocke pas de fuseau horaire. Si vous prétendez que le fuseau horaire est stocké dans java.util.Date, veuillez fournir une référence.
Jim
6
@Jim, il s'agit du code source de java.util.Date. Il contient fastTime comme epoque unix ainsi que le cdate BaseCalendar.Date qui est utilisé en faveur de fastTime, s'il est défini. Celui-ci contient des informations de fuseau horaire. Je le comprends pour qu'une instance de Date puisse contenir des informations de fuseau horaire, mais ce n'est peut-être pas le cas.
eis
2
@Jim Je n'ai pas dit que vous pouviez le définir, je disais qu'il contient cette information. Je ne vois aucun moyen de le définir comme utilisateur non plus. Je pense que OP est correct en ce qu'il semble toujours être le fuseau horaire par défaut utilisateur / système.
eis
95

tl; dr

… Analysé… à partir d'une chaîne… le fuseau horaire n'est pas spécifié… je veux définir un fuseau horaire spécifique

LocalDateTime.parse( "2018-01-23T01:23:45.123456789" )  // Parse string, lacking an offset-from-UTC and lacking a time zone, as a `LocalDateTime`.
    .atZone( ZoneId.of( "Africa/Tunis" ) )              // Assign the time zone for which you are certain this date-time was intended. Instantiates a `ZonedDateTime` object.

Aucun fuseau horaire dans juDate

Comme les autres bonnes réponses l'ont indiqué, un java.util.Date n'a pas de fuseau horaire . Il représente UTC / GMT (pas de décalage de fuseau horaire). Très déroutant car sa toStringméthode applique le fuseau horaire par défaut de la JVM lors de la génération d'une représentation String.

Évitez juDate

Pour cela et pour bien d'autres raisons, vous devez éviter d'utiliser le java.util.Date & .Calendar & java.text.SimpleDateFormat intégré. Ils sont notoirement gênants.

Utilisez plutôt le package java.time fourni avec Java 8 .

java.time

Les classes java.time peuvent représenter un moment sur la timeline de trois manières:

  • UTC ( Instant)
  • Avec un décalage ( OffsetDateTimeavec ZoneOffset)
  • Avec un fuseau horaire ( ZonedDateTimeavec ZoneId)

Instant

Dans java.time , le bloc de construction de base est Instant, un moment sur la ligne de temps en UTC. Utilisez des Instantobjets pour une grande partie de votre logique métier.

Instant instant = Instant.now();

OffsetDateTime

Appliquez un décalage par rapport à l'UTC pour ajuster le temps d'horloge murale d' une localité .

Appliquez un ZoneOffsetpour obtenir un OffsetDateTime.

ZoneOffset zoneOffset = ZoneOffset.of( "-04:00" );
OffsetDateTime odt = OffsetDateTime.ofInstant( instant , zoneOffset );

ZonedDateTime

Il vaut mieux appliquer un fuseau horaire , un décalage ainsi que les règles de gestion des anomalies telles que l' heure d'été (DST) .

Appliquez un ZoneIdà un Instantpour obtenir un ZonedDateTime. Spécifiez toujours un nom de fuseau horaire correct . N'utilisez jamais 3-4 abréviations telles que ESTou ISTqui ne sont ni uniques ni standardisées.

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

LocalDateTime

Si la chaîne d'entrée n'avait aucun indicateur de décalage ou de zone, analysez comme a LocalDateTime.

Si vous êtes certain du fuseau horaire prévu, attribuez a ZoneIdpour produire a ZonedDateTime. Voir l'exemple de code ci-dessus dans la section tl; dr en haut.

Chaînes formatées

Appelez la toStringméthode sur l'une de ces trois classes pour générer une chaîne représentant la valeur date-heure au format ISO 8601 standard. La ZonedDateTimeclasse étend le format standard en ajoutant le nom du fuseau horaire entre parenthèses.

String outputInstant = instant.toString(); // Ex: 2011-12-03T10:15:30Z
String outputOdt = odt.toString(); // Ex: 2007-12-03T10:15:30+01:00
String outputZdt = zdt.toString(); // Ex: 2007-12-03T10:15:30+01:00[Europe/Paris]

Pour d'autres formats, utilisez la DateTimeFormatterclasse. Il est généralement préférable de laisser cette classe générer des formats localisés en utilisant le langage humain et les normes culturelles attendus de l'utilisateur. Ou vous pouvez spécifier un format particulier.


Tableau de tous les types de 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.*cours.

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 de futurs ajouts possibles à java.time. Vous trouverez peut - être des classes utiles ici, comme Interval, YearWeek, YearQuarteret plus .


Joda-Time

Bien que Joda-Time soit toujours activement maintenu, ses fabricants nous ont dit de migrer vers java.time dès que cela serait pratique. Je laisse cette section intacte comme référence, mais je suggère d'utiliser la java.timesection ci-dessus à la place.

Dans Joda-Time , un objet date-heure ( DateTime) connaît vraiment son fuseau horaire attribué. Cela signifie un décalage par rapport à l'UTC et aux règles et à l'historique de l'heure d'été (DST) de ce fuseau horaire et à d'autres anomalies de ce type.

String input = "2014-01-02T03:04:05";
DateTimeZone timeZone = DateTimeZone.forID( "Asia/Kolkata" );
DateTime dateTimeIndia = new DateTime( input, timeZone );
DateTime dateTimeUtcGmt = dateTimeIndia.withZone( DateTimeZone.UTC );

Appelez la toStringméthode pour générer une chaîne au format ISO 8601 .

String output = dateTimeIndia.toString();

Joda-Time offre également de riches capacités pour générer toutes sortes d'autres formats de chaîne.

Si nécessaire, vous pouvez convertir Joda-Time DateTime en java.util.Date.

Java.util.Date date = dateTimeIndia.toDate();

Recherchez StackOverflow pour "date Joda" pour trouver de nombreux autres exemples, certains assez détaillés.


En fait, il y a un fuseau horaire intégré dans un java.util.Date, utilisé pour certaines fonctions internes (voir les commentaires sur cette réponse). Mais ce fuseau horaire interne n'est pas exposé en tant que propriété et ne peut pas être défini. Ce fuseau horaire interne n'est pas celui utilisé par la toStringméthode pour générer une représentation sous forme de chaîne de la valeur date-heure; au lieu de cela, le fuseau horaire par défaut actuel de la JVM est appliqué à la volée. Donc, en sténographie, nous disons souvent «juDate n'a pas de fuseau horaire». Déroutant? Oui. Encore une autre raison d'éviter ces vieilles classes fatiguées.

Basil Bourque
la source
3
"Aucun fuseau horaire dans juDate" est faux. JuDate contient une information de fuseau horaire dans sa BaseCalendar.Date cdatepropriété si elle est définie. Jetez un œil au code source ici . Vous ne pouvez pas définir le fuseau horaire d'un objet juDate, sauf en modifiant le fuseau horaire par défaut de la JVM en appelant TimeZone.setDefault(TimeZone.getTimeZone("NEW_TIME_ZONE"));. Ainsi, il y a un décalage de fuseau horaire et vous pouvez obtenir le décalage en appelant la méthode obsolète juDate.getTimezoneOffset ()
Thai Bui
3
@blquythai Correct, vous avez fait vos devoirs. Tout comme moi, ayant déjà vu ce code source. Il y a un fuseau horaire enfoui là-dedans. Mais à toutes fins pratiques, ce fuseau horaire est ignoré. Un java.util.Date fonctionne sans aucun fuseau horaire, en fait étant en UTC, tout en ignorant ce fuseau horaire enterré. À l'exception de la toStringméthode qui applique le fuseau horaire par défaut actuel de la JVM; ignorant à nouveau le fuseau horaire enfoui. Donc, par souci de concision, nous disons qu'un java.util.Date n'a pas de fuseau horaire. Comme l'art , c'est un mensonge qui dit la vérité.
Basil Bourque
@blquythai Quant à l'appel TimeZone.setDefault, vous ne définissez pas le fuseau horaire de l'objet java.util.Date - l'objet Date ignore toujours son fuseau horaire enfoui, agissant efficacement en UTC. Vous affecteriez la toStringméthode de Date . La définition de la valeur par défaut modifie le fuseau horaire par défaut de la JVM qui est généralement défini sur le fuseau horaire du système d'exploitation hôte. Cet appel n'est pas recommandé car il affecte tout le code dans tous les threads de toutes les applications exécutées dans cette machine virtuelle Java, et le fait à la volée pendant leur exécution. Étant grossier et dangereux, cet appel ne devrait être considéré qu'en dernier recours.
Basil Bourque
5
Cette zone de temps est très souvent (utilisé equals, hashcode, getTime..) Si vous jetez un oeil à la equalsméthode, il appelle getTime()qui appelle getTimeImpl(), qui appelle normalize()si la cdatepropriété est pas normalisée. Dans la normalize()méthode, la dernière condition if recalcule les millisecondes depuis le 1/1/70 en fonction des informations de fuseau horaire stockées si le fuseau horaire de cdateest différent du fuseau horaire de l'environnement JVM actuel sur lequel il s'exécute. (Jetez un oeil à sun.util.calendar.AbstractCalendar getCalendarDate(long millis, CalendarDate date))
Thai Bui
2
@MichalM Selon vos conseils, il y a quelque temps, j'ai ajouté une postface sur la zone intégrée.
Basil Bourque
75

Vous pouvez également définir le fuseau horaire au niveau JVM

Date date1 = new Date();
System.out.println(date1);

TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
// or pass in a command line arg: -Duser.timezone="UTC"

Date date2 = new Date();
System.out.println(date2);

production:

Thu Sep 05 10:11:12 EDT 2013
Thu Sep 05 14:11:12 UTC 2013
starmer
la source
Cela m'a aidé. définition de timeZone dans SDF n'a fait aucune différence
vishnu viswanath
Je pense que c'est plus fiable. (+1)
foobar
23
Attention: les appels TimeZone.setDefaultsont assez drastiques, car ils affectent l'ensemble de la JVM, affectent tous les autres objets et threads. Voir cette réponse pour plus de détails, y compris encore plus de complications si vous utilisez un SecurityManager. Ajouter encore plus de complication: ce comportement a changé dans différentes versions de Java, comme indiqué dans cette question .
Basil Bourque
Cela définit un fuseau horaire commun à tous les threads générés après cette déclaration, non?
Jaydev
C'est une meilleure réponse à la question que celle acceptée.
francogrex
10

Si vous ne devez travailler qu'avec des classes JDK standard, vous pouvez utiliser ceci:

/**
 * Converts the given <code>date</code> from the <code>fromTimeZone</code> to the
 * <code>toTimeZone</code>.  Since java.util.Date has does not really store time zome
 * information, this actually converts the date to the date that it would be in the
 * other time zone.
 * @param date
 * @param fromTimeZone
 * @param toTimeZone
 * @return
 */
public static Date convertTimeZone(Date date, TimeZone fromTimeZone, TimeZone toTimeZone)
{
    long fromTimeZoneOffset = getTimeZoneUTCAndDSTOffset(date, fromTimeZone);
    long toTimeZoneOffset = getTimeZoneUTCAndDSTOffset(date, toTimeZone);

    return new Date(date.getTime() + (toTimeZoneOffset - fromTimeZoneOffset));
}

/**
 * Calculates the offset of the <code>timeZone</code> from UTC, factoring in any
 * additional offset due to the time zone being in daylight savings time as of
 * the given <code>date</code>.
 * @param date
 * @param timeZone
 * @return
 */
private static long getTimeZoneUTCAndDSTOffset(Date date, TimeZone timeZone)
{
    long timeZoneDSTOffset = 0;
    if(timeZone.inDaylightTime(date))
    {
        timeZoneDSTOffset = timeZone.getDSTSavings();
    }

    return timeZone.getRawOffset() + timeZoneDSTOffset;
}

Le mérite revient à ce poste .

diadyne
la source
1
Le décalage des fuseaux horaires n'est pas toujours constant. En fait, ils peuvent changer pour des raisons géopolitiques. TimeZone.getDSTSavings () ne prend pas cela en compte et renvoie toujours le décalage actuel . Cela signifie que vous pouvez obtenir une mauvaise conversion au cas où vous auriez affaire à une date historique avec une fromTimeZone / toTimeZone qui a changé de décalage depuis cette date.
andrerobot
6

java.util.Calendarest la manière habituelle de gérer les fuseaux horaires en utilisant uniquement les classes JDK. Apache Commons propose d'autres alternatives / utilitaires qui peuvent être utiles. La note de Edit Spong m'a rappelé que j'avais entendu de très bonnes choses à propos de Joda-Time (même si je ne l'ai pas utilisé moi-même).

TJ Crowder
la source
+1 pour Joda Time. Bien qu'il ne fournisse aucune fonctionnalité supplémentaire que vous ne pouviez pas obtenir à partir de l'API Java standard (que j'ai trouvée dans tous les cas - heureux d'être montré autrement), Joda Time facilite certaines tâches.
Joshua Hutchison
2
@JoshuaHutchison Joda-Time a des tonnes de fonctionnalités supplémentaires. Exemple: Représentant des portées de temps avec les classes Period, Durationet Interval. Ces portées comprennent des méthodes de comparaison tels que contains, abuts, overlapet gap. Et PeriodFormatterBuilderpeut construire des phrases descriptives telles que "15 ans et 8 mois".
Basil Bourque
1

Convertissez la date en chaîne et faites-le avec SimpleDateFormat.

    SimpleDateFormat readFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
    readFormat.setTimeZone(TimeZone.getTimeZone("GMT" + timezoneOffset));
    String dateStr = readFormat.format(date);
    SimpleDateFormat writeFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
    Date date = writeFormat.parse(dateStr);
avisper
la source
1
Les réponses à Stack Overflow devraient avoir une discussion ou une explication. Ce site est censé être plus qu'une bibliothèque d'extraits de code.
Basil Bourque
merci @Basil Bourque pour ton commentaire. Je
modifie
0

Si quelqu'un en a besoin, si vous devez convertir un XMLGregorianCalendarfuseau horaire en fuseau horaire UTC, il vous suffit de définir le fuseau horaire 0, puis d'appeler toGregorianCalendar()- il restera le même fuseau horaire, mais le Datesait comment le convertir en le vôtre, afin que vous puissiez obtenir les données à partir de là.

XMLGregorianCalendar xmlStartTime = DatatypeFactory.newInstance()
    .newXMLGregorianCalendar(
        ((GregorianCalendar)GregorianCalendar.getInstance());
xmlStartTime.setTimezone(0);
GregorianCalendar startCalendar = xmlStartTime.toGregorianCalendar();
Date startDate = startCalendar.getTime();
XMLGregorianCalendar xmlStartTime = DatatypeFactory.newInstance()
    .newXMLGregorianCalendar(startCalendar);
xmlStartTime.setHour(startDate.getHours());
xmlStartTime.setDay(startDate.getDate());
xmlStartTime.setMinute(startDate.getMinutes());
xmlStartTime.setMonth(startDate.getMonth()+1);
xmlStartTime.setTimezone(-startDate.getTimezoneOffset());
xmlStartTime.setSecond(startDate.getSeconds());
xmlStartTime.setYear(startDate.getYear() + 1900);
System.out.println(xmlStartTime.toString());

Résultat:

2015-08-26T12:02:27.183Z
2015-08-26T14:02:27.183+02:00
EpicPandaForce
la source
0

Ce code a été utile dans une application sur laquelle je travaille:

    Instant date = null;
    Date sdf = null;
    String formatTemplate = "EEE MMM dd yyyy HH:mm:ss";
    try {
        SimpleDateFormat isoFormat = new SimpleDateFormat("EEE MMM dd yyyy HH:mm:ss");
        isoFormat.setTimeZone(TimeZone.getTimeZone(ZoneId.of("US/Pacific")));
        sdf = isoFormat.parse(timeAtWhichToMakeAvailable);
        date = sdf.toInstant();

    } catch (Exception e) {
        System.out.println("did not parse: " + timeAtWhichToMakeAvailable);
    }

    LOGGER.info("timeAtWhichToMakeAvailable: " + timeAtWhichToMakeAvailable);
    LOGGER.info("sdf: " + sdf);
    LOGGER.info("parsed to: " + date);
VikR
la source