作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.
Punit Jajodia's profile image

Punit Jajodia

验证专家 in 工程

Punit是一位多才多艺的软件工程师和企业家. 他从事大数据和实时3D模拟研究,是MEAN stack专家.

以前在

MAQ软件
分享

作为软件开发人员,您无法逃避日期操作. 开发者构建的几乎每个应用都会有一些需要从用户那里获取日期/时间的组件, stored in a database, 和 displayed back to the user.

问任何程序员他们处理日期和时区的经验,他们可能会分享一些战争故事. 处理日期和时间字段当然不是一件复杂的事情,但通常会很繁琐且容易出错.

关于这个话题有数百篇文章, 然而, most are either 太 academic, focusing on nitty-gritty details, or they are 太 patchy, 提供简短的代码片段而不附带太多解释. 这本深入的JS 日期Time操作指南应该可以帮助你理解与时间和日期相关的编程概念和最佳实践,而不必浏览关于这个主题的大量信息.

在本文中, 我将帮助您清晰地思考日期和时间字段,并建议一些最佳实践来帮助您避免日期/时间地狱. 在这里,我们将探讨正确操作日期和时间值所必需的一些关键概念, 便于存储日期Time值并通过api传输它们的格式, 和更多的.

对于初学者来说, 生产代码的正确答案几乎总是使用合适的库,而不是自己编写库. 本文中讨论的日期Time计算的潜在困难只是冰山一角, but they’re still helpful to k现在 about, with or without a library.

如果你正确地理解了日期时间库,它们会有所帮助

日期库在很多方面都能让你的生活更轻松. 它们极大地简化了日期解析、日期算术和逻辑运算以及日期格式. 您可以为前端和后端找到一个可靠的日期库来为您完成大部分繁重的工作.

然而,我们经常使用日期库而不考虑日期/时间的实际工作方式. 日期/time is a complex concept. 由于错误的理解而出现的错误可能非常难以理解和修复, even with the help of date libraries. 作为一名程序员, 您需要了解基础知识,并能够欣赏日期库解决的问题,以充分利用它们.

此外,日期/时间库只能带您到此为止. 所有日期库的工作方式都是让您访问方便的数据结构来表示日期Time. 如果您通过REST API发送和接收数据, 您最终需要将日期转换为字符串,反之亦然,因为JSON没有原生数据结构来表示日期Time. 我在这里概述的概念将帮助您避免在进行这些日期到字符串和字符串到日期的转换时可能出现的一些常见问题.

注意: Even though I have used JavaScript 作为本文中讨论的编程语言, these are general concepts that apply, 在很大程度上, 几乎所有的编程语言和它们的数据库. 所以即使你从来没有写过一行JavaScript, 请随意继续阅读,因为我几乎不假设本文中有任何JavaScript的先验知识.

St和ardizing the Time

A 日期Time is a very specific point in time. Let’s think about this. 当我写这篇文章的时候,我笔记本电脑上的时钟显示7月21日下午1:29. This is what we call “local time,“我在我周围的挂钟和手表上看到的时间.

Give or take a few minutes, 如果我约朋友下午三点在附近的咖啡馆见面, 大概那个时候我能在那儿见到她. 类似的, 如果我说,就不会有任何困惑了, 例如, “let’s meet in one 和 a half hours.“我们经常和住在同一个城市或时区的人这样谈论时间.

让我们考虑一个不同的场景:我想告诉一个住在乌普萨拉的朋友, Sweden that I want to talk to him at 5 PM. 我给他发了一条信息:“嘿,安东,我们下午5点谈吧.我立刻得到的回答是:“你的时间还是我的时间。?”

安东告诉我他住在中欧时区,也就是UTC+01:00. I live in UTC+05:45. This means that when it is 5 PM where I live, it is 5 PM - 05:45 = 11:15 AM UTC, 换算成Uppsala的UTC上午11:15 + 01:00 =下午12:15, perfect for both of us.

也, 注意时区(欧洲中部时间)和时区偏移量(UTC+05:45)之间的差异。. 各国也可以出于政治原因决定改变夏令时的时区偏移量. 几乎每年都有至少一个国家的规则发生变化, 这意味着任何包含这些规则的代码都必须保持最新——值得考虑的是,你的代码库依赖于应用程序的每一层.

这也是我们建议在大多数情况下只使用前端处理时区的另一个原因. 当它没有, 当数据库引擎使用的规则与前端或后端不匹配时会发生什么?

管理两个不同版本的时间的问题, 相对于用户和普遍接受的标准, 是很困难的, 在编程的世界中更是如此,因为精确度是关键,即使是一秒钟也会产生巨大的差异. 解决这些问题的第一步是将日期Time存储为UTC格式.

St和ardizing the Format

标准化时间很好,因为我只需要存储UTC时间,只要我知道用户的时区, I can always convert to their time. 相反,如果我知道用户的本地时间和他们的时区,我可以将其转换为UTC.

但是可以用许多不同的格式指定日期和时间. 对于日期, 你可以写“Jul 30”或“30 July”或“7/30”(或30/7), depending on where you live). 至于时间,你可以写“9:30 PM”或“2130”.

世界各地的科学家聚集在一起解决这个问题,并决定采用一种格式来描述时间,程序员非常喜欢这种格式,因为它简短而精确. We like to call it “ISO date format,这是ISO-8601扩展格式的简化版本,它看起来像这样:

显示ISO-8601扩展格式的简化版本的图像,称为ISO日期格式.

对于00:00或UTC,我们使用“Z”代替,这意味着祖鲁时间,UTC的另一个名称.

JS 日期 操纵 和 Arithmetic

Before we start with best practices, 我们将学习使用JavaScript进行日期操作,以掌握语法和一般概念. Although we use the JavaScript 日期Time format, 您可以轻松地将这些信息改编为您最喜欢的编程语言.

我们将使用日期算法来解决大多数开发人员遇到的与日期相关的常见问题.

我的目标是使您能够轻松地从字符串创建日期对象并从中提取组件. 这是一个日期库可以帮助你的事情, 但了解幕后是如何完成的总是更好的.

一旦我们弄脏了日期/时间, 这样就更容易思考我们面临的问题, extract the best practices, 然后继续. If you want to skip to the best practices, 请随意这样做, 但我强烈建议你至少浏览一下下面的日期计算部分.

的 JavaScript 日期 Object

编程语言包含有用的结构,使我们的生活更容易. 的 日期 object is one such thing. 它提供了在JavaScript中获取当前时间的方便方法, store a date in a variable, perform date arithmetic, 和 format the date based on the user’s locale.

由于浏览器实现之间的差异以及对夏令时(DST)的错误处理, 对于任务关键型应用程序,不建议依赖于日期对象,您可能应该使用类似的日期Time库 Luxon, date-fns, or 一天js. (不管你用什么,都要避免曾经流行的Moment.js—often simply called 时刻,因为它现在已被弃用.)

But for educational purposes, 我们将使用日期()对象提供的方法来学习JavaScript如何处理日期Time.

Getting Current 日期

const current日期 = 新 日期();

如果不向日期构造函数传递任何东西, 返回的date对象包含当前日期和时间.

然后你可以格式化它,只提取日期部分,如下所示:

const current日期 = 新 日期();

const currentDayOfMonth = current日期.获取当前日期();
const currentMonth = current日期.getMonth (); // Be careful! 一月份是0,不是1
const currentYear = current日期.getFullYear ();

const dateString = currentDayOfMonth + "-" + (currentMonth + 1) + "-" + currentYear;
// "27-11-2020"

注意:“1月是0”的陷阱很常见,但并不普遍. 任何语言(或配置格式)的文档都值得仔细检查.g.(cron显然是基于1的),然后才开始使用它.

Getting the JavaScript Timestamp

如果你想在JavaScript中获取当前时间戳, 您可以创建一个新的日期对象并使用取得时间()方法.

const current日期 = 新 日期();
const timestamp = current日期.取得时间();

在JavaScript中,时间戳是自1970年1月1日以来所经过的毫秒数. 这使得JavaScript从时间戳到日期和从时间戳到日期的转换非常简单, provided the UTC time zone is used.

If you don’t intend to support 日期.现在() 直接获取时间戳,而不必创建新的日期对象.

解析日期

将字符串转换为JavaScript日期对象有不同的方式.

日期对象的构造函数接受各种各样的日期格式:

const date1 = 新 日期("Wed, 27 July 2016 13:30:00");
const date2 = 新 日期("Wed, 27 July 2016 07:45:00 UTC");
const date3 = 新 日期("27 July 2016 13:30:00 UTC+05:45");

请注意,您不需要包含星期几,因为JS可以确定任何日期的星期几.

你也可以将年、月、日、小时、分钟和秒作为单独的参数传入:

const date = 新 日期(2016, 6, 27, 13, 30, 0);

Of course, you can always use ISO date format:

const date = 新 日期("2016-07-27T07:45:00Z");

但是,如果没有显式地提供时区,可能会遇到麻烦!

const date1 = 新 日期("25 July 2016");
const date2 = 新 日期("July 25, 2016");

这两种方式都会给你2016年7月25日当地时间00:00:00.

If you use the ISO format, 即使你只给出日期而不给出时间和时区, 它将自动接受时区为UTC.

这意味着:

新 日期("25 July 2016").取得时间() !== 新 日期("2016-07-25").取得时间()
新 日期("2016-07-25").取得时间() === 新 日期("2016-07-25T00:00:00Z").取得时间()

格式化日期

幸运的是, 现代JavaScript在标准中内置了一些方便的国际化函数 Intl 名称空间,使日期格式化成为一种简单的操作.

For this we’ll need two objects: a 日期 和一个 Intl.日期TimeFormat, initialized with our output preferences. 假设我们想使用美式(M/D/YYYY)格式,这看起来像:

const firstValentineOf的Decade = 新 日期(2020, 1, 14); // 1 for February
const enUSFormatter = 新 Intl.日期TimeFormat('en-US');
控制台.日志(enUSFormatter.format(firstValentineOf的Decade));
// 2/14/2020

如果我们想要荷兰语(D/M/YYYY)格式,我们只需将不同的文化代码传递给 日期TimeFormat 构造函数:

const nlBEFormatter = 新 Intl.日期TimeFormat('nl-BE');
控制台.日志(nlBEFormatter.format(firstValentineOf的Decade));
// 14/2/2020

或者是美式格式的更长的形式,加上月份的名称:

const longEnUSFormatter = 新 Intl.日期TimeFormat('en-US', {
    :“数值”,
    月:“长”,
    天:“数值”,
});
控制台.日志(longEnUSFormatter.format(firstValentineOf的Decade));
// February 14, 2020

现在, 如果我们想要在每个月的每一天有一个合适的顺序格式,那就是, “14”而不是“14”——不幸的是,这需要一点变通, 因为 一天’s only valid values as of this writing are “数字” or “便是”. 借款 Flavio Copes’ version of Mathias Bynens’ code to leverage another part of Intl 为此,我们可以通过自定义当月的日期输出 formatToParts ():

const pluralRules = 新 Intl.PluralRules('en-US', {
    类型:“顺序”
})
Const后缀= {
    “一”:“圣”,
    “两个”:“和”,
    “几”:“路”,
    “其他”:“th”
}
const convertToOrdinal = (number) => `${number}${suffixes[pluralRules.选择(数字)]}’
//此时:
// convertToOrdinal("1") === "1st"
// convertToOrdinal("2") === "2nd"
/ /等.

const extractValueAndCustomizeDayOfMonth = (part) => {
    如果(部分.Type === "一天") {
        返回 convertToOrdinal(part.值);
    }
    返回一部分.价值;
};
控制台.日志(
    longEnUSFormatter.formatToParts(firstValentineOf的Decade)
    .map(extractValueAndCustomizeDayOfMonth)
    .加入(" ")
);
// February 14th, 2020

不幸的是, formatToParts 在撰写本文时,Internet Explorer (IE)根本不支持, but all other desktop, 移动, 后端(i).e. 节点.js)技术 一定要有支持. 对于那些需要支持IE和绝对需要序数的人, the sidenote below (or better, a proper date library) provides an answer.

如果您需要支持较旧的浏览器,如IE 11版本之前, 日期格式在JavaScript中比较困难,因为没有标准的日期格式函数 strftime Python或PHP.

In PHP 例如, the 函数 strftime("今天是%b %d %Y %X", mktime(5,10,0,12,30,99)) 给你 To一天 is Dec 30 1999 05:10:00.

你可以使用 different combination of letters 之前 % to get the date in different formats. (小心, 并不是每种语言都赋予每个字母相同的含义——尤其是, “M”和“M”可以互换为分钟和月.)

If you are sure of the format you want to use, 最好使用上面提到的JavaScript函数提取单个比特,然后自己创建一个字符串.

var current日期 =  日期();
var date =当前日期.获取当前日期();
var month =当前日期.getMonth (); 
var year =当前日期.getFullYear ();

We can get the date in MM/DD/YYYY format as

var month日期Year  = (month+1) + "/" +日期+ "/" +;

这个解决方案的问题是,它可能会给日期一个不一致的长度,因为一个月的一些月份和天数是一位数,而另一些是两位数. This can be problematic, 例如, 如果要在表列中显示日期, 因为 the dates don’t line up.

我们可以通过使用一个“垫”函数来解决这个问题,该函数添加一个前导0.

函数 (n) {
    返回 n<10 ? '0'+n : n;
}

现在,我们使用MM/DD/YYYY格式获得正确的日期:

var mmddyyyy = 垫(month + 1) + "/" + 垫(日期)+ "/" +;

如果我们想要DD-MM-YYYY,过程类似:

var ddmmyyyy = 垫(date) + "-" + 垫(月)+ 1) + "-" +;

让我们加大赌注,尝试以“月、日、年”格式打印日期. 我们需要一个月索引到名字的映射:

var monthNames = [
    “1月”, “2”, “3”, “4”, “可能”, “6月”, “7”, “八月”, “9”, “十月”, “11月”, “12月”
];

var date与FullMonthName = monthNames[month] + " " + 垫(日期)+ ", " +;

有些人喜欢将日期显示为2013年1月1日. No problem, all we need is a helper 函数 序数 1返回第1位,12返回第12位,103返回第103位,等等., 和 the rest is simple:

var 序数日期 = 序数(date) + " " + monthNames[month] + ", " +;

从date对象中确定星期几很容易,所以让我们添加它:

var 一天sOfWeek = [“太阳”, “我的”, “星期二”, “结婚”, “星期四”, “星期五”, “坐”];


序数日期与DayOfWeek = 一天sOfWeek[当前日期].getDay ()] + ", " + 序数日期;

的 bigger point here is, 一旦你从日期中提取了数字, the formatting is mostly related to strings.

Changing the 日期 Format

一旦您知道如何解析日期并格式化它, 将日期从一种格式更改为另一种格式只是将两者结合起来的问题.

例如, if you have a date in the format Jul 21, 并希望将格式更改为21-07-2013, it can be achieved like this:

const 替换 = 新 日期("Jul 21, 2013");
const 一天OfMonth = 替换.获取当前日期();
const month = 替换.getMonth ();
const year = 替换.getFullYear ();

功能键(n) {
    返回 n<10 ? '0'+n : n
}

const ddmmyyyy = 垫(一天OfMonth) + "-" + 垫(月)+ 1) + "-" +年份;
// "21-07-2013"

使用JavaScript日期对象的本地化函数

我们上面讨论的日期格式化方法应该适用于大多数应用程序, 但如果你真的想本地化日期的格式, I suggest you use the 日期 对象的 toLocale日期String() 方法:

const 今天 = 新 日期().toLocale日期String('en-GB', {  
    天:“数值”,
    月:“短”,
    :“数值”,
});

…gives us something like 2016.7.26.

将区域设置更改为“en-US”将显示“Jul 26, 2016”. Notice how the formatting changed, 但显示选项仍然保持不变——这是一个非常有用的功能. As shown in the previous section, the 新er Intl.日期TimeFormat-based technique works very similarly to this, 但是允许您重用格式化器对象,这样您只需要设置一次选项.

toLocale日期String(), 传递格式化选项是个好习惯, even if the output looks fine on your computer. 这可以防止UI在使用非常长的月份名称或由于较短的月份名称而显得笨拙的意外区域中出现故障.

If I wanted the full month “July” instead, 我所做的就是将选项中的月份参数改为“long”. JavaScript h和les everything for me. For en-US, I 现在 get July 26, 2016.

注意:如果您希望浏览器自动使用用户的区域设置, 可以将" undefined "作为第一个参数.

如果你想显示日期的数字版本,而又不想纠结于MM/DD/YYYY vs. 对于不同地区的DD/MM/YYYY,我建议以下简单的解决方案:

const 今天 = 新 日期().toLocale日期String(undefined, {
    天:“数值”,
    月:“数值”,
    :“数值”,
});

On my computer, this outputs 7/26/2016. 如果你想确保月份和日期都是两位数字,只需改变选项:

const 今天 = 新 日期().toLocale日期String(undefined, {
    天:“便是”,
    月:“便是”,
    :“数值”,
});

这个输出 07/26/2016. Just what we wanted!

你也可以使用一些其他相关的函数来本地化时间和日期的显示方式:

CodeOutput描述
现在.toLocaleTimeString()
"4:21:38 AM"Display localized version of only time
现在.toLocaleTimeString(undefined, {
    小时:“便是”,
    分钟:“便是”,
    第二:“便是”,
});
"04:21:38 AM"根据提供的选项显示本地化时间
现在.toLocaleString ()
"7/22/2016, 4:21:38 AM"Display date 和 time for user's locale
现在.toLocaleString(undefined, {
    天:“数值”,
    月:“数值”,
    :“数值”,
    小时:“便是”,
    分钟:“便是”,
});
"7/22/2016, 04:21 AM"根据提供的选项显示本地化的日期和时间

Calculating Relative 日期s 和 Times

下面是一个将20天添加到JavaScript日期(1)的示例.e.,计算已知日期后20天的日期):

const 替换 = 新 日期("July 20,2016 15:00:00");
const nextDayOfMonth = 替换.get日期() + 20;
替换.set日期(nextDayOfMonth);
const 新日期 = 替换.toLocaleString ();

原来的date对象现在表示7月20日之后20天的日期 新日期 包含表示该日期的本地化字符串. 在我的浏览器中, 新日期 contains “8/9/2016, 3:00:00 PM”.

要计算比全天更精确的相对时间戳,可以使用 日期.取得时间()日期.凝固时间() 用整数表示从某一时期开始的毫秒数,即, 1月1日, 1970. 例如,如果你想知道17小时后的时间:

const msSinceEpoch = (新 日期()).取得时间();
const 17enhouslater = 新 日期(msSinceEpoch + 17 * 60 * 60 * 1000);

比较日期

与其他与日期有关的事情一样,比较日期也有它自己的陷阱.

First, we need to create date objects. 幸运的是, <, >, <=, 和 >= all work. 因此,比较2014年7月19日和2014年7月18日很容易:

const date1 = 新 日期("July 19, 2014");
const date2 = 新 日期("July 28, 2014");

if(date1 > date2) {
    控制台.日志("First date is more recent");
} else {
    控制台.日志("Second date is more recent");
}

Checking for equality is trickier, 因为表示相同日期的两个日期对象仍然是两个不同的日期对象,不会相等. Comparing date strings is a bad idea 因为, 例如, “7月20日, “2014”和“20 July 2014”表示相同的日期,但具有不同的字符串表示形式. 下面的代码片段说明了第一点:

const date1 = 新 日期("June 10, 2003");
const date2 = 新 日期(date1);

const equalOrNot = date1 == date2 ? "equal" : "不平等的";
控制台.日志(equalOrNot);

这将输出 不平等的.

这种特殊情况可以通过比较日期(它们的时间戳)的整数等效来修复,如下所示:

date1.取得时间() == date2.取得时间()

I’ve seen this example in lots of places, 但我不喜欢它,因为你通常不会从另一个日期对象创建一个日期对象. 所以我觉得这个例子只从学术的角度来看很重要. 也, 这要求两个日期对象引用完全相同的秒, 然而,你可能只想知道它们是否指的是同一天、同一小时或同一分钟.

Let’s look at a more practical example. 您正在尝试比较用户输入的生日是否与您从API获得的幸运日期相同.

const userEnteredString = "12/20/1989"; // MM/DD/YYYY format
const dateStringFromAPI = "1989-12-20T00:00:00Z";

const dateFromUserEnteredString =新的日期(userEnteredString)
const datefromapi = 新 日期(dateStringFromAPI);

if (dateFromUserEnteredString.取得时间() == dateFromAPIString.取得时间()){
    transferOneMillionDollarsToUserAccount();
} else {
    doNothing ();
}

两者都代表了相同的日期,但不幸的是,你的用户不会得到一百万美元.

这里有一个问题:JavaScript总是假设时区是浏览器提供的时区,除非另有显式指定.

对我来说,这意味着, 新 日期 ("12/20/1989") 将创建一个日期1989-12-20T00:00:00+5:45或1989-12-19T18:15:00Z,这与时间戳1989-12-20T00:00:00 z不一样.

不可能只更改现有日期对象的时区, 所以我们现在的目标是创建一个新的日期对象,但是使用UTC而不是本地时区.

在创建date对象时,我们将忽略用户的时区并使用UTC. 的re are two ways to do it:

  1. 从用户输入日期创建一个ISO格式的日期字符串,并用它来创建一个date对象. 使用有效的ISO日期格式来创建date对象,同时使UTC与本地的意图非常清楚.
const userEntered日期 = "12/20/1989";
const parts = userEntered日期.分割(“/”);

const userEntered日期ISO = parts[2] +“-”+ parts[0] +“-”+ parts[1];

const userEntered日期Obj = 新 日期(userEntered日期ISO + " t00:00:00 . 00 z ");
const dateFromAPI = 新 日期("1989-12-20:00:00 . 00 z ");

const result = userEntered日期Obj.取得时间() == dateFromAPI.取得时间(); // true

这也适用于如果你没有指定时间,因为它将默认为午夜(1).e., 00:00:00Z):

const userEntered日期 = 新 日期("1989-12-20");
const dateFromAPI = 新 日期("1989-12-20:00:00 . 00 z ");
const result = userEntered日期.取得时间() == dateFromAPI.取得时间(); // true

记住:如果向日期构造函数传递一个正确的ISO日期格式的字符串YYYY-MM-DD, it assumes UTC automatically.

  1. JavaScript provides a neat 日期.UTC()函数,可用于获取日期的UTC时间戳. 我们从日期中提取组件并将它们传递给函数.
const userEntered日期 = 新 日期("12/20/1989");
const userEntered日期TimeStamp = 日期.UTC (userEntered日期.getFullYear(), userEntered日期.getMonth (), userEntered日期.get日期(), 0, 0, 0);

const dateFromAPI = 新 日期("1989-12-20:00:00 . 00 z ");
const result = userEntered日期TimeStamp == dateFromAPI.取得时间(); // true
...

Finding the Difference Between Two 日期s

您将遇到的一个常见场景是找出两个日期之间的差异.

We discuss two use cases:

Finding the Number of Days Between Two 日期s

Convert both dates to UTC time stamp, 找出以毫秒为单位的差异,并找出等效的天数.

const dateFromAPI = "2016-02-10T00:00:00Z";

const 现在 = 新 日期();
const dateFromAPI = (新 日期(dateFromAPI)).取得时间();
const 现在TimeStamp = 现在.取得时间();

const microSecondsDiff = Math.abs(datefromAPITimeStamp - 现在TimeStamp);

/ /数学.round is used instead of Math.floor to account for certain DST cases
// Number of milliseconds per 一天 =
// 24小时/天* 60分钟/小时* 60秒/分钟* 1000毫秒/秒
const 一天sDiff = Math.round(microSecondsDiff / (1000 * 60 * 60 * 24));

控制台.日志(一天sDiff);

Finding User’s Age from 的ir 日期 of Birth

const birth日期FromAPI = "12/10/1989";

注意: We have a non-st和ard format. 阅读API文档以确定这是指10月12日还是12月10日. Change to ISO format accordingly.

const parts = birth日期FromAPI.分割(“/”);
常量birth日期ISO = parts[2] +“-”+ parts[0] +“-”+ parts[1];

const birth日期 =  新 日期(birth日期ISO);
const 今天 = 新 日期();

让年龄=今天.getFullYear() - birth日期.getFullYear ();

如果今天(.getMonth () < birth日期.getMonth ()) {
    年龄——;
}

如果今天(.getMonth () == birth日期.getMonth () && 今天.get日期() < birth日期.获取当前日期()){
    年龄——;
}

我知道有更简洁的方法来编写这段代码,但我喜欢这样写,因为逻辑非常清晰.

Suggestions to Avoid 日期 Hell

现在我们熟悉了日期算法, 我们能够理解应该遵循的最佳实践以及遵循它们的原因.

Getting 日期Time from User

如果你从用户那里得到日期和时间, 你很可能在寻找他们当地的日期时间. We saw in the date arithmetic section that the 日期 构造函数可以以多种不同的方式接受日期.

为了避免混淆,我总是建议使用 新 日期(年、月、日、时、分、秒、毫秒) 即使您已经有了有效的可解析格式的日期,也要格式化. 如果你团队中的所有程序员都遵循这个简单的规则, 从长远来看,维护代码将非常容易,因为它是显式的,您可以使用 日期 构造函数.

的 cool part is that you can use the variations that allow you to omit any of the last four parameters if they are zero; i.e., 新 日期(2012, 10, 12) 等于 新 日期(2012, 10, 12, 0, 0, 0, 0) 因为未指定参数默认为零.

例如, 如果您使用的日期和时间选择器为您提供日期2012-10-12和时间12:30, 你可以提取这些部分并创建一个新的日期对象,如下所示:

const dateFromPicker = "2012-10-12";
const timeFromPicker = "12:30";

const dateParts = dateFromPicker.分割(“-”);
const timeParts = timeFromPicker.分割(“:”);
const local日期 = 新 日期(dateParts[0], dateParts[1]-1, dateParts[2], timeParts[0], timeParts[1]);

尽量避免从字符串创建日期,除非它是ISO日期格式. 请使用日期(年、月、日、小时、分钟、秒、微秒)方法.

Getting Only the 日期

If you are getting only the date, a user’s birthdate for instance, 最好将格式转换为有效的ISO日期格式,以消除在转换为UTC时可能导致日期向前或向后移动的任何时区信息. 例如:

const dateFromPicker = "12/20/2012";
const dateParts = dateFromPicker.分割(“/”);
const ISO日期 = dateParts[2] +“-”+ dateParts[0] +“-”+ dateParts[1];
const birth日期 = 新 日期(ISO日期).toISOString ();

In case you forgot, if you create a 日期 对象,输入为有效的ISO日期格式(YYYY-MM-DD)。, 它将默认为UTC而不是默认为浏览器的时区.

存储日期

Always store the 日期Time in UTC. 始终将ISO日期字符串或时间戳发送到后端.

一代又一代的计算机程序员在试图向用户显示正确的当地时间的痛苦经历之后,已经认识到了这个简单的真理. 将本地时间存储在后端是一个坏主意, 最好让浏览器在前端处理到本地时间的转换.

也, 很明显,您永远不应该发送像“7月20日”这样的日期Time字符串, 1989 12:10 PM” to the back end. Even if you send the time zone as well, 您正在增加其他程序员理解您的意图并正确解析和存储日期的工作量.

使用 toISOString () or toJSON () 日期对象的方法将本地日期Time转换为UTC.

const dateFromUI = "12-13-2012";
const timeFromUI = "10:20";
const dateParts = dateFromUI.分割(“-”);
const timeParts = timeFromUI.分割(“:”);

const date = 新 date (dateParts[2], dateParts[0]-1, dateParts[1], timeParts[0], timeParts[1]);

const dateISO = date.toISOString ();

$.post("http://example.com/", {date: dateISO}, ...)

Displaying the 日期 和 Time

  1. 从REST API获取时间戳或ISO格式的日期.
  2. 创建一个 日期 object.
  3. 使用 toLocaleString () or toLocale日期String()toLocaleTimeString() 方法或日期库以显示本地时间.
const dateFromAPI = "2016-01-02T12:30:00Z";

const local日期 = 新 日期(dateFromAPI);
const local日期String = local日期.toLocale日期String(undefined, {  
    天:“数值”,
    月:“短”,
    :“数值”,
});


const localTimeString = local日期.toLocaleTimeString(undefined, {
    小时:“便是”,
    分钟:“便是”,
    第二:“便是”,
});

When Should You Store the Local Time Too?

“有时是 important to k现在 the time zone in which an event occurred, 转换到单一时区将不可逆转地抹去这些信息.

“如果你在做市场推广,想知道哪些客户在午餐时间下了订单, 一个看起来是在格林尼治时间中午下的订单,如果它实际上是在纽约的早餐时间下的,那就没什么用了.”

如果遇到这种情况,最好也节约当地时间. 像往常一样, 我们想创建ISO格式的日期, 但我们得先找出时区偏移量.

日期对象的 getTimeZoneOffset () 函数告诉我们将给定的本地时间加上得到等效的UTC时间的分钟数. 我建议将其转换为(+-)hh:mm格式,因为这样可以更明显地表明它是一个时区偏移量.

const 现在 = 新 日期();
Const tz = 现在.gettime zoneOffset();

For my time zone +05:45, 得到-345, this is not only the opposite sign, 但像-345这样的数字可能会让后端开发人员完全困惑. So we convert this to +05:45.

const sign = tz > 0 ? "-" : "+";

const hours = 垫(Math.地板(数学.abs (tz) / 60));
const minutes = 垫(Math.abs (tz) % 60);

const tzOffset = sign + hours + ":" + minutes;

现在我们获取其余的值,并创建一个表示本地日期Time的有效ISO字符串.

const local日期Time = 现在.getFullYear () +
                      "-" +
                      垫(现在.getMonth () + 1) +
                      "-" +
                      垫(现在.获取当前日期())+
                      "T" +
                      垫(现在.getHours ()) +
                      ":" +
                      垫(现在.getMinutes ()) +
                      ":" +
                      垫(现在.getSeconds ());

如果需要,可以将UTC和本地日期包装在一个对象中.

const event日期 = {
    utc:现在.toISOString (),
    local: local日期Time,
    tzOffset: tzOffset,
}

现在, 在后端, 如果你想知道事件是否发生在当地时间中午之前, you can parse the date 和 simply use the getHours () 函数.

const local日期String = event日期.当地的;
const local日期 = 新 日期(local日期String);

如果(local日期.getHours () < 12) {
    控制台.日志("Event happened before noon local time");
}

我们没有使用 tzOffset 在这里,但是我们仍然存储它,因为我们将来可能需要它来进行调试. 你可以只发送时区偏移量和UTC时间. 但我也喜欢存储本地时间,因为您最终将不得不将日期存储在数据库中,并且单独存储本地时间允许您根据字段直接查询,而不必执行计算以获得本地日期.

有时, even with the local time zone stored, 您将希望在特定时区显示日期. 例如, 如果事件的时间是虚拟的,那么它们在当前用户的时区中可能更有意义, 或者在它们实际发生的时区, 如果不是的话. In any case, it’s worth looking beforeh和 at established solutions for formatting with explicit time zone names.

Server 和 Database Configuration

始终将您的服务器和数据库配置为使用UTC时区. (Note that UTC 和 GMT are 这不是一回事例如,-GMT可能意味着在夏季切换到英国夏令时,而UTC则永远不会.)

我们已经看到时区转换有多么痛苦, especially when they are unintended. 始终发送UTC 日期Time并将服务器配置为UTC时区可以使您的生活更轻松. 您的后端代码将更加简单和清晰,因为它不需要进行任何时区转换. 来自世界各地服务器的日期Time数据可以毫不费力地进行比较和排序.

后端的代码应该能够假设服务器的时区是UTC(但仍然应该有一个检查来确保)。. 一个简单的配置检查节省了每次编写新的日期Time代码时考虑转换的代码.

It’s Time for Better 日期 H和ling

日期 manipulation is a hard problem. 本文中实际示例背后的概念适用于JavaScript之外的领域, 并且只是正确处理日期Time数据和计算的开始. Plus, every helper library will come with 它自己的一套 of nuances—which is even true of the eventual official st和ard support for these types of operations.

的 bottom line is: Use ISO on the back end, 并让前端为用户正确格式化内容. 专业程序员会意识到其中的一些细微差别, 并且将(更加坚定地)在后端和前端都使用支持良好的日期Time库. 数据库端的内置函数是另一回事, 但希望这篇文章能提供足够的背景知识,让你在这种情况下做出更明智的决定, 太.

Consult the author or an expert on this topic.
预约电话
Punit Jajodia's profile image
Punit Jajodia
验证专家 in 工程

位于 Kathm和u, Central Development Region, Nepal

成员自 2016年7月6日

作者简介

Punit是一位多才多艺的软件工程师和企业家. 他从事大数据和实时3D模拟研究,是MEAN stack专家.

Toptal作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.

以前在

MAQ软件

World-class articles, delivered weekly.

Subscription implies consent to our 隐私政策

World-class articles, delivered weekly.

Subscription implies consent to our 隐私政策

Toptal开发者

加入总冠军® 社区.