如何从用户日期和时区输入中将UTC日期正确存储在数据库中?

时间:2014-09-08 15:03:15

标签: javascript datetime timezone

我的javascript应用程序必须创建事件,这些事件将作为UTC存储在数据库中,以便之后可以在3个不同的时区显示它们。

我发现难以弄清楚的棘手部分是,在创建事件时,用户必须选择时区以及日期。

这个想法是: - 用户从带有时区的附加下拉列表中选择日期+所需时区。 - 我将UTC存储在数据库中 - 所有用户都可以在3个不同的时区看到日期。

您可能会问,为什么有人需要额外的下拉列表来选择其他时区,当选择带有日期选择器的日期时ALREADY默认包含时区。

示例:让我们成为美国公民吉姆,他使用美国东部时间华盛顿时间一生规划活动;他正在访问中国,进入中国网吧,并希望使用此应用程序计划活动。 datepicker将选择当地时区,即中国标准时间。但是吉姆希望通过EDT计划,并确保该应用程序正确处理所有事情。

因此,他必须从额外的下拉列表中选择所需的时区。

所以我的问题是。 因为我允许用户选择所需的时区,我首先必须将用户输入的日期转换为THAT时区,然后再将其转换为UTC,然后再存储它吗? 或者,在将事件保存在数据库中时,我对时区转换完全不感兴趣吗?

那么哪一步是正确的: - 获取当地日期+选定的时区 - 将本地日期转换为用户选择的时区 - 将日期转换为UTC - 存储到DB - 阅读时,使用所选时区转换为3个时区

或 - 获取当地日期+选定的时区 - 将日期转换为UTC,忽略时区 - 存储到DB - 阅读时,使用所选时区转换为3个时区


稍后编辑 - 我在流星中这样做,所以javascript服务器端。 DB是mongodb,因此出于性能原因,日期必须保存为JS日期对象(在utc ofcourse中)。


第二次编辑

下面是我尝试的实现(它不起作用,就像我在KST上午07:00输入事件日期时,输出从数据库读回的最终结果并转换回KST时区显示的任何内容07:00 AM)

这一切都从这里开始 - 这是一个服务器端方法,它从日期选择器中读取日期,从时间戳中读取时间,从下拉列表中读取时区:

var pStartDate = GetDateAndTimeFromPostData(eventAttributes.startDate, eventAttributes.startTime, eventAttributes.timezone);

这里我尝试从不同的控件(datepicker,timepicker,timezone ddl)构建所选日期:

function GetDateAndTimeFromPostData(dt, tm, timezone)
    {
        var t = tm.split(":");
        var hour =  t[0];
        var min = t[1];

        var finalDate = new Date(dt.getFullYear(), dt.getMonth(), dt.getDate(), hour, min);
        var utcConverted =  ConvertUserTimezoneToServerTimezone(finalDate, timezone);

        return utcConverted;
    }

这里我尝试进行时区转换:

function ConvertUserTimezoneToServerTimezone(dateToConvert, tz)
    {
        var userTimezonedDate;

        switch(tz)
        {
            case "EDT":
            {
                userTimezonedDate = moment.tz(dateToConvert, "America/New_York");
                break;
            }
            case "CEST":
            {
                userTimezonedDate = moment.tz(dateToConvert, "Europe/Berlin");
                break;
            }
            case "KST":
            {
                userTimezonedDate =  moment.tz(dateToConvert, "Asia/Seoul");
                break;
            }
            case "CST":
            {
                userTimezonedDate = moment.tz(dateToConvert, "Asia/Shanghai");
                break;
            }
        }

        var utcDateFromUserTimezonedDate = userTimezonedDate.utc().toDate();
        return utcDateFromUserTimezonedDate; 
    }

故障已在上面的代码中,因为utc日期不保存为KST,而是保存为GMT(我的本地时区)。

作为旁注,我不太明白时区如何转换;当我进入timezone网站时,我在chrome dev工具中写道:

var x = new Date();
var y = moment.tz(x, "Asia/Seoul");
y.utc().toDate()

我实际上期待一个显示KST的日期对象吗?但它显示GMT + 2,我的本地tz。

  

2014年9月8日星期一23:44:05 GMT + 0200(中欧夏令时)

我也试过向后思考这个问题,比如存储的日期对象应该怎么样但是这也让人感到困惑 - 它应该保存为选定的时区吗?例如Mon Sep 08 2014 23:44:05 GMT + 0900(KST)如果没有,我的日期对象怎么样?

再次,我使用javascript服务器端与meteor和mongoDB作为数据库。

非常感谢,

2 个答案:

答案 0 :(得分:5)

您提供的选项都不合适。

  • 如果您允许用户在美国东部时间安排活动,那么他将输入该时区的时间。他目前在中国的事实无关紧要。因此,从他当地的中国标准时间转换为UTC不是您想要做的事情。

  • 由于您要存储 future / 事件,因此仅存储 UTC值不是最好的建议。您应该存储原始输入的值和原始选定的时区。您也可以存储UTC时间,但您应该随时准备重新计算它。

    这很重要,因为时区规则可能会在输入事件的时间和事件的实际时间之间发生变化。例如,也许不是美国,用户在俄罗斯。 There are changes coming this year (2014),并且需要更新您的时区数据。如果在应用数据更新之前安排了事件,则您将使用规则来计算UTC值。如果事件发生在此更改之后(例如,2014年11月),那么一旦您应用更新,事件时间就会错误地更改。

如果您想在JavaScript中执行此操作,则有several libraries for working with time zones in JavaScript。但是,我建议这主要是在后端使用JavaScript时,例如使用Node.js应用程序。

使用服务器端代码,这种类型的问题通常更容易。您可能应该研究使用后端代码中使用的语言处理时区的选项。

为了进一步阅读,我已多次写过这篇文章:

关于您的修改 - 我可以就您提供的代码提供以下建议:

  • 切勿尝试将时区缩写映射到特定时区。正如你所能see in this list on Wikipedia,有太多含糊之处。即使在你自己的4个时区列表中也存在一些问题。具体而言,EDT仅用于America/New_York年中的部分时间 - 一年中的另一部分是EST。虽然您有Asia/Shanghai的CST,但它也可以申请America/Chicago和其他几个地方。 (CST有5种不同的含义。)

  • 除了时区缩写的下拉列表外,还有一些其他选项:

    • 如果您只需要处理几个时区,则可以提供一个下拉菜单。只需在值中使用时区ID,在文本中使用时区的全名即可。例如:

      <select name="tz">
          <option value="America/New_York">Eastern Time (North America)</option>
          <option value="Europe/Berlin">Central European Time</option>
          <option value="Asia/Seoul">Korean Standard Time</option>
          <option value="Asia/Shanghai">China Standard Time</option>
      </select>
      
    • 如果要列出世界上的所有时区,您可能会发现放入单个下拉列表的时间太长。在这种情况下,请提供两个下拉列表。第一个选择一个国家,然后第二个选择所选国家/地区内的时区。由于许多国家/地区只有一个时区,因此部分用户根本不必从第二个列表中进行选择。

    • 如果您想要更具互动性的方法,请考虑使用基于地图的时区选择器控件,例如this onethis one

  • GetDateAndTimeFromPostData函数中,构造一个Date对象。您需要记住,Date对象在内部始终是UTC,但对于大多数输入和输出,它会采用本地时区的行为。通过 local ,我的意思是代码运行的计算机的本地。在服务器端功能中,它将成为服务器的时区 - 在这种情况下不合适。由于您已经在使用moment.js,因此无需使用Date对象。

  • 请确保您了解在现有时刻对象上调用.tz(zone)与调用moment.tz(value, zone)之间存在差异。先前调整特定时区的时刻,后者创建一个新的时刻,在特定时区中表示。

  • 关于你的旁注,你正在击败时刻和时刻的目的:

    • 由于x代表当前的日期和时间,因此moment.tz(x, "Asia/Seoul")moment().tz("Asia/Seoul")

    • 相同
    • y.utc()正在将值转换回UTC,因此将.tz(...)称为

    • 是没有意义的
    • .toDate()将所有内容放回Date对象,该对象在内部表示UTC,但始终使用本地时区显示其输出。

    • 最后,您只是将当前日期和时间作为Date对象,因此整个事情将减少到new Date()

  • 关于MongoDB,其ISODate类型只是存储UTC值。因此,对于需要转换为UTC的代码部分,请考虑以下内容:

    moment.tz([2014,0,1,10,0],'Asia/Seoul').toISOString()
    

    或许您可以直接传递等效的Date对象 - 具体取决于您使用Mongo客户端的方式:

    moment.tz([2014,0,1,10,0],'Asia/Seoul').toDate()
    
  • 请考虑我在最初的回复中所说的内容。 UTC值将帮助您了解偶数应该运行的时间 - 但您不应该忘记原始输入值!如果eventAttributes.startDate已经是Date个对象,那么它可能已经转换为UTC,因此您丢失了原始输入值。您应该确保使用表示用户提供的ISO字符串将值从客户端传递到服务器。例如,传递2014-12-25T12:34:00。请勿尝试在此处传递偏移量,或转换为UTC,或传递整数。除非您准确传递用户提供的内容,否则无法存储该值。

    将原始输入值存储在Mongo中时 - 将其存储为字符串。如果您将其存储为Date,那么它也会被Mongo调整为UTC。

答案 1 :(得分:2)

在JavaScript中,您可以 使用UTC,使用计算机上的本地时间(但不知道其时区;您最多可以确定偏移到UTC)。

很多人都没有尝试在JS中转换时区,但所有这些方法都注定要失败,因为DST,闰年/秒等等。有很多陷阱使得实现非常复杂。

时间转换操作最好在后端完成。

你说你将日期存储在数据库中,所以我假设你将它转移到某个后端。

让我们假设您的后端是一些PHP应用程序,您将从日期选择器以及时区收集纯日期作为简单字符串(例如2014-09-08 15:12:00)。

在PHP中,您现在将执行以下操作:

$timestamp = strtotime($_POST['date']);
$dateLocal = new \DateTime("@$timestamp", new \DateTimezone($_POST['timezone']));
$dateUtc = $dateLocal->setTimezone(new \DateTimezone('UTC'));

现在,$dateUtc包含日期为UTC的坚如磐石的DateTime对象,您现在可以进一步处理它。

顺便说一句,如果你想在做任何其他事情之前向用户显示转换后的时区,我会把它实现为某种AJAX服务。如前所述,尝试在JavaScript中转换时区注定要失败。