Saltar al contenido

Cómo convertir UTC y zona horaria local en Java

Posterior a de una prolongada selección de información resolvimos esta impedimento que presentan ciertos los lectores. Te ofrecemos la solución y deseamos servirte de gran ayuda.

Solución:

ACTUALIZAR: Esta respuesta ahora está desactualizada. los Joda-Time la biblioteca ahora es suplantada por la java.time marco integrado en Java 8 y posterior. Vea esta nueva respuesta.

Códigos de tres letras

Debe evitar el uso de códigos de zona horaria de 3 o 4 letras, como EST o IST. No son estándar ni únicos.

Utilice los nombres adecuados de las zonas horarias, en su mayoría Continent/CityOrRegion tal como America/Montreal o Asia/Kolkata.

Joda-Time

Las clases java.util.Date/Calendar son notoriamente malas. Evite su uso. Utilice Joda-Time o, en Java 8, las nuevas clases java.time. * Definidas por JSR 310 e inspiradas en Joda-Time.

Observe cuánto más simple y obvio es el código Joda-Time que se muestra a continuación. Joda-Time incluso sabe contar: ¡enero es 1, no 0!

Zona horaria

En Joda-Time, una instancia de DateTime conoce su propia zona horaria.

Sydney Australia tiene una hora estándar de 10 horas por delante de UTC / GMT y un horario de verano (DST) de 11 horas por delante. El horario de verano se aplica a la fecha especificada por la pregunta.

Consejo: no pienses así …

La hora UTC es 11 horas más tarde que la mía

Piensa así …

El horario de verano de Sydney está 11 horas por delante de UTC / GMT.

El trabajo de fecha y hora se vuelve más fácil y menos propenso a errores si piensa, trabaja y almacena en UTC / GMT. Convierta solo a fecha y hora localizadas para la presentación en la interfaz de usuario. Piense globalmente, visualice localmente. Sus usuarios y sus servidores pueden moverse fácilmente a otras zonas horarias, por lo que olvídate del tuyo zona horaria. Siempre especifique una zona horaria, nunca asuma o confíe en el valor predeterminado.

Código de ejemplo

Aquí hay un código de ejemplo usando Joda-Time 2.3 y Java 8.

// Better to specify a time zone explicitly than rely on default.
// Use time zone names, not 3-letter codes. 
// This list is not quite up-to-date (read page for details): http://joda-time.sourceforge.net/timezones.html
DateTimeZone timeZone = DateTimeZone.forID("Australia/Sydney");
DateTime dateTime = new DateTime(2014, 1, 14, 11, 12, 0, timeZone);
DateTime dateTimeUtc = dateTime.toDateTime(DateTimeZone.UTC); // Built-in constant for UTC (no time zone offset).

Volcar a la consola…

System.out.println("dateTime: " + dateTime);
System.out.println("dateTimeUtc: " + dateTimeUtc);

Cuando se ejecuta …

dateTime: 2014-01-14T11:12:00.000+11:00
dateTime in UTC: 2014-01-14T00:12:00.000Z

Probablemente pretendía establecer la zona horaria en su formateador, no en el Calendario (o además en el Calendario, ¡no está 100% claro lo que quiere lograr)! La zona horaria utilizada para crear la representación humana proviene de SimpleDateFormat. Toda la información de la “zona horaria” se pierde del Calendario cuando la convierte de nuevo en un java.util.Date llamando getTime().

El código:

Calendar c2 = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
c2.set(year, month, date, hourOfDay, minute, second);
System.out.println(sdf.format(c2.getTime()));

está imprimiendo 14/01/2014 10:12:00 porque las 11 a. m. UTC que se muestran en Syndey (la zona horaria de su formateador) son las 10 p. m. (use HH en el formato de 24 horas)

Esto imprimiría lo que parece que pretendías hacer:

SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy hh:mm:ss z");
System.out.println(sdf.format(c1.getTime()));

sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
System.out.println(sdf.format(c1.getTime()));

El concepto de ‘milisegundos UTC’ no tiene sentido. Una cantidad de milisegundos es solo un punto fijo en la historia, no tiene una zona horaria asociada. Le agregamos una zona horaria para convertirlo en representaciones legibles por humanos.

editar: Sí, la ambigüedad de usar ‘EST’ tanto para la hora del este (de EE. UU.) como para la hora del este (australiana) ha sido una trampa en Java desde siempre.

tl; dr

Utilice moderno java.time clases.

ZonedDateTime
.of( 2014 , 1 , 14 , 11 , 12 , 0 , 0 , ZoneId.of( "Australia/Sydney" ) )
.toInstant()
.toEpochMilli() 

1389658320000

Yendo en la otra dirección.

Instant
.ofEpochMilli( 1_389_658_320_000L )  // .toString(): 2014-01-14T00:12:00Z
.atZone( 
    ZoneId.of( "Australia/Sydney" ) 
)                                    // .toString(): 2014-01-14T11:12+11:00[Australia/Sydney]
.format(
    DateTimeFormatter
    .ofPattern ( 
        "dd/MM/uuuu HH:mm:ss z" , 
        new Locale( "en" , "AU" ) 
    )
)

14/01/2014 11:12:00 AEDT

java.time

Está utilizando terribles clases de fecha y hora que se volvieron obsoletas hace años por la adopción de JSR 310 que define el java.time clases.

Tengo curiosidad sobre la zona horaria en Java.

Para su información, un desplazamiento de UTC es simplemente una cantidad de horas, minutos, segundos. Cuando decimos “UTC” o ponemos un Z al final de un string, nos referimos a una compensación de cero horas-minutos-segundos, para la propia UTC.

Una zona horaria es mucho más. Una zona horaria es un historial de cambios pasados, presentes y futuros en el desplazamiento utilizado por las personas de una región en particular. Los políticos de todo el mundo tienen una extraña inclinación por cambiar la compensación de su jurisdicción.

Quiero obtener la hora UTC en milisegundos desde un dispositivo y enviarla al servidor.

Para el momento actual, use Instant. Un Instant internamente es el número de segundos enteros desde la referencia de época del primer momento de 1970 UTC, más una fracción de segundo en nanosegundos.

Instant now = Instant.now() ;  // Capture current moment in UTC.
long millisecondsSinceEpoch = now.toEpochMilli() ;

Yendo en la otra dirección.

Instant instant = Instant.ofEpochMilli( millisecondsSinceEpoch ) ;

El servidor lo convertirá a la zona horaria local …

Especifique la zona horaria deseada / esperada por el usuario.

Si no se especifica ninguna zona horaria, la JVM aplica implícitamente su zona horaria predeterminada actual. Ese valor predeterminado puede cambiar en cualquier momento durante el tiempo de ejecución (!), Por lo que sus resultados pueden variar. Es mejor especificar la zona horaria deseada / esperada explícitamente como argumento. Si es crítico, confirme la zona con su usuario.

Especifique un nombre de zona horaria adecuado en el formato de Continent/Region, tal como America/Montreal, Africa/Casablanca, o Pacific/Auckland. Nunca use la abreviatura de 2-4 letras como EST o IST ya que son no true zonas horarias, no estandarizadas y ni siquiera únicas (!).

ZoneId z = ZoneId.of( "America/Montreal" ) ;  
ZonedDateTime zdt = instant.atZone( z ) ;

… cuando muestra la hora a los usuarios

Localizar automáticamente para el idioma y la cultura del usuario.

Para localizar, especifique:

  • FormatStyle para determinar cuánto tiempo o abreviado debe string ser.
  • Locale para determinar:
    • los lenguaje humano para traducir el nombre del día, el nombre del mes, etc.
    • los normas culturales decidir cuestiones de abreviatura, uso de mayúsculas, puntuación, separadores, etc.

Ejemplo:

Locale l = Locale.CANADA_FRENCH ;   // Or Locale.US, Locale.JAPAN, etc.
DateTimeFormatter f = 
    DateTimeFormatter
    .ofLocalizedDateTime( FormatStyle.FULL )
    .withLocale( l )
;
String output = zdt.format( f );

La zona horaria de mi sistema es Australia / Sydney (UTC + 11:00)

La zona horaria predeterminada actual de su servidor debería ser irrelevante para su programa. Siempre especifique la zona horaria deseada / esperada. Francamente, hacer opcional la zona horaria (y Locale) argumento de los diversos métodos de fecha y hora es uno de los pocos defectos de diseño en java.time estructura.

Sugerencia: Por lo general, es mejor configurar los servidores en UTC como su zona horaria predeterminada actual.

Por cierto, tenga claro que la zona horaria y la configuración regional tienen nada que ver el uno con el otro. Es posible que desee el idioma japonés para mostrar un momento como se ve en Africa/Tunis zona horaria.

ZoneID zAuSydney = ZoneId.of( "Australia/Sydney" ) ;
ZonedDateTime zdt = instant.atZone( zAuSydney ) ;
String output = zdt.format(
    DateTimeFormatter
    .localizedDateTime( FormatStyle.LONG )
    .withLocale( new Locale( "en" , "AU" ) ;
) ;

int año = 2014; …

Tenga en cuenta que java.time utiliza una numeración sensata, a diferencia de las clases heredadas. Los meses son del 1 al 12 de enero a diciembre y los días de la semana son del 1 al 7 de lunes a domingo.

LocalDate ld = LocalDate.of( 2014 , 1 , 14 ) ;
LocalTime lt = LocalTime.of( 11 , 12 ) ;
ZoneId z = ZoneId.of( "Australia/Sydney" ) ;
ZonedDateTime zdt = ZonedDateTime.of( ld , lt , z ) ;

zdt.toString () = 2014-01-14T11: 12 + 11: 00[Australia/Sydney]

En general, es mejor localizar automáticamente para la visualización, como se ve arriba. Pero si insiste, puede codificar un patrón de formato.

Locale locale = new Locale ( "en" , "AU" );
ZoneId z = ZoneId.of ( "Australia/Sydney" );
ZonedDateTime zdt = ZonedDateTime.of ( 2014 , 1 , 14 , 11 , 12 , 0 , 0 , z );

zdt.toString (): 2014-01-14T11: 12 + 11: 00[Australia/Sydney]

Especifique ese patrón de formato suyo.

DateTimeFormatter f = DateTimeFormatter.ofPattern ( "dd/MM/uuuu HH:mm:ss z" , locale );
String output = zdt.format ( f );

salida = 14/01/2014 11:12:00 AEDT

Su pregunta estaba interesada en un recuento de milisegundos desde la época de 1970-01-01T00: 00: 00Z. Así que ajústese de la zona horaria de Australia a UTC. El mismo momento, el mismo punto en la línea de tiempo, diferente hora del reloj de pared.

Instant instant = zdt.toInstant() ;  // Adjust from time zone to UTC.

instant.toString (): 2014-01-14T00: 12: 00Z

Tenga en cuenta la diferencia en la hora del día entre instant y zdt.

Pensé que podría tener 13/01/2014 00:12:00 para c2 porque la hora UTC es 11 horas más tarde que la mía.

➥ Como solicitó, doce minutos después de las 11 a. M. En la zona de Sydney es el mismo momento que doce minutos después de la medianoche en UTC, porque Australia/Sydney en esa fecha es once horas antes de UTC.

Calcule milisegundos desde época.

long millisecondsSinceEpoch = instant.toEpochMilli() ;

Si eres capaz, tienes el poder dejar una reseña acerca de qué te ha gustado de este ensayo.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *