settimezone - java timezone
¿Cómo manejar calendarios TimeZones usando Java? (9)
Tengo un valor Timestamp que proviene de mi aplicación. El usuario puede estar en cualquier TimeZone local.
Como esta fecha se usa para un servicio web que asume que el tiempo dado siempre está en GMT, necesito convertir el parámetro del usuario de decir (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, entonces lo que necesito es:
El usuario ingresa: 5/1/2008 6:12 PM (EST)
El parámetro para WS debe ser : 5/1/2008 6:12 PM (GMT)
Sé que TimeStamps siempre se supone que están en GMT de forma predeterminada, pero al enviar el parámetro, aunque 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 facilitar la lectura):
[1 de mayo de 2008, 11:12 p.m.]
java.time
El enfoque moderno usa las clases java.time que suplantaron las problemáticas clases heredadas de fecha y hora incluidas con las primeras versiones de Java.
La clase java.sql.Timestamp
es una de esas clases heredadas. Ya no es necesario. En su lugar, use Instant
u otras clases de java.time directamente con su base de datos usando JDBC 4.2 y posterior.
La clase Instant
representa un momento en la línea de tiempo en UTC con una resolución de nanoseconds (hasta nueve (9) dígitos de una fracción decimal).
Instant instant = myResultSet.getObject( … , Instant.class ) ;
Si debe interoperar con una Timestamp
existente, conviértala inmediatamente en java.time a través de los nuevos métodos de conversión agregados a las clases anteriores.
Instant instant = myTimestamp.toInstant() ;
Para ajustar en otra zona horaria, especifique la zona horaria como un objeto ZoneId
. Especifique un nombre de zona horaria adecuada en el formato continent/region
, como America/Montreal
, Africa/Casablanca
o Pacific/Auckland
. Nunca use las pseudo-zonas de 3-4 letras, como EST
o IST
ya que no son zonas horarias verdaderas, no están estandarizadas y ni siquiera son únicas (!).
ZoneId z = ZoneId.of( "America/Montreal" ) ;
Aplicar al Instant
para producir un objeto ZonedDateTime
.
ZonedDateTime zdt = instant.atZone( z ) ;
Para generar una cadena para mostrar al usuario, busque para DateTimeFormatter
para encontrar muchas discusiones y ejemplos.
Su pregunta se trata realmente de ir en la otra dirección, desde la entrada de datos del usuario hasta los objetos de fecha y hora. Por lo general, es mejor romper su ingreso de datos en dos partes, una fecha y una hora del día.
LocalDate ld = LocalDate.parse( dateInput , DateTimeFormatter.ofPattern( "M/d/uuuu" , Locale.US ) ) ;
LocalTime lt = LocalTime.parse( timeInput , DateTimeFormatter.ofPattern( "H:m a" , Locale.US ) ) ;
Tu pregunta no está clara. ¿Desea interpretar la fecha y la hora ingresadas por el usuario para estar en UTC? ¿O en otra zona horaria?
Si se refería a UTC, cree un OffsetDateTime
con un desplazamiento usando la constante para UTC, ZoneOffset.UTC
.
OffsetDateTime odt = OffsetDateTime.of( ld , lt , ZoneOffset.UTC ) ;
Si se refería a otra zona horaria, combine junto con un objeto de zona horaria, un ZoneId
. Pero, ¿qué zona horaria? Puede detectar una zona horaria predeterminada. O, si es crítico, debe confirmar con el usuario para estar seguro de su intención.
ZonedDateTime zdt = ZonedDateTime.of( ld , lt , z ) ;
Para obtener un objeto más simple que siempre está en UTC por definición, extrae un Instant
.
Instant instant = odt.toInstant() ;
…o…
Instant instant = zdt.toInstant() ;
Enviar a su base de datos
myPreparedStatement.setObject( … , instant ) ;
Acerca de java.time
El marco java.time está integrado en Java 8 y posterior. Estas clases suplantan a las problemáticas antiguas clases legacy fecha y hora como java.util.Date
, Calendar
y SimpleDateFormat
.
El proyecto Joda-Time , ahora en modo de mantenimiento , aconseja la migración a las clases java.time .
Para obtener más información, consulte el Tutorial de Oracle . Y busque para obtener muchos ejemplos y explicaciones. La especificación es JSR 310 .
¿Dónde obtener las clases de java.time?
- Java SE 8 , Java SE 9 y posterior
- Incorporado.
- Parte de la API Java estándar con una implementación integrada.
- Java 9 agrega algunas características y correcciones menores.
- Java SE 6 y Java SE 7
- Gran parte de la funcionalidad de java.time se transfiere a Java 6 y 7 en ThreeTen-Backport .
- Android
- Versiones posteriores de las implementaciones del paquete de Android de las clases java.time.
- Para Android anterior, el proyecto ThreeTenABP adapta ThreeTen-Backport (mencionado anteriormente). Consulte Cómo utilizar ThreeTenABP ....
El proyecto ThreeTen-Extra amplía java.time con clases adicionales. Este proyecto es un terreno de prueba para posibles adiciones futuras a java.time. Puede encontrar algunas clases útiles aquí, como Interval
, YearWeek
, YearQuarter
y more .
Algo que me ha funcionado en el pasado fue determinar el desplazamiento (en milisegundos) entre la zona horaria del usuario y GMT. Una vez que tenga el desplazamiento, puede simplemente sumar / restar (dependiendo de la dirección de la conversión) para obtener el tiempo apropiado en cualquier zona horaria. Por lo general, lo conseguiría estableciendo el campo de milisegundos de un objeto de calendario, pero estoy seguro de que podría aplicarlo fácilmente a un objeto de marca de tiempo. Aquí está el código que uso para obtener el desplazamiento
int offset = TimeZone.getTimeZone(timezoneId).getRawOffset();
timezoneId es la identificación de la zona horaria del usuario (como EST).
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 obtenía de mi aplicación se estaba ajustando al TimeZone del usuario. Entonces, si el usuario ingresara a las 6:12 p.m. (EST), obtendría las 2:12 p.m. (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 5/1/2008 6:12 PM (EST)
Hora actual del usuario: EST
Offset actual de GMT (en horas): - 4 (Normalmente -5, excepto que se ajusta el horario de verano)
TS de ACP: 2008-05-01 14: 12: 00.0
Fecha de calendario convertida de TS usando GMT y US_EN Configuración regional: 5/1/08 6:12 PM (GMT)
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;
}
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 desaprovechada es usar
Calendar.get(Calendar.ZONE_OFFSET) + Calendar.get(Calendar.DST_OFFSET)) / (60 * 1000)
pero eso tendría que hacerse en el lado del cliente, ya que ese sistema sabe en qué zona horaria se encuentra.
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"));
Usted dice que la fecha se usa en conexión con servicios web, así que supongo que se serializa en una cadena en algún momento.
Si este es el caso, debería echar un vistazo al método setTimeZone de la clase DateFormat. Esto dicta qué zona horaria se usará al imprimir la marca de tiempo.
Un simple ejemplo:
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());
Los objetos Date y Timestamp son indiferentes en 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 solo entran en la imagen en GregorianCalendar (no se necesita directamente para esta tarea) y SimpleDateFormat , que necesita un desplazamiento de zona horaria para convertir entre campos separados y valores de Fecha (o de larga duración ).
El problema del PO está 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 se puede imprimir fácilmente como "11.12 GMT" o cualquier otra zona horaria, pero nunca cambiará a "6.12 GMT" .
No hay forma de hacer que SimpleDateFormat analice "06:12" como "HH: MM" (valor predeterminado en la zona horaria local) de forma predeterminada a UTC; SimpleDateFormat es demasiado inteligente para su propio bien.
Sin embargo, puede convencer a cualquier instancia de SimpleDateFormat de usar la zona horaria correcta si la coloca explícitamente en la entrada: simplemente agregue una cadena fija al "06:12" recibido (y adecuadamente validado) para analizar "06:12 GMT" como "HH: MM z" .
No hay necesidad de configuración explícita de los campos GregorianCalendar o de recuperar y usar las compensaciones de la zona horaria y del horario de verano.
El problema real es segregar las entradas predeterminadas a la zona horaria local, las entradas predeterminadas a UTC y las entradas que realmente requieren una indicación de zona horaria explícita.
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á el resultado si paso la hora actual ("12:09:05 EDT" desde Calendar.getInstance()
) en:
DEPURACIÓN - el calendario de entrada tiene fecha [jue 23 de octubre 12:09:05 EDT 2008]
DEBUG - offset es -14400000
DEBUG - Calcuta GMT creada 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()
devuelve una Date
en su zona horaria actual, y también que no hay ningún método para modificar la zona horaria de un calendario y que la fecha subyacente también se haya transferido. Dependiendo del tipo de parámetro que tome su servicio web, es posible que solo desee que la WS trate en términos de milisegundos de época.