Cómo ignorar la zona horaria del usuario y forzar la fecha () usar una zona horaria específica

104

En una aplicación JS, recibo la marca de tiempo (eq. 1270544790922 ) Del servidor (Ajax).

Basándome en esa marca de tiempo, creo un Dateobjeto usando:

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

Ahora, _date marca de tiempo decodificada en la zona horaria de la configuración regional del usuario actual. No quiero eso.

Me gustaría _date convertir esta marca de tiempo a la hora actual en la ciudad de Helsinki en Europa (sin tener en cuenta la zona horaria actual del usuario).

¿Cómo puedo hacer eso?

warpech
fuente
Sé que el desplazamiento de zona horaria en Helsinki es +2 en invierno y +3 en horario de verano. Pero, ¿quién sabe cuándo es el horario de verano? Solo algunos mecanismos de configuración regional que no están disponibles en JS
warpech
Es posible, pero sin usar métodos nativos de Javascript, porque javascript no tiene un método para determinar un historial de transición de zona horaria de otra zona horaria que no sea la zona horaria actual del sistema del usuario (y, por cierto, depende del navegador al menos cuando vamos a las fechas de los 80). Pero de esta manera es posible: stackoverflow.com/a/12814213/1691517 y creo que mi respuesta te da el resultado correcto.
Timo Kähkönen

Respuestas:

64

El valor subyacente de un objeto Date está realmente en UTC. Para probar esto, notará que si escribe new Date(0)usted verá algo como: Wed Dec 31 1969 16:00:00 GMT-0800 (PST). 0 se trata como 0 en GMT, pero el .toString()método muestra la hora local.

Nota importante: UTC significa código de tiempo universal . La hora actual en este momento en 2 lugares diferentes es la misma UTC, pero la salida se puede formatear de manera diferente.

Lo que necesitamos aquí es algo de formato.

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"

Esto funciona, pero ... realmente no puede usar ninguno de los otros métodos de fecha para sus propósitos, ya que describen la zona horaria del usuario. Lo que desea es un objeto de fecha relacionado con la zona horaria de Helsinki. Sus opciones en este punto son usar alguna biblioteca de terceros (recomiendo esto), o piratear el objeto de fecha para que pueda usar la mayoría de sus métodos.

Opción 1: una zona horaria de momento de terceros

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

Esto parece mucho más elegante que lo que vamos a hacer a continuación.

Opción 2: piratear el objeto de fecha

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)

Todavía piensa que es GMT-0700 (PDT), pero si no mira demasiado fijamente, puede confundirlo con un objeto de fecha que sea útil para sus propósitos.

Convenientemente me salté una parte. Necesitas poder definir currentHelsinkiOffset. Si puede usar date.getTimezoneOffset()en el lado del servidor, o simplemente usar algunas declaraciones if para describir cuándo ocurrirán los cambios de zona horaria, eso debería resolver su problema.

Conclusión : creo que, especialmente para este propósito, debe usar una biblioteca de fechas como moment-timezone .

Parris
fuente
también ... se puede hacer una tarea similar, simplemente usando el desplazamiento gmt desde una ubicación. En ese caso, no necesita javascript en absoluto.
Parris
lo siento, pero no, quiero decir exactamente lo contrario :) Edité la pregunta, tal vez esté más clara ahora
warpech
Ok, cambié mi solución. Creo que esto es lo que estás buscando.
Parris
Desafortunadamente, eso es lo único que también se me ocurrió. Pensé que tal vez el navegador podría generar "_helsinkiOffset" para mí.
warpech
2
Creo que *60*60debería ser *60000, en cambio , ya que getTime está en milisegundos y getTimezoneOffset está en minutos, de los cuales hay 60000 milisegundos en un minuto, no 60 * 60 == 3600
AaronLS
20

Para tener en cuenta los milisegundos y la zona horaria del usuario, utilice lo siguiente:

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
fuente
Para reemplazar el uso de un desplazamiento fijo para central, usé el concepto de crear una fecha usando CST con una hora fija de 00:00, luego getUTCHHours de esa fecha.
grantwparks
+1 Esto funcionó para mí. No estoy seguro de cómo puede funcionar 'la' respuesta sin tener que lidiar con milisegundos.
Chris Wallis
2
@Ehren, ¿no debería agregar timezoneOffset para llegar a gmt y luego restar el desplazamiento central?
codificador
15

Solo otro enfoque

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);

¡Salud!

Mohanraj Balasubramaniam
fuente
2
Esto podría tener resultados no deseados debido al horario de verano. Si el cliente se encuentra en una zona horaria que usa DST, el resultado del análisis podría tener una diferencia de una hora (algunas zonas usan fracciones de hora). Por ejemplo: el usuario está en NY y hoy es 4 de julio. Eso significa que el usuario está en GMT -0400 (zona horaria del este desde que comenzó el horario de verano); la marca de tiempo aprobada es para el 30 de enero, que es GMT-0500 (zona horaria estándar del este, no hay horario de verano en esa época del año). El resultado será una hora de descanso, porque getTimezoneOffset () le da el desplazamiento ahora, no lo que era en enero.
Dimitar Darazhanski
2
Para solucionar este problema, debe tomar la compensación de tiempo de la fecha en la que está pasando (no el turno de hora actual) : new Date().getTimezoneOffset()debe cambiarse anew Date(timestampStr).getTimezoneOffset()
Dimitar Darazhanski
13

Tengo la sospecha de que la Respuesta no da el resultado correcto. En la pregunta, el autor de la pregunta quiere convertir la marca de tiempo del servidor a la hora actual en Hellsinki sin tener en cuenta la zona horaria actual del usuario.

Es el hecho de que la zona horaria del usuario puede ser lo que sea, por lo que no podemos confiar en ella.

Si por ejemplo. La marca de tiempo es 1270544790922 y tenemos una función:

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);

Cuando un neoyorquino visita la página, la alerta (_helsenkiTime) imprime:

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

Y cuando un finlandés visita la página, la alerta (_helsenkiTime) imprime:

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

Por lo tanto, la función es correcta solo si el visitante de la página tiene la zona horaria objetivo (Europa / Helsinki) en su computadora, pero falla en casi todas las demás partes del mundo. Y debido a que la marca de tiempo del servidor suele ser la marca de tiempo de UNIX, que por definición está en UTC, el número de segundos desde la época de Unix (1 de enero de 1970 a las 00:00:00 GMT), no podemos determinar DST o no DST a partir de la marca de tiempo.

Entonces, la solución es DESCONECTAR la zona horaria actual del usuario e implementar alguna forma de calcular la compensación UTC si la fecha está en DST o no. Javascript no tiene un método nativo para determinar el historial de transición DST de otra zona horaria que la zona horaria actual del usuario. Podemos lograr esto de la manera más simple usando el script del lado del servidor, porque tenemos fácil acceso a la base de datos de la zona horaria del servidor con el historial de transición completo de todas las zonas horarias.

Pero si no tiene acceso a la base de datos de la zona horaria del servidor (o de cualquier otro servidor) Y la marca de tiempo está en UTC, puede obtener una funcionalidad similar codificando las reglas de DST en Javascript.

Para cubrir fechas en los años 1998 - 2099 en Europa / Helsinki, puede utilizar la siguiente función ( 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;
}

Ejemplos 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);​

Y esto imprime lo siguiente independientemente de la zona horaria del usuario:

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

Por supuesto, si puede devolver la marca de tiempo en una forma en la que el desplazamiento (DST o no DST) ya está agregado a la marca de tiempo en el servidor, no tiene que calcularlo en el lado del cliente y puede simplificar mucho la función. PERO recuerde NO usar timezoneOffset (), porque entonces tiene que lidiar con la zona horaria del usuario y este no es el comportamiento deseado.

Timo Kähkönen
fuente
2
nótese bien. Helsinki solo tiene una 'l'. Este error realmente resta valor a esta respuesta.
Ben McIntyre
@BenMcIntyre Es una pequeña broma. O se supone que es uno de ellos. :)
Timo Kähkönen
ah, nunca codifique su propia implementación de hora / zona horaria ... pero soy demasiado vago para -1
mb21
3

Suponiendo que obtenga la marca de tiempo en la hora de Helsinki, crearía un objeto de fecha establecido en la medianoche del 1 de enero de 1970 UTC (para ignorar la configuración de la zona horaria local del navegador). Luego, simplemente agregue la cantidad necesaria de milisegundos.

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

Tenga cuidado más tarde, para preguntar siempre los valores UTC del objeto de fecha. De esta forma, los usuarios verán los mismos valores de fecha independientemente de la configuración local. De lo contrario, los valores de fecha se cambiarán según la configuración de la hora local.

Tibor
fuente
0

Podrías usar setUTCMilliseconds()

var _date = new Date();
_date.setUTCMilliseconds(1270544790922);
jimasun
fuente
Esta respuesta es incorrecta. setUTCMilliseconds agrega el número de milisegundos especificados a la fecha.
Tibor
@Tibor: de MozDev: el setUTCMilliseconds()método establece los milisegundos para una fecha específica de acuerdo con la hora universal. [...] Si un parámetro que especifica está fuera del rango esperado, setUTCMilliseconds()intenta actualizar la información de fecha en el objeto Fecha en consecuencia. En otras palabras, crea un Dateobjeto con la marca de tiempo Unix dada, en UTC.
jimasun
De w3schools: El método setUTCMilliseconds () establece los milisegundos (de 0 a 999), de acuerdo con la hora universal. Pruebe la respuesta anterior y compruébelo usted mismo.
Tibor
De MDN: Parámetros milisegundos Valor: Un número entre 0 y 999, que representa los milisegundos ...
Tibor
Su ejemplo no funciona como se esperaba, porque new Date () crea un objeto de fecha con la fecha local actual. Y después de eso, agrega el número de milisegundos especificado. Entonces, en su ejemplo, si la fecha local actual es 2017-05-04, la fecha resultante será en algún lugar posterior al año 4027 ...
Tibor