Pregunta

Tengo un valor de marca de tiempo que proviene de mi aplicación. El usuario puede estar en cualquier zona horaria local determinada.

Dado que esta fecha se usa para un servicio web que asume que el tiempo dado siempre está en GMT, tengo la necesidad de convertir el parámetro del usuario de, por ejemplo, (EST) a (GMT). Aquí está el truco: el usuario es ajeno a su TZ. Ingresa la fecha de creación que desea enviar al WS, por lo que lo que necesito es:

El usuario ingresa: 5/1/2008 6:12 PM (EST)
El parámetro para el WS debe ser : 5/1/2008 6:12 PM (GMT)

Sé que TimeStamps siempre debe estar en GMT por defecto, pero al enviar el parámetro, a pesar de que creé mi Calendario desde el TS (que se supone que está en GMT), las horas siempre están apagadas a menos que el usuario esté en GMT. ¿Qué me estoy perdiendo?

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

Con el Código anterior, esto es lo que obtengo como resultado (Formato corto para una fácil lectura):

[1 de mayo de 2008 a las 11:12 p.m.]

¿Fue útil?

Solución 2

Gracias a todos por responder. Después de una nueva investigación llegué a la respuesta correcta. Como lo mencionó Skip Head, el TimeStamped que estaba obteniendo de mi aplicación estaba siendo ajustado al TimeZone del usuario. Así que si el Usuario ingresara a las 6:12 PM (EST) obtendría 2:12 PM (GMT). Lo que necesitaba era una forma de deshacer la conversión para que el tiempo ingresado por el usuario sea el tiempo que envié a la solicitud del servidor web. Así es como logré esto:

// 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()));

La salida del código es: (Usuario ingresado 01/05/2008 6:12 PM (EST)

Zona horaria actual del usuario: EST
Desfase actual de GMT (en horas): - 4 (normalmente -5, excepto que está ajustado por DST)
TS de la ACP: 2008-05-01 14: 12: 00.0
Fecha del calendario convertida desde TS usando GMT y US_EN Locale: 5/1/08 6:12 PM (GMT)

Otros consejos

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

Aquí está la salida si paso la hora actual (" 12: 09: 05 EDT " de Calendar.getInstance () ) en:

  

DEBUG - el calendario de entrada tiene fecha [Jue 23 de octubre 12:09:05 EDT 2008]
  DEBUG - el desplazamiento es -14400000
  DEBUG - Creado GMT cal con fecha [Jue 23 de octubre 08:09:05 EDT 2008]

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

La parte confusa aquí es que Calendar.getTime () te devuelve una Date en tu zona horaria actual, y también que no hay ningún método para modificar la zona horaria de un Calendario y tener la fecha subyacente rodada también. Dependiendo del tipo de parámetro que tome su servicio web, es posible que desee tener la oferta WS en términos de milisegundos desde la época.

Dice que la fecha se utiliza en relación con los servicios web, por lo que supongo que en algún momento se serializa en una cadena.

Si este es el caso, debería echar un vistazo a setTimeZone method de la clase DateFormat. Esto determina qué zona horaria se usará al imprimir la marca de tiempo.

Un ejemplo 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());

Puedes resolverlo con 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"));

Parece que su TimeStamp se está configurando en la zona horaria del sistema de origen.

Esto está en desuso, pero debería funcionar:

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

La forma no en desuso es utilizar

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

pero eso debería hacerse en el lado del cliente, ya que el sistema sabe en qué zona horaria se encuentra.

Método para convertir de una zona horaria a otra (probablemente funcione :)).

/**
 * 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;
}
Los objetos

Fecha y Marca de tiempo no tienen en cuenta la zona horaria: representan un cierto número de segundos desde la época, sin comprometerse con una interpretación particular de ese instante como horas y días . Las zonas horarias ingresan la imagen solo en GregorianCalendar (no es necesario directamente para esta tarea) y en SimpleDateFormat , que necesitan un desplazamiento de zona horaria para convertir entre campos separados y Fecha (o largo ) valores.

El problema del OP se encuentra justo al comienzo de su procesamiento: el usuario ingresa horas, que son ambiguas, y se interpretan en la zona horaria local, no GMT; en este punto, el valor es " 6: 12 EST " , que puede imprimirse fácilmente como " 11.12 GMT " o cualquier otra zona horaria, pero nunca lo hará cambiar a " 6.12 GMT " .

No hay forma de hacer que el SimpleDateFormat analice " 06: 12 " como " HH: MM " (el valor predeterminado es la zona horaria local) por defecto es UTC; SimpleDateFormat es un poco demasiado inteligente por su propio bien.

Sin embargo, puedes convencer a cualquier instancia de SimpleDateFormat para que use la zona horaria correcta si la pones explícitamente en la entrada: solo agrega una cadena fija a la cadena recibida (y debidamente validada) " ; 06: 12 " para analizar " 06: 12 GMT " como " HH: MM z " .

No hay necesidad de establecer explícitamente los campos GregorianCalendar o de recuperar y usar la zona horaria y las compensaciones de horario de verano.

El problema real es separar las entradas que están predeterminadas en la zona horaria local, las entradas que están predeterminadas en UTC y las entradas que realmente requieren una indicación explícita de la zona horaria.

Algo que me ha funcionado en el pasado fue determinar el desplazamiento (en milisegundos) entre la zona horaria del usuario y la GMT. Una vez que tenga el desplazamiento, puede simplemente agregar / restar (dependiendo de la forma en que se realice la conversión) para obtener el tiempo adecuado en cualquiera de las zonas horarias. Normalmente lo lograría configurando el campo de milisegundos de un objeto Calendario, pero estoy seguro de que podría aplicarlo fácilmente a un objeto de marca de tiempo. Aquí está el código que utilizo para obtener el desplazamiento

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

timezoneId es el ID de la zona horaria del usuario (como EST).

java.time

The modern approach uses the java.time classes that supplanted the troublesome legacy date-time classes bundled with the earliest versions of Java.

The java.sql.Timestamp class is one of those legacy classes. No longer needed. Instead use Instant or other java.time classes directly with your database using JDBC 4.2 and later.

The Instant class represents a moment on the timeline in UTC with a resolution of nanoseconds (up to nine (9) digits of a decimal fraction).

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

If you must interoperate with an existing Timestamp, convert immediately into java.time via the new conversion methods added to the old classes.

Instant instant = myTimestamp.toInstant() ;

To adjust into another time zone, specify the time zone as a ZoneId object. Specify a proper time zone name in the format of continent/region, such as America/Montreal, Africa/Casablanca, or Pacific/Auckland. Never use the 3-4 letter pseudo-zones such as EST or IST as they are not true time zones, not standardized, and not even unique(!).

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

Apply to the Instant to produce a ZonedDateTime object.

ZonedDateTime zdt = instant.atZone( z ) ;

To generate a string for display to the user, search Stack Overflow for DateTimeFormatter to find many discussions and examples.

Your Question is really about going the other direction, from user data-entry to the date-time objects. Generally best to break your data-entry into two parts, a date and a time-of-day.

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

Your Question is not clear. Do you want to interpret the date and the time entered by the user to be in UTC? Or in another time zone?

If you meant UTC, create a OffsetDateTime with an offset using the constant for UTC, ZoneOffset.UTC.

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

If you meant another time zone, combine along with a time zone object, a ZoneId. But which time zone? You might detect a default time zone. Or, if critical, you must confirm with the user to be certain of their intention.

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

To get a simpler object that is always in UTC by definition, extract an Instant.

Instant instant = odt.toInstant() ;

…or…

Instant instant = zdt.toInstant() ; 

Send to your database.

myPreparedStatement.setObject( … , instant ) ;

About java.time

The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.

The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.

To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.

Where to obtain the java.time classes?

The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top