【问题标题】:Calculating BST time from Date object?从日期对象计算 BST 时间?
【发布时间】:2016-04-21 12:47:13
【问题描述】:

我已经查看了一些关于类似主题的问题,但没有一个涉及计算目的地时区,考虑到 DST(夏令时)。我正在尝试编写一个简单的小部件,向访问该页面的任何人显示特定时区的实时本地时间。感兴趣的时区是 BST (what is BST?),但如果可能的话,我希望看到任何语言环境的通用实现。这是我的尝试,使用原生 JavaScript:

function getBST() {
    var date = new Date(),
        utc = date.getTime() + date.getTimezoneOffset() * 60000,
        local = new Date(utc), // this is GMT, not BST
        day = local.getDate(),
        mon = local.getMonth() + 1,
        year = local.getFullYear(),
        hour = local.getHours(),
        minute = ('0' + local.getMinutes()).slice(-2),
        second = ('0' + local.getSeconds()).slice(-2),
        suffix = hour < 12 ? 'AM' : 'PM';

    hour = (hour - 24) % 12 + 12;

    return mon + '/' + day + '/' + year + ', ' + hour + ':' + minute + ':' + second + ' ' + suffix;
}

setInterval(function () {
  document.body.textContent = getBST();
}, 1000);

根据@RobG 的回答,我编写了以下代码:

function resetDay(date) {
  date.setUTCMilliseconds(0);
  date.setUTCSeconds(0);
  date.setUTCMinutes(0);
  date.setUTCHours(1);

  return date;
}

function lastDay(date, day) {
  while (date.getUTCDay() !== day) {
    date.setUTCDate(date.getUTCDate() - 1);
  }

  return date;
}

function adjustDST(date, begin, end) {
  if (date >= begin && date < end) {
    date.setUTCHours(date.getUTCHours() + 1);
  }

  return date;
}

function updateBST() {
  var date = new Date();
  var begin = resetDay(new Date());

  begin.setUTCMonth(3, -1);
  begin = lastDay(begin, 0);

  var end = resetDay(new Date());

  end.setUTCMonth(10, -1);
  end = lastDay(end, 0);

  date = adjustDST(date, begin, end);

  var day = date.getUTCDate(),
    mon = date.getUTCMonth() + 1,
    year = date.getUTCFullYear(),
    hour = date.getUTCHours(),
    minute = ('0' + date.getUTCMinutes()).slice(-2),
    second = ('0' + date.getUTCSeconds()).slice(-2),
    suffix = hour < 12 ? 'AM' : 'PM';

  hour = (hour - 24) % 12 + 12;

  return mon + '/' + day + '/' + year + ', ' + hour + ':' + minute + ':' + second + ' ' + suffix;
}

setInterval(function () {
  document.body.textContent = updateBST();
}, 1000);

我通过在调试器中暂停并更改当前日期的月份来测试 BST,输出是一个小时后,所以它似乎可以正常工作。感谢大家的帮助!

【问题讨论】:

  • 我建议查看像 MomentMoment Timezone 这样的库。 JavaScript 中的日期和时区是出了名的难,编写自己的日期和时区很容易出错。
  • @MikeMcCaughan—“JavaScript 中的日期和时区是出了名的难……”。不,他们不是,只是有很多糟糕的信息,人们没有花时间去理解它们。日期是非常简单的对象。
  • 所以,如果它们不是那么难,请回答问题:P。请注意,在纯 JavaScript 中,无需参考外部库。
  • @MikeMcCaughan——我现在没有时间写代码,但我已经有了答案。我大约 10 小时后回来。
  • 为了迂腐,让我说“BST”具体是 UTC+1。英国有时在英国夏令时,有时在格林威治标准时间。如果您尝试协调单个时区的转换,您可以将其称为英国时间或伦敦时间。 IANA 使用Europe/London 标识符。 “转换为 BST”的想法会让我觉得你只想要 UTC+1,即使 BST 没有生效。

标签: javascript date dst timezone-offset


【解决方案1】:

只要您知道何时进入和退出夏令时,支持一个时区并不难。

JavaScript 日期由 UTC 时间值和基于主机系统设置的时区偏移量组成。因此,您需要做的就是将您想要的时区偏移量应用到 UTC 时间,然后在任何时区都有您的时间。

虽然有一些事实上的标准(例如IATA timezone codesIANA timezones),但没有用于缩写时区的标准化系统。我猜 BST 是指英国夏令时,也称为英国夏令时 (BDT) 和英国夏令时 (BDST)。它也可能是孟加拉国标准时间或布干维尔标准时间,也称为“BST”。

有各种库(例如Moment Timezone)使用 IANA 代码,可以在任何时间(或多或少)提供任何支持的时区的时间。

BST 每年 3 月最后一个星期日 01:00 UTC 开始,到 10 月最后一个星期日 01:00 UTC 结束,所以算法是:

  1. 根据系统的本地设置创建一个新日期(例如new Date()
  2. 根据 UTC 值创建 BST 的开始和结束日期(今年,2016-03-27T01:00:00Z 和 2016-03-30T02:00:00Z)
  3. 查看当前 UTC 时间是否在该范围内
  4. 如果是这样,请将 #1 中创建的日期的 UTC 时间增加 1 小时
  5. 根据日期的 UTC 值输出格式化字符串

就是这样,唯一困难的部分是找到合适的周日日期。您根本不需要考虑本地时区偏移,因为一切都基于 UTC,而 Date 对象也是。

我现在没有时间提供代码,所以请尝试一下,我可以在大约 10 小时内回复您。

编辑

所以这里是代码。前两个函数是助手,一个获取一个月中的最后一个星期日,另一个格式化一个带偏移量的 ISO 8601 字符串。大部分工作都在这两个功能中。希望cmets足够了,如果需要更多解释,请询问。

我没有在字符串中包含毫秒,如果需要,可以随意添加,在格式化字符串的偏移部分之前添加+ '.' + ('00' + d.getUTCMilliseconds()).slice(-3)

请注意,如果开始或停止夏令时的日期发生更改,则需要修改该功能,但这种情况并不常见。历史日期当然需要一个小型数据库,了解特定年份和时段的夏令时开始和结束时间。

/* Return a Date for the last Sunday in a month
** @param {number} year - full year number (e.g. 2015)
** @param {number} month - calendar month number (jan=1)
** @returns {Date} date for last Sunday in given month
*/
function getLastSunday(year, month) {
  // Create date for last day in month
  var d = new Date(year, month, 0);
  // Adjust to previous Sunday
  d.setDate(d.getDate() - d.getDay());
  return d;
}

/* Format a date string as ISO 8601 with supplied offset
** @param {Date} date - date to format
** @param {number} offset - offset in minutes (+east, -west), will be
**                          converted to +/-00:00
** @returns {string} formatted date and time
**
** Note that javascript Date offsets are opposite: -east, +west but 
** this function doesn't use the Date's offset.
*/
function formatDate(d, offset) {
  function z(n){return ('0'+n).slice(-2)}
  // Default offset to 0
  offset = offset || 0;
  // Generate offset string
  var offSign = offset < 0? '-' : '+';
  offset = Math.abs(offset);
  var offString = offSign + ('0'+(offset/60|0)).slice(-2) + ':' + ('0'+(offset%60)).slice(-2);
  // Generate date string
  return d.getUTCFullYear() + '-' + z(d.getUTCMonth()+1) + '-' + z(d.getUTCDate()) +
         'T' + z(d.getUTCHours()) + ':' + z(d.getUTCMinutes()) + ':' + z(d.getUTCSeconds()) +
         offString;
}

/* Return Date object for current time in London. Assumes
** daylight saving starts at 01:00 UTC on last Sunday in March
** and ends at 01:00 UTC on the last Sunday in October.
** @param {Date} d - date to test. Default to current
**                   system date and time
** @param {boolean, optional} obj - if true, return a Date object. Otherwise, return
**                        an ISO 8601 formatted string
*/
function getLondonTime(d, obj) {
  // Use provided date or default to current date and time
  d = d || new Date();

  // Get start and end dates for daylight saving for supplied date's year
  // Set UTC date values and time to 01:00
  var dstS = getLastSunday(d.getFullYear(), 3);
  var dstE = getLastSunday(d.getFullYear(), 10);
  dstS = new Date(Date.UTC(dstS.getFullYear(), dstS.getMonth(), dstS.getDate(),1));
  dstE = new Date(Date.UTC(dstE.getFullYear(), dstE.getMonth(), dstE.getDate(),1));
  // If date is between dstStart and dstEnd, add 1 hour to UTC time
  // and format using +60 offset
  if (d > dstS && d < dstE) {
    d.setUTCHours(d.getUTCHours() +1);
    return formatDate(d, 60);
  }
  // Otherwise, don't adjust and format with 00 offset
  return obj? d : formatDate(d);
}

document.write('Current London time: ' + getLondonTime(new Date()));

【讨论】:

  • 感谢算法,我没有意识到你可以忽略本地偏移,因为我见过的大多数解决方案实际上都考虑了它。
  • 是的,只是非UTC方法默认使用它。 ;-)
  • 因此,您确实需要这些时区偏移的外部参考(特别是对于一般情况,您不知道提前日期,因为偏移更改的日期因历史年份而异),这就是困难所在,为什么我提到它很困难,为什么我引用了和你一样的库,Moment Timezone。
  • 我更新了我的问题,以展示我对您提供的算法的普通实现。再次感谢您的帮助。
  • @RobG - 这种方法的问题是 JavaScript 中的 Date 对象仍会认为它在本地时区,因此它可能会决定“调整”接近 DST 转换的值在当地时区。如果目标时区的 DST 规则明显不同,可能会导致结果不正确。本质上,该缺陷在第 4 步和第 5 步之间——如果你在Date 对象之外添加你很好——但如果你返回Date 对象来生成字符串结果,它可能会失败。 (时刻避免这个 BTW)
【解决方案2】:

这在纯 JS 中是不可能的,只使用 Date 方法,但是(当然)有一个库:http://momentjs.com/timezone/

例子:

moment.tz("Europe/London").format(); // 2016-01-15T09:21:08-07:00

【讨论】:

  • 这也不是我的反对意见,但这是可能的,moment.js 和 Moment Timezone 是纯 JS。 ;-)
  • @RobG 他们可能是“纯粹的”但他们不是“香草”
  • @RobG 好吧,我猜每个 JS 库都是纯 JS……也许“纯”不是最好的选择,你有什么建议?
  • @Ilya 很抱歉,我不知道谁对你投了反对票,但我不能再接受你的回答,因为 RobG 提供的正是我所需要的。
  • 好吧,这很糟糕,因为我认为实际上重新编码 moment.js 是一个非常糟糕的主意,除非你有太多空闲时间。
【解决方案3】:

伦敦时间/英国夏令时:

const now = new Date();
console.log(now.toLocaleString('en-GB', { timeZone: 'Europe/London' })); 

【讨论】:

    猜你喜欢
    • 2019-09-18
    • 1970-01-01
    • 1970-01-01
    • 2015-11-16
    • 1970-01-01
    • 2019-07-22
    • 2020-01-21
    • 1970-01-01
    • 2011-09-24
    相关资源
    最近更新 更多