javascript 有没有办法在只使用UTC日期时,以当地时区设定小时/分钟?

djmepvbi  于 2022-10-30  发布在  Java
关注(0)|答案(2)|浏览(138)

我想在我的Node.js后端应用程序中使用UTC日期,但是,我需要能够在本地/用户指定的时区中设置时间(小时和分钟)。
我正在寻找一个解决方案,无论是在纯JS或使用dayjs。我不是在moment中寻找解决方案。
看起来使用dayjs我可以很容易地解决这个问题,但是,我找不到完成这个问题的方法。
我可以通过使用dayjs.utc()dayjs.tz(someDate, 'Etc/UTC')来使用UTC时区。
当使用dayjs.utc()时,我不能为 * 任何东西 * 使用/指定其他时区,因此我找不到一种方法来告诉dayjs我想在特定(非UTC)时区中设置小时/分钟。
当使用dayjs.tz()时,我仍然不能定义我想要设置为特定日期的时间的时区。

纯JS格式的示例

我的区域设置时区是Europe/Slovakia(CEST = UTC+02,带DST; CET = UTC+1,不带DST),但是,我希望它适用于任何时区。

// Expected outcome
// old: 2022-10-29T10:00:00.000Z
// new time: 10h 15m CEST
// new: 2022-10-29T08:15:00.000Z

// Plain JS
const now = new Date('2022-10-29T10:00:00.000Z')
const hours = 10
const minutes = 15
now.setHours(10)
now.setMinutes(15)

// As my default timezone is `Europe/Bratislava`, it seems to work as expected
console.log(now)
// Output: 2022-10-29T08:15:00.000Z

// However, it won't work with timezones other than my local timezone

(几乎)解决方案

我刚刚创建了下面的函数。我认为当返回的日期(年、月和日值)与本地日期不同时,可能会有问题(请参见下面return之前的代码注解)。有人能修复这个问题吗?
您是否发现此函数中存在任何其他问题/错误?:)

**已更新:**请参阅my answer与更新得代码.

/**
 * Set timezoned time to a date object
 *
 * @param   {Date}    arg1.date      Date
 * @param   {Object}  arg1.time      Time
 * @param   {string}  arg1.timezone  Timezone of the time
 * @return  {Date}    Date updated with timezoned time
 */
function setLocalTime({date, time, timezone} = {
  date: new Date(),
  time: {hour: 0, minute: 0, second: 0, millisecond: 0},
  timezone: 'Europe/Bratislava'
}) {
  const defaultTime = {hour: 0, minute: 0, second: 0, millisecond: 0}
  const defaultTimeKeys = Object.keys(defaultTime)

  // src: https://stackoverflow.com/a/44118363/3408342
  if (!Intl || !Intl.DateTimeFormat().resolvedOptions().timeZone) {
    throw new Error('`Intl` API is not available or it does not contain a list of timezone identifiers in this environment')
  }

  if (!(date instanceof Date)) {
    throw Error('`date` must be a `Date` object.')
  }

  try {
    Intl.DateTimeFormat(undefined, {timeZone: timezone})
  } catch (e) {
    throw Error('`timezone` must be a valid IANA timezone.')
  }

  if (
    typeof time !== 'undefined'
    && typeof time !== 'object'
    && time instanceof Object
    && Object.keys(time).every(v => defaultTimeKeys.indexOf(v) !== -1)
  ) {
    throw Error('`time` must be an object of `{hour: number, minute: number, second: number, millisecond: number}` format, where numbers should be valid time values.')
  }

  time = Object.assign({}, defaultTime, time)

  const userTimezoneOffsetHours = new Intl
    .DateTimeFormat('en', {timeZone: timezone, timeZoneName: 'longOffset'})
    .format(date)
    .match(/[\d:+-]+$/)?.[0]

  // TODO: This might cause an issue when the `date` date in UTC is different from `date` date in user-defined timezone, e.g. `2022-10-28T22:00:00.000Z` → [UTC] `2022-10-28`, [UTC+02] `2022-10-29`.
  return new Date(`${date.getUTCFullYear()}-${(date.getUTCMonth() + 1).toString().padStart(2, '0')}-${date.getUTCDate().toString().padStart(2, '0')}T${time.hour.toString().padStart(2, '0')}:${time.minute.toString().padStart(2, '0')}:${time.second.toString().padStart(2, '0')}.${time.millisecond.toString().padStart(3, '0')}${userTimezoneOffsetHours}`)
}

setLocalTime()
// Output
// 2022-10-28T22:00:00.000Z

setLocalTime({date: new Date(2022, 0, 1)})
// Output
// 2021-12-30T22:00:00.000Z

setLocalTime({date: new Date(2022, 0, 1), timezone: 'America/Toronto', time: {hour: 4, minute: 45}})
// Output
// 2021-12-31T02:45:00.000Z
x6492ojm

x6492ojm1#

在JavaScript中,Date对象通常需要本地(浏览器)时间的时间定义,并以UTC时间存储。您可以使用.setUTC<time part>()方法显式设置UTC时区的时间。您可以使用.setUTCHours()函数(参数为hours-2)设置GMT+2时区的时间hours

const d=new Date(),hours=10,minutes=40;
d.setUTCHours(hours-2);d.setUTCMinutes(minutes);
console.log("GMT/UTC:",d); // UTC time ("Z")
console.log("New York:",d.toLocaleString("en-US",{timeZone:"America/New_York"})); // local time US
console.log("Berlin:",d.toLocaleString("de-DE",{timeZone:"Europe/Berlin"})); // local time Germany (GMT+2)

.setUTCMinutes()setMinutes()方法在大多数情况下会得到相同的结果。一个值得注意的例外是时区“亚洲/加尔各答”将应用30分钟的偏移。

fcipmucu

fcipmucu2#

我创建了下面的函数。它可以与我的测试一起工作,但是,如果你发现了问题,我希望听到它。)

/**
 * Set timezoned time to a date object
 *
 * @param   {Date}    arg1.date      Date
 * @param   {Object}  arg1.time      Time
 * @param   {string}  arg1.timezone  Timezone of the time
 * @return  {Date}    Date updated with timezoned time
 */
function setLocalTime(dto = {
  date: new Date(),
  time: {hour: 0, millisecond: 0, minute: 0, second: 0},
  timezone: 'Europe/Bratislava'
}) {
  const defaultTime = {hour: 0, millisecond: 0, minute: 0, second: 0}
  const defaultTimeKeys = Object.keys(defaultTime)

  // src: https://stackoverflow.com/a/44118363/3408342
  if (!Intl || !Intl.DateTimeFormat().resolvedOptions().timeZone) {
    throw new Error('`Intl` API is not available or it does not contain a list of timezone identifiers in this environment')
  }

  if (!(dto.date instanceof Date)) {
    throw Error('`date` must be a `Date` object.')
  }

  try {
    Intl.DateTimeFormat(undefined, {timeZone: dto.timezone})
  } catch (e) {
    throw Error('`timezone` must be a valid IANA timezone.')
  }

  if (
    typeof dto.time !== 'undefined'
    && typeof dto.time !== 'object'
    && dto.time instanceof Object
    && Object.keys(dto.time).every(v => defaultTimeKeys.indexOf(v) !== -1)
  ) {
    throw Error('`time` must be an object of `{hour: number, minute: number, second: number, millisecond: number}` format, where numbers should be valid time values.')
  }

  dto.time = Object.assign({}, defaultTime, dto.time)

  // Note: We need to specify a date in order to also consider DST settings.
  const localisedDate = new Intl
    .DateTimeFormat('en-GB', {timeZone: dto.timezone, timeZoneName: 'longOffset'})
    .format(dto.date)

  const userTimezoneOffsetHours = localisedDate.match(/[\d+:-]+$/)?.[0]
  // Note: `dateParts` array contains date parts in the following order: day of month, month (1-indexed), year
  const dateParts = localisedDate.split(/[,/]/).slice(0, 3)

  return new Date(`${dateParts[2]}-${dateParts[1]}-${dateParts[0]}T${dto.time.hour.toString().padStart(2, '0')}:${dto.time.minute.toString().padStart(2, '0')}:${dto.time.second.toString().padStart(2, '0')}.${dto.time.millisecond.toString().padStart(3, '0')}${userTimezoneOffsetHours}`)
}

console.log('no parameters:\n', setLocalTime())
// Output
// 2022-10-28T22:00:00.000Z

console.log('{date: new Date(2022, 0, 1)}:\n', setLocalTime({date: new Date(2022, 0, 1)}))
// Output
// 2021-12-30T22:00:00.000Z

console.log("{date: new Date(2022, 0, 1), timezone: 'America/Toronto', time: {hour: 4, minute: 45}}:\n", setLocalTime({date: new Date(2022, 0, 1), timezone: 'America/Toronto', time: {hour: 4, minute: 45}}))
// Output
// 2021-12-31T02:45:00.000Z

相关问题