Как инициализировать дату JavaScript для определенного часового пояса


232

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

Например, я Feb 28 2013 7:00 PM ET,тогда могу

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

Насколько я знаю, я могу установить время UTC или местное время. Но как мне установить время в другом часовом поясе?

Я пытался использовать сложение / вычитание смещения от UTC, но я не знаю, как противостоять переходу на летнее время. Я не уверен, что направляюсь в правильном направлении.

Как я могу пойти о преобразовании времени из другого часового пояса в местное время в JavaScript?



1
У объектов даты нет часового пояса, они обозначены как UTC.
RobG

Ответы:


352

Задний план

DateОбъект JavaScript отслеживает время в UTC внутри, но обычно принимает ввод и производит вывод в местное время компьютера, на котором он работает. Здесь очень мало возможностей для работы со временем в других часовых поясах.

Внутреннее представление Dateобъекта - это одно число, представляющее количество миллисекунд, прошедших с тех пор 1970-01-01 00:00:00 UTC, без учета високосных секунд. В самом объекте Date нет часового пояса или формата строки. При использовании различных функций Dateобъекта локальный часовой пояс компьютера применяется к внутреннему представлению. Если функция создает строку, то информация о локали компьютера может быть принята во внимание, чтобы определить, как создать эту строку. Детали варьируются в зависимости от функции, а некоторые зависят от реализации.

Единственные операции, которые Dateобъект может выполнять с нелокальными часовыми поясами:

  • Он может анализировать строку, содержащую числовое смещение UTC из любого часового пояса. Он использует это для настройки анализируемого значения и сохраняет эквивалент UTC. Исходное местное время и смещение не сохраняются в результирующем Dateобъекте. Например:

    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)
  • В средах, в которых реализован API интернационализации ECMASCript (он же Intl), Dateобъект может создавать строку, зависящую от локали, настроенную для данного идентификатора часового пояса. Это достигается с помощью timeZoneопции toLocaleStringи ее вариантов. Большинство реализаций будет поддерживать идентификаторы часовых поясов IANA, такие как 'America/New_York'. Например:

    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)

    Большинство современных сред поддерживают полный набор идентификаторов часовых поясов IANA ( см. Таблицу совместимости здесь ). Однако, имейте в виду , что только идентификатор требуемого для подкрепляться Intl является 'UTC', таким образом , вы должны тщательно проверить , если вам необходимо поддерживать старые браузеры или атипичные среды (например, легкие устройства ВГД).

Библиотеки

Есть несколько библиотек, которые можно использовать для работы с часовыми поясами. Хотя они по-прежнему не могут заставить Dateобъект вести себя по-другому, они обычно реализуют стандартную базу данных часовых поясов IANA и предоставляют функции для ее использования в JavaScript. Современные библиотеки используют данные о часовых поясах, предоставляемые API-интерфейсом Intl, но более старые библиотеки обычно имеют накладные расходы, особенно если вы работаете в веб-браузере, поскольку база данных может стать немного больше. Некоторые из этих библиотек также позволяют выборочно сокращать набор данных либо по поддерживаемым часовым поясам, либо по диапазону дат, с которыми вы можете работать.

Вот библиотеки для рассмотрения:

Intl-based библиотеки

Новая разработка должна выбрать одну из этих реализаций, которые полагаются на API-интерфейс Intl для своих данных часового пояса:

Не международные библиотеки

Эти библиотеки поддерживаются, но несут бремя упаковки собственных данных часовых поясов, которые могут быть довольно большими.

* Хотя ранее были рекомендованы Moment и Moment-Timezone, команда Moment теперь предпочитает, чтобы пользователи выбрали Luxon для новой разработки.

Прекращенные библиотеки

Эти библиотеки были официально прекращены и больше не должны использоваться.

Будущие предложения

В Предложение TC39 Временные цели , чтобы обеспечить новый набор стандартных объектов для работы с датами и временем в самом языке JavaScript. Это будет включать поддержку объекта, осведомленного о часовом поясе.


1
Пожалуйста, измените «представить» на «вывод / синтаксический анализ», поскольку представленные временные метки не зависят от часового пояса
Bergi

1
@ Берги - я переосмыслил это и согласен с тобой. Обновил мой ответ соответственно.
Мэтт Джонсон-Пинт

1
Когда вы сделаете это в консоли Firebug: var date_time = new Date()значение date_timeIS (для меня в AEST) Date {Sun Aug 21 2016 22:18:47 GMT+1000 (AEST)}и так поэтому кажется , что это уже хранится часовой пояс. Как я могу убедиться, date_timeчто время указано только в формате UTC, без учета часового пояса?
user1063287

Хм, согласно этому ответу ( stackoverflow.com/a/8047885/1063287 ), это так: new Date().getTime();
user1063287

1
@ user1063287 - метод toString использует смещение часового пояса хоста для получения даты и времени в «местном» часовом поясе. У самого объекта даты есть значение времени, которое является смещением от 1970-01-01T00: 00: 00Z, так что фактически UTC. Чтобы увидеть дату и время UTC, используйте toISOString .
RobG

69

Как сказал Мэтт Джонсон

Если вы можете ограничить свое использование современными веб-браузерами, теперь вы можете делать следующее без каких-либо специальных библиотек:

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

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

Таким образом, хотя браузер не может считывать часовые пояса IANA при создании даты или имеет какие-либо методы для изменения часовых поясов в существующем объекте Date, кажется, что есть взлом:

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()}`);


6
Мне интересно, почему этот ответ не получает больше любви. Для моих целей это абсолютно лучшее решение. В моем приложении все даты указаны в формате UTC, но их необходимо вводить или выводить в явных часовых поясах IANA в пользовательском интерфейсе. Для этого я использую это решение, и оно отлично работает. Простой, эффективный, стандартный.
logidelic

2
@logidelic это довольно новый пост. Если честно, я удивился, когда меня поразило, что ты можешь использовать toLocaleStringдля достижения обратного эффекта, и да, это должно быть общеизвестно иди, напиши статью об этом и упоминая меня :-)
commonpike

1
Для среды узла, которая еще не находится в GMT, приведенный выше код должен измениться на var invdate = new Date (date.toLocaleString ('en-US', {timeZone: ianatz})); в var invdate = new Date ($ {date.toLocaleString ('en-US', {timeZone: ianatz})} GMT`);
Майк П.

5
Браузеры не обязаны анализировать вывод toLocaleString , поэтому он основан на ошибочной предпосылке.
RobG

3
"... почему этот ответ не получает больше любви?" Потому что Dateобъект, который он возвращает, - ложь. Даже в приведенном примере вывод строки включает часовой пояс локального компьютера. На моем компьютере оба результата говорят: «GMT-0700 (тихоокеанское летнее время)», где это правильно только для первого (Торонто на самом деле находится по восточному времени.) За исключением только вывода строки, временная метка, хранящаяся в Dateобъекте, была сместился в другой момент времени. Это может быть полезной техникой, но имеет много предостережений - в первую очередь, что Dateфункции объекта не будут иметь нормального вывода.
Мэтт Джонсон-Пинт

30

Вы можете указать смещение часового пояса new Date(), например:

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

или

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

Так как Dateхранить время UTC (т.е. getTimeвозвращает в UTC), javascript преобразует время в UTC, а когда вы вызываете такие вещи, как toStringjavascript, преобразует время UTC в местный часовой пояс браузера и возвращает строку в местном часовом поясе, т.е. если я использую UTC+8:

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

Также вы можете использовать обычный getHours/Minute/Secondметод:

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

(Это 8означает, что после того, как время конвертировано в мое местное время UTC+8, число часов будет 8.)


16
Синтаксический анализ любого формата, кроме расширенного формата ISO 8601, зависит от реализации и на него не следует полагаться. Стандарт аббревиатур для часовых поясов отсутствует, например, «EST» может обозначать любую из трех разных зон.
RobG

1
Примеры в этом комментарии часто не работают в Internet Explorer. Как упоминалось в предыдущем комментарии к этому сообщению, ISO 8601 имеет важное значение. Я подтвердил, прочитав Спецификацию языка издания ECMA-262 (Javascript 5).
Дакусан

12

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

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

Как использовать с часовым поясом и местным временем:

dateWithTimeZone("America/Los_Angeles",2019,8,8,0,0,0)

Это решение подходило для моих нужд. Я не был уверен, как именно я могу объяснить летнее время.
Хоссейн Амин

Почему бы просто не вернуться tzDateпрямо?
Леви

8

Я нашел наиболее поддерживаемый способ сделать это, не беспокоясь о сторонних библиотеках, - использовать getTimezoneOffsetдля вычисления соответствующей метки времени или обновления времени, а затем использовать обычные методы для получения необходимой даты и времени.

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

РЕДАКТИРОВАТЬ

Ранее я использовал UTCметоды при выполнении преобразований даты, что было неправильно. При добавлении смещения ко времени использование локальных getфункций вернет желаемые результаты.


Где вы рассчитали timezone_offset?
Ананд Сомани

1
К сожалению, я неправильно обозначил одну из моих переменных. timezone_offsetдолжно было быть offset, или наоборот. Я отредактировал свой ответ, чтобы показать правильное имя переменной.
Шон Кокерилл

3

Я столкнулся с аналогичной проблемой с модульными тестами (особенно в шутку, когда модульные тесты запускаются локально для создания снимков, а затем сервер CI запускается (потенциально) в другом часовом поясе, что приводит к сбою сравнения снимков). Я высмеял наш Dateи некоторые вспомогательные методы, например, так:

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

});

2

Попробуйте использовать ctoc из npm. https://www.npmjs.com/package/ctoc_timezone

Он имеет простую функциональность для изменения часовых поясов (большинство часовых поясов около 400) и всех пользовательских форматов, которые вы хотите отображать.


2

Основываясь на ответах выше, я использую эту собственную строку для преобразования длинной строки часового пояса в строку из трех букв:

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

Это даст PDT или PST в зависимости от предоставленной даты. В моем конкретном случае использования на базе Salesforce (Aura / Lightning) мы можем получить часовой пояс пользователя в длинном формате из бэкэнда.


Просто потому, что мне нравится время от времени играть педантично, «America / Los_Angeles» - это не часовой пояс, это «репрезентативное местоположение» для определенных правил смещения и перехода на летнее время и истории этих изменений (согласно базе данных часовых поясов IANA ) , ;-)
RobG

Ха-ха, вы должны
Шейн

2

Я знаю, что он опоздал на 3 года, но, возможно, он может помочь кому-то еще, потому что я не нашел ничего подобного, за исключением библиотеки моментов и часовых поясов, которая не совсем совпадает с тем, что он просит здесь.

Я сделал нечто подобное для немецкого часового пояса, это немного сложно из-за летнего времени и високосных лет, когда у вас есть 366 дней.

может потребоваться небольшая работа с функцией isDaylightSavingTimeInGermany, в то время как разные часовые пояса меняются в разное время летнего времени.

в любом случае, проверьте эту страницу: https://github.com/zerkotin/german-timezone-converter/wiki

основные методы: convertLocalDateToGermanTimezone convertGermanDateToLocalTimezone

Я приложил усилия для документирования этого, так что это не будет так запутанно.


Это должен быть комментарий, это не ответ.
RobG

1

Попробуйте: date-from-timezone , он разрешает ожидаемую дату с помощью нативно доступного Intl.DateTimeFormat.

Я использовал этот метод в одном из своих проектов уже несколько лет, но теперь я решил опубликовать его как небольшой проект ОС :)


1

Может быть, это поможет вам

/**
 * 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')
);


0

Для пользователей Ionic, я был чертовски с этим, потому что .toISOString()должен использоваться с шаблоном HTML.

Это захватит текущую дату, но, конечно, может быть добавлено к предыдущим ответам на выбранную дату.

Я получил это исправить с помощью этого:

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

* 60000 указывает на UTC -6, который является CST, поэтому независимо от того, какая временная зона необходима, число и разность могут быть изменены.


Это не тот ответ, который ищет спрашивающий. Он искал, чтобы получить дату в конкретном часовом поясе.
Ричард Вергис

@RichardVergis В вопросе четко указано, что пользователь хочет перейти на местное время, но не указано, в каком часовом поясе он находится. Я указал свой часовой пояс в качестве примера, и пользователь может четко прочитать на основе моего примера, что он может ввести их часовой пояс смещен.
Стивен Ромеро

-2

Столкнулся с той же проблемой, использовал этот

Console.log (Date.parse («13 июня 2018 10:50:39 GMT + 1»));

Он вернет миллисекунды, на которые вы можете проверить, если +100 к Тимзоне инициализировать британское время. Надеюсь, это поможет !!


3
(Похоже, что это сообщение не дает качественного ответа на вопрос. Пожалуйста, измените свой ответ или просто опубликуйте его как комментарий к вопросу).
sɐunıɔ ןɐ qɐp
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.