¿Cómo establecer la zona horaria de un java.util.Date?

225

He analizado a java.util.Datedesde a Stringpero establece la zona horaria local como la zona horaria del dateobjeto.

La zona horaria no se especifica en el Stringque Datese analiza. Quiero establecer una zona horaria específica del dateobjeto.

¿Cómo puedo hacer eso?

Yatendra Goel
fuente
8
Si bien no es realmente una respuesta a su pregunta, he usado Joda Time después de verlo mencionado aquí varias veces. Me parece más racional que las API estándar, y puede hacer este tipo de cosas con bastante facilidad.
clstrfsck
2
@msandiford Hoy en día, use clases java.time en lugar de Joda-Time. El proyecto Joda-Time se encuentra ahora en modo de mantenimiento , y el equipo aconseja migrar a las clases java.time . Ver Tutorial de Oracle .
Basil Bourque

Respuestas:

315

Utilice DateFormat. Por ejemplo,

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");
Codificador ZZ
fuente
66
Si la fecha se crea desde la clase Calendario, puede establecer la zona horaria para Calendario.
lwpro2
19
@ lwpro2 esa declaración es engañosa; Puede establecer la zona horaria para un objeto Calendario, pero obtener un objeto Fecha usando el método getTime () devolverá un objeto Fecha con la zona horaria de la computadora host.
BrDaHa
Tengo problemas para analizar 20/02/15 14:44. ¿Alguien puede ayudar
MS
@BrDaHa es correcto. Deberá hacerlo TimeZone.setDefault()antes de llamar getTime()para que el nuevo objeto de fecha esté en la zona horaria que desee. En JDK 1.8, Calendar.getTime()llamadas return new Date(getTimeInMillis());.
jpllosa
182

Tenga en cuenta que los java.util.Dateobjetos no contienen ninguna información de zona horaria por sí mismos: no puede establecer la zona horaria en un Dateobjeto. Lo único que Datecontiene un objeto es una cantidad de milisegundos desde la "época" - 1 de enero de 1970, 00:00:00 UTC.

Como muestra ZZ Coder, establece la zona horaria en el DateFormatobjeto, para indicarle en qué zona horaria desea mostrar la fecha y la hora.

Jesper
fuente
77
Después de buscar en Google, experimentar y agotar, me di cuenta de que esta es una adición precisa y útil a la respuesta, y vale la pena resaltar: la fecha solo contiene el valor de milisegundos . Si nos fijamos en la fuente, solo hay un longcampo llamado fastTime. Date.toString()en realidad usa un Calendarpara interpretar este milisegundo de tiempo. Por lo tanto, imprimir un Datearchivo parece tener una zona horaria (predeterminada), lo que genera preguntas comprensibles sobre cómo configurar esa zona horaria.
David Carboni
3
hay información de zona horaria dentro de los objetos de fecha. Pero puede ser cierto, no puedes cambiarlo.
lwpro2
3
@ Iwpro2, Jesper afirma (y acepto) que el objeto Date no almacena una zona horaria. Si está afirmando que la zona horaria está almacenada dentro de java.util.Date, proporcione una referencia.
Jim
66
@ Jim, este es el código fuente de java.util.Date. Contiene fastTime como unix epoch, así como BaseCalendar.Date cdate que se usa a favor de fastTime, si está definido. Ese contiene información de zona horaria. Lo entiendo para que una instancia de Date pueda contener información de zona horaria, pero puede que no.
EIS
2
@ Jim No dije que pudieras configurarlo, estaba diciendo que contiene esta información. Tampoco veo ninguna forma de configurarlo como usuario. Creo que OP es correcto porque parece ser siempre la zona horaria predeterminada del usuario / sistema.
Eis
95

tl; dr

... analizado ... desde una Cadena ... la zona horaria no está especificada ... Quiero establecer una zona horaria específica

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.

Sin zona horaria en juDate

Como indicaron las otras respuestas correctas, un java.util.Date no tiene zona horaria . Representa UTC / GMT (sin desplazamiento de zona horaria). Muy confuso porque su toStringmétodo aplica la zona horaria predeterminada de la JVM al generar una representación de cadena.

Evite el juDate

Por esta y muchas otras razones, debe evitar usar el java.util.Date & .Calendar & java.text.SimpleDateFormat incorporado. Son notoriamente problemáticos.

En su lugar, use el paquete java.time incluido con Java 8 .

java.time

Las clases java.time pueden representar un momento en la línea de tiempo de tres maneras:

  • UTC ( Instant)
  • Con un desplazamiento ( OffsetDateTimecon ZoneOffset)
  • Con una zona horaria ( ZonedDateTimecon ZoneId)

Instant

En java.time , el elemento básico es Instantun momento en la línea de tiempo en UTC. Use Instantobjetos para gran parte de su lógica empresarial.

Instant instant = Instant.now();

OffsetDateTime

Aplique un desplazamiento desde UTC para ajustar la hora del reloj de pared de alguna localidad .

Aplica un ZoneOffsetpara obtener un OffsetDateTime.

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

ZonedDateTime

Es mejor aplicar una zona horaria , un desplazamiento más las reglas para manejar anomalías como el horario de verano (DST) .

Aplica una ZoneIda una Instantpara obtener una ZonedDateTime. Siempre especifique un nombre de zona horaria adecuado . Nunca use 3-4 abreviaturas como ESTo ISTque no sean únicas ni estandarizadas.

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

LocalDateTime

Si la cadena de entrada carecía de algún indicador de desplazamiento o zona, analice como a LocalDateTime.

Si está seguro de la zona horaria prevista, asigne a ZoneIdpara producir a ZonedDateTime. Vea el ejemplo de código anterior en la sección tl; dr en la parte superior.

Cadenas formateadas

Llame al toStringmétodo en cualquiera de estas tres clases para generar una Cadena que represente el valor de fecha y hora en el formato estándar ISO 8601 . La ZonedDateTimeclase extiende el formato estándar agregando el nombre de la zona horaria entre paréntesis.

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]

Para otros formatos, use la DateTimeFormatterclase. En general, es mejor dejar que esa clase genere formatos localizados utilizando el lenguaje humano y las normas culturales esperadas por el usuario. O puede especificar un formato particular.


Tabla de todos los tipos de fecha y hora en Java, tanto modernos como heredados


Sobre java.time

El marco java.time está integrado en Java 8 y versiones posteriores. Estas clases suplantar la vieja problemáticos heredados clases de 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 Stack Overflow para obtener muchos ejemplos y explicaciones. La especificación es JSR 310 .

Puede intercambiar objetos java.time directamente con su base de datos. Utilice un controlador JDBC compatible con JDBC 4.2 o posterior. No hay necesidad de cadenas, no hay necesidad de java.sql.*clases.

¿Dónde obtener las clases java.time?

El proyecto ThreeTen-Extra extiende java.time con clases adicionales. Este proyecto es un campo de pruebas para posibles adiciones futuras a java.time. Usted puede encontrar algunas clases útiles aquí, como Interval, YearWeek, YearQuarter, y más .


Joda-Time

Si bien Joda-Time aún se mantiene activamente, sus creadores nos han dicho que migremos a java.time tan pronto como sea conveniente. Dejo esta sección intacta como referencia, pero sugiero usar la java.timesección anterior en su lugar.

En Joda-Time , un objeto de fecha y hora ( DateTime) realmente conoce su zona horaria asignada. Eso significa una compensación de UTC y las reglas y el historial del horario de verano de esa zona horaria (DST) y otras anomalías similares.

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

Llame al toStringmétodo para generar una cadena en formato ISO 8601 .

String output = dateTimeIndia.toString();

Joda-Time también ofrece capacidades ricas para generar todo tipo de otros formatos de cadena.

Si es necesario, puede convertir de Joda-Time DateTime a java.util.Date.

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

Busque StackOverflow para "joda date" para encontrar muchos más ejemplos, algunos bastante detallados.


En realidad no es una zona horaria incrustada en un java.util.Date, que se utiliza para algunas funciones internas (véanse los comentarios sobre esta respuesta). Pero esta zona horaria interna no está expuesta como una propiedad y no se puede establecer. Esta zona horaria interna no es la utilizada por el toStringmétodo para generar una representación de cadena del valor de fecha y hora; en cambio, la zona horaria predeterminada actual de la JVM se aplica sobre la marcha. Entonces, como taquigrafía, a menudo decimos "juDate no tiene zona horaria". ¿Confuso? Si. Otra razón más para evitar estas viejas clases cansadas.

Albahaca Bourque
fuente
3
"No hay zona horaria en juDate" está mal. Hay una información de zona horaria en juDate almacenada en su BaseCalendar.Date cdatepropiedad si se establece. Echa un vistazo al código fuente aquí . No puede establecer la zona horaria de un objeto juDate excepto cambiando la zona horaria predeterminada de la JVM llamando TimeZone.setDefault(TimeZone.getTimeZone("NEW_TIME_ZONE"));. Por lo tanto, hay un desplazamiento de zona horaria y puede obtener el desplazamiento llamando al método obsoleto juDate.getTimezoneOffset ()
Thai Bui
3
@blquythai Correcto, hiciste tu tarea. Al igual que yo, después de haber visto ese código fuente antes. No es una zona horaria enterrados allí. Pero a todos los efectos prácticos, se ignora esa zona horaria. Un java.util.Date funciona sin zona horaria, en efecto, está en UTC, mientras que ignora esa zona horaria enterrada. Excepto por el toStringmétodo que aplica la zona horaria predeterminada actual de la JVM; nuevamente ignorando la zona horaria enterrada. Entonces, por brevedad, decimos que java.util.Date no tiene zona horaria. Como el arte , es una mentira que dice la verdad.
Basil Bourque
Como @blquythai para llamar TimeZone.setDefault, se le no ajustar el huso horario del objeto java.util.Date - el objeto Date sigue haciendo caso omiso de su zona de tiempo enterrada, actuando efectivamente en UTC. Afectaría el toStringmétodo de Date . La configuración predeterminada cambia la zona horaria predeterminada de la JVM, que generalmente se establece en la zona horaria del sistema operativo host. Esa llamada no se recomienda ya que afecta todo el código en todos los hilos de todas las aplicaciones que se ejecutan en esa JVM, y lo hace sobre la marcha mientras se ejecutan. Siendo grosero y peligroso, esa llamada solo debe considerarse como un último recurso.
Basil Bourque
55
Esa zona horaria se utiliza muy a menudo (utilizado en equals, hashcode, getTime..) Si se echa un vistazo en el equalsmétodo, se llama a getTime()la cual las llamadas getTimeImpl(), que las llamadas normalize()si el cdateinmueble no se normaliza. En el normalize()método, la última condición if vuelve a calcular los milisegundos desde 1/1/70 en función de su información de zona horaria almacenada si la zona horaria de cdatees diferente de la zona horaria del entorno JVM actual en el que se está ejecutando. (Echa un vistazo a sun.util.calendar.AbstractCalendar getCalendarDate(long millis, CalendarDate date))
Thai Bui
2
@MichalM Por su consejo, hace algún tiempo agregué un epílogo sobre la zona incrustada.
Basil Bourque
75

También puede establecer la zona horaria en el nivel 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);

salida:

Thu Sep 05 10:11:12 EDT 2013
Thu Sep 05 14:11:12 UTC 2013
starmer
fuente
Esto me ayudo. establecer timeZone en SDF no hizo ninguna diferencia
vishnu viswanath
Creo que es más confiable. (+1)
foobar
23
Cuidado: las llamadas TimeZone.setDefaultson bastante drásticas, ya que afectan a toda la JVM, afectan a todos los demás objetos y subprocesos. Consulte esta respuesta para obtener detalles que incluyen incluso más complicaciones si está ejecutando con un SecurityManager. Agregando aún más complicaciones: este comportamiento ha cambiado en varias versiones de Java, como se discutió en esta pregunta .
Basil Bourque
Esto establece una zona horaria común en todos los hilos generados después de esta declaración, ¿verdad?
Jaydev
Esta es una mejor respuesta a la pregunta que la aceptada.
francogrex
10

Si debe trabajar solo con clases JDK estándar, puede usar esto:

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

El crédito va a esta publicación .

diadyne
fuente
1
El desplazamiento de las zonas horarias no siempre es constante. De hecho, pueden cambiar debido a razones geopolíticas. TimeZone.getDSTSavings () no tiene esto en cuenta y siempre devuelve el desplazamiento actual . Esto significa que puede obtener una conversión incorrecta en caso de que esté lidiando con una fecha histórica con fromTimeZone / toTimeZone que ha cambiado las compensaciones desde esa fecha.
andrerobot
6

java.util.Calendares la forma habitual de manejar zonas horarias usando solo clases JDK. Apache Commons tiene algunas alternativas / utilidades adicionales que pueden ser útiles. Editar la nota de Spong me recordó que he escuchado cosas realmente buenas sobre Joda-Time (aunque no lo he usado yo mismo).

TJ Crowder
fuente
+1 para Joda Time. Si bien no proporciona ninguna funcionalidad adicional que no podría obtener de la API Java estándar (que he encontrado en cualquier caso, feliz de que se muestre lo contrario), Joda Time facilita algunas tareas.
Joshua Hutchison
2
@JoshuaHutchison Joda-Time tiene toneladas de funcionalidad adicional. Ejemplo: En representación de espacios de tiempo con las clases Period, Durationy Interval. Esos vanos incluyen métodos de comparación tales como contains, abuts, overlap, y gap. Y PeriodFormatterBuilderpuede construir frases descriptivas como "15 años y 8 meses".
Basil Bourque
1

Convierta la fecha en cadena y hágalo con 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
fuente
1
Se espera que las respuestas sobre Stack Overflow tengan alguna discusión o explicación. Este sitio está destinado a ser más que una biblioteca de fragmentos de código.
Basil Bourque
gracias @Basil Bourque por tu comentario. Edito la respuesta
avisper
0

Si alguien alguna vez necesita esto, si necesita convertir una XMLGregorianCalendarzona horaria a su zona horaria actual desde UTC, entonces todo lo que necesita hacer es configurar la zona horaria 0, luego llamar toGregorianCalendar(): permanecerá en la misma zona horaria, pero Datesabe cómo convertirla a el suyo, para que pueda obtener los datos desde allí.

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

Resultado:

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

Este código fue útil en una aplicación en la que estoy trabajando:

    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
fuente