JavaScript查找闰年

30

当月份为二月时,我该如何让下面的代码正常运行?目前它会到达日期,但在到达判断是否为闰年的if语句之前停止。

 if (month == 2) {
    if (day == 29) {
        if (year % 4 != 0 || year % 100 == 0 && year % 400 != 0) {
            field.focus();
             field.value = month +'/' +  '';
        }
    }
    else if (day > 28) {
        field.focus();
             field.value = month +'/' +  '';
    }
}

停止是怎么回事?有错误吗? - Pekka
它从不计算年份以查看是否为闰年,而是直接跳转到field.focus和field.value,无论是否为闰年。 - Juan Almonte
你的条件看起来有点奇怪 - 因为它们目前的写法只检查day是否大于或等于29(基于 day == 29day > 28 的if语句)。我猜想你的意思是要写成 day <= 28,如果是这样的话,你可以去掉第二个 else if 条件,直接用一个 else 条件就可以了。在闰年的条件中增加一组括号可能更安全: if (year % 4 != 0 || (year % 100 == 0 && year % 400 != 0)) - JW8
1
你可能需要展示一些周围的代码来说明这些变量是如何被设置的。如果你使用了日期对象,请记住它使用基于零的月份。 - nnnnnn
16个回答

129

最好使用日期对象来处理日期时间,例如:

isLeap = new Date(year, 1, 29).getMonth() == 1

由于人们一直在询问这个功能的具体工作原理,它与JS如何从年-月-日计算日期值有关(详情请看这里)。基本上,它首先计算每个月的第一天,然后再加N -1天。因此,当我们要求非闰年的2月29日时,结果将为2月1日+28天= 3月1日:

> new Date(2015, 1, 29)
< Sun Mar 01 2015 00:00:00 GMT+0100 (CET)

在闰年中,2月的第1天加上28等于29日:

> new Date(2016, 1, 29)
< Mon Feb 29 2016 00:00:00 GMT+0100 (CET)

在上述代码中,我将日期设置为2月29日,并查看是否发生了翻转。如果没有(月份仍为1,即2月),则这是一个闰年,否则是一个非闰年。


17
哇塞,这个家伙真是个高手。:) - Šime Vidas
10
仅适用于低计算量场景,因为这种方法比使用类似于isLeap = !((yr % 4) || (!(yr % 100) && (yr % 400)));的方法慢大约100倍。http://jsperf.com/ily/7 - iCollect.it Ltd
这里的 == 1 是做什么用的? - Ryman Holmes
在 JavaScript 的 Date 对象中,月份是从零开始计数的。在闰年中,1 月的第 29 天(即二月)是有效的,因此 getMonth() 返回 1(二月)。在非闰年中,JavaScript 会将 2 月 29 日“纠正”为 3 月 1 日,因此 getMonth() 将返回 2(三月)。 - WildlyInaccurate
这在IE11上不起作用,因为传递的2月29日会被更正为2月28日,导致Date()对象返回1并错误地标记为闰年。 - AdamJB

21

与使用new Date()相比,这个方法快约100倍!

更新:

这个最新版本使用了底部3位的位运算(它是否是4的倍数),以及检查年份是否是16的倍数(二进制中底部4位为15),并且是否是25的倍数。

ily = function(y) {return !(y & 3 || !(y % 25) && y & 15);};

http://jsperf.com/ily/15

这个版本比我之前的版本又稍微快了一些(参见下面):

ily = function(yr) {return !((yr % 4) || (!(yr % 100) && (yr % 400)));};

http://jsperf.com/ily/7

相比broc.seib的条件运算符版本,它还要快5%。

速度测试结果:http://jsperf.com/ily/6

预期逻辑测试结果:

alert(ily(1900)); // false
alert(ily(2000)); // true
alert(ily(2001)); // false
alert(ily(2002)); // false
alert(ily(2003)); // false
alert(ily(2004)); // true
alert(ily(2100)); // false
alert(ily(2400)); // true

7

正确和快速:

ily = function(yr) { return (yr%400)?((yr%100)?((yr%4)?false:true):false):true; }

如果你正在循环或计算纳秒,那么这比通过一个新的Date()对象运行你的年份快两个数量级。在这里比较性能:http://jsperf.com/ily

2
由于这只是将布尔比较与布尔结果组合在一起,因此您可以使用&&、||和短路来表达它,而不需要条件运算符。此外,速度快约5%:http://jsperf.com/ily/6 - iCollect.it Ltd
你现在知道什么是否定吗? - Frondor

6
isLeap = !(new Date(year, 1, 29).getMonth()-1)

在大多数CPU体系结构中,减一操作比比较操作更快。


1
如果你需要每秒计算成千上万个这样的数据,我可能会同意,但是当它们之间的速度差几乎可以忽略不计时,可读性应该优先于速度。 - iCollect.it Ltd
刚刚进行了一些速度测试,new Date 的速度大约比使用布尔逻辑慢100倍(例如 !((yr % 4) || (!(yr % 100) && (yr % 400))))。为了追求速度,我可能已经牺牲了可读性,但是100倍的速度提升也许是值得的 :) - iCollect.it Ltd

3

更精确的闰年历史计算。

下面的代码考虑到闰年是在公元前45年引入的儒略日历中的,并且大部分西方世界在公元1582年采用了格里高利历,而0年代表1BC。

isLeap = function(yr) {
  if (yr > 1582) return !((yr % 4) || (!(yr % 100) && (yr % 400)));
  if (yr >= 0) return !(yr % 4);
  if (yr >= -45) return !((yr + 1) % 4);
  return false;
};

1752年,英国及其殖民地采用了格雷戈里亚历,因此如果您比较偏向于英式观点,那么这个版本更好(我们假设英国在43年开始罗马征服时采用了朱利安历)。

isLeap = function(yr) {
  if (yr > 1752) return !((yr % 4) || (!(yr % 100) && (yr % 400)));
  if (yr >= 43) return !(yr % 4);
  return false;
};

对于历史课来说,这个问题是:闰年不是追溯引入的吗? - inetphantom

3
JavaScript预计将推出一个新的日期/时间API,它将公开一个新的全局对象-Temporal。这个全局对象为JS开发人员提供了更好的处理日期/时间的方式。目前它是一个stage 3的提案,希望不久就能使用。
临时API公开了一个检查闰年的好属性-inLeapYear。如果特定日期是闰年,则返回true,否则返回false。下面我们使用with()将由plainDateISO返回的日期转换为我们特定年份的日期:
const isLeap = year => Temporal.now.plainDateISO().with({year}).inLeapYear;
console.log(isLeap(2020)); // true
console.log(isLeap(2000)); // true
console.log(isLeap(1944)); // true

console.log(isLeap(2021)); // false
console.log(isLeap(1999)); // false

如果您只想检查当前系统日期时间是否为闰年,则可以省略`.with()`:
// true if this year is a leap year, false if it's not a leap year
const isLeap = Temporal.now.plainDateISO().inLeapYear; 

2

你可以轻松地通过从momentjs调用.isLeapYear()来使其工作:

var notLeapYear = moment('2018-02-29')
console.log(notLeapYear.isLeapYear()); // false

var leapYear = moment('2020-02-29')
console.log(leapYear.isLeapYear()); // true
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.21.0/moment.min.js"></script>


2
function isLeap(year) {   
    if ( (year % 4 === 0 && year % 100 !== 0) || (year % 4 === 0 && year % 100 === 0 && year % 400 === 0) ) {
        return 'Leap year.'
    } else {
        return 'Not leap year.';
    }
}

1
使用这个:
Date.prototype.isLeap = function() {
    return new Date(this.getFullYear(), 1, 29).getMonth() == 1;
};

Date.prototype.isLeap = function() {
    return new Date(this.getFullYear(), 1, 29).getMonth() == 1;
};

console.log(new Date("10 Jan 2020").isLeap()); // True

console.log(new Date("10 Jan 2022").isLeap()); // False


1

我使用这个方法是因为我不愿意把一月称作0,二月称作1。 对于我、PHP和可读日期来说,二月=2。我知道数字永远不会变,但这使得我的思维在不同的代码中保持一致。

var year = 2012;
var isLeap = new Date(year,2,1,-1).getDate()==29;

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