Как игнорировать часовой пояс пользователя и заставить Date () использовать определенный часовой пояс


104

В приложении JS я получаю метку времени (экв. 1270544790922) С сервера (Ajax).

На основе этой отметки времени я создаю Dateобъект, используя:

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

Теперь _dateдекодированная временная метка в часовом поясе текущего пользователя. Я не хочу этого.

Я хотел бы _ dateпреобразовать эту метку времени в текущее время в городе Хельсинки в Европе (без учета текущего часового пояса пользователя).

Как я могу это сделать?


Я знаю, что смещение часового пояса в Хельсинки составляет +2 зимой и +3 в летнее время. Но кто знает, когда сейчас летнее время? Только какой-то механизм локали, которого нет в JS
warpech

Это возможно, но без использования собственных методов Javascript, потому что javascript не имеет метода для определения истории переходов часового пояса для другого часового пояса, кроме текущего часового пояса системы пользователя (и, кстати, это зависит от браузера, по крайней мере, когда мы переходим к датам 80-х годов). Но так это возможно: stackoverflow.com/a/12814213/1691517, и я думаю, что мой ответ даст вам правильный результат.
Timo Kähkönen

Ответы:


64

Базовое значение объекта Date фактически находится в формате UTC. Чтобы доказать это, заметим , что если вы печатаете new Date(0)вы увидите что - то вроде: Wed Dec 31 1969 16:00:00 GMT-0800 (PST). 0 рассматривается как 0 в GMT, но .toString()метод показывает местное время.

Большое примечание: UTC означает универсальный временной код. Текущее время прямо сейчас в 2 разных местах совпадает с UTC, но вывод может быть отформатирован по-разному.

Что нам здесь нужно, так это форматирование

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"

Это работает, но .... вы не можете использовать какие-либо другие методы даты для своих целей, поскольку они описывают часовой пояс пользователя. Вам нужен объект даты, связанный с часовым поясом Хельсинки. На этом этапе вы можете использовать какую-нибудь стороннюю библиотеку (я рекомендую это) или взломать объект даты, чтобы вы могли использовать большинство его методов.

Вариант 1 - сторонний, например, момент-часовой пояс

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

Это выглядит намного элегантнее, чем то, что мы собираемся сделать дальше.

Вариант 2 - Взломать объект даты

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)

Он по-прежнему считает, что это GMT-0700 (PDT), но если вы не будете слишком пристально смотреть, вы можете принять это за объект даты, который полезен для ваших целей.

Я удобно пропустил часть. Вы должны уметь определять currentHelsinkiOffset. Если вы можете использовать date.getTimezoneOffset()на стороне сервера или просто использовать некоторые операторы if, чтобы описать, когда произойдут изменения часового пояса, это должно решить вашу проблему.

Заключение - я думаю, что специально для этой цели вам следует использовать библиотеку дат, например, момент-часовой пояс .


также ... аналогичную задачу можно выполнить, просто используя смещение gmt из одного места. В этом случае вам вообще не нужен javascript.
Пэррис

извините но нет, я имею в виду с точностью до наоборот :) Отредактировал вопрос, может теперь он понятнее
варпеч

Хорошо, я изменил свое решение. Я думаю, это то, что вы ищете.
Пэррис

К сожалению, это единственное, что я придумал. Я подумал, что, возможно, браузер сможет сгенерировать для меня "_helsinkiOffset".
warpech

2
Я считаю, *60*60что вместо этого должно быть *60000, поскольку getTime находится в миллисекундах, а getTimezoneOffset - в минутах, из которых 60000 миллисекунд в минуте, а не 60 * 60 == 3600
AaronLS

20

Чтобы учесть миллисекунды и часовой пояс пользователя, используйте следующее:

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

Чтобы заменить фиксированное смещение для центрального, я использовал концепцию создания даты с использованием CST с фиксированным временем 00:00, а затем getUTCHHours этой даты.
grantwparks

+1 У меня это сработало. Не уверен, как ответ может работать без миллисекунд.
Крис Уоллис

2
@Ehren, разве вы не должны добавить timezoneOffset, чтобы добраться до gmt, а затем вычесть центральное смещение?
кодер

15

Просто другой подход

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

Ура!


2
Это могло привести к непредвиденным результатам из-за перехода на летнее время. Если клиент находится в часовом поясе, в котором используется летнее время, результат синтаксического анализа может отличаться на час (в некоторых зонах используются доли часа). Например: пользователь находится в Нью-Йорке, а сегодня 4 июля. Это означает, что пользователь находится в GMT -0400 (часовой пояс восточного дневного света с момента перехода на летнее время); переданная временная метка относится к 30 января, то есть по Гринвичу-0500 (восточно-стандартное время - без летнего времени в это время года). Результат будет через час, потому что getTimezoneOffset () дает смещение сейчас, а не то, что было в январе.
Димитар Даражанский

2
Чтобы решить эту проблему, вам нужно взять смещение даты, которую вы передаете (а не текущий сдвиг времени) : new Date().getTimezoneOffset()следует изменить наnew Date(timestampStr).getTimezoneOffset()
Димитар Даражанский

13

У меня есть подозрение, что ответ не дает правильного результата. В вопросе спрашивающий хочет преобразовать временную метку с сервера в текущее время в Hellsinki, не обращая внимания на текущий часовой пояс пользователя.

Дело в том, что часовой пояс пользователя может быть любым, поэтому мы не можем ему доверять.

Если, например. отметка времени - 1270544790922 и у нас есть функция:

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

Когда житель Нью-Йорка посещает страницу, alert (_helsenkiTime) печатает:

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

А когда финляндец посещает страницу, alert (_helsenkiTime) печатает:

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

Таким образом, функция верна только в том случае, если посетитель страницы имеет целевой часовой пояс (Европа / Хельсинки) на своем компьютере, но не работает почти во всех остальных частях мира. И поскольку отметка времени сервера обычно представляет собой отметку времени UNIX, которая по определению в UTC, количество секунд с начала эпохи Unix (1 января 1970 00:00:00 GMT), мы не можем определить DST или не-DST по отметке времени.

Таким образом, решение состоит в том, чтобы НЕ УЧИТЫВАТЬ текущий часовой пояс пользователя и реализовать какой-либо способ вычисления смещения UTC независимо от того, находится ли дата в летнем времени или нет. Javascript не имеет собственного метода для определения истории перехода на летнее время для другого часового пояса, кроме текущего часового пояса пользователя. Мы можем добиться этого проще всего, используя скрипт на стороне сервера, потому что у нас есть легкий доступ к базе данных часовых поясов сервера со всей историей переходов для всех часовых поясов.

Но если у вас нет доступа к базе данных часовых поясов сервера (или любого другого сервера) И метка времени находится в UTC, вы можете получить аналогичную функциональность, жестко закодировав правила DST в Javascript.

Чтобы охватить даты в 1998-2099 годах в Европе / Хельсинки, вы можете использовать следующую функцию ( 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;
}

Примеры использования:

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

И это выводит следующее независимо от часового пояса пользователя:

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

Конечно, если вы можете вернуть метку времени в форме, в которой смещение (DST или не-DST) уже добавлено к метке времени на сервере, вам не нужно вычислять ее на стороне клиента, и вы можете значительно упростить функцию. НО не забывайте НЕ использовать timezoneOffset (), потому что тогда вам придется иметь дело с часовым поясом пользователя, а это нежелательное поведение.


2
nb. В Хельсинки только одно «я». Эта ошибка действительно отвлекает от этого ответа.
Бен Макинтайр

@BenMcIntyre Это небольшая шутка. Или должен быть таким. :)
Timo Kähkönen

ах, никогда не
кодируйте

3

Предполагая, что вы получаете отметку времени по времени Хельсинки, я бы создал объект даты, установленный на полночь 1 января 1970 года по всемирному координированному времени (для игнорирования настроек локального часового пояса браузера). Затем просто добавьте к нему необходимое количество миллисекунд.

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

Остерегайтесь позже, чтобы всегда запрашивать значения UTC из объекта даты. Таким образом, пользователи будут видеть одни и те же значения даты независимо от локальных настроек. В противном случае значения даты будут смещены в соответствии с настройками местного времени.


0

Вы могли бы использовать setUTCMilliseconds()

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

Это неверный ответ. setUTCMilliseconds добавляет число указанных миллисекунд к Date.
Тибор

@Tibor: Из MozDev: setUTCMilliseconds()метод устанавливает миллисекунды для указанной даты по всемирному координированному времени. [...] Если указанный вами параметр находится за пределами ожидаемого диапазона, setUTCMilliseconds()пытается соответствующим образом обновить информацию о дате в объекте Date. Другими словами, вы создаете Dateобъект с указанной меткой времени Unix в UTC.
jimasun

Из w3schools: метод setUTCMilliseconds () устанавливает миллисекунды (от 0 до 999) по всемирному координированному времени. Пожалуйста, попробуйте ответ выше и убедитесь в этом сами.
Тибор

Из MDN: Параметры millisecondsValue: Число от 0 до 999, представляющее миллисекунды ...
Тибор,

Ваш пример не работает должным образом, потому что new Date () создает объект даты с текущей местной датой. И после этого добавляет к нему указанное количество миллисекунд. Итак, в вашем примере, если текущая местная дата - 04.05.2017, результирующая дата будет где-то после 4027 года ...
Тибор,
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.