Comment gérer les calendriers en utilisant Java?

j'ai une valeur Timestamp qui vient de mon application. L'utilisateur peut être dans n'importe quel fuseau horaire local.

comme cette date est utilisée pour un service Web qui suppose que l'heure indiquée est toujours en GMT, j'ai besoin de convertir le paramètre de l'utilisateur de say (EST) en (GMT). Voici le kicker: l'utilisateur est inconscient de son TZ. Il entre la date de création qu'il veut envoyer au WS, donc ce dont j'ai besoin c'est:

L'utilisateur entre: 5/1/2008 6: 12 PM (EST)

le paramètre à la WS doit être : 5/1/2008 6: 12 PM (GMT)

je sais que les horodateurs sont toujours censés être au format GMT par défaut, mais lors de l'envoi du paramètre, même si j'ai créé mon calendrier à partir du TS (qui est censé être au format GMT), les heures sont toujours désactivées sauf si l'utilisateur est au format GMT. Ce qui me manque?

Timestamp issuedDate = (Timestamp) getACPValue(inputs_, "issuedDate");
Calendar issueDate = convertTimestampToJavaCalendar(issuedDate);
...
private static java.util.Calendar convertTimestampToJavaCalendar(Timestamp ts_) {
  java.util.Calendar cal = java.util.Calendar.getInstance(
      GMT_TIMEZONE, EN_US_LOCALE);
  cal.setTimeInMillis(ts_.getTime());
  return cal;
}

avec le Code précédent, c'est ce que j'obtiens en tant que résultat (format court pour une lecture facile):

[May 1, 2008 11:12 PM]

88
demandé sur John Topley 2008-10-23 19:15:48

9 réponses

public static Calendar convertToGmt(Calendar cal) {

    Date date = cal.getTime();
    TimeZone tz = cal.getTimeZone();

    log.debug("input calendar has date [" + date + "]");

    //Returns the number of milliseconds since January 1, 1970, 00:00:00 GMT 
    long msFromEpochGmt = date.getTime();

    //gives you the current offset in ms from GMT at the current date
    int offsetFromUTC = tz.getOffset(msFromEpochGmt);
    log.debug("offset is " + offsetFromUTC);

    //create a new calendar in GMT timezone, set to this date and add the offset
    Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
    gmtCal.setTime(date);
    gmtCal.add(Calendar.MILLISECOND, offsetFromUTC);

    log.debug("Created GMT cal with date [" + gmtCal.getTime() + "]");

    return gmtCal;
}

Voici la sortie si je passe le temps actuel ("12: 09: 05 EDT" de Calendar.getInstance() ) dans:

DEBUG-input calendar has date [Thu Oct 23 12:09: 05 EDT 2008]

DEBUG-offset is -14400000

DEBUG-création GMT cal avec date [Jeu Oct 23 08: 09: 05 HAE 2008]

12:09:05 GMT is 8:09:05 EDT.

la partie confuse ici est que Calendar.getTime() vous retourne un Date dans votre fuseau horaire actuel, et aussi qu'il n'y a aucune méthode pour modifier le fuseau horaire d'un calendrier et avoir la date sous-jacente roulé aussi. Selon le type de paramètre que votre service web prend, Vous pouvez juste vouloir avoir l'affaire WS en termes de millisecondes de l'époque.

60
répondu matt b 2008-10-23 16:23:12

merci à tous d'avoir répondu. Après une enquête plus approfondie, j'ai le droit de réponse. Comme L'a mentionné Skip Head, le timestamp que j'obtenais de mon application était ajusté au fuseau horaire de l'utilisateur. Donc, si L'utilisateur entrait 18: 12 PM (EST), j'obtiendrais 14:12 PM (GMT). Ce dont j'avais besoin était un moyen de défaire la conversion de sorte que le temps saisi par l'utilisateur soit le temps que j'ai envoyé à la requête du serveur web. Voici comment j'ai accompli ceci:

// Get TimeZone of user
TimeZone currentTimeZone = sc_.getTimeZone();
Calendar currentDt = new GregorianCalendar(currentTimeZone, EN_US_LOCALE);
// Get the Offset from GMT taking DST into account
int gmtOffset = currentTimeZone.getOffset(
    currentDt.get(Calendar.ERA), 
    currentDt.get(Calendar.YEAR), 
    currentDt.get(Calendar.MONTH), 
    currentDt.get(Calendar.DAY_OF_MONTH), 
    currentDt.get(Calendar.DAY_OF_WEEK), 
    currentDt.get(Calendar.MILLISECOND));
// convert to hours
gmtOffset = gmtOffset / (60*60*1000);
System.out.println("Current User's TimeZone: " + currentTimeZone.getID());
System.out.println("Current Offset from GMT (in hrs):" + gmtOffset);
// Get TS from User Input
Timestamp issuedDate = (Timestamp) getACPValue(inputs_, "issuedDate");
System.out.println("TS from ACP: " + issuedDate);
// Set TS into Calendar
Calendar issueDate = convertTimestampToJavaCalendar(issuedDate);
// Adjust for GMT (note the offset negation)
issueDate.add(Calendar.HOUR_OF_DAY, -gmtOffset);
System.out.println("Calendar Date converted from TS using GMT and US_EN Locale: "
    + DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT)
    .format(issueDate.getTime()));

le code la sortie est: (Utilisateur a entré 5/1/2008 6:12H (heure de l'est)

fuseau horaire de L'utilisateur actuel: EST

Décalage courant à partir du GMT (en hrs): -4 (normalement -5, sauf ajustement DST)

TS des pays ACP: 2008-05-01 14:12:00.0

Date du calendrier converti de TS en utilisant GMT et US_EN Locale: 5/1/08 6: 12 PM (GMT)

29
répondu Jorge Valois 2008-10-23 18:03:18

vous dites que la date est utilisée en relation avec les services web, donc je suppose que c'est sérialisé dans une chaîne à un moment donné.

si c'est le cas, vous devriez jeter un oeil à la méthode setTimeZone de la classe DateFormat. Ceci dicte fuseau horaire qui sera utilisé lors de l'impression du timbre de temps.

un exemple simple:

SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
formatter.setTimeZone(TimeZone.getTimeZone("UTC"));

Calendar cal = Calendar.getInstance();
String timestamp = formatter.format(cal.getTime());
20
répondu Henrik Aasted 2009-02-02 12:41:36

vous pouvez le résoudre avec Joda Time :

Date utcDate = new Date(timezoneFrom.convertLocalToUTC(date.getTime(), false));
Date localDate = new Date(timezoneTo.convertUTCToLocal(utcDate.getTime()));

Java 8:

LocalDateTime localDateTime = LocalDateTime.parse("2007-12-03T10:15:30");
ZonedDateTime fromDateTime = localDateTime.atZone(
    ZoneId.of("America/Toronto"));
ZonedDateTime toDateTime = fromDateTime.withZoneSameInstant(
    ZoneId.of("Canada/Newfoundland"));
12
répondu Vitalii Fedorenko 2014-04-21 00:22:27

méthode pour passer d'un fuseau horaire à un autre(cela fonctionne probablement :) ).

/**
 * Adapt calendar to client time zone.
 * @param calendar - adapting calendar
 * @param timeZone - client time zone
 * @return adapt calendar to client time zone
 */
public static Calendar convertCalendar(final Calendar calendar, final TimeZone timeZone) {
    Calendar ret = new GregorianCalendar(timeZone);
    ret.setTimeInMillis(calendar.getTimeInMillis() +
            timeZone.getOffset(calendar.getTimeInMillis()) -
            TimeZone.getDefault().getOffset(calendar.getTimeInMillis()));
    ret.getTime();
    return ret;
}
7
répondu Helpa 2009-04-06 09:42:02

on dirait que votre horodateur est réglé sur le fuseau horaire du système d'origine.

c'est déprécié, mais ça devrait marcher:

cal.setTimeInMillis(ts_.getTime() - ts_.getTimezoneOffset());

la manière non dépréciée est d'utiliser

Calendar.get(Calendar.ZONE_OFFSET) + Calendar.get(Calendar.DST_OFFSET)) / (60 * 1000)

mais cela devrait être fait du côté du client, puisque ce système sait dans quel fuseau horaire il est.

7
répondu Skip Head 2014-12-24 14:08:52

Date et Timestamp les objets sont timezone-oublieux: ils représentent un certain nombre de secondes depuis l'époque, sans s'engager à une interprétation particulière de cet instant comme des heures et des jours. Timezones entrer l'image seulement dans GregorianCalendar (pas directement nécessaire pour cette tâche) et SimpleDateFormat , qui ont besoin d'un décalage de fuseau horaire pour convertir entre les champs séparés et Date (ou long ) valeurs.

le problème de L'OP est au début de son traitement: l'utilisateur entre des heures, qui sont ambiguës, et elles sont interprétées dans le fuseau horaire local, non-GMT; à ce point la valeur est " 6:12 EST " , qui peut être facilement imprimé en tant que " 11.12 GMT ou tout autre fuseau horaire, mais ne va jamais changer en "6.12 GMT .

il n'y a aucun moyen de faire le SimpleDateFormat qui parse "06:12" comme " HH:MM " (défaut au fuseau horaire local) par défaut à UTC à la place; SimpleDateFormat est un peu trop intelligent pour son propre bien.

cependant, vous pouvez convaincre n'importe quelle instance SimpleDateFormat d'utiliser le bon fuseau horaire si vous le mettez explicitement dans le entrée: ajoutez simplement une chaîne de caractères fixe à l' (et validées) "06:12" pour analyser "06:12 GMT" comme "HH:MM z" .

il n'est pas nécessaire de définir explicitement les champs GregorianCalendar ni d'extraire et d'utiliser des décalages de fuseau horaire et d'heure d'été.

le vrai problème est de séparer les entrées par défaut dans le fuseau horaire local., entrées par défaut à l'UTC, et les contributions qui en ont réellement besoin explicite d'une indication de fuseau horaire.

6
répondu user412090 2012-02-06 11:28:47

quelque chose qui a fonctionné pour moi dans le passé était de déterminer le décalage (en millisecondes) entre le fuseau horaire de l'utilisateur et GMT. Une fois que vous avez l'offset, vous pouvez simplement ajouter/soustraire (en fonction de la façon dont la conversion va) pour obtenir l'heure appropriée dans l'un ou l'autre des fuseaux horaires. D'habitude, j'y parviendrais en définissant le champ millisecondes d'un objet Calendar, mais je suis sûr que vous pourriez facilement l'appliquer à un objet timestamp. Voici le code que j'utilise pour obtenir l'offset

int offset = TimeZone.getTimeZone(timezoneId).getRawOffset();

timezoneId est l'id du fuseau horaire de l'utilisateur (comme EST).

4
répondu Adam 2008-10-23 15:59:25

de java.time

l'approche moderne utilise le java.les classes de temps qui ont supplanté les troublantes classes de date-heure héritées groupées avec les premières versions de Java.

la classe java.sql.Timestamp est l'une de ces classes héritées. N'est plus nécessaire. Utilisez plutôt Instant ou autre java.les classes de temps directement avec votre base de données en utilisant JDBC 4.2 et plus tard.

le Instant classe représente un moment sur la ligne de temps dans UTC avec une résolution de nanosecondes (jusqu'à neuf (9) chiffres d'une fraction décimale).

Instant instant = myResultSet.getObject( … , Instant.class ) ; 

si vous devez interopérer avec un Timestamp existant , convertissez immédiatement en java.temps via les nouvelles méthodes de conversion ajoutées aux anciennes classes.

Instant instant = myTimestamp.toInstant() ;

pour s'ajuster dans un autre fuseau horaire, préciser fuseau horaire comme objet ZoneId . Préciser un nom du fuseau horaire approprié dans le format continent/region , tel que America/Montreal , Africa/Casablanca , ou Pacific/Auckland . N'utilisez jamais les pseudo-zones de 3-4 lettres comme EST ou IST car elles sont pas vrai fuseaux horaires, non standardisé, et même pas unique(!).

ZoneId z = ZoneId.of( "America/Montreal" ) ;

S'appliquent au Instant pour produire un objet ZonedDateTime .

ZonedDateTime zdt = instant.atZone( z ) ;

pour générer une chaîne de caractères à afficher à l'utilisateur, recherchez DateTimeFormatter pour trouver de nombreuses discussions et exemples.

votre Question est vraiment d'aller dans l'autre direction, de la saisie des données de l'utilisateur aux objets date-time. Généralement, il est préférable de diviser votre saisie de données en deux parties, une date et une heure de la journée.

LocalDate ld = LocalDate.parse( dateInput , DateTimeFormatter.ofPattern( "M/d/uuuu" , Locale.US ) ) ;
LocalTime lt = LocalTime.parse( timeInput , DateTimeFormatter.ofPattern( "H:m a" , Locale.US ) ) ;

votre Question Est pas clair. Voulez-vous interpréter la date et l'heure entrées par l'utilisateur pour être dans UTC? Ou dans un autre fuseau horaire?

si vous voulez dire UTC, créez un OffsetDateTime avec un décalage utilisant la constante pour UTC, ZoneOffset.UTC .

OffsetDateTime odt = OffsetDateTime.of( ld , lt , ZoneOffset.UTC ) ;

si vous voulez dire un autre fuseau horaire, combinez avec un objet de fuseau horaire, un ZoneId . Mais quel fuseau horaire? Vous pouvez détecter un fuseau horaire par défaut. Ou, si critique, vous devez confirmer avec l'utilisateur d'être certains de leur intention.

ZonedDateTime zdt = ZonedDateTime.of( ld , lt , z ) ;

pour obtenir un objet plus simple qui est toujours en UTC par définition, extraire un Instant .

Instant instant = odt.toInstant() ;

... ou ...

Instant instant = zdt.toInstant() ; 

Envoyer à votre base de données.

myPreparedStatement.setObject( … , instant ) ;

à Propos de java.time

Le de java.time framework est construit dans Java 8 et plus tard. Ces classes supplanter le vieux gênant legacy les classes de date-heure telles que java.util.Date , Calendar , & SimpleDateFormat .

le projet Joda-Time , maintenant en mode maintenance , conseille la migration vers le java.temps des classes.

pour en savoir plus, voir le Oracle Tutorial . Et rechercher le débordement de la pile pour de nombreux exemples et explications. La spécification est JSR 310 .

où obtenir le java.les classes de temps?

Le ThreeTen-Extra le projet s'étend java.temps avec des cours supplémentaires. Ce projet est un terrain d'expérimentation pour d'éventuelles futures additions à java.temps. Vous pouvez trouver ici quelques cours utiles tels que: Interval , YearWeek , YearQuarter , et plus .

1
répondu Basil Bourque 2018-01-26 06:43:50