Trabalhando com fusos horários em JavaScript



Recentemente, eu estava trabalhando na tarefa de adicionar fusos horários à biblioteca de calendário JS mantida por minha equipe. Eu conhecia bem o suporte inútil ao fuso horário no JavaScript, mas esperava que a abstração dos objetos de dados existentes facilitasse a solução da maioria das dificuldades.

No entanto, meus sonhos foram pó. Quando me dediquei à tarefa, percebi que é realmente difícil trabalhar com fusos horários nesse idioma. Era extremamente difícil implementar algo mais complicado do que simplesmente formatar a exibição da hora e calcular a data usando operações complexas (funções do calendário). Adquiri uma experiência valiosa na solução desse problema, e isso trouxe novas dificuldades.

Neste artigo, quero discutir o que me deparei e como ele foi resolvido. Enquanto escrevia o texto, percebi que o motivo de todas as dificuldades era meu pouco entendimento do tópico fusos horários. À luz dessa conscientização, sugiro primeiro falar detalhadamente sobre a definição e os padrões e só depois passar para o JavaScript.

O que é um fuso horário?


Um fuso horário é uma região geográfica que usa o mesmo horário local definido pelo governo. Muitos países se relacionam inteiramente a qualquer fuso horário específico e, nos territórios de grandes estados, como a Rússia e os EUA, vários fusos horários são usados. É curioso que, embora a China também seja grande o suficiente, ela aceite apenas um fuso horário. Às vezes, isso leva a situações estranhas quando o nascer do sol na parte ocidental do país começa por volta das 10 da manhã.

GMT, UTC e Offset


GMT


A hora local da Coréia do Sul é designada como GMT+09:00 . GMT significa Greenwich Mean Time (GMT), ou seja, desta vez no relógio do Observatório Real de Greenwich, Reino Unido. Está localizado no meridiano de referência. O sinal de rádio do sistema GMT começou a ser transmitido em 5 de fevereiro de 1924 e se tornou um padrão mundial em 1º de janeiro de 1972.

UTC


Muitas pessoas pensam que GMT e UTC são a mesma coisa, geralmente usando-os como sistemas intercambiáveis. Mas isso é um erro. O sistema UTC surgiu em 1972 como uma maneira de compensar o efeito da rotação da Terra. O sistema é baseado no Tempo Atômico Internacional, calculado pela frequência de vibrações eletromagnéticas dos átomos de césio. Em outras palavras, o UTC é um substituto mais preciso para o GMT. Embora a diferença em tempo real entre os dois sistemas seja muito pequena, é melhor para os desenvolvedores de software confiar no UTC.

Um fato interessante: quando o UTC ainda estava sendo desenvolvido, foi sugerido nos países de língua inglesa o nome de CUT (Tempo Universal Coordenado) e nos países de língua francesa - TUC (Temps Universal Coordonn). No entanto, nenhum dos campos conseguiu vencer e eles concordaram em chamar o sistema de UTC, ortografando as duas opções propostas (C, T e U).

Deslocamento


+09:00 no UTC+09:00 significa que a hora local é de 9 horas à frente da hora UTC padrão. Ou seja, quando são 21h na Coréia do Sul, é meio-dia na região UTC. A diferença entre a hora UTC padrão e a hora local é chamada de "deslocamento", que é expressa em valores positivos ou negativos: +09:00 , -03:00 , etc.

Em muitos países, é costume atribuir nomes únicos aos seus fusos horários. Por exemplo, o fuso horário da Coréia do Sul é chamado KST (Horário Padrão da Coréia), e seu deslocamento é expresso como KST = UTC+09:00 . No entanto, o deslocamento +09:00 usado não apenas pela Coréia do Sul, mas também pelo Japão, Indonésia e muitos outros países; portanto, a relação entre as compensações e correias não é expressa como 1: 1, mas como 1: N. Uma lista de países com um deslocamento de +09:00 é apresentada aqui .

Algumas compensações funcionam não apenas por horas. Por exemplo, na Coréia do Norte, o horário padrão é +08:30 , enquanto na Austrália, em algumas regiões, +8:45 , +09:30 e +10:30 .

Uma lista completa de compensações do UTC está aqui .

Fuso horário! == deslocamento?


Como eu disse, usamos nomes de fuso horário (KST, JST) com deslocamentos como intercambiáveis, sem distinguir entre eles. Mas será errado considerar o mesmo tempo e a mudança de uma região específica. Existem várias razões:

Horário de Verão (DST)


Em alguns países, esse termo é desconhecido, mas em muitos estados o horário de verão é praticado, principalmente na Europa. Para isso, é adotado o termo internacional DST - Horário de verão. Significa mover o relógio no verão por uma hora antes do horário padrão relativo.

Por exemplo, a Califórnia usa PST (horário padrão do Pacífico) no inverno e PDT (horário de verão do Pacífico, UTC-07:00 ) no inverno. Nos Estados Unidos e no Canadá, o termo Horário do Pacífico (PT, Horário do Pacífico) é usado para regiões que usam dois fusos horários.

Quando o horário de verão começa e termina? Tudo depende do país. Por exemplo, nos EUA e no Canadá até 2006, o horário de verão era usado das 2h no primeiro domingo de abril às 12h no último domingo de outubro. E a partir de 2007, o horário de verão começou a contar de 2 noites no segundo domingo de março a 2 noites no primeiro domingo de novembro. Na Europa, diferentes países praticam o uso progressivo do horário de verão, dependendo de cada fuso horário.

Os fusos horários estão mudando?


Cada país determina quais fusos horários usar, para que o horário local possa mudar por qualquer motivo político e / ou econômico. Por exemplo, nos Estados Unidos, as fronteiras do horário de verão foram alteradas em 2007 porque George W. Bush iniciou a política energética em 2005. O Egito e a Rússia costumavam mudar para o horário de verão, mas o abandonam desde 2011.

Em alguns casos, o governo pode alterar não apenas o horário de verão, mas também o horário padrão. Por exemplo, Samoa costumava usar o deslocamento UTC-10:00 , mas depois mudou para UTC+14:00 para reduzir as perdas no comércio devido à diferença de horário com a Austrália e a Nova Zelândia. Essa decisão levou à perda de vidas do país durante um dia inteiro - 30 de dezembro de 2011, conforme noticiado por jornais de todo o mundo .

Na Holanda, o deslocamento +0:19:32.13 usado desde 1909, de 1937 o país passou para +00:20 e de 1940 para +01:00 , desde então, a hora principal não mudou lá.

Fuso horário 1: deslocamento N


Portanto, o fuso horário pode ter uma ou mais compensações. O horário aceito como padrão depende das atuais razões políticas e / ou econômicas em um país específico.

Na vida cotidiana, isso não causa dificuldades até você tentar sistematizar esses dados com base em algumas regras. Imagine que você deseja definir o horário padrão no seu smartphone usando algum tipo de viés. Se você mora em uma região onde o horário de verão é praticado, seu smartphone deve saber exatamente quando voltar e voltar. Ou seja, você precisa estabelecer o relacionamento entre o horário padrão e o verão em um fuso horário (por exemplo, horário do Pacífico).

Mas isso não pode ser feito com algumas regras simples. Por exemplo, quando nos EUA em 2007 o início e o fim do horário de verão foram alterados, em 31 de maio de 2006 era para usar PDT ( -07:00 ) como horário padrão e em 31 de março de 2007 - PST ( -08:00 ). Acontece que, para se referir a um fuso horário específico, você precisa conhecer todo o histórico da alteração nos fusos horários ou a data da alteração das regras de horário de verão.

Você pode dizer: "O fuso horário em Nova York é PST ( -08:00 )." No entanto, é necessário esclarecer: "O fuso horário atual em Nova York é PST". Além disso, para uma implementação precisa do sistema, você precisa usar uma expressão ainda mais precisa. Esqueça o termo "fuso horário". Você tem que dizer: "Agora em Nova York, o PST é usado como horário padrão".

Então, o que devemos usar em vez do deslocamento para determinar o fuso horário de uma região específica? O nome desta região. Mais precisamente, você deve agrupar em um único fuso horário as regiões nas quais eles alternam igualmente para o verão e o horário padrão. Você pode usar nomes como PT (horário do Pacífico), mas eles combinam apenas o horário padrão atual e o horário de verão e não levam necessariamente em conta todas as alterações históricas. Além disso, como a PT está em circulação apenas nos EUA e no Canadá, você precisa confiar nos padrões estabelecidos de organizações respeitáveis ​​para garantir a universalidade do seu software.

Banco de dados de fuso horário da IANA


Devo admitir que as informações sobre fusos horários são um banco de dados, não um conjunto de regras, porque essas informações devem conter todas as alterações históricas relevantes. Existem vários bancos de dados padrão projetados para resolver tarefas relacionadas a fusos horários. Na maioria das vezes, o banco de dados de fuso horário da IANA é usado e geralmente é chamado de banco de dados tz (ou tzdata). O banco de dados contém dados históricos sobre alterações no horário padrão e no horário de verão em todo o mundo. Além disso, está organizado para que seja possível verificar todos os dados históricos e garantir que a hora seja precisa a partir da hora do Unix ( 1970.01/01 00:00:00 ). Embora você possa encontrar informações no banco de dados antes de 1970, sua precisão não é garantida.

A convenção de nomenclatura usa a regra de região / local. O nome do continente ou oceano (Ásia, América, Oceano Pacífico) é geralmente usado como região e os nomes das principais cidades (Seul, Nova York) como local. O motivo é que as cidades geralmente duram mais que os países. Por exemplo, o fuso horário da Coréia do Sul é Asia/Seoul e Asia/Tokyo . Embora os dois países usem o mesmo deslocamento UTC+09:00 , a hora local mudou de forma diferente, portanto foram divididos em fusos horários diferentes.

A base da IANA é administrada por muitas comunidades de desenvolvedores e historiadores. Os dados históricos recentemente encontrados são inseridos imediatamente e as políticas atuais são atualizadas, para que o banco de dados hoje possa ser considerado a fonte mais confiável. Além disso, é usado em muitos sistemas Unix, incluindo Linux e MacOS, além de várias linguagens de programação populares, incluindo Java e PHP.

Observe que o Windows usa o banco de dados da Microsoft . No entanto, é impreciso em termos de dados históricos e é suportado apenas pela própria Microsoft. Portanto, a base é menos confiável que a base da IANA.

JavaScript e IANA


A funcionalidade relacionada ao fuso horário é implementada em JavaScript do nada. Por padrão, o idioma usa o cinto de região atual (mais precisamente, o cinto selecionado durante a instalação do SO) e não há como alterá-lo. Além disso, até as especificações do padrão de banco de dados em JavaScript são vagas, e você mesmo entenderá isso se decidir lidar com as especificações do ES2015. Sobre o fuso horário local e a disponibilidade do horário de verão, existem apenas algumas declarações vagas. Por exemplo, o horário de verão é definido da seguinte forma: ECMAScript 2015 - Ajuste do horário de verão .

Esse é um algoritmo dependente da implementação que usa as melhores informações de fuso horário disponíveis para determinar a configuração de horário local de verão do DaylightSavingTA (t), calculada em milissegundos. Uma implementação do ECMAScript deve ajudá-lo a determinar melhor sua configuração de horário de verão local.

Parece que eles apenas dizem: "Cara, tente fazer isso funcionar". Entre outras coisas, você deve resolver o problema de compatibilidade com diferentes navegadores. Você diz: “Que bagunça!” E então leia a seguinte linha:

Nota: recomendamos que você use as informações do banco de dados de fuso horário da IANA http://www.iana.org/time-zones/ em suas implementações.

Sim As especificações da ECMA dão a você uma recomendação tão despretensiosa de usar o banco de dados da IANA, porque o JavaScript não possui um banco de dados padrão especial. Como resultado, diferentes navegadores usam suas próprias operações com fusos horários, que geralmente são incompatíveis entre si, para calcular o horário. Posteriormente, na ECMA, para a API internacional, foi Intl.DateTimeFormat a opção de usar dados da IANA no formato ECMA-402 Intl.DateTimeFormat . Mas essa opção é muito menos confiável que os análogos em outras linguagens de programação.

Fuso horário no ambiente servidor-cliente


Considere um cenário simples: precisamos determinar o fuso horário. Suponha que criamos um calendário que processe informações de tempo. Quando um usuário no ambiente do cliente digita uma data e hora na janela de registro, a data é transferida para o servidor e armazenada no banco de dados. Em seguida, o cliente recebe do servidor a data registrada na programação para exibição na tela.

Aqui você precisa decidir alguma coisa. E se alguns dos clientes que acessam o servidor estiverem em fusos horários diferentes? O evento na programação, que foi registrado em Seul em 10 de março de 2017 às 23h30, em Nova York deve ser exibido em 10 de março de 2017 às 9h30. Para que o servidor atenda clientes de fusos horários diferentes, a programação armazenada nele deve conter valores absolutos que não dependem do fuso horário. Cada servidor tem sua própria maneira de armazenar esses valores. Essa questão está além do escopo do artigo, pois tudo depende de um servidor ou banco de dados específico. Em geral, a data e a hora transmitidas do cliente para o servidor devem ser apresentadas na forma de valores com base em um único deslocamento (geralmente UTC), ou na forma de valores contendo informações sobre o fuso horário do ambiente do cliente.

Normalmente, esses dados são transmitidos como hora Unix no formato UTC ou de acordo com o padrão ISO-8601 com informações de deslocamento. Se, no nosso exemplo, convertermos Seul 21.30 em 10 de março de 2017 para hora do Unix, obteremos o valor inteiro 1489113000 . E no formato ISO-8601, o valor da sequência é 2017–03–10T11:30:00+09:00 .

Se você usa JavaScript em um ambiente de navegador, deve converter o valor digitado conforme descrito acima e depois convertê-lo novamente para corresponder ao fuso horário do usuário. É necessário resolver esses dois problemas. Do ponto de vista da linguagem de programação, a primeira operação é chamada "análise" e a segunda é "formatação". Agora vamos ver como isso é feito em JavaScript.

Mesmo quando você trabalha com JS em um ambiente de servidor usando o Node.js, pode ser necessário analisar os dados recebidos do cliente. Porém, como os fusos horários dos servidores e bancos de dados geralmente são sincronizados e a formatação é atribuída aos clientes, em um ambiente de navegador, você precisa decidir sobre vários fatores. Além disso, explicarei em relação ao ambiente do navegador.

Objeto de data JavaScript


As tarefas que envolvem trabalho com um determinado horário ou horário são resolvidas usando o objeto Date . Este é um objeto nativo definido no ECMAScript, como Array ou Function . Isto é, na maioria das vezes, é implementado usando código nativo como C ++. A API está bem descrita na documentação do MDN . A classe java.util.Date do Java teve um grande impacto no objeto, por isso herdou algumas propriedades indesejáveis, como as características dos dados mutáveis ​​e um mês a partir do zero.

Sob o capô, um objeto Date JavaScript funciona com o tempo usando valores absolutos no formato de horário Unix. Mas construtores e métodos como as funções parse() , getHour() , setHour() e outros são afetados pelo fuso horário do cliente (mais precisamente, o cinto especificado no SO no qual o navegador está sendo executado). Portanto, se você criar um objeto Date diretamente usando dados inseridos pelo usuário, o fuso horário local do cliente será refletido nesses dados.

Como mencionei, o JavaScript não fornece nenhuma maneira de alterar arbitrariamente o fuso horário. Portanto, assumimos que podemos usar diretamente o valor do fuso horário especificado no navegador.

Criando um objeto de data usando a entrada do usuário


Vamos voltar ao primeiro exemplo. Suponha que um usuário inseriu o horário de Seul em seu dispositivo às 11h30 de 11 de março de 2017. Esses dados são salvos em cinco números: 2017, 2, 11, 11 e 30 - ano, mês, dia, horas e minutos, respectivamente (desde que o mês começa em 0, o valor deve ser 3–1 = 2). Usando o construtor, você pode criar facilmente um objeto Date :

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

Se você observar o valor retornado por d1.toString() , verá que o valor absoluto do objeto criado é 11.00 de 11 de março de 2017, foi calculado usando a +09:00 (KST).

Você pode usar dados de sequência no construtor. Se você aplicá-los a Date , o objeto chama internamente Date.parse() e Date.parse() valor correto. Esse recurso suporta as especificações RFC2888 e ISO-8601 . Mas a documentação MDN para Date.parse () diz que o valor retornado por esse método depende do navegador, e o formato do tipo de seqüência de caracteres pode afetar o valor final exato. Portanto, é melhor não usar esse método. Por exemplo, no Safari e no Internet Explorer, um valor de sequência como 2015–10–12 12:00:00 retornará NaN , enquanto no Chrome e Firefox ele retornará o fuso horário local. Em algumas situações, um valor baseado em UTC é retornado.

Criando um objeto de data usando dados do servidor


Suponha que você deseja receber dados de um servidor. Se eles estiverem na forma de hora numérica do Unix, você poderá simplesmente usar o construtor para criar um objeto Date . Ainda não mencionei que, quando o construtor Date recebe um único valor como um único parâmetro, ele calcula o valor do tempo do Unix em milissegundos (nota: JS processa o tempo do Unix em milissegundos. Isso significa que o segundo valor deve ser multiplicado por 1000). Ao executar o código a seguir, obtemos o mesmo valor do exemplo anterior:

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

E se, em vez do tempo Unix, use o tipo de string ISO-8601? Como expliquei acima, o método Date.parse() se torna não confiável e é melhor não usá-lo. No entanto, a partir do ECMAScript 5, é possível usar construções de seqüência de caracteres no formato ISO-8601 no construtor Date no Internet Explorer 9.0 e superior.

Se você não estiver usando o navegador mais recente, verifique se o Z no final dos valores. Sem ele, seu navegador antigo pode interpretar o valor com base na hora local, não no UTC. Aqui está um exemplo de uso no 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" 

De acordo com a especificação, em ambos os casos o mesmo valor deve ser obtido. Mas eles são diferentes. Em um navegador posterior, os valores serão os mesmos. Para evitar esse problema, sempre adicione Z no final da linha se as informações de fuso horário não estiverem disponíveis.

Criando uma data para passar para o servidor


Agora você pode usar a Date criada anteriormente, recuperar ou adicionar livremente a hora com base nos fusos horários locais. Somente no final do processamento, não esqueça de converter os dados para o formato anterior antes de devolvê-los ao servidor.

Se for a hora do Unix, você poderá usar o método getTime() (não se esqueça dos milissegundos).

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

Que tal um valor de sequência no formato ISO-8601? Como eu disse, o Internet Explorer 9.0 e superior oferecem suporte ao ECMAScript 5 e versões posteriores suportam o ISO-8601. Portanto, usando os toISOString() ou toJSON() , é possível criar uma sequência no ISO-8601 ( toJSON() pode ser usado para chamadas recursivas com JSON.stringify() e outros). Ambos os métodos dão o mesmo resultado, exceto ao processar dados invá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 

Você pode usar os toGMTString() ou toUTCString() para criar uma string UTC. Isso resultará em um valor que esteja em conformidade com a RFC-1123 .

O objeto Date inclui toString() , toLocaleString() e seus métodos de extensão. Mas eles são de pouca utilidade, pois são usados ​​principalmente para retornar seqüências de caracteres com base no fuso horário local, e os valores retornados dependem do navegador e do SO.

Alterar seu fuso horário local


Como você pode ver, existe algum tipo de suporte a fuso horário no 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 ? Não. 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, . .

Conclusão


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

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


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


All Articles