Como ignorar o fuso horário do usuário e forçar Data () a usar fuso horário específico

104

Em um aplicativo JS, recebo o carimbo de data / hora (eq. 1270544790922) Do servidor (Ajax).

Com base nesse carimbo de data / hora, crio o Dateobjeto usando:

var _date = new Date();
_date.setTime(1270544790922);

Agora, _datetimestamp decodificado no fuso horário local do usuário atual. Eu não quero isso.

Gostaria de _ dateconverter este carimbo de data / hora para a hora atual na cidade de Helsinque, na Europa (desconsiderando o fuso horário atual do usuário).

Como eu posso fazer isso?

warpech
fonte
Eu sei que a diferença de fuso horário em Helsinque é +2 no inverno e +3 no horário de verão. Mas quem sabe quando é o horário de verão? Apenas algum mecanismo de localidade que não está disponível em JS
warpech
É possível, mas não usando métodos nativos de Javascript, porque o javascript não tem nenhum método para determinar um histórico de transição de fuso horário de outro fuso horário do que o fuso horário atual do sistema do usuário (e é dependente do navegador pelo menos quando vamos para as datas dos anos 80). Mas assim é possível: stackoverflow.com/a/12814213/1691517 e acho que minha resposta deu o resultado correto.
Timo Kähkönen

Respostas:

64

O valor subjacente de um objeto Date está, na verdade, em UTC. Para provar isso, observe que se você digitar new Date(0)você vai ver algo como: Wed Dec 31 1969 16:00:00 GMT-0800 (PST). 0 é tratado como 0 no GMT, mas o .toString()método mostra a hora local.

Nota importante, UTC significa código de tempo universal . A hora atual agora em 2 lugares diferentes é o mesmo UTC, mas a saída pode ser formatada de forma diferente.

O que precisamos aqui é alguma formatação

var _date = new Date(1270544790922); 
// outputs > "Tue Apr 06 2010 02:06:30 GMT-0700 (PDT)", for me
_date.toLocaleString('fi-FI', { timeZone: 'Europe/Helsinki' });
// outputs > "6.4.2010 klo 12.06.30"
_date.toLocaleString('en-US', { timeZone: 'Europe/Helsinki' });
// outputs > "4/6/2010, 12:06:30 PM"

Isso funciona, mas ... você não pode realmente usar qualquer um dos outros métodos de data para seus propósitos, pois eles descrevem o fuso horário do usuário. O que você deseja é um objeto de data relacionado ao fuso horário de Helsinque. Suas opções neste ponto são usar alguma biblioteca de terceiros (eu recomendo isso), ou hackear o objeto de data para que você possa usar a maioria de seus métodos.

Opção 1 - um terceiro, como fuso horário

moment(1270544790922).tz('Europe/Helsinki').format('YYYY-MM-DD HH:mm:ss')
// outputs > 2010-04-06 12:06:30
moment(1270544790922).tz('Europe/Helsinki').hour()
// outputs > 12

Isso parece muito mais elegante do que o que vamos fazer a seguir.

Opção 2 - hackear o objeto de data

var currentHelsinkiHoursOffset = 2; // sometimes it is 3
var date = new Date(1270544790922);
var helsenkiOffset = currentHelsinkiHoursOffset*60*60000;
var userOffset = _date.getTimezoneOffset()*60000; // [min*60000 = ms]
var helsenkiTime = new Date(date.getTime()+ helsenkiOffset + userOffset);
// Outputs > Tue Apr 06 2010 12:06:30 GMT-0700 (PDT)

Ele ainda pensa que é GMT-0700 (PDT), mas se você não olhar muito fixamente, pode ser capaz de confundir isso com um objeto de data que é útil para seus propósitos.

Eu convenientemente pulei uma parte. Você precisa ser capaz de definir currentHelsinkiOffset. Se você puder usar date.getTimezoneOffset()no lado do servidor ou apenas usar algumas instruções if para descrever quando ocorrerão as alterações de fuso horário, isso deve resolver o seu problema.

Conclusão - acho que, especialmente para esse propósito, você deve usar uma biblioteca de datas como o fuso horário do momento .

Parris
fonte
também ... uma tarefa semelhante pode ser feita, simplesmente usando o deslocamento gmt de um local. Você não precisa de javascript nesse caso.
Parris
desculpe, mas não, quero dizer exatamente o oposto :) Eu editei a pergunta, talvez esteja mais claro agora
warpech
Ok, mudei minha solução. Eu acho que isto é o que você está procurando.
Parris
Infelizmente, essa é a única coisa que eu também inventei. Eu pensei que talvez o navegador pudesse gerar "_helsinkiOffset" para mim.
warpech de
2
Eu acredito que *60*60deveria ser *60000, já que getTime está em milissegundos e getTimezoneOffset está em minutos, dos quais existem 60.000 milissegundos em um minuto, não 60 * 60 == 3600
AaronLS
20

Para contabilizar milissegundos e o fuso horário do usuário, use o seguinte:

var _userOffset = _date.getTimezoneOffset()*60*1000; // user's offset time
var _centralOffset = 6*60*60*1000; // 6 for central time - use whatever you need
_date = new Date(_date.getTime() - _userOffset + _centralOffset); // redefine variable
Ehren
fonte
Para substituir o uso de um deslocamento fixo por central, usei o conceito de criar uma data usando CST com um horário fixo de 00:00 e, em seguida, getUTCHHours dessa data.
grantwparks
+1 Isso funcionou para mim. Não sei como 'a' resposta pode funcionar sem lidar com milissegundos.
Chris Wallis
2
@Ehren você não deveria adicionar timezoneOffset para chegar ao gmt e então subtrair o deslocamento central?
codificador de
15

Apenas outra abordagem

function parseTimestamp(timestampStr) {
  return new Date(new Date(timestampStr).getTime() + (new Date(timestampStr).getTimezoneOffset() * 60 * 1000));
};

//Sun Jan 01 2017 12:00:00
var timestamp = 1483272000000;
date = parseTimestamp(timestamp);
document.write(date);

Felicidades!

Mohanraj Balasubramaniam
fonte
2
Isso pode ter resultados indesejados devido ao horário de verão. Se o cliente estiver em um fuso horário que usa o horário de verão, o resultado da análise pode ficar atrasado em uma hora (algumas zonas usam frações de hora). Ex: o usuário está em NY e hoje é 4 de julho. Isso significa que o usuário está em GMT -0400 (fuso horário de verão do leste desde o início do DST); o carimbo de data / hora passado é para 30 de janeiro, que é GMT-0500 (fuso horário padrão do leste - sem horário de verão nessa época do ano). O resultado será uma hora de atraso, porque getTimezoneOffset () fornece o deslocamento agora, não o que era em janeiro.
Dimitar Darazhanski
2
Para corrigir esse problema, você precisa obter a diferença de horário da data em que está passando (não a mudança de horário atual) : new Date().getTimezoneOffset()deve ser alterada paranew Date(timestampStr).getTimezoneOffset()
Dimitar Darazhanski
13

Tenho a suspeita de que a Resposta não dá o resultado correto. Na pergunta, o autor da pergunta deseja converter a data e hora do servidor para a hora atual no Hellsinki, desconsiderando o fuso horário atual do usuário.

É o fato de que o fuso horário do usuário pode ser qualquer, então não podemos confiar nele.

Se, por exemplo, timestamp é 1270544790922 e temos uma função:

var _date = new Date();
_date.setTime(1270544790922);
var _helsenkiOffset = 2*60*60;//maybe 3
var _userOffset = _date.getTimezoneOffset()*60*60; 
var _helsenkiTime = new Date(_date.getTime()+_helsenkiOffset+_userOffset);

Quando um nova-iorquino visita a página, o alerta (_helsenkiTime) imprime:

Tue Apr 06 2010 05:21:02 GMT-0400 (EDT)

E quando um Finlandês visita a página, o alerta (_helsenkiTime) imprime:

Tue Apr 06 2010 11:55:50 GMT+0300 (EEST)

Portanto, a função está correta apenas se o visitante da página tiver o fuso horário de destino (Europa / Helsinque) em seu computador, mas falhar em quase todas as outras partes do mundo. E como o carimbo de data / hora do servidor geralmente é o carimbo de data / hora UNIX, que é, por definição, em UTC, o número de segundos desde a época do Unix (1 de janeiro de 1970 00:00:00 GMT), não podemos determinar DST ou não DST a partir do carimbo de data / hora.

Portanto, a solução é DISREGARD o fuso horário atual do usuário e implementar alguma forma de calcular o deslocamento UTC, esteja a data em DST ou não. O Javascript não tem um método nativo para determinar o histórico de transição do horário de verão de outro fuso horário diferente do atual do usuário. Podemos fazer isso da forma mais simples usando o script do lado do servidor, porque temos fácil acesso ao banco de dados de fuso horário do servidor com todo o histórico de transição de todos os fusos horários.

Mas se você não tiver acesso ao banco de dados de fuso horário do servidor (ou de qualquer outro servidor) E o carimbo de data / hora estiver em UTC, você pode obter a funcionalidade semelhante codificando as regras de DST em Javascript.

Para cobrir as datas nos anos de 1998 a 2099 na Europa / Helsinque, você pode usar a seguinte função ( jsfiddled ):

function timestampToHellsinki(server_timestamp) {
    function pad(num) {
        num = num.toString();
        if (num.length == 1) return "0" + num;
        return num;
    }

    var _date = new Date();
    _date.setTime(server_timestamp);

    var _year = _date.getUTCFullYear();

    // Return false, if DST rules have been different than nowadays:
    if (_year<=1998 && _year>2099) return false;

    // Calculate DST start day, it is the last sunday of March
    var start_day = (31 - ((((5 * _year) / 4) + 4) % 7));
    var SUMMER_start = new Date(Date.UTC(_year, 2, start_day, 1, 0, 0));

    // Calculate DST end day, it is the last sunday of October
    var end_day = (31 - ((((5 * _year) / 4) + 1) % 7))
    var SUMMER_end = new Date(Date.UTC(_year, 9, end_day, 1, 0, 0));

    // Check if the time is between SUMMER_start and SUMMER_end
    // If the time is in summer, the offset is 2 hours
    // else offset is 3 hours
    var hellsinkiOffset = 2 * 60 * 60 * 1000;
    if (_date > SUMMER_start && _date < SUMMER_end) hellsinkiOffset = 
    3 * 60 * 60 * 1000;

    // Add server timestamp to midnight January 1, 1970
    // Add Hellsinki offset to that
    _date.setTime(server_timestamp + hellsinkiOffset);
    var hellsinkiTime = pad(_date.getUTCDate()) + "." + 
    pad(_date.getUTCMonth()) + "." + _date.getUTCFullYear() + 
    " " + pad(_date.getUTCHours()) + ":" +
    pad(_date.getUTCMinutes()) + ":" + pad(_date.getUTCSeconds());

    return hellsinkiTime;
}

Exemplos de uso:

var server_timestamp = 1270544790922;
document.getElementById("time").innerHTML = "The timestamp " + 
server_timestamp + " is in Hellsinki " + 
timestampToHellsinki(server_timestamp);

server_timestamp = 1349841923 * 1000;
document.getElementById("time").innerHTML += "<br><br>The timestamp " + 
server_timestamp + " is in Hellsinki " + timestampToHellsinki(server_timestamp);

var now = new Date();
server_timestamp = now.getTime();
document.getElementById("time").innerHTML += "<br><br>The timestamp is now " +
server_timestamp + " and the current local time in Hellsinki is " +
timestampToHellsinki(server_timestamp);​

E isso imprime o seguinte, independentemente do fuso horário do usuário:

The timestamp 1270544790922 is in Hellsinki 06.03.2010 12:06:30

The timestamp 1349841923000 is in Hellsinki 10.09.2012 07:05:23

The timestamp is now 1349853751034 and the current local time in Hellsinki is 10.09.2012 10:22:31

Obviamente, se você puder retornar o carimbo de data / hora em uma forma em que o deslocamento (DST ou não DST) já foi adicionado ao carimbo de data / hora no servidor, você não precisa calculá-lo no cliente e pode simplificar muito a função. MAS lembre-se de NÃO usar timezoneOffset (), porque então você terá que lidar com o fuso horário do usuário e este não é o comportamento desejado.

Timo Kähkönen
fonte
2
nb. Helsinque tem apenas um 'l'. Esse erro realmente prejudica essa resposta.
Ben McIntyre de
@BenMcIntyre É uma piadinha. Ou deveria ser tal. :)
Timo Kähkönen
ah, nunca codifique sua própria implementação de fuso horário / horário ... mas estou com preguiça de -1
mb21
3

Presumindo que você obtenha o carimbo de data / hora no horário de Helsinque, eu criaria um objeto de data definido para meia-noite de 1 ° de janeiro de 1970 UTC (para desconsiderar as configurações de fuso horário local do navegador). Em seguida, basta adicionar o número necessário de milissegundos a ele.

var _date	= new Date( Date.UTC(1970, 0, 1, 0, 0, 0, 0) );
_date.setUTCMilliseconds(1270544790922);

alert(_date); //date shown shifted corresponding to local time settings
alert(_date.getUTCFullYear());    //the UTC year value
alert(_date.getUTCMonth());       //the UTC month value
alert(_date.getUTCDate());        //the UTC day of month value
alert(_date.getUTCHours());       //the UTC hour value
alert(_date.getUTCMinutes());     //the UTC minutes value

Cuidado depois, para sempre pedir valores UTC do objeto de data. Dessa forma, os usuários verão os mesmos valores de data, independentemente das configurações locais. Caso contrário, os valores de data serão alterados de acordo com as configurações de hora local.

Tibor
fonte
0

Você poderia usar setUTCMilliseconds()

var _date = new Date();
_date.setUTCMilliseconds(1270544790922);
Jimasun
fonte
Esta resposta está errada. setUTCMilliseconds adiciona o número de milissegundos especificado à data.
Tibor
@Tibor: Do MozDev: O setUTCMilliseconds()método define os milissegundos para uma data especificada de acordo com a hora universal. [...] Se um parâmetro que você especifica está fora do intervalo esperado, setUTCMilliseconds()tenta atualizar as informações de data no objeto Date de acordo. Em outras palavras, você cria um Dateobjeto com o carimbo de data / hora Unix fornecido, em UTC.
jimasun
De w3schools: O método setUTCMilliseconds () define os milissegundos (de 0 a 999), de acordo com a hora universal. Por favor, tente a resposta acima e veja por si mesmo.
Tibor
De MDN: Parâmetros millisecondsValue: Um número entre 0 e 999, representando os milissegundos ...
Tibor
Seu exemplo não funciona conforme o esperado, porque new Date () cria um objeto de data com a data local atual. E depois disso adiciona o número de milissegundos especificado para ele. Então, em seu exemplo, se a data local atual for 04-05-2017, a data resultante será em algum lugar após o ano 4027 ...
Tibor