Jak zainicjować datę JavaScript do określonej strefy czasowej

232

Mam datę i godzinę w określonej strefie czasowej jako ciąg i chcę ją przekonwertować na czas lokalny. Ale nie wiem, jak ustawić strefę czasową w obiekcie Date.

Na przykład mam Feb 28 2013 7:00 PM ET,wtedy mogę

var mydate = new Date();
mydate.setFullYear(2013);
mydate.setMonth(02);
mydate.setDate(28);
mydate.setHours(7);
mydate.setMinutes(00);  

O ile mi wiadomo, mogę ustawić czas UTC lub czas lokalny. Ale jak ustawić czas w innej strefie czasowej?

Próbowałem użyć dodawania / odejmowania przesunięcia od UTC, ale nie wiem, jak przeciwdziałać oszczędnościom światła dziennego. Nie jestem pewien, czy zmierzam we właściwym kierunku.

Jak mogę konwertować czas z innej strefy czasowej na czas lokalny w javascript?

pavanred
źródło
1
Obiekty daty nie mają strefy czasowej, są UTC.
RobG

Odpowiedzi:

351

tło

DateObiekt JavaScript wewnętrznie śledzi czas w UTC, ale zazwyczaj przyjmuje dane wejściowe i generuje dane wyjściowe w czasie lokalnym komputera, na którym działa. Ma bardzo mało możliwości pracy z czasem w innych strefach czasowych.

Wewnętrzna reprezentacja Dateobiektu jest pojedynczą liczbą, reprezentującą liczbę milisekund, które upłynęły od tego czasu 1970-01-01 00:00:00 UTC, bez względu na sekundy przestępne. W samym obiekcie Date nie ma strefy czasowej ani formatu łańcucha. Gdy Dateużywane są różne funkcje obiektu, lokalna strefa czasowa komputera jest stosowana do wewnętrznej reprezentacji. Jeśli funkcja generuje ciąg, wówczas informacje o lokalizacji komputera mogą być brane pod uwagę w celu ustalenia, jak wygenerować ten ciąg. Szczegóły różnią się w zależności od funkcji, a niektóre dotyczą konkretnej implementacji.

Jedyne operacje, które Dateobiekt może wykonać w nielokalnych strefach czasowych to:

  • Może analizować ciąg zawierający numeryczne przesunięcie UTC z dowolnej strefy czasowej. Wykorzystuje to do dostosowania analizowanej wartości i zapisuje ekwiwalent UTC. Pierwotny czas lokalny i przesunięcie nie są zachowywane w wynikowym Dateobiekcie. Na przykład:

    var d = new Date("2020-04-13T00:00:00.000+08:00");
    d.toISOString()  //=> "2020-04-12T16:00:00.000Z"
    d.valueOf()      //=> 1586707200000  (this is what is actually stored in the object)
    
  • W środowiskach, w których zaimplementowano interfejs API internacjonalizacji ECMASCript (znany również jako „Intl”), Dateobiekt może wygenerować ciąg specyficzny dla ustawień regionalnych dostosowany do danego identyfikatora strefy czasowej. Dokonuje się tego poprzez timeZoneopcję toLocaleStringi jej odmiany. Większość implementacji obsługuje identyfikatory stref czasowych IANA, takie jak 'America/New_York'. Na przykład:

    var d = new Date("2020-04-13T00:00:00.000+08:00");
    d.toLocaleString('en-US', { timeZone: 'America/New_York' })
    //=> "4/12/2020, 12:00:00 PM"
    // (midnight in China on Apring 13th is noon in New York on April 12th)
    

    Większość współczesnych środowisk obsługuje pełny zestaw identyfikatorów stref czasowych IANA ( patrz tabela kompatybilności tutaj ). Należy jednak pamiętać, że jedynym identyfikatorem wymaganym do obsługi przez Intl jest 'UTC', dlatego należy dokładnie sprawdzić, czy trzeba obsługiwać starsze przeglądarki lub nietypowe środowiska (na przykład lekkie urządzenia IoT).

Biblioteki

Istnieje kilka bibliotek, których można używać do pracy ze strefami czasowymi. Chociaż nadal nie mogą sprawić, by Dateobiekt zachowywał się inaczej, zazwyczaj implementują standardową bazę danych stref czasowych IANA i zapewniają funkcje umożliwiające korzystanie z niej w JavaScript. Nowoczesne biblioteki używają danych strefy czasowej dostarczonych przez Intl API, ale starsze biblioteki zwykle mają narzuty, szczególnie jeśli pracujesz w przeglądarce internetowej, ponieważ baza danych może być nieco większa. Niektóre z tych bibliotek umożliwiają również selektywne zmniejszenie zestawu danych, według których stref czasowych są obsługiwane i / lub według zakresu dat, z którymi możesz pracować.

Oto biblioteki do rozważenia:

Biblioteki międzynarodowe

Nowy programista powinien wybrać jedną z tych implementacji, które opierają się na interfejsie API Intl dla danych strefy czasowej:

Biblioteki inne niż międzynarodowe

Te biblioteki są utrzymywane, ale przenoszą ciężar pakowania własnych danych strefy czasowej, które mogą być dość duże.

* Chociaż Moment i Moment-Strefa czasowa były wcześniej zalecane, zespół Moment woli teraz użytkownicy wybrali Luxon do nowego rozwoju.

Biblioteki wycofane z produkcji

Biblioteki te zostały oficjalnie wycofane i nie powinny być dłużej używane.

Przyszłe propozycje

Do Wniosek TC39 czasowe ma na celu dostarczenie nowego zestawu standardowych obiektów do pracy z datami i godzinami w języku samej JavaScript. Obejmie to obsługę obiektu rozpoznającego strefę czasową.

Matt Johnson-Pint
źródło
1
Zmień „reprezentuj” na „wyjście / parsowanie”, ponieważ reprezentowane znaczniki czasu są niezależne od strefy czasowej
Bergi
1
@Bergi - Zastanowiłem się nad tym i zgadzam się z tobą. Zaktualizowałem odpowiednio moją odpowiedź.
Matt Johnson-Pint,
1
Kiedy to zrobić w konsoli Firebug: var date_time = new Date()wartość date_timejest (dla mnie w AEST) Date {Sun Aug 21 2016 22:18:47 GMT+1000 (AEST)}a więc w związku z tym wydaje się, że jest przechowywany strefę czasową. Jak mogę się upewnić, że date_timejest to wyłącznie czas UTC, bez uwzględnienia żadnej strefy czasowej?
user1063287,
Hmm, zgodnie z tą odpowiedzią ( stackoverflow.com/a/8047885/1063287 ), jest to: new Date().getTime();
użytkownik1063287
1
@ user1063287 - metoda toString wykorzystuje przesunięcie strefy czasowej hosta do utworzenia daty i godziny w „lokalnej” strefie czasowej. Sam obiekt daty ma wartość czasu, która jest przesunięciem w stosunku do 1970-01-01T00: 00: 00Z, więc efektywnie UTC. Aby zobaczyć datę i godzinę UTC, użyj toISOString .
RobG
69

Jak powiedział Matt Johnson

Jeśli możesz ograniczyć użycie do nowoczesnych przeglądarek internetowych, możesz teraz wykonywać następujące czynności bez żadnych specjalnych bibliotek:

new Date().toLocaleString("en-US", {timeZone: "America/New_York"})

To nie jest kompleksowe rozwiązanie, ale działa w wielu scenariuszach, które wymagają tylko konwersji danych wyjściowych (z czasu UTC lub czasu lokalnego do określonej strefy czasowej, ale nie w przeciwnym kierunku).

Więc chociaż przeglądarka nie może odczytać stref czasowych IANA podczas tworzenia daty lub ma jakiekolwiek metody zmiany stref czasowych w istniejącym obiekcie Date, wydaje się, że wokół niego jest hack:

function changeTimezone(date, ianatz) {

  // suppose the date is 12:00 UTC
  var invdate = new Date(date.toLocaleString('en-US', {
    timeZone: ianatz
  }));

  // then invdate will be 07:00 in Toronto
  // and the diff is 5 hours
  var diff = date.getTime() - invdate.getTime();

  // so 12:00 in Toronto is 17:00 UTC
  return new Date(date.getTime() + diff);

}

// E.g.
var there = new Date();
var here = changeTimezone(there, "America/Toronto");

console.log(`Here: ${here.toString()}\nToronto: ${there.toString()}`);

pospolity
źródło
6
Zastanawiam się, dlaczego ta odpowiedź nie zyskuje więcej miłości. Dla moich celów jest to absolutnie najlepsze rozwiązanie. W mojej aplikacji wszystkie daty są UTC, ale muszą być wprowadzane lub wyprowadzane w jawnych strefach czasowych IANA w interfejsie użytkownika. W tym celu korzystam z tego rozwiązania i działa ono doskonale. Prosty, skuteczny, standardowy.
logidelic
2
@logidelic to całkiem nowy post. szczerze mówiąc, zadziwiłem się, kiedy mnie uderzyło, że można użyć toLocaleStringodwrotnego efektu i tak, to powinna być powszechna wiedza! idź napisz artykuł na ten temat i wspomnij o mnie :-)
commonpike
1
W przypadku środowiska węzła, którego jeszcze nie ma w GMT, powyższy kod musi zostać zmieniony z var invdate = new Date (date.toLocaleString ('en-US', {timeZone: ianatz})); na var invdate = nowa data ($ {date.toLocaleString ('en-US', {timeZone: ianatz})} GMT`);
Mike P.
5
Przeglądarki nie są zobowiązane do analizowania wyniku toLocaleString , więc jest on oparty na błędnym założeniu.
RobG
3
„... dlaczego ta odpowiedź nie zyskuje więcej miłości?” - ponieważ Dateobiekt, który zwraca, jest kłamstwem. Nawet w pokazanym przykładzie dane wyjściowe ciągu zawierają strefę czasową komputera lokalnego. Na moim komputerze oba wyniki mówią „GMT-0700 (Pacific Daylight Time)”, gdzie jest to poprawne tylko dla pierwszego (Toronto jest w rzeczywistości w czasie wschodnim). Poza wyjściem łańcucha, znacznik czasu przechowywany w Dateobiekcie został przesunięty do innego punktu w czasie. Może to być użyteczna technika, ale wiąże się z wieloma zastrzeżeniami - przede wszystkim, że Datefunkcje obiektów nie będą miały normalnej wydajności.
Matt Johnson-Pint
30

Możesz określić przesunięcie strefy czasowej new Date(), na przykład:

new Date('Feb 28 2013 19:00:00 EST')

lub

new Date('Feb 28 2013 19:00:00 GMT-0500')

Od czasu Datezapisania czasu UTC (tzn. getTimeZwrotów w UTC), javascript przekonwertuje czas na UTC, a kiedy wywołasz rzeczy takie jak toStringjavascript, przekonwertuje czas UTC na lokalną strefę czasową przeglądarki i zwróci ciąg w lokalnej strefie czasowej, tj. Jeśli używam UTC+8:

> new Date('Feb 28 2013 19:00:00 GMT-0500').toString()
< "Fri Mar 01 2013 08:00:00 GMT+0800 (CST)"

Możesz także użyć normalnej getHours/Minute/Secondmetody:

> new Date('Feb 28 2013 19:00:00 GMT-0500').getHours()
< 8

(Oznacza 8to, że po przeliczeniu czasu na czas lokalny - UTC+8liczba godzin to 8.)

maowtm
źródło
16
Analiza dowolnego formatu innego niż format rozszerzony ISO 8601 zależy od implementacji i nie należy na nim polegać. Nie ma standardu dla skrótów strefy czasowej, np. „EST” może reprezentować dowolną z 3 różnych stref.
RobG,
1
Przykłady w tym komentarzu często nie działają w przeglądarce Internet Explorer. Jak wspomniano w poprzednim komentarzu do tego posta, ważna jest ISO 8601. Potwierdziłem to, czytając specyfikację języka ECMA-262 (JavaScript 5.).
Dakusan
12

To powinno rozwiązać Twój problem, zachęcamy do oferowania poprawek. Ta metoda uwzględni także czas letni dla danej daty.

dateWithTimeZone = (timeZone, year, month, day, hour, minute, second) => {
  let date = new Date(Date.UTC(year, month, day, hour, minute, second));

  let utcDate = new Date(date.toLocaleString('en-US', { timeZone: "UTC" }));
  let tzDate = new Date(date.toLocaleString('en-US', { timeZone: timeZone }));
  let offset = utcDate.getTime() - tzDate.getTime();

  date.setTime( date.getTime() + offset );

  return date;
};

Jak korzystać ze strefą czasową i czasem lokalnym:

dateWithTimeZone("America/Los_Angeles",2019,8,8,0,0,0)
kurczaki
źródło
To rozwiązanie spełniło moje potrzeby. Nie byłem pewien, jak dokładnie mogę uwzględnić czas letni.
Hossein Amin
Dlaczego nie wrócić tzDatebezpośrednio?
levi
8

Znalazłem najbardziej obsługiwany sposób, aby to zrobić, nie martwiąc się o bibliotekę strony trzeciej, polegającą getTimezoneOffsetna obliczeniu odpowiedniego znacznika czasu lub aktualizacji godziny, a następnie użyciu normalnych metod w celu uzyskania niezbędnej daty i godziny.

var mydate = new Date();
mydate.setFullYear(2013);
mydate.setMonth(02);
mydate.setDate(28);
mydate.setHours(7);
mydate.setMinutes(00);

// ET timezone offset in hours.
var timezone = -5;
// Timezone offset in minutes + the desired offset in minutes, converted to ms.
// This offset should be the same for ALL date calculations, so you should only need to calculate it once.
var offset = (mydate.getTimezoneOffset() + (timezone * 60)) * 60 * 1000;

// Use the timestamp and offset as necessary to calculate min/sec etc, i.e. for countdowns.
var timestamp = mydate.getTime() + offset,
    seconds = Math.floor(timestamp / 1000) % 60,
    minutes = Math.floor(timestamp / 1000 / 60) % 60,
    hours   = Math.floor(timestamp / 1000 / 60 / 60);

// Or update the timestamp to reflect the timezone offset.
mydate.setTime(mydate.getTime() + offset);
// Then Output dates and times using the normal methods.
var date = mydate.getDate(),
    hour = mydate.getHours();

EDYTOWAĆ

Poprzednio korzystałem z UTCmetod podczas transformacji daty, co było niepoprawne. Po dodaniu przesunięcia do czasu użycie getfunkcji lokalnych zwróci pożądane wyniki.

Shaun Cockerill
źródło
Gdzie obliczyłeś timezone_offset?
Anand Somani
1
Ups, błędnie oznaczyłem jedną ze swoich zmiennych. timezone_offsetpowinno być offsetlub odwrotnie. Zmodyfikowałem swoją odpowiedź, aby pokazać poprawną nazwę zmiennej.
Shaun Cockerill
3

Wystąpił podobny problem z testami jednostkowymi (szczególnie, gdy testy jednostkowe są uruchamiane lokalnie w celu utworzenia migawek, a następnie serwer CI działa (potencjalnie) w innej strefie czasowej, co powoduje niepowodzenie porównania migawek). Kpiłem sobie z naszych Datei niektórych metod wspierających takich jak:

describe('...', () => {
  let originalDate;

  beforeEach(() => {
    originalDate = Date;
    Date = jest.fn(
      (d) => {
        let newD;
        if (d) {
          newD = (new originalDate(d));
        } else {
          newD = (new originalDate('2017-05-29T10:00:00z'));
        }
        newD.toLocaleString = () => {
          return (new originalDate(newD.valueOf())).toLocaleString("en-US", {timeZone: "America/New_York"});
        };
        newD.toLocaleDateString = () => {
          return (new originalDate(newD.valueOf())).toLocaleDateString("en-US", {timeZone: "America/New_York"});
        };
        newD.toLocaleTimeString = () => {
          return (new originalDate(newD.valueOf())).toLocaleTimeString("en-US", {timeZone: "America/New_York"});
        };
        return newD;
      }
    );
    Date.now = () => { return (Date()); };
  });

  afterEach(() => {
    Date = originalDate;
  });

});
JRulle
źródło
2

Spróbuj użyć ctoc z npm. https://www.npmjs.com/package/ctoc_timezone

Ma prostą funkcjonalność do zmiany stref czasowych (większość stref czasowych około 400) i wszystkie niestandardowe formaty, które mają być wyświetlane.

Sandeep Chowdary
źródło
2

Opierając się na powyższych odpowiedziach, używam tego natywnego linera do konwersji długiego ciągu strefy czasowej na ciąg trzech liter:

var longTz = 'America/Los_Angeles';
var shortTz = new Date().
    toLocaleString("en", {timeZoneName: "short", timeZone: longTz}).
    split(' ').
    pop();

To da PDT lub PST w zależności od podanej daty. W moim szczególnym przypadku użycia, rozwijając się na Salesforce (Aura / Lightning), możemy uzyskać strefę czasową użytkownika w długim formacie z backendu.

Shane
źródło
Tylko dlatego, że od czasu do czasu lubię grać w pedant, „America / Los_Angeles” nie jest strefą czasową, jest „reprezentatywną lokalizacją” dla określonych zasad przesunięcia i czasu letniego oraz historii tych zmian (według bazy danych stref czasowych IANA ) . ;-)
RobG
Haha, musisz opublikować ten komentarz do wielu odpowiedzi tutaj: P
Shane
2

Wiem, że to 3 lata za późno, ale może może pomóc komuś innemu, ponieważ nie znalazłem czegoś takiego poza biblioteką strefy czasowej, która nie jest dokładnie taka sama, jak o to tutaj prosi.

Zrobiłem coś podobnego dla niemieckiej strefy czasowej, jest to trochę skomplikowane ze względu na czas letni i lata przestępne, w których masz 366 dni.

może potrzebować trochę pracy z funkcją „isDaylightSavingTimeInGermany”, podczas gdy różne strefy czasowe zmieniają się w różnych momentach czasu letniego.

w każdym razie sprawdź tę stronę: https://github.com/zerkotin/german-timezone-converter/wiki

głównymi metodami są: convertLocalDateToGermanTimezone convertGermanDateToLocalTimezone

Włożyłem dużo wysiłku w udokumentowanie tego, więc nie będzie to tak mylące.

zerkotin
źródło
To powinien być komentarz, to nie jest odpowiedź.
RobG
1

Wypróbuj: date-from-timezone , rozwiązuje spodziewaną datę przy pomocy natywnie dostępnej Intl.DateTimeFormat.

Stosowałem tę metodę w jednym z moich projektów już od kilku lat, ale teraz postanowiłem opublikować ją jako mały projekt systemu operacyjnego :)

Mariusz Nowak
źródło
0

Dla użytkowników Ionic miałem z tym piekło, ponieważ .toISOString()musi być używany z szablonem HTML.

Spowoduje to pobranie bieżącej daty, ale oczywiście można ją dodać do poprzednich odpowiedzi dla wybranej daty.

Naprawiłem to za pomocą tego:

date = new Date();
public currentDate: any = new Date(this.date.getTime() - this.date.getTimezoneOffset()*60000).toISOString();

* 60000 wskazuje UTC -6, który jest CST, więc bez względu na strefę czasową można zmienić liczbę i różnicę.

Stephen Romero
źródło
To nie jest odpowiedź, której szuka pytający. Chciał ustalić datę w określonej strefie czasowej.
Richard Vergis,
@RichardVergis Pytanie wyraźnie stwierdza, że ​​użytkownik chce zmienić czas lokalny, ale nie określa, w której strefie czasowej się znajduje. Podałem moją strefę czasową jako przykład, a użytkownik może wyraźnie odczytać na podstawie mojego przykładu, może wprowadzić przesunięcie strefy czasowej.
Stephen Romero
0

Może to ci pomoże

/**
 * Shift any Date timezone.
 * @param {Date} date - Date to update.
 * @param {string} timezone - Timezone as `-03:00`.
 */
function timezoneShifter(date, timezone) {
  let isBehindGTM = false;
  if (timezone.startsWith("-")) {
    timezone = timezone.substr(1);
    isBehindGTM = true;
  }

  const [hDiff, mDiff] = timezone.split(":").map((t) => parseInt(t));
  const diff = hDiff * 60 + mDiff * (isBehindGTM ? 1 : -1);
  const currentDiff = new Date().getTimezoneOffset();

  return new Date(date.valueOf() + (currentDiff - diff) * 60 * 1000);
}



const _now = new Date()
console.log(
  [
    "Here: " + _now.toLocaleString(),
    "Greenwich: " + timezoneShifter(_now, "00:00").toLocaleString(),
    "New York: " + timezoneShifter(_now, "-04:00").toLocaleString(),
    "Tokyo: " + timezoneShifter(_now, "+09:00").toLocaleString(),
    "Buenos Aires: " + timezoneShifter(_now, "-03:00").toLocaleString(),
  ].join('\n')
);

JuanM04
źródło
-2

Napotkałem ten sam problem, użyłem tego

Console.log (Date.parse („13 czerwca 2018 10:50:39 GMT + 1”));

Zwróci milisekundy, do których możesz sprawdzić, czy strefa czasowa +100 zainicjalizuje czas brytyjski. Mam nadzieję, że to pomoże !!

Sandeep Kumar
źródło
3
(Wydaje się, że ten post nie zawiera wysokiej jakości odpowiedzi na pytanie. Edytuj swoją odpowiedź lub po prostu opublikuj ją jako komentarz do pytania).
sɐunıɔ ןɐ qɐp