tzupdater jre for downloads developers java timezone dst java-time

java - for - jre 11 download



Obteniendo ZoneId desde SimpleTimeZone (3)

Aquí prefiero la solución porque es simple, pero necesita una fecha y una zona horaria como parámetros para recuperar el ZoneId

private ZoneId getZoneOffsetFor(final Date date, final TimeZone timeZone){ int offsetInMillis = getOffsetInMillis(date, timeZone); return ZoneOffset.ofTotalSeconds( offsetInMillis / 1000 ); } private int getOffsetInMillis(final Date date, final TimeZone timeZone){ int offsetInMillis = timeZone.getRawOffset(); if(timeZone.inDaylightTime(date)){ offsetInMillis += timeZone.getDSTSavings(); } return offsetInMillis; }

Usando Java tengo una instancia de SimpleTimeZone con la información GMT de compensación y horario de verano de un sistema heredado.

Me gustaría recuperar ZoneId para poder usar la API de Java 8 time.

En realidad, toZoneId devuelve un ZoneId sin las informaciones de horario de verano

SimpleTimeZone stz = new SimpleTimeZone( 2 * 60 * 60 * 1000, "GMT", Calendar.JANUARY,1,1,1, Calendar.FEBRUARY,1,1,1, 1 * 60 * 60 * 1000); stz.toZoneId();


No creo que esto sea posible. Uno también puede preguntar qué sentido tendría. Si un huso horario no está en la base de datos de Olson, apenas se usa en el mundo real, entonces, ¿qué buen uso podría tener en su programa? Comprendo, por supuesto, el caso en el que su programa heredado crea un SimpleTimeZone que representa un huso horario utilizado en la naturaleza, pero solo le da un ID incorrecto para que TimeZone.toZoneId() no pueda realizar la traducción correcta.

Revisé el código fuente de TimeZone.toZoneId() . Se basa únicamente en el valor obtenido del método getID . No mira las reglas de compensación o zona. Y, lo que es más importante, SimpleTimeZone no anula el método. Por lo tanto, parece que si SimpleTimeZone tiene una ID conocida (incluidas las abreviaturas fruncidas como EST o ACT), obtendrá el ZoneId correcto. De lo contrario, no lo harás.

Así que supongo que su mejor opción es averiguar cuál es la identificación correcta de zona horaria para la zona horaria que su código heredado intenta darle, y luego obtener su ZoneId de ZoneId.of(String) . O mejor, construya su propio mapa de alias que contenga la ID o las ID de su código heredado y la ID / s moderna correspondiente, y luego use ZoneId.of(String, Map<String,String>) .

Un posible intento de automatizar la traducción sería iterar a través de las zonas horarias disponibles (que TImeZone.getTimeZone(String) través de TimeZone.getAvailableIDs() y TImeZone.getTimeZone(String) ) y compararlas con cada una que use hasSameRules() . A continuación, cree su ZoneId partir de la cadena ID o la TimeZone obtenida de ella.

Lo siento, esta no fue la respuesta que esperabas.


Primero que nada, cuando lo haces:

SimpleTimeZone stz = new SimpleTimeZone(2 * 60 * 60 * 1000, "GMT", Calendar.JANUARY, 1, 1, 1, Calendar.FEBRUARY, 1, 1, 1, 1 * 60 * 60 * 1000);

Está creando una zona horaria con ID igual a "GMT" . Cuando llamas a toZoneId() , solo llama a ZoneId.of("GMT") (usa la misma ID como parámetro, como ya se dijo en la respuesta de @Ole VV ). Y, a continuación, la clase ZoneId carga las informaciones de Daylight Saving configuradas en la JVM (no mantiene las mismas reglas del objeto SimpleTimeZone original).

Y de acuerdo con ZoneId javadoc : si el ID de la zona es igual a ''GMT'' , ''UTC'' o ''UT'', entonces el resultado es un ZoneId con el mismo ID y las mismas reglas que ZoneOffset.UTC . Y ZoneOffset.UTC no tiene reglas de DST en absoluto.

Por lo tanto, si desea tener una instancia de ZoneId con las mismas reglas de horario de verano, tendrá que crearlas a mano (no sabía que era posible, pero en realidad lo es, consulte a continuación).

Sus reglas de horario de verano

En cuanto a SimpleTimeZone javadoc , la instancia que creó tiene las siguientes reglas (de acuerdo con mis pruebas):

  • el desplazamiento estándar es +02:00 (2 horas por delante UTC / GMT)
  • El horario de verano comienza el primer domingo de enero (eche un vistazo al javadoc para obtener más detalles), 1 milisegundo después de la medianoche (aprobó 1 como horas de inicio y fin)
  • cuando está en horario de verano, el desplazamiento cambia a +03:00
  • El horario de verano termina el primer domingo de febrero, 1 milisegundo después de la medianoche (a continuación, la compensación vuelve a +02:00 )

De hecho, de acuerdo con javadoc, debería haber pasado un número negativo en los parámetros de dayOfWeek para que funcione de esta manera, por lo que la zona horaria debería crearse así:

stz = new SimpleTimeZone(2 * 60 * 60 * 1000, "GMT", Calendar.JANUARY, 1, -Calendar.SUNDAY, 1, Calendar.FEBRUARY, 1, -Calendar.SUNDAY, 1, 1 * 60 * 60 * 1000);

Pero en mis pruebas, ambas funcionaron de la misma manera (tal vez corrige los valores no negativos). De todos modos, hice algunas pruebas solo para verificar estas reglas. Primero creé un SimpleDateFormat con su zona horaria personalizada:

TimeZone t = TimeZone.getTimeZone("America/Sao_Paulo"); SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss Z"); sdf.setTimeZone(t);

Luego probé con las fechas límites (antes del inicio y el final del horario de verano):

// starts at 01/01/2017 (first Sunday of January) ZonedDateTime z = ZonedDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.ofHours(2)); // 01/01/2017 00:00:00 +0200 (not in DST yet, offset is still +02) System.out.println(sdf.format(new Date(z.toInstant().toEpochMilli()))); // 01/01/2017 01:01:00 +0300 (DST starts, offset changed to +03) System.out.println(sdf.format(new Date(z.plusMinutes(1).toInstant().toEpochMilli()))); // ends at 05/02/2017 (first Sunday of February) z = ZonedDateTime.of(2017, 2, 5, 0, 0, 0, 0, ZoneOffset.ofHours(3)); // 05/02/2017 00:00:00 +0300 (in DST yet, offset is still +03) System.out.println(sdf.format(new Date(z.toInstant().toEpochMilli()))); // 04/02/2017 23:01:00 +0200 (DST ends, offset changed to +02 - clock moves back 1 hour: from midnight to 11 PM of previous day) System.out.println(sdf.format(new Date(z.plusMinutes(1).toInstant().toEpochMilli())));

El resultado es:

01/01/2017 00:00:00 +0200
01/01/2017 01:01:00 +0300
05/02/2017 00:00:00 +0300
02/04/2017 23:01:00 +0200

Por lo tanto, sigue las reglas descritas anteriormente (01/01/2017 a medianoche el desplazamiento es +0200 , un minuto después está en horario de verano (el desplazamiento es ahora de +0300 , ocurre lo contrario a 05/02 (el horario de verano finaliza y el desplazamiento se remonta a +0200 )).

Crea un ZoneId con las reglas anteriores

Desafortunadamente no puede extender ZoneId y ZoneOffset , y tampoco puede cambiarlos porque ambos son inmutables. Pero es posible crear reglas personalizadas y asignarlas a un nuevo ZoneId .

Y no parece tener una forma de exportar directamente las reglas desde SimpleTimeZone a ZoneId , por lo que tendrá que crearlas a mano.

Primero, necesitamos crear un ZoneRules , una clase que contiene todas las reglas sobre cuándo y cómo cambia el desplazamiento. Para crearlo, necesitamos construir una lista de 2 clases:

  • ZoneOffsetTransition : define una fecha específica para un cambio de desplazamiento. Debe haber al menos uno para que funcione (con una lista vacía falló)
  • ZoneOffsetTransitionRule : define una regla general, no limitada a una fecha específica (como "primer domingo de enero, el desplazamiento cambia de X a Y" ). Debemos tener 2 reglas (una para el inicio de DST y otra para el fin de DST)

Entonces, vamos a crearlos:

// offsets (standard and DST) ZoneOffset standardOffset = ZoneOffset.ofHours(2); ZoneOffset dstOffset = ZoneOffset.ofHours(3); // you need to create at least one transition (using a date in the very past to not interfere with the transition rules) LocalDateTime startDate = LocalDateTime.MIN; LocalDateTime endDate = LocalDateTime.MIN.plusDays(1); // DST transitions (date when it happens, offset before and offset after) - you need to create at least one ZoneOffsetTransition start = ZoneOffsetTransition.of(startDate, standardOffset, dstOffset); ZoneOffsetTransition end = ZoneOffsetTransition.of(endDate, dstOffset, standardOffset); // create list of transitions (to be used in ZoneRules creation) List<ZoneOffsetTransition> transitions = Arrays.asList(start, end); // a time to represent the first millisecond after midnight LocalTime firstMillisecond = LocalTime.of(0, 0, 0, 1000000); // DST start rule: first Sunday of January, 1 millisecond after midnight ZoneOffsetTransitionRule startRule = ZoneOffsetTransitionRule.of(Month.JANUARY, 1, DayOfWeek.SUNDAY, firstMillisecond, false, TimeDefinition.WALL, standardOffset, standardOffset, dstOffset); // DST end rule: first Sunday of February, 1 millisecond after midnight ZoneOffsetTransitionRule endRule = ZoneOffsetTransitionRule.of(Month.FEBRUARY, 1, DayOfWeek.SUNDAY, firstMillisecond, false, TimeDefinition.WALL, standardOffset, dstOffset, standardOffset); // list of transition rules List<ZoneOffsetTransitionRule> transitionRules = Arrays.asList(startRule, endRule); // create the ZoneRules instance (it''ll be set on the timezone) ZoneRules rules = ZoneRules.of(start.getOffsetAfter(), end.getOffsetAfter(), transitions, transitions, transitionRules);

No pude crear una ZoneOffsetTransition que comienza en el primer milisegundo después de la medianoche (en realidad comienzan exactamente a medianoche), porque la fracción de segundos debe ser cero (si no lo es, ZoneOffsetTransition.of() arroja una excepción). Entonces, decidí establecer una fecha en el pasado ( LocalDateTime.MIN ) para no interferir con las reglas.

Pero las instancias de ZoneOffsetTransitionRule funcionan exactamente como se esperaba (el horario de verano comienza y termina 1 milisegundo después de la medianoche, al igual que la instancia de SimpleTimeZone ).

Ahora debemos establecer este ZoneRules a una zona horaria. Como dije, ZoneId no se puede extender (el constructor no es público) ni tampoco ZoneOffset (es una clase final ). Inicialmente pensé que la única forma de establecer las reglas era crear una instancia y configurarla mediante la reflexión, pero en realidad la API proporciona una forma de crear ZoneId ''s al extender la clase java.time.zone.ZoneRulesProvider :

// new provider for my custom zone id''s public class CustomZoneRulesProvider extends ZoneRulesProvider { @Override protected Set<String> provideZoneIds() { // returns only one ID return Collections.singleton("MyNewTimezone"); } @Override protected ZoneRules provideRules(String zoneId, boolean forCaching) { // returns the ZoneRules for the custom timezone if ("MyNewTimezone".equals(zoneId)) { ZoneRules rules = // create the ZoneRules as above return rules; } return null; } // returns a map with the ZoneRules, check javadoc for more details @Override protected NavigableMap<String, ZoneRules> provideVersions(String zoneId) { TreeMap<String, ZoneRules> map = new TreeMap<>(); ZoneRules rules = getRules(zoneId, false); if (rules != null) { map.put(zoneId, rules); } return map; } }

Tenga en cuenta que no debe establecer el ID en "GMT", "UTC" ni en ningún ID válido (puede verificar todos los ID existentes con ZoneId.getAvailableZoneIds() ). "GMT" y "UTC" son nombres especiales utilizados internamente por la API y pueden conducir a un comportamiento inesperado. Así que elija un nombre que no exista; he elegido MyNewTimezone (sin espacios, de lo contrario, fallará porque ZoneRegion arroja una excepción si hay un espacio en el nombre).

Probemos esta nueva zona horaria. La nueva clase debe registrarse utilizando el método ZoneRulesProvider.registerProvider :

// register the new zonerules provider ZoneRulesProvider.registerProvider(new CustomZoneRulesProvider()); // create my custom zone ZoneId customZone = ZoneId.of("MyNewTimezone"); DateTimeFormatter fmt = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss Z"); // starts at 01/01/2017 (first Sunday of January) ZonedDateTime z = ZonedDateTime.of(2017, 1, 1, 0, 0, 0, 0, customZone); // 01/01/2017 00:00:00 +0200 (not in DST yet, offset is still +02) System.out.println(z.format(fmt)); // 01/01/2017 01:01:00 +0300 (DST starts, offset changed to +03) System.out.println(z.plusMinutes(1).format(fmt)); // ends at 05/02/2017 (first Sunday of February) z = ZonedDateTime.of(2017, 2, 5, 0, 0, 0, 0, customZone); // 05/02/2017 00:00:00 +0300 (in DST yet, offset is still +03) System.out.println(z.format(fmt)); // 04/02/2017 23:01:00 +0200 (DST ends, offset changed to +02 - clock moves back 1 hour: from midnight to 11 PM of previous day) System.out.println(z.plusMinutes(1).format(fmt));

El resultado es el mismo (por lo que las reglas son las mismas que usa SimpleTimeZone ):

01/01/2017 00:00:00 +0200
01/01/2017 01:01:00 +0300
05/02/2017 00:00:00 +0300
02/04/2017 23:01:00 +0200

Notas:

  • CustomZoneRulesProvider crea solo un nuevo ZoneId , pero por supuesto puede ampliarlo para crear más. Consulte el javadoc para obtener más detalles sobre cómo corregir la implementación de su propio proveedor de reglas.
  • Debe verificar exactamente cuáles son las reglas de sus zonas horarias personalizadas antes de crear las ZoneRules . Una forma es usar el método SimpleTimeZone.toString() (que devuelve el estado interno del objeto) y leer el javadoc para saber cómo los parámetros afectan las reglas.
  • No he probado suficientes casos para saber si hay alguna fecha específica donde las reglas SimpleTimeZone y ZoneId comporten de una manera diferente. He probado algunas fechas en diferentes años y parece estar funcionando bien.