Trabajando con zonas horarias en JavaScript



Recientemente, estaba trabajando en la tarea de agregar zonas horarias a la biblioteca de calendario JS mantenida por mi equipo. Era muy consciente del soporte de zona horaria inútil en JavaScript, pero esperaba que abstraer objetos de datos existentes facilitaría la resolución de la mayoría de las dificultades.

Sin embargo, mis sueños se hicieron polvo. Cuando profundicé en la tarea, me di cuenta de que es realmente difícil trabajar con zonas horarias en este idioma. Implementar algo más complicado que simplemente formatear la visualización de la hora y calcular la fecha usando operaciones complejas (funciones de calendario) fue extremadamente difícil. Adquirí una valiosa experiencia en la solución de este problema, y ​​esto conllevó nuevas dificultades.

En este artículo quiero discutir lo que encontré y cómo se resolvió. Mientras escribía el texto, me di cuenta de que la razón de todas las dificultades era mi pobre comprensión del tema de las zonas horarias. A la luz de esta conciencia, propongo hablar primero en detalle sobre la definición y los estándares, y solo luego pasar a JavaScript.

¿Qué es una zona horaria?


Una zona horaria es una región geográfica que utiliza la misma hora local establecida por el gobierno. Muchos países se relacionan por completo con cualquier zona horaria en particular, y en los territorios de los grandes estados, como Rusia y Estados Unidos, se utilizan varias zonas horarias. Es curioso que aunque China también es lo suficientemente grande, solo acepta una zona horaria. A veces esto lleva a situaciones extrañas cuando el amanecer en la parte occidental del país comienza alrededor de las 10 de la mañana.

GMT, UTC y Offset


GMT


La hora local de Corea del Sur se designa como GMT+09:00 . GMT significa Greenwich Mean Time (GMT), es decir, esta vez en el reloj del Observatorio Real de Greenwich, Reino Unido. Se encuentra en el primer meridiano. La señal de radio del sistema GMT comenzó a transmitirse el 5 de febrero de 1924, y se convirtió en un estándar mundial el 1 de enero de 1972.

UTC


Mucha gente piensa que GMT y UTC son lo mismo, usándolos a menudo como sistemas intercambiables. Pero esto es un error. El sistema UTC apareció en 1972 como una forma de compensar el efecto de la rotación de la Tierra. El sistema se basa en el tiempo atómico internacional, calculado por la frecuencia de las vibraciones electromagnéticas de los átomos de cesio. En otras palabras, UTC es un reemplazo más preciso para GMT. Aunque la diferencia en tiempo real entre los dos sistemas es muy pequeña, es mejor que los desarrolladores de software confíen en UTC.

Un hecho interesante: cuando UTC aún se estaba desarrollando, se sugirió en los países de habla inglesa que se llamara CUT (Tiempo Universal Coordinado), y en los países de habla francesa - TUC (Coordinación Universal de Temps). Sin embargo, ninguno de los campamentos pudo ganar, y acordaron llamar al sistema UTC, deletreando las dos opciones propuestas (C, T y U).

Offset


+09:00 en UTC+09:00 significa que la hora local está 9 horas por delante de la hora UTC estándar. Es decir, cuando son las 9 p.m. en Corea del Sur, es mediodía en la región UTC. La diferencia entre la hora UTC estándar y la hora local se denomina "desplazamiento", que se expresa como valores positivos o negativos: +09:00 , -03:00 , etc.

En muchos países, es costumbre dar a sus zonas horarias nombres únicos. Por ejemplo, la zona horaria de Corea del Sur se llama KST (hora estándar de Corea), su desplazamiento se expresa como KST = UTC+09:00 . Sin embargo, la compensación +09:00 utilizada no solo por Corea del Sur, sino también por Japón, Indonesia y muchos otros países, por lo tanto, la relación entre las compensaciones y los cinturones no se expresa como 1: 1, sino como 1: N. Aquí se presenta una lista de países con una compensación de +09:00 .

Algunas compensaciones operan no solo por horas. Por ejemplo, en Corea del Norte, el tiempo estándar es +08:30 , mientras que en Australia en algunas regiones +10:30 +8:45 , +09:30 y +10:30 .

Una lista completa de compensaciones UTC está aquí .

Zona horaria! == desplazamiento?


Como dije, usamos nombres de zonas horarias (KST, JST) con desplazamientos como intercambiables, sin distinguir entre ellos. Pero será erróneo considerar el mismo tiempo y el cambio de una región en particular. Hay varias razones:

Horario de verano (DST)


En algunos países, este término es desconocido, pero en muchos estados se practica el horario de verano, principalmente en Europa. Para esto, se adopta el término internacional DST: horario de verano. Significa mover el reloj en verano durante una hora antes de la hora estándar relativa.

Por ejemplo, California usa PST (hora estándar del Pacífico) en invierno y PDT (hora de verano del Pacífico, UTC-07:00 ) en invierno. En los Estados Unidos y Canadá, el término Hora del Pacífico (PT, Hora del Pacífico) se usa para las regiones que usan dos zonas horarias.

¿Cuándo comienza y termina el horario de verano? Todo depende del país. Por ejemplo, en EE. UU. Y Canadá hasta 2006, el horario de verano se usó desde las 2 a.m. del primer domingo de abril hasta las 12 a.m. del último domingo de octubre. Y a partir de 2007, el horario de verano comenzó a contar desde 2 noches el segundo domingo de marzo hasta 2 noches el primer domingo de noviembre. En Europa, diferentes países practican el uso progresivo de DST dependiendo de cada zona horaria.

¿Están cambiando las zonas horarias?


Cada país determina qué zonas horarias usar, por lo que la hora local puede cambiar por cualquier razón política y / o económica. Por ejemplo, en los Estados Unidos, las fronteras del horario de verano se cambiaron en 2007 porque George W. Bush inició la política energética en 2005. Egipto y Rusia solían cambiar al horario de verano, pero lo han abandonado desde 2011.

En algunos casos, el gobierno puede cambiar no solo el horario de verano, sino también el horario estándar. Por ejemplo, antes Samoa usaba la compensación UTC-10:00 , pero luego cambiaron a UTC+14:00 para reducir las pérdidas en el comercio debido a la diferencia horaria con Australia y Nueva Zelanda. Esta decisión condujo a la pérdida de vidas del país un día entero, el 30 de diciembre de 2011, según lo informado por los periódicos de todo el mundo .

En los Países Bajos, el desplazamiento +0:19:32.13 ha utilizado desde 1909, desde 1937 el país cambió a +00:20 , y desde 1940 a +01:00 , desde entonces el horario principal no ha cambiado allí.

Zona horaria 1: Offset N


Por lo tanto, la zona horaria puede tener uno o más desplazamientos. El tiempo que se acepta como estándar depende de las razones políticas y / o económicas actuales en un país en particular.

En la vida cotidiana, esto no causa dificultades hasta que intente sistematizar estos datos en función de algunas reglas. Imagine que desea establecer la hora estándar en su teléfono inteligente utilizando algún tipo de sesgo. Si vive en una región donde se practica el horario de verano, entonces su teléfono inteligente debe saber exactamente cuándo ir y venir. Es decir, debe establecer la relación entre el horario estándar y el horario de verano en una zona horaria (por ejemplo, hora del Pacífico).

Pero esto no se puede hacer con un par de reglas simples. Por ejemplo, cuando en EE. UU. En 2007 se modificaron el comienzo y el final del horario de verano, el 31 de mayo de 2006 se suponía que debía usar PDT ( -07:00 ) como hora estándar y el 31 de marzo de 2007 - PST ( -08:00 ). Resulta que para referirse a una zona horaria específica, debe conocer el historial completo del cambio en las zonas horarias o la fecha de cambio de las reglas de horario de verano.

Puede decir: "La zona horaria en Nueva York es PST ( -08:00 )". Sin embargo, debe aclararse: "La zona horaria actual en Nueva York es PST". Además, para una implementación precisa del sistema, debe usar una expresión aún más precisa. Olvídese del término "zona horaria". Debe decir: "Ahora en Nueva York, el PST se usa como hora estándar".

Entonces, ¿qué debemos usar en lugar del desplazamiento para determinar la zona horaria de una región en particular? El nombre de esta región. Más precisamente, debe agrupar en una zona horaria única aquellas regiones en las que cambian por igual al horario de verano y al horario estándar. Puede usar nombres como PT (hora del Pacífico), pero solo combinan la hora estándar actual y la hora de verano, y no necesariamente tienen en cuenta todos los cambios históricos. Además, dado que PT está en circulación solo en los EE. UU. Y Canadá, debe confiar en los estándares establecidos de las organizaciones acreditadas para garantizar la universalidad de su software.

Base de datos de zonas horarias de la IANA


Debo admitir que la información sobre zonas horarias es más bien una base de datos, no un conjunto de reglas, porque esta información debe contener todos los cambios históricos relevantes. Existen varias bases de datos estándar diseñadas para resolver tareas relacionadas con zonas horarias. Con mayor frecuencia, se utiliza la base de datos de zonas horarias de la IANA , y generalmente se denomina base de datos tz (o tzdata). La base de datos contiene datos históricos sobre los cambios en el horario estándar y el horario de verano en todo el mundo. Además, está organizado de modo que sea posible verificar todos los datos históricos y asegurarse de que la hora sea precisa a partir de la hora de Unix ( 1970.01/01 00:00:00 ). Aunque puede encontrar información en la base de datos incluso antes de 1970, su precisión no está garantizada.

La convención de nomenclatura utiliza la regla de región / lugar. El nombre del continente u océano (Asia, América, Océano Pacífico) se usa generalmente como región, y los nombres de las principales ciudades (Seúl, Nueva York) como lugar. La razón es que las ciudades suelen durar más que los países. Por ejemplo, la zona horaria de Corea del Sur es Asia/Seoul y Asia/Tokyo . Aunque ambos países usan la misma compensación UTC+09:00 , su hora local cambió de manera diferente, por lo que se dividieron en diferentes zonas horarias.

La base de IANA está dirigida por muchas comunidades de desarrolladores e historiadores. Los datos históricos recién encontrados se ingresan de inmediato y las políticas actuales se actualizan, por lo que la base de datos actual puede considerarse la fuente más confiable. Además, muchos sistemas Unix, incluidos Linux y MacOS, lo utilizan bajo el capó, así como varios lenguajes de programación populares, como Java y PHP.

Tenga en cuenta que Windows usa la base de datos de Microsoft . Sin embargo, es inexacto en términos de datos históricos y solo es compatible con Microsoft. Por lo tanto, la base es menos confiable que la base IANA.

JavaScript e IANA


La funcionalidad relacionada con la zona horaria se implementa en JavaScript de la nada. De forma predeterminada, el idioma utiliza el cinturón de región actual (más precisamente, el cinturón seleccionado durante la instalación del sistema operativo), y no hay forma de cambiarlo. Además, incluso las especificaciones para el estándar de base de datos en JavaScript son vagas, y usted mismo lo comprenderá si decide tratar con la especificación para ES2015. Sobre la zona horaria local y la disponibilidad de DST, solo hay un par de declaraciones vagas. Por ejemplo, DST se define de la siguiente manera: ECMAScript 2015 - Ajuste del horario de verano .

Este es un algoritmo dependiente de la implementación que utiliza la mejor información de zona horaria disponible para determinar la configuración del horario de verano local DaylightSavingTA (t), que se calcula en milisegundos. Una implementación de ECMAScript debería ayudarlo a determinar mejor su configuración de horario de verano local.

Parece que solo dicen: "Amigos, intenten que esto funcione". Entre otras cosas, debe resolver el problema de compatibilidad con diferentes navegadores. Usted dice: "¡Qué desastre!" Y luego lee la siguiente línea:

Nota: le recomendamos que utilice información de la base de datos de zonas horarias de la IANA http://www.iana.org/time-zones/ en sus implementaciones.

Si Las especificaciones de ECMA le dan la pelota con una recomendación sin pretensiones para usar la base de datos de IANA, porque JavaScript no tiene una base de datos estándar especial. Como resultado, diferentes navegadores usan sus propias operaciones con zonas horarias, que a menudo son incompatibles entre sí, para calcular la hora. Más tarde, en ECMA, para la API internacional, se Intl.DateTimeFormat la opción de usar los datos de IANA en formato ECMA-402 Intl.DateTimeFormat . Pero esta opción es mucho menos confiable que los análogos en otros lenguajes de programación.

Zona horaria en entorno servidor-cliente


Considere un escenario simple: necesitamos determinar la zona horaria. Supongamos que creamos un calendario que procesará la información del tiempo. Cuando un usuario en el entorno del cliente ingresa una fecha y hora en la ventana de registro, la fecha se transfiere al servidor y se almacena en la base de datos. Luego, el cliente recibe del servidor la fecha registrada en el programa para su visualización en la pantalla.

Aquí debes decidir algo. ¿Qué sucede si algunos de los clientes que acceden al servidor están en diferentes zonas horarias? El evento en el calendario, que se registró en Seúl el 10 de marzo de 2017 a las 23.30, en Nueva York debería mostrarse como el 10 de marzo de 2017 a las 9.30. Para que el servidor sirva a clientes de diferentes zonas horarias, la programación almacenada debe contener valores absolutos que no dependen de la zona. Cada servidor tiene su propia forma de almacenar tales valores, esta pregunta está más allá del alcance del artículo, ya que todo depende de un servidor o base de datos específicos. En general, la fecha y la hora transmitidas desde el cliente al servidor deben presentarse en forma de valores basados ​​en un único desplazamiento (generalmente UTC) o en forma de valores que contienen información sobre la zona horaria del entorno del cliente.

Típicamente, dichos datos se transmiten como hora Unix en formato UTC o de acuerdo con el estándar ISO-8601 con información de desplazamiento. Si en nuestro ejemplo convertimos Seúl 21.30 el 10 de marzo de 2017 a la hora de Unix, entonces obtenemos el valor entero 1489113000 . Y en el formato ISO-8601, el valor de la cadena es 2017–03–10T11:30:00+09:00 .

Si usa JavaScript en un entorno de navegador, debe convertir el valor ingresado como se describe anteriormente y luego volver a convertirlo para que coincida con la zona horaria del usuario. Es necesario resolver ambos problemas. Desde el punto de vista del lenguaje de programación, la primera operación se llama "análisis" y la segunda "formateo". Ahora veamos cómo se hace esto en JavaScript.

Incluso cuando trabaja con JS en un entorno de servidor utilizando Node.js, es posible que deba analizar los datos recibidos del cliente. Pero dado que las zonas horarias de los servidores y las bases de datos generalmente están sincronizadas y el formato se asigna a los clientes, en un entorno de navegador debe decidir varios factores. Además explicaré en relación con el entorno del navegador.

Objeto de fecha de JavaScript


Las tareas relacionadas con el trabajo con un tiempo u hora determinados se resuelven utilizando el objeto Date . Este es un objeto nativo definido en ECMAScript, como Array o Function . Es decir, en su mayor parte, se implementa utilizando código nativo como C ++. La API está bien descrita en la documentación de MDN . La clase java.util.Date de Java tuvo un gran impacto en el objeto, por lo que heredó algunas propiedades indeseables, como las características de los datos mutables y un mes a partir de cero.

Bajo el capó, un objeto Date JavaScript funciona con el tiempo utilizando valores absolutos en formato de tiempo Unix. Pero los constructores y métodos como las funciones parse() , getHour() , setHour() y otros se ven afectados por la zona horaria del cliente (más precisamente, el cinturón especificado en el sistema operativo en el que se ejecuta el navegador). Por lo tanto, si crea un objeto Date directamente utilizando datos ingresados ​​por el usuario, la zona horaria local del cliente se reflejará en estos datos.

Como mencioné, JavaScript no proporciona ninguna forma de cambiar arbitrariamente la zona horaria. Por lo tanto, suponemos que podemos usar directamente el valor de zona horaria especificado en el navegador.

Crear un objeto de fecha utilizando la entrada del usuario


Volvamos al primer ejemplo. Supongamos que un usuario ingresó la hora de Seúl en su dispositivo a las 11.30 del 11 de marzo de 2017. Estos datos se guardan en cinco números: 2017, 2, 11, 11 y 30: año, mes, día, horas y minutos, respectivamente (dado que el mes comienza en 0, el valor debe ser 3–1 = 2). Usando el constructor, puede crear fácilmente un objeto Date :

 const d1 = new Date(2017, 2, 11, 11, 30); d1.toString(); // Sat Mar 11 2017 11:30:00 GMT+0900 (KST) 

Si observa el valor devuelto por d1.toString() , verá que el valor absoluto del objeto creado es 11.00 11 de marzo de 2017, se calculó utilizando la mezcla +09:00 (KST).

Puede usar datos de cadena en el constructor. Si los aplica a Date , el objeto llama internamente a Date.parse() y Date.parse() valor correcto. Esta característica es compatible con las especificaciones RFC2888 e ISO-8601 . Pero la documentación de MDN para Date.parse () dice que el valor devuelto por este método depende del navegador, y el formato del tipo de cadena puede afectar el valor final exacto. Por lo tanto, es mejor no usar este método. Por ejemplo, en Safari e Internet Explorer, un valor de cadena como 2015–10–12 12:00:00 devuelve NaN , mientras que en Chrome y Firefox devuelve la zona horaria local. En algunas situaciones, se devuelve un valor basado en UTC.

Crear un objeto de fecha utilizando datos del servidor


Suponga que desea recibir datos de un servidor. Si están en forma de tiempo numérico de Unix, simplemente puede usar el constructor para crear un objeto Date . Todavía no he mencionado que cuando el constructor Date recibe un valor único como un parámetro único, calcula el valor de tiempo Unix en milisegundos (nota: JS procesa el tiempo Unix en milisegundos. Esto significa que el segundo valor debe multiplicarse por 1000). Al ejecutar el siguiente código, obtenemos el mismo valor que en el ejemplo anterior:

 const d1 = new Date(1489199400000); d1.toString(); // Sat Mar 11 2017 11:30:00 GMT+0900 (KST) 

¿Y si en lugar de Unix-time usa el tipo de cadena ISO-8601? Como expliqué anteriormente, el método Date.parse() se vuelve poco confiable y es mejor no usarlo. Sin embargo, comenzando con ECMAScript 5, puede usar construcciones de cadena en el formato ISO-8601 en el constructor de Date en Internet Explorer 9.0 y superior.

Si no está utilizando el navegador más reciente, asegúrese de que la Z al final de los valores. Sin él, su antiguo navegador puede interpretar el valor en función de la hora local, no UTC. Aquí hay un ejemplo de uso en Internet Explorer 10:

 const d1 = new Date('2017-03-11T11:30:00'); const d2 = new Date('2017-03-11T11:30:00Z'); d1.toString(); // "Sat Mar 11 11:30:00 UTC+0900 2017" d2.toString(); // "Sat Mar 11 20:30:00 UTC+0900 2017" 

Según la especificación, en ambos casos se debe obtener el mismo valor. Pero son diferentes. En un navegador posterior, los valores serán los mismos. Para evitar este problema, agregue siempre Z al final de la línea si la información de zona horaria no está disponible.

Crear una fecha para pasar al servidor


Ahora puede usar la Date creada anteriormente, recuperar o agregar la hora libremente según las zonas horarias locales. Solo al final del procesamiento no olvide convertir los datos al formato anterior antes de devolverlos al servidor.

Si es tiempo de Unix, puede usar el método getTime() (no se olvide de milisegundos).

 const d1 = new Date(2017, 2, 11, 11, 30); d1.getTime(); // 1489199400000 

¿Qué pasa con un valor de cadena en formato ISO-8601? Como dije, Internet Explorer 9.0 y superior son compatibles con ECMAScript 5, y las versiones posteriores son compatibles con ISO-8601. Por lo tanto, usando los toISOString() o toJSON() , puede crear una cadena en ISO-8601 ( toJSON() puede usarse para llamadas recursivas con JSON.stringify() y otros). Ambos métodos dan el mismo resultado, excepto cuando se procesan datos no válidos:

 const d1 = new Date(2017, 2, 11, 11, 30); d1.toISOString(); // "2017-03-11T02:30:00.000Z" d1.toJSON(); // "2017-03-11T02:30:00.000Z" const d2 = new Date('Hello'); d2.toISOString(); // Error: Invalid Date d2.toJSON(); // null 

Puede usar los métodos toGMTString() o toUTCString() para crear una cadena UTC. Esto dará como resultado un valor que cumple con RFC-1123 .

El objeto Date incluye toString() , toLocaleString() y sus métodos de extensión. Pero son de poca utilidad, ya que se utilizan principalmente para devolver cadenas basadas en la zona horaria local, y los valores devueltos dependen del navegador y el sistema operativo.

Cambia tu zona horaria local


Como puede ver, hay algún tipo de soporte de zona horaria en JS. ? ? , JS . , . . , .

. . 11.30 11 2017 - . Unix- , - -05:00 . , .

getTimeZoneOffset() . API JavaScript, . :

 const seoul = new Date(1489199400000); seoul.getTimeZoneOffset(); // -540 

-540 , 540 . , ( +09:00 ). , , . -, 60 * 5 = 300 . 840 Date . getXX . :

 function formatDate(date) { return date.getFullYear() + '/' + (date.getMonth() + 1) + '/' + date.getDate() + ' ' + date.getHours() + ':' + date.getMinutes(); } const seoul = new Date(1489199400000); const ny = new Date(1489199400000 - (840 * 60 * 1000)); formatDate(seoul); // 2017/3/11 11:30 formatDate(ny); // 2017/3/10 21:30 

formatDate() -. , . , ? , . , — , . ( ).


, . , -, 11 15 . setDate() Date , , .

 ny.setDate(15); formatDate(ny); // 2017/3/15 21:30 

, . , ? , getTime() getISOString() . , .

 const time = ny.getTime() + (840 * 60 * 1000); // 1489631400000 

, , . Date ? No Date 11- 15-, 4 ( 24 * 4 * 60 * 60 * 1000 ). - 10- 15-, 5 ( 24* 5 * 60 * 60 * 1000 ). .

. , . - 12 , 15 2017 -04:00 , -05:00 . , 780 , 60 , .

 const time = ny.getTime() + (780 * 60 * 1000); // 1489627800000 

, - , .

, . , , , . , , IANA .

, Date , . , . , . . JS . .

Moment Timezone


Moment — JavaScript-, . API , . Moment Timezone , . IANA API, .

. , . .

, Moment Timezone:

 const seoul = moment(1489199400000).tz('Asia/Seoul'); const ny = moment(1489199400000).tz('America/New_York'); seoul.format(); // 2017-03-11T11:30:00+09:00 ny.format(); // 2017-03-10T21:30:00-05:00 seoul.date(15).format(); // 2017-03-15T11:30:00+09:00 ny.date(15).format(); // 2017-03-15T21:30:00-04:00 

seoul , ny -05:00 -04:00 . format() , ISO-8601, . .

Conclusión


API , JavaScript, . , API, Internet Explorer 9 . , . , getTimezoneOffset() . , . Moment Timezone.

, , . : . , , , . , JavaScript . , .

Enlaces utiles


Source: https://habr.com/ru/post/438286/


All Articles