JavaScript中仅比较日期部分而不比较时间的比较方法

532

下面的代码有什么问题?

也许只比较日期而不是时间会更简单。但我不确定该如何做,我已经搜索了,但没有找到与我的问题完全相同的情况。

顺便说一下,当我在警报框中显示这两个日期时,它们会显示为完全相同。

我的代码:

window.addEvent('domready', function() {
    var now = new Date();
    var input = $('datum').getValue();
    var dateArray = input.split('/');
    var userMonth = parseInt(dateArray[1])-1;
    var userDate = new Date();
    userDate.setFullYear(dateArray[2], userMonth, dateArray[0], now.getHours(), now.getMinutes(), now.getSeconds(), now.getMilliseconds());

    if (userDate > now)
    {
        alert(now + '\n' + userDate);
    }
});

有没有更简单的方法来比较日期而不包括时间?


7
date1.toDateString() === date2.toDateString() 这段代码与日期相关,意思是判断 date1date2 是否在同一天。 - Naveen
从死亡中复活:当我在日期上使用date.setFullYear(yyyy,mm,dd)时,我得到了错误的结果,当我在日期上使用toString时,月份增加了1。在这个链接(https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setFullYear)上在FireFox和Edge上进行了测试。 - Cheshiremoe
28个回答

941
我还在学习JavaScript,对于比较两个没有时间的日期,我找到的唯一方法是使用Date对象的setHours方法,并将小时、分钟、秒和毫秒设置为零。然后比较这两个日期。
例如,
date1 = new Date()
date2 = new Date(2011,8,20)

date2将会被设置为零点的时、分、秒和毫秒,但是date1将会保留它创建时的时间。如果要去除date1上的时、分、秒和毫秒,请按照以下步骤操作:

date1.setHours(0,0,0,0)

现在,您可以将这两个日期仅作为日期进行比较,而不必担心时间元素。

85
请注意,使用date1 === date2进行测试似乎无法提供一致的行为;最好使用date1.valueOf() === b.valueOf()或甚至是date1.getTime() === date2.getTime()。奇怪的事情。 - Erwin Wessels
7
注意:如果date1和date2分别在冬季和夏季,并且您计划使用addDays(1)从一个日期迭代到另一个日期,问题在于由于夏令时的存在,它们将不具有相同的时区,因此最后一次比较应该给出相等日期的比较将无法正常工作,因为这两个日期实际上并不是在00:00:00:0。 - Oliver
13
我知道这是一个老问题,但当我们搜索时,它会在谷歌上排名靠前。请注意这个答案。如果用户的时区与日期对象的创建者不同,这将给出错误的答案。例如,将计算机操作系统的时区更改为美国东部时间。打开浏览器控制台并键入“var date2 = new Date(2011,8,20)”。现在将操作系统的时区更改为美国太平洋时间。在同一浏览器控制台中键入“date2.toDateString()”,你将得到“Mon Sep 19 2011”,而不是9月20日星期二! - Adam
11
请注意,setHours() 方法会根据浏览器自动检测的当前时区来设置时间。尝试运行以下代码: t = new Date("2016-02-29T01:45:49.098Z"); t.setHours(0,0,0,0); console.log(t.toJSON()); 将打印出 "2016-02-28T15:00:00.000Z",即日期为 28 而不是 29。您当前所在的时区是 Asia/Tokyo - transang
11
这个答案应该更新为使用 setUTCxxx 方法,而不是使用本地/时区感知的变体。 - Stijn de Witt
显示剩余5条评论

248

时区问题需注意

使用日期对象直接表示日期会带来精度过高的问题。您需要管理时间和时区以避免这种情况,但它们可能在任何步骤中悄然回来。这个问题的被接受的答案也陷入了这个陷阱。

Javascript日期对象没有时区概念。它是一个时刻(自纪元以来的滴答数),具有用于转换成字符串和解析字符串的便捷(静态)函数,默认使用设备的“本地”时区,或者如果指定,则是UTC或其他时区。要使用日期对象表示只是日期™,您需要让您的日期表示所涉及日期的UTC午夜。这是一种常见而必要的约定,它使您能够处理与其创建季节或时区无关的日期。因此,您需要非常警惕,以管理时区的概念,无论是创建您的午夜UTC日期对象还是序列化它时。

很多人都对控制台的默认行为感到困惑。如果将日期放入控制台,您会看到输出会包括您的时区。这只是因为控制台在您的日期上调用了toString(),而toString()给出了一个本地表示。潜在的日期没有时区!(只要时间匹配时区偏移量,您仍然有一个午夜UTC日期对象)

反序列化(或创建午夜UTC日期对象)

这是舍入步骤,其中有两个“正确”的答案。大多数情况下,您希望日期反映用户的本地时区。我在这里的日期是什么。在纽西兰和美国的用户可以同时单击并通常获得不同的日期。在这种情况下,请执行以下操作...

// create a date (utc midnight) reflecting the value of myDate and the environment's timezone offset.
new Date(Date.UTC(myDate.getFullYear(),myDate.getMonth(), myDate.getDate()));

有时,国际可比性比当地精度更重要。在这种情况下,请执行以下操作...

// the date in London of a moment in time. Device timezone is ignored.
new Date(Date.UTC(myDate.getUTCFullYear(), myDate.getUTCMonth(), myDate.getUTCDate()));

反序列化日期

通常,传输中的日期格式会采用YYYY-MM-DD格式。要反序列化它们,请按照以下方式操作...

var midnightUTCDate = new Date( dateString + 'T00:00:00Z');

序列化

在创建时已经考虑好了时区管理,现在需要确保在将其转换回字符串表示时将时区排除在外。因此,您可以安全地使用以下方法:

  • toISOString()
  • getUTCxxx()
  • getTime() //返回没有时间或时区的数字。
  • .toLocaleDateString("fr",{timeZone:"UTC"}) // 无论您想要哪个区域设置,但一定是 UTC。

完全避免其他所有内容,特别是...

  • getYear()getMonth()getDate()

因此,要回答您7年前的问题...

<input type="date" onchange="isInPast(event)">
<script>
var isInPast = function(event){
  var userEntered = new Date(event.target.valueAsNumber); // valueAsNumber has no time or timezone!
  var now = new Date();
  var today = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate() ));
  if(userEntered.getTime() < today.getTime())
    alert("date is past");
  else if(userEntered.getTime() == today.getTime())
    alert("date is today");
  else
    alert("date is future");

}
</script>

点击这里查看...

2022年更新...测试免费使用...

以下代码已经被打包成npm包Epoq。代码也托管在Github上,欢迎使用 :-)

2019年更新...免费使用...

鉴于此回答的受欢迎程度,我将所有的功能都添加到了代码中。下面的函数返回一个包装后的日期对象,并且只公开那些仅与只有日期相关的函数。

使用Date对象调用它,就会解析为反映用户时区的JustADate。使用字符串调用:如果字符串是带有指定时区的ISO 8601,则我们将仅四舍五入时间部分。如果未指定时区,则会将其转换为反映本地时区的日期,就像对待日期对象一样。

function JustADate(initDate){
  var utcMidnightDateObj = null
  // if no date supplied, use Now.
  if(!initDate)
    initDate = new Date();

  // if initDate specifies a timezone offset, or is already UTC, just keep the date part, reflecting the date _in that timezone_
  if(typeof initDate === "string" && initDate.match(/(-\d\d|(\+|-)\d{2}:\d{2}|Z)$/gm)){  
     utcMidnightDateObj = new Date( initDate.substring(0,10) + 'T00:00:00Z');
  } else {
    // if init date is not already a date object, feed it to the date constructor.
    if(!(initDate instanceof Date))
      initDate = new Date(initDate);
      // Vital Step! Strip time part. Create UTC midnight dateObj according to local timezone.
      utcMidnightDateObj = new Date(Date.UTC(initDate.getFullYear(),initDate.getMonth(), initDate.getDate()));
  }

  return {
    toISOString:()=>utcMidnightDateObj.toISOString(),
    getUTCDate:()=>utcMidnightDateObj.getUTCDate(),
    getUTCDay:()=>utcMidnightDateObj.getUTCDay(),
    getUTCFullYear:()=>utcMidnightDateObj.getUTCFullYear(),
    getUTCMonth:()=>utcMidnightDateObj.getUTCMonth(),
    setUTCDate:(arg)=>utcMidnightDateObj.setUTCDate(arg),
    setUTCFullYear:(arg)=>utcMidnightDateObj.setUTCFullYear(arg),
    setUTCMonth:(arg)=>utcMidnightDateObj.setUTCMonth(arg),
    addDays:(days)=>{
      utcMidnightDateObj.setUTCDate(utcMidnightDateObj.getUTCDate + days)
    },
    toString:()=>utcMidnightDateObj.toString(),
    toLocaleDateString:(locale,options)=>{
      options = options || {};
      options.timeZone = "UTC";
      locale = locale || "en-EN";
      return utcMidnightDateObj.toLocaleDateString(locale,options)
    }
  }
}


// if initDate already has a timezone, we'll just use the date part directly
console.log(JustADate('1963-11-22T12:30:00-06:00').toLocaleDateString())
// Test case from @prototype's comment
console.log("@prototype's issue fixed... " + JustADate('1963-11-22').toLocaleDateString())


5
你的回答包含非常有价值的信息!不幸的是,你实际上没有回答OP的实际问题,这阻止了我对它进行点赞。尝试添加一个小的代码片段来回答OP的问题。 - Stijn de Witt
2
哈哈哈 @ '7年太晚'!我喜欢你的风格user1585345!你得到了我的投票! - Stijn de Witt
我发现这很有帮助。请注意,“国际可比性”片段中有一个错别字。我认为它应该是:new Date(Date.UTC(myDate.getUTCFullYear(), myDate.getUTCMonth(), myDate.getUTCDate())); - RikRak
请注意,这个函数将一个日期+时间包装成刚过午夜UTC的时间,并且日期+时间在不同的语言环境下可能会被解释为不同的日期。例如,JustADate('1963-11-22').toLocaleDateString() 返回 "11/21/1963",这是西大西洋时区以西时区的前一天日期。因此,如果目标是将日期字符串解释为以另一种格式显示相同的日历日期,而不是将一个设置为UTC的Date进行包装,那么最好修改为包装一个设置为SST(美属萨摩亚时间)的sstMidnightDateObj,这样几乎所有语言环境下都返回相同的日历日期。 - prototype
@prototype 你的问题是因为 new Date("yyyy-MM-dd") 实际上创建了一个午夜UTC日期对象,(至少在Chrome中是这样。它可能是实现相关的)。这在我的代码中没有得到很好的处理,但现在已经修复了。 - bbsimonbb

101

这个怎么样?

Date.prototype.withoutTime = function () {
    var d = new Date(this);
    d.setHours(0, 0, 0, 0);
    return d;
}

它允许您比较日期部分,而不会影响您变量的值:

var date1 = new Date(2014,1,1);
new Date().withoutTime() > date1.withoutTime(); // true

这绝对是对原帖最优雅的回答。我没有为我的目的创建单独的函数,只包含了两行代码。 - DanimalReks
4
向原型添加自定义功能并不是一个好主意。 - andrey.shedko
4
为什么要@andrey.shedko?不附带理由地说某事是错误的或不好的做法对所有人都没有好处。如果它起作用并且是你项目中可以重复使用且能够减少手动修剪的内容,那么就可以放心使用。请注意,不改变原意,使翻译更通俗易懂,不提供任何解释。 - Grant
正如接受的答案中所指出的,如果您的时区不是UTC/GMT,使用setHours方法将会得到错误的时间。可以更新withoutTime原型来使用Date.UTC方法。 - freedomn-m
正如接受的答案中所指出的,使用setHours方法会在时区不是UTC/GMT时得到错误的时间。可以更新withoutTime原型来使用Date.UTC方法。 - undefined

29

使用Moment.js

如果您有包括第三方库的选项,建议查看Moment.js。它能够让处理DateDateTime变得更加简单。

例如,要判断一个日期是否在另一个日期之后,但不考虑时间,可以像这样操作:

var date1 = new Date(2016,9,20,12,0,0); // October 20, 2016 12:00:00
var date2 = new Date(2016,9,20,12,1,0); // October 20, 2016 12:01:00

// Comparison including time.
moment(date2).isAfter(date1); // => true

// Comparison excluding time.
moment(date2).isAfter(date1, 'day'); // => false

你传递给 isAfter 的第二个参数是比较的精度,可以是以下任何一个:yearmonthweekdayhourminutesecond


6
从momentjs解决方案转换为Date()+sethours解决方案,速度提升了100倍。小心朋友们 :) - 2c2c
@2c2c 很有趣。了解性能影响肯定是很好的。你在基准测试中使用了多少个操作? - Joshua Pinter
1
我大约进行了100万次比较,但没有设置实际的基准测试。 - 2c2c
3
Moment 正处于维护模式(https://momentjs.com/docs/),如果您还没有使用它,就不应再使用它。有替代方案,而日期对象比以前更好。 - bbsimonbb

26

只需像下面这样使用 .toDateString 进行比较:

new Date().toDateString();

这将只返回日期部分,而不是时间或时区,就像这样:

"Fri Feb 03 2017"

因此,两个日期可以以这种格式进行比较,而不包括其时间部分。


4
这是个不错的开始,但因为已经是一个字符串,所以无法进行比较。若要比较,请通过以下方式将其转换回日期对象:new Date(new Date().toDateString()); - Grid Trekkor
2
@GridTrekkor 正确,但如果你只想进行快速的相等性测试,这是最简单的解决方案。 - C.M.
尽可能地,我不想使用另一个库。这太完美了! - undefined

17

如果您只是比较日期而没有时间组件,另一个解决方案可能会感觉不对,但是它可以避免所有 Date() 时间和时区问题,那就是直接使用字符串比较来比较 ISO 格式的日期:

> "2019-04-22" <= "2019-04-23"
true
> "2019-04-22" <= "2019-04-22"
true
> "2019-04-22" <= "2019-04-21"
false
> "2019-04-22" === "2019-04-22"
true

您可以使用以下代码获取当前日期(UTC日期,不一定是用户本地的日期):

> new Date().toISOString().split("T")[0]
"2019-04-22"

我支持这种方法的原因是为了让程序员更简单 -- 采用这种方法比正确处理日期和偏移量要容易得多,而且不太可能搞砸,尽管可能会牺牲一些速度(我没有比较过性能)。


1
你也可以使用 new Date().toISOString().substr(0, 10) 来得到日期字符串 "2019-04-22"。 - stomy
对于本地日期(非UTC日期),您可以使用new Date().toLocaleDateString() // "8/26/2019"。但是,然后比较文本将不准确(例如"8/26/2019">="9/29/2001"是错误的)。因此,您需要将其转换为new Date(new Date().toLocaleDateString())以准确地与另一个日期进行比较。 - stomy

17

只需在两个日期上使用toDateString()。 toDateString不包括时间,因此对于同一日期的2个时间,值将相等,如下所示。

var d1 = new Date(2019,01,01,1,20)
var d2 = new Date(2019,01,01,2,20)
console.log(d1==d2) // false
console.log(d1.toDateString() == d2.toDateString()) // true

显然,这个问题中其他人提出的时区问题有一定的合理性,但在许多情况下,这些问题并不相关。


4
对于我的情况来说,这个解决方案比被选中的答案和得票更高的答案要好得多。标题中明确指出“不比较时间”,这个解决方案正是这样做的,省去了设置时间来操作比较的必要性。回答“事件X和事件Y是否在同一天发生”比仅比较日期更有意义,而不是回答“事件X的日期00:00:00.00 AM是否与事件Y的日期00:00:00.00完全相同?”这正是许多其他回答者所做的。 - Mr.K

9
这可能是一个更清晰的版本,同时请注意,在使用 parseInt 时应始终使用基数。
window.addEvent('domready', function() {
    // Create a Date object set to midnight on today's date
    var today = new Date((new Date()).setHours(0, 0, 0, 0)),
    input = $('datum').getValue(),
    dateArray = input.split('/'),
    // Always specify a radix with parseInt(), setting the radix to 10 ensures that
    // the number is interpreted as a decimal.  It is particularly important with
    // dates, if the user had entered '09' for the month and you don't use a
    // radix '09' is interpreted as an octal number and parseInt would return 0, not 9!
    userMonth = parseInt(dateArray[1], 10) - 1,
    // Create a Date object set to midnight on the day the user specified
    userDate = new Date(dateArray[2], userMonth, dateArray[0], 0, 0, 0, 0);

    // Convert date objects to milliseconds and compare
    if(userDate.getTime() > today.getTime())
    {
            alert(today+'\n'+userDate);
    }
});

查看 MDC parseInt 页面以获取有关基数的更多信息。

JSLint 是一个很棒的工具,可以捕获像缺少基数这样的问题,以及许多其他可能导致模糊且难以调试的错误。它强制您使用更好的编码标准,因此避免未来的头痛。我在编写每个 JavaScript 项目时都使用它。


@EmKay,现在这应该不是什么大问题了,因为ES5已经成为大多数浏览器的标准,它禁止了八进制解释。但是,一些浏览器可能仍然出于向后兼容的原因使用旧的八进制解释方式,因此我建议在可预见的未来继续使用基数。 - Useless Code
如果您使用严格模式,八进制解释将会抛出错误,这是一件好事,因为它可以帮助您找到可能被误解的错误。 - Useless Code

8

比较日期的一种高效而准确的方法是:

Math.floor(date1.getTime() / 86400000) > Math.floor(date2.getTime() / 86400000);

它忽略时间部分,适用于不同的时区,并且您还可以使用等式比较符 == 进行比较。86400000是一天中的毫秒数(= 24 * 60 * 60 * 1000)。

请注意,等式运算符 == 不应该用于比较日期对象,因为当您期望相等测试起作用时,它会失败,因为它比较的是两个日期对象(而不是比较两个日期)。例如:

> date1;
outputs: Thu Mar 08 2018 00:00:00 GMT+1300

> date2;
outputs: Thu Mar 08 2018 00:00:00 GMT+1300

> date1 == date2;
outputs: false

> Math.floor(date1.getTime() / 86400000) == Math.floor(date2.getTime() / 86400000);
outputs: true

注:如果您比较的是时间部分为零的Date对象,则可以使用date1.getTime() == date2.getTime(),但这几乎没有优化价值。当直接比较Date对象时,您可以使用<><=>=,因为这些运算符在进行比较之前会先调用.valueOf()来转换Date对象。


无法工作;例如 new Date (2017, 10, 2, 1).getTime()/86400000 将小时更改为2,您将得到不同的整数(下取整)结果。 - Omu
@omu 或许你需要学习更好的测试方法。它确实可以工作,尽管有些丑陋。如果时间是纳秒级别,可能需要四舍五入到最近的秒,但其他答案也有同样的问题。我认为其他一些答案在清晰度和可维护性方面更好。 - robocat
Math.round 无法解决问题,请在 Chrome 开发者工具控制台中尝试 new Date (2017, 10, 2, 1).getTime()/86400000。使用 Math.round,你会在 12 和 13 小时之间得到不同的结果。 - Omu
@omu 你说的都是显而易见的,我并没有说Math.round()会起作用:如何编写代码取决于你的epsilon。而且说Math.floor()在12和13不起作用,实际上是在说Math.floor()起作用了!到目前为止,你还没有真正包含你认为不起作用的Math.floor()代码。我使用Math.floor()进行了测试,没有任何问题。请提供一个完整的使用Math.floor()的示例,并给出你得到的输出结果。 - robocat
@omu 提供一个 jsbin.com(或等效网站)的链接,以展示你的问题。我已经不再从事 JavaScript 相关的工作了,而且我也不是很热衷于帮助你解决问题 :-) - robocat
我的问题已经解决了,我只是想说你发布的解决方案是错误的。 - Omu

4

由于我在这里没有看到类似的方法,并且我不喜欢将h/m/s/ms设置为0,因为它可能会导致与更改后的本地时间区域准确转换的date对象发生问题(我这样假设),让我在此介绍这个刚刚编写的小函数:

+:易于使用,完成基本的比较操作(比较日期、月份和年份而不需要考虑时间)。
-:看起来这完全是“常规思维”的反面。

function datecompare(date1, sign, date2) {
    var day1 = date1.getDate();
    var mon1 = date1.getMonth();
    var year1 = date1.getFullYear();
    var day2 = date2.getDate();
    var mon2 = date2.getMonth();
    var year2 = date2.getFullYear();
    if (sign === '===') {
        if (day1 === day2 && mon1 === mon2 && year1 === year2) return true;
        else return false;
    }
    else if (sign === '>') {
        if (year1 > year2) return true;
        else if (year1 === year2 && mon1 > mon2) return true;
        else if (year1 === year2 && mon1 === mon2 && day1 > day2) return true;
        else return false;
    }    
}

用法:

datecompare(date1, '===', date2) 用于检查相等性,
datecompare(date1, '>', date2) 用于检查大小关系,
!datecompare(date1, '>', date2) 用于检查小于或等于关系。

此外,显然,您可以在位置上交换 date1date2 以实现任何其他简单比较。


不错,加加,帮了我大忙,现在希望 ISO 转换不会出问题,哈哈,谢谢。 - edencorbin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接