28

Performance is of the utmost importance on this one guys... This thing needs to be lightning fast!


How would you validate the number of days in a given month?

My first thought was to make an array containing the days of a given month, with the index representing the month:

var daysInMonth = [
    31, // January
    28, // February
    31, // March
    etc.
];

And then do something along the lines of:

function validateDaysInMonth(days, month)
{
    if (days < 1 || days > daysInMonth[month]) throw new Error("Frack!");
}

But... What about leap years? How can I implement checking for leap years and keep the function running relatively fast?


Update: I'd like you guys to show me some code which does the days in month- leap year validation.

Here's the flowchart describing the logic used today:


(source: about.com)

Community
  • 1
  • 1
cllpse
  • 21,396
  • 37
  • 131
  • 170
  • 2
    +1, the question led to some really nice tips and tricks :) – Moayad Mardini Sep 16 '09 at 14:08
  • 3
    but all this logic is already built in to the javascript engine... WHy recode it ? Unless it is just for exercise, you can use the javascript Date object: var daysInMonth = new Date(aDate.getYear(), 1+aDate.getMonth(), 0).getDate(); – Charles Bretana Sep 16 '09 at 14:22
  • I'd like to see how something that is evenly divisible by 4 and by 100 is not divisible by 400. – n1313 Sep 16 '09 at 14:43
  • You wanted fast. Have added a faster answer below (approx twice the speed of the accepted answer using boolean logic and a binary maths trick). – iCollect.it Ltd Jan 14 '15 at 17:06

14 Answers14

64
function daysInMonth(m, y) { // m is 0 indexed: 0-11
    switch (m) {
        case 1 :
            return (y % 4 == 0 && y % 100) || y % 400 == 0 ? 29 : 28;
        case 8 : case 3 : case 5 : case 10 :
            return 30;
        default :
            return 31
    }
}

function isValid(d, m, y) {
    return m >= 0 && m < 12 && d > 0 && d <= daysInMonth(m, y);
}
nickf
  • 537,072
  • 198
  • 649
  • 721
  • I think this is really cooL! Great! – vpram86 Sep 16 '09 at 13:55
  • 1
    The comment says that m is zero-indexed, but still your switch relies on the months being one-indexed; 1: January, 2: February, etc. – cllpse Sep 16 '09 at 13:57
  • And.. Nice work! Not that this was a pop-quiz, but you get an A+ :) – cllpse Sep 16 '09 at 13:57
  • Actually, it's flawed. If m is 0-indexed (as the comment states), March has 28 or 29 days... – Tor Haugen Sep 16 '09 at 14:01
  • ah thanks Pax - yeah brainfart on february there.. the other ones were good though! ;) – nickf Sep 16 '09 at 14:03
  • historical note: that's the gregorian leap year formula. wikipedia "gregorian calendar". qoute: "The last day of the Julian calendar was Thursday, 4 October 1582 and this was followed by the first day of the Gregorian calendar, Friday, 15 October 1582 (the cycle of weekdays was not affected)." also: "the British Empire (including the eastern part of what is now the United States) adopted the Gregorian calendar in 1752 by which time it was necessary to correct by 11 days. Wednesday, 2 September 1752 was followed by Thursday, 14 September 1752". good thing noone cares about dates that old? – ifatree Sep 16 '09 at 15:16
  • Wouldn't it make sense to put "y % 400 == 0" before "(y % 4 == 0 && y % 100)", performance-wise? – cllpse Sep 16 '09 at 17:19
  • 1
    @rooster, I thought about that, but the only performance gain you'll get will be on those years which are divisible by 400, so unless you're working with a lot of dates in the year 2000 (or 2400), then no. – nickf Sep 16 '09 at 23:18
  • Thanks, nickf. I am typically working with dates ranging from 2000 till 2010 (current year), so I guess I'll switch it round. – cllpse Sep 18 '09 at 10:56
  • 2
    2010, current year? Can you like, email me lotto results or something?? – nickf Sep 18 '09 at 14:27
  • Just to be clear: the months are zero-indexed and the days one-indexed, since this is what is returned from `Date.prototype.getMonth()` and `Date.prototype.getDate()` – nickf Oct 20 '16 at 16:11
  • Since we are talking about squeezing cycles here, I have two suggestions for modest improvements: 1) Move your switch cases around to put most likely (31 days) on top and least likely (February) on bottom. Better yet, index everything but February from a const array. 2) February case - most likely (3 out of 4) is not divisible by 4 but that case will do the division by 400. Suggest `return y%4!=0 ? 28 : (y%100!=0 ? 29 : (y%400!=0 ? 28 : 29))` to avoid unnecessary divisions. – Chris Maurer Mar 04 '22 at 17:56
15

I've been doing this using the Date object (assuming it's compiled, and hence blindingly fast compared to scripting).

The trick is that if you enter a too high number for the date part, the Date object wraps over into the next month. So:

var year = 2009;
var month = 1;
var date = 29;

var presumedDate = new Date(year, month, date);

if (presumedDate.getDate() != date)
    WScript.Echo("Invalid date");
else
    WScript.Echo("Valid date");

This will echo "Invalid date" because presumedDate is actually March 1st.

This leaves all the trouble of leap years etc to the Date object, where I don't have to worry about it.

Neat trick, eh? Dirty, but that's scripting for you...

Tor Haugen
  • 19,509
  • 9
  • 45
  • 63
  • This looks like the best solution but is Date expected to do that in all implementation by ECMA standard? – the_drow Sep 16 '09 at 14:00
  • 2
    nice trick, and it's definitely a good idea to build on and use established things like the Date class, however I just did some benchmarks and this is significantly slower (+650%) than the method I proposed! – nickf Sep 16 '09 at 14:02
  • +1 for the trick. Although it's not very friendly, I wouldn't be able to understand it without your explanation! – Moayad Mardini Sep 16 '09 at 14:06
6

This will not perform as well as the accepted answer. I threw this in here because I think it is the simplest code. Most people would not need to optimize this function.

function validateDaysInMonth(year, month, day)
{
    if (day < 1 || day > 31 || (new Date(year, month, day)).getMonth() != month)
        throw new Error("Frack!");
}

It takes advantage of the fact that the javascript Date constructor will perform date arithmetic on dates that are out of range, e.g., if you do:

var year = 2001; //not a leap year!
var month = 1 //February
var day = 29; //not a valid date for this year
new Date(year, month, day);

the object will return Mar 1st, 2001 as the date.

D'Arcy Rittich
  • 167,292
  • 40
  • 290
  • 283
5

If the month isn't February, get the number from the array. Otherwise, check if the year is leap to return 29, or return 28. Is there a problem with that?

Moayad Mardini
  • 7,271
  • 5
  • 41
  • 58
4
function caldays(m,y)
{
    if (m == 01 || m == 03 || m == 05 || m == 07 || m == 08 || m == 10 || m == 12)
    {
        return 31;              
    }
    else if (m == 04 || m == 06 || m == 09 || m == 11)
    {
        return 30;        
    }
    else
    {    
        if ((y % 4 == 0) || (y % 400 == 0 && y % 100 != 0))
        {    
            return 29;          
        }
        else 
        {
            return 28;              
        }
    }    
}

source: http://www.dotnetspider.com/resources/20979-Javascript-code-get-number-days-perticuler-month-year.aspx

Laurent Etiemble
  • 27,111
  • 5
  • 56
  • 81
JuanZe
  • 8,007
  • 44
  • 58
4

Moment.js

Have you tried moment.js?

The validation is quite easy to use:

var m = moment("2015-11-32");
m.isValid(); // false

I don't know about the performances but hum the project is stared 11,000+ times on GitHub (kind of a quality guarantee).

Source: http://momentjs.com/docs/#/parsing/is-valid/

Yves M.
  • 29,855
  • 23
  • 108
  • 144
4

In computer terms, new Date() and regular expression solutions are slow! If you want a super-fast (and super-cryptic) one-liner, try this one (assuming m is in Jan=1 format):

The only real competition for speed is from @GitaarLab, so I have created a head-to-head JSPerf for us to test on: http://jsperf.com/days-in-month-head-to-head/5

I keep trying different code changes to get the best performance.

Current version

After looking at this related question Leap year check using bitwise operators (amazing speed) and discovering what the 25 & 15 magic number represented, I have come up with this optimized hybrid of answers:

function getDaysInMonth(m, y) {
    return m===2 ? y & 3 || !(y%25) && y & 15 ? 28 : 29 : 30 + (m+(m>>3)&1);
}

JSFiddle: http://jsfiddle.net/TrueBlueAussie/H89X3/22/

JSPerf results: http://jsperf.com/days-in-month-head-to-head/5

For some reason, (m+(m>>3)&1) is more efficient than (5546>>m&1) on almost all browsers.


It works based on my leap year answer here: javascript to find leap year this answer here Leap year check using bitwise operators (amazing speed) as well as the following binary logic.

A quick lesson in binary months:

If you interpret the index of the desired months (Jan = 1) in binary you will notice that months with 31 days either have bit 3 clear and bit 0 set, or bit 3 set and bit 0 clear.

Jan = 1  = 0001 : 31 days
Feb = 2  = 0010
Mar = 3  = 0011 : 31 days
Apr = 4  = 0100
May = 5  = 0101 : 31 days
Jun = 6  = 0110
Jul = 7  = 0111 : 31 days
Aug = 8  = 1000 : 31 days
Sep = 9  = 1001
Oct = 10 = 1010 : 31 days
Nov = 11 = 1011
Dec = 12 = 1100 : 31 days

That means you can shift the value 3 places with >> 3, XOR the bits with the original ^ m and see if the result is 1 or 0 in bit position 0 using & 1. Note: It turns out + is slightly faster than XOR (^) and (m >> 3) + m gives the same result in bit 0.

JSPerf results: http://jsperf.com/days-in-month-perf-test/6

Community
  • 1
  • 1
iCollect.it Ltd
  • 92,391
  • 25
  • 181
  • 202
3

I'm mostly agreeing w/ Moayad. I'd use a table lookup, with an if check on February and the year.

pseudocode:

Last_Day = Last_Day_Of_Month[Month];
Last_Day += (Month == February && Leap_Year(Year)) ? 1 : 0;

Note that Leap_Year() can't be implemented simply as (Year % 4 == 0), because the rules for leap years are way more complex than that. Here's an algorithm cribbed from Wikipedia

bool Leap_Year (int year) {
   return ((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0);
}
T.E.D.
  • 44,016
  • 10
  • 73
  • 134
3

I agree with Moayad and TED. Stick with the lookup table unless the month is February. If you need an algorithm for checking leap years, wikipedia has two:

if year modulo 400 is 0 then leap
 else if year modulo 100 is 0 then no_leap
 else if year modulo 4 is 0 then leap
 else no_leap

A more direct algorithm (terms may be grouped either way):

function isLeapYear (year):
 if ((year modulo 4 is 0) and (year modulo 100 is not 0)) or (year modulo 400 is 0)
  then true
 else false
Simon P Stevens
  • 27,303
  • 5
  • 81
  • 107
3

all this logic is already built in to the javascript engine... Why recode it ? Unless you are doing this just as an exercise, you can use the javascript Date object:

Like this:

function daysInMonth(aDate) {
      return new Date(aDate.getYear(), aDate.getMonth()+1, 0).getDate();      
   }
Charles Bretana
  • 143,358
  • 22
  • 150
  • 216
2

Assuming the JS Date object standard where months are numbered from 0, and you have your daysInMonth array:

var days = daysInMonth[month] + ((month === 1) && (year % 4 === 0) && ((year % 100 !== 0) || (year % 400 === 0)));

will give you the number of days in the month, with 28 increased to 29 iff the month is February and the year is a leap year.

NickFitz
  • 34,537
  • 8
  • 43
  • 40
1

The days of each month are known easily by exception of February for detail of leap years:

I leave an implementation based on the algorithm that solves this problem:

Algorithm

if (year is not divisible by 4) then (it is a common year)
else if (year is not divisible by 100) then (it is a leap year)
else if (year is not divisible by 400) then (it is a common year)
else (it is a leap year)

Implementation in Javascript

/**
 * Doc: https://en.wikipedia.org/wiki/Leap_year#Algorithm
 * param : month is indexed: 1-12
 * param: year
 **/
  function daysInMonth(month, year) {
   switch (month) {
    case 2 : //Febrary
     if (year % 4) {
      return 28; //common year
     }
     if (year % 100) {
      return 29; //  leap year
     }
     
     if (year % 400) {
      return 28; //common year
     }
     return 29; //  leap year
    case 9 : case 4 : case 6 : case 11 :
     return 30;
    default :
     return 31
   }
  }
    
    /** Testing daysInMonth Function **/
    $('#month').change(function() {
        var mVal = parseInt($(this).val());
        var yVal = parseInt($('#year').val());
        $('#result').text(daysInMonth(mVal, yVal));
    });
    
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<label>Year</label>
<input type='number' id='year' min='1000' max='2500'>

<label>month</label>
<input type='number' id='month' min='1' max='12'>
<h1>
   days: <span id='result' style='color:#E650A0'></span>
</h1>
fitorec
  • 4,257
  • 2
  • 24
  • 18
0

You can use DateTime to solve this:

new DateTime('20090901')->format('t'); // gives the days of the month
Martin
  • 719
  • 4
  • 11
0

Other solution:

// monthIndex: 0:jan, 11: dic
function daysInMonth (monthIndex, year) {
  if (monthIndex ==1) // february case
    return (year % 4 == 0 && year % 100) || year % 400 == 0 ? 29 : 28
  const monthsWith30days = [3, 5, 8, 10]
  return monthsWith30days.includes(monthIndex) ? 30 : 31
}

// testing
const year = new Date().getFullYear()
for (let m=0; m < 12; m += 1) {
  console.log(`year ${year} month ${m} has ${daysInMonth(m, year)} days`)
}

OUTPUT

'year 2022 month 0 has 31 days'
'year 2022 month 1 has 28 days'
'year 2022 month 2 has 31 days'
'year 2022 month 3 has 30 days'
'year 2022 month 4 has 31 days'
'year 2022 month 5 has 30 days'
'year 2022 month 6 has 31 days'
'year 2022 month 7 has 31 days'
'year 2022 month 8 has 30 days'
'year 2022 month 9 has 31 days'
'year 2022 month 10 has 30 days'
'year 2022 month 11 has 31 days'
fitorec
  • 4,257
  • 2
  • 24
  • 18