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

Amc*_*tty 3 javascript timezone datetime

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

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

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

您可能会问为什么在选择带有日期选择器的日期时,默认情况下任何人都需要其他下拉列表来选择另一个时区。

例子:让美国公民吉姆(Jim)在美国东部时间(EDT)华盛顿时间安排他的一生。他正在访问中国,进入一家中国网吧,并希望使用此应用来计划活动。日期选择器将选择本地时区,即中国标准时间。但是Jim希望通过EDT进行计划,并确保该应用程序能够正确处理所有事情。

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

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

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

或-获取本地日期+所选时区-将日期转换为UTC,不考虑时区-存储到数据库-读取时,使用所选时区转换为3个时区


稍后编辑-我在流星中执行此操作,因此是javascript服务器端。DB是mongodb,因此出于性能原因,日期必须另存为JS日期对象(当然是utc)。


第二次编辑

下面是我尝试过的实现(如我输入事件日期07:00 AM KST,输出从数据库读回并在该KST时区转换回的最终结果时,除07:00之外,它不起作用上午)

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

var pStartDate = GetDateAndTimeFromPostData(eventAttributes.startDate, eventAttributes.startTime, eventAttributes.timezone);
Run Code Online (Sandbox Code Playgroud)

在这里,我尝试从不同的控件(datepicker,timepicker,时区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;
    }
Run Code Online (Sandbox Code Playgroud)

在这里我尝试时区转换:

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; 
    }
Run Code Online (Sandbox Code Playgroud)

该错误已经在上面的代码中,因为utc中的日期未保存为KST,而是保存为GMT(我的本地时区)。

附带说明一下,我不太了解时区如何转换;当我进入时区网站并使用chrome dev工具编写此代码时:

var x = new Date();
var y = moment.tz(x, "Asia/Seoul");
y.utc().toDate()
Run Code Online (Sandbox Code Playgroud)

我实际上希望返回显示KST的日期对象?但显示的是GMT + 2,即我的本地tz。

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

我还尝试过倒退思考,例如存储的日期对象应该是什么样子,但这也很令人困惑-应该将其保存为选定的时区吗?例如2014年9月8日星期一23:44:05 GMT + 0900(KST)如果没有,我的存储日期对象应如何显示?

再次,我使用流星和mongoDB作为数据库的javascript服务器端。

非常感谢,

Mat*_*int 5

您提供的两种选择均不合适。

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

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

    这很重要,因为时区规则可能在输入事件的时间与事件的实际时间之间发生变化。例如,用户可能在俄罗斯而不是美国。 今年(2014年)将进行更改,并且需要更新您的时区数据。如果事件是在应用数据更新之前安排的,则您将使用规则来计算UTC值。如果事件是在此更改后发生的(例如,2014年11月),则应用更新后,事件时间将错误地更改。

如果要在JavaScript中完成所有操作,则有几个库可用于处理JavaScript中的时区。但是,我主要在后端使用JavaScript(例如与Node.js应用程序一起)时建议这样做。

使用服务器端代码通常更容易解决这类问题。您可能应该调查使用后端代码中使用的语言的时区选项。

为了进一步阅读,我已经写了好几次了:

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

  • 切勿尝试将时区缩写映射到特定时区。如您在Wikipedia上的此列表中所见,含糊不清。即使在您自己的4个时区列表中,也存在一些问题。具体来说,EDT仅在一年中的一部分时间使用America/New_York-该年的另一部分是EST。而且,尽管您拥有的CST Asia/Shanghai,它也可以申请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>
      
      Run Code Online (Sandbox Code Playgroud)
    • 如果要列出世界上所有时区,可能会发现它太长而无法放入单个下拉列表。在这种情况下,请提供两个下拉列表。第一个会选择一个国家,然后第二个会选择所选国家/地区内的时区。由于许多国家/地区只有一个时区,因此某些用户根本不需要从第二个列表中进行选择。

    • 如果您想要一种更具交互性的方法,请考虑基于地图的时区选择器控件,例如thisthis

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

  • 确保您了解调用.tz(zone)现有的moment对象与调用之间有区别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()
    
    Run Code Online (Sandbox Code Playgroud)

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

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

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