Jak zignorować strefę czasową użytkownika i wymusić użycie określonej strefy czasowej przez funkcję Date ()

104

W aplikacji JS otrzymuję znacznik czasu (eq. 1270544790922) Z serwera (Ajax).

Na podstawie tego znacznika czasu tworzę Dateobiekt używając:

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

Teraz _datezdekodowany znacznik czasu w bieżącej strefie czasowej użytkownika. Nie chcę tego.

Chciałbym _ dateprzekonwertować ten znacznik czasu na aktualny czas w mieście Helsinki w Europie (bez względu na aktualną strefę czasową użytkownika).

Jak mogę to zrobić?

warpech
źródło
Wiem, że przesunięcie strefy czasowej w Helsinkach wynosi +2 w zimie i +3 w czasie letnim. Ale kto wie, kiedy jest czas letni? Tylko jakiś mechanizm locale, który nie jest dostępny w JS
warpech
Jest to możliwe, ale nie przy użyciu natywnych metod Javascript, ponieważ javascript nie ma metody określania historii przejścia strefy czasowej w innej strefie czasowej niż bieżąca strefa czasowa systemu użytkownika (a przy okazji jest ona zależna od przeglądarki przynajmniej od czasów lat 80-tych). Ale tak jest to możliwe: stackoverflow.com/a/12814213/1691517 i myślę, że moja odpowiedź daje poprawny wynik.
Timo Kähkönen

Odpowiedzi:

64

Bazowa wartość obiektu Date jest w rzeczywistości w czasie UTC. Aby to udowodnić, należy zauważyć, że jeśli wpiszesz new Date(0)zobaczysz coś takiego: Wed Dec 31 1969 16:00:00 GMT-0800 (PST). 0 jest traktowane jako 0 w GMT, ale .toString()metoda pokazuje czas lokalny.

Ważna uwaga, UTC oznacza uniwersalny kod czasowy. Bieżący czas w 2 różnych miejscach to ten sam czas UTC, ale dane wyjściowe można sformatować inaczej.

Potrzebujemy tutaj trochę formatowania

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"

To działa, ale ... tak naprawdę nie możesz użyć żadnej innej metody daty do swoich celów, ponieważ opisują strefę czasową użytkownika. To, czego potrzebujesz, to obiekt daty powiązany ze strefą czasową Helsinek. W tym momencie możesz użyć biblioteki innej firmy (polecam) lub zhakować obiekt daty, abyś mógł używać większości jego metod.

Opcja 1 - zewnętrzna strefa czasowa, taka jak moment

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

Wygląda to o wiele bardziej elegancko niż to, co zamierzamy zrobić dalej.

Opcja 2 - Zhakuj obiekt daty

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)

Nadal myśli, że to GMT-0700 (PDT), ale jeśli nie gapisz się zbyt intensywnie, możesz pomylić to z obiektem daty, który jest przydatny do twoich celów.

Dogodnie pominąłem część. Musisz umieć zdefiniować currentHelsinkiOffset. Jeśli możesz użyć date.getTimezoneOffset()po stronie serwera lub po prostu użyć instrukcji if, aby opisać, kiedy nastąpi zmiana strefy czasowej, powinno to rozwiązać problem.

Wniosek - myślę, że szczególnie w tym celu powinieneś użyć biblioteki dat, takiej jak strefa czasowa .

Parris
źródło
również ... podobne zadanie można wykonać, po prostu używając przesunięcia gmt z jednego miejsca. W takim przypadku w ogóle nie potrzebujesz JavaScript.
Parris
przepraszam ale nie, mam na myśli dokładnie odwrotnie :) pytanie zredagowałem, może teraz jest jaśniejsze
warpech
Ok, zmieniłem rozwiązanie. Myślę, że właśnie tego szukasz.
Parris
Niestety to jedyna rzecz, na którą też wpadłem. Pomyślałem, że przeglądarka może wygenerować dla mnie „_helsinkiOffset”.
warpech
2
Uważam, że *60*60zamiast tego powinno być *60000, ponieważ getTime jest w milisekundach, a getTimezoneOffset w minutach, z czego 60000 milisekund na minutę, a nie 60 * 60 == 3600
AaronLS
20

Aby uwzględnić milisekundy i strefę czasową użytkownika, użyj następujących poleceń:

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
źródło
Aby zastąpić użycie stałego przesunięcia dla centralnego, użyłem koncepcji tworzenia daty za pomocą CST ze stałą godziną 00:00, a następnie getUTCHHours tej daty.
grantwparks
+1 To zadziałało dla mnie. Nie jestem pewien, jak „odpowiedź” może działać bez zajmowania milisekund.
Chris Wallis
2
@Ehren nie powinieneś dodać timezoneOffset, aby dostać się do gmt, a następnie odjąć centralne przesunięcie?
koder
15

Po prostu inne podejście

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

Twoje zdrowie!

Mohanraj Balasubramaniam
źródło
2
Mogło to mieć niezamierzone skutki ze względu na czas letni. Jeśli klient znajduje się w strefie czasowej, w której jest używany czas letni, wynik analizy może być przesunięty o godzinę (niektóre strefy używają ułamków godziny). Np .: użytkownik jest w Nowym Jorku, a dzisiaj jest 4 lipca. Oznacza to, że użytkownik jest w strefie czasowej GMT -0400 (wschodnia strefa czasowa letnia od czasu rozpoczęcia czasu letniego); podana sygnatura czasowa dotyczy 30 stycznia, czyli GMT-0500 (Wschodnia standardowa strefa czasowa - bez czasu letniego o tej porze roku). Rezultatem będzie godzina przerwy, ponieważ getTimezoneOffset () podaje teraz przesunięcie, a nie to, co było w styczniu.
Dimitar Darazhanski
2
Aby rozwiązać ten problem, musisz wziąć przesunięcie czasowe daty, w którejnew Date().getTimezoneOffset()new Date(timestampStr).getTimezoneOffset()
mijasz
13

Podejrzewam, że odpowiedź nie daje prawidłowego wyniku. W pytaniu pytający chce przekonwertować znacznik czasu z serwera na aktualny czas w Hellsinki bez względu na aktualną strefę czasową użytkownika.

Chodzi o to, że strefa czasowa użytkownika może być czymkolwiek, więc nie możemy jej ufać.

Jeśli np. sygnatura czasowa to 1270544790922 i mamy funkcję:

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

Kiedy nowojorczyk odwiedza stronę, alert (_helsenkiTime) drukuje:

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

Kiedy finalista odwiedza stronę, wyświetla alert (_helsenkiTime):

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

Tak więc funkcja jest prawidłowa tylko wtedy, gdy odwiedzający stronę ma docelową strefę czasową (Europa / Helsinki) na swoim komputerze, ale zawodzi w prawie każdej innej części świata. A ponieważ znacznik czasu serwera jest zwykle sygnaturą czasową UNIX, która z definicji jest podana w czasie UTC, czyli liczbie sekund, które upłynęły od Epoki Uniksa (1 stycznia 1970 00:00:00 GMT), nie możemy określić czasu letniego ani czasu innego niż czas letni na podstawie znacznika czasu.

Tak więc rozwiązaniem jest ODRZUCENIE bieżącej strefy czasowej użytkownika i zaimplementowanie sposobu obliczenia przesunięcia UTC, niezależnie od tego, czy data jest w czasie letnim, czy nie. W JavaScript nie ma natywnej metody określania historii przejścia na czas letni w innej strefie czasowej niż bieżąca strefa czasowa użytkownika. Możemy to osiągnąć najprościej za pomocą skryptu po stronie serwera, ponieważ mamy łatwy dostęp do bazy danych stref czasowych serwera z całą historią przejścia wszystkich stref czasowych.

Ale jeśli nie masz dostępu do bazy danych stref czasowych serwera (lub innego serwera) ORAZ sygnatura czasowa jest w czasie UTC, możesz uzyskać podobną funkcjonalność, zakodowując na stałe reguły czasu letniego w JavaScript.

Aby uwzględnić daty w latach 1998-2099 w Europie / Helsinkach, można skorzystać z następującej funkcji ( 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;
}

Przykłady użycia:

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

I drukuje to, niezależnie od strefy czasowej użytkownika:

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

Oczywiście, jeśli możesz zwrócić znacznik czasu w postaci, w której przesunięcie (DST lub inne niż DST) jest już dodane do znacznika czasu na serwerze, nie musisz go obliczać po stronie klienta i możesz bardzo uprościć funkcję. ALE pamiętaj, aby NIE używać timezoneOffset (), ponieważ wtedy musisz poradzić sobie ze strefą czasową użytkownika i nie jest to pożądane zachowanie.

Timo Kähkönen
źródło
2
nb. Helsinki mają tylko jedno „l”. Ten błąd naprawdę umniejsza tę odpowiedź.
Ben McIntyre
@BenMcIntyre To mały żart. Albo podobno taki. :)
Timo Kähkönen
ach, nigdy nie koduj własnej implementacji czasu / strefy czasowej ... ale jestem zbyt leniwy na -1
mb21
3

Zakładając, że otrzymujesz sygnaturę czasową według czasu helsińskiego, utworzyłbym obiekt daty ustawiony na północ 1 stycznia 1970 czasu UTC (w celu zignorowania lokalnych ustawień strefy czasowej przeglądarki). Następnie dodaj do niego wymaganą liczbę milisekund.

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

Uważaj później, aby zawsze pytać o wartości UTC z obiektu daty. W ten sposób użytkownicy zobaczą te same wartości dat niezależnie od ustawień lokalnych. W przeciwnym razie wartości dat zostaną przesunięte zgodnie z ustawieniami czasu lokalnego.

Tibor
źródło
0

Możesz użyć setUTCMilliseconds()

var _date = new Date();
_date.setUTCMilliseconds(1270544790922);
jimasun
źródło
Ta odpowiedź jest błędna. setUTCMilliseconds dodaje liczbę określonych milisekund do Date.
Tibor
@Tibor: Z MozDev: setUTCMilliseconds()Metoda ustawia milisekundy dla określonej daty zgodnie z czasem uniwersalnym. [...] Jeśli określony parametr jest poza spodziewanym zakresem, setUTCMilliseconds()podejmuje próbę odpowiedniego zaktualizowania informacji o dacie w obiekcie Date. Innymi słowy, tworzysz Dateobiekt z podaną sygnaturą czasową Uniksa w UTC.
jimasun
Z w3schools: Metoda setUTCMilliseconds () ustawia milisekundy (od 0 do 999) zgodnie z czasem uniwersalnym. Spróbuj odpowiedzi powyżej i przekonaj się sam.
Tibor
Z MDN: Parametry milliseconds Wartość: Liczba z przedziału od 0 do 999, reprezentująca milisekundy ...
Tibor
Twój przykład nie działa zgodnie z oczekiwaniami, ponieważ new Date () tworzy obiekt daty z bieżącą datą lokalną. A potem dodaje określoną liczbę milisekund. W twoim przykładzie, jeśli aktualna lokalna data to 04.05.2017, wynikowa data będzie przypadać gdzieś po roku 4027 ...
Tibor