# Leap Years&Co.

• Continuing the discussion from Practical ValiDATEion:

>attempted leap year handling which makes February 29, 2000 a perfectly valid date

Oh fuck, it is a perfectly valid date.

Looks like the right time I pull out the date handling code we have in our codebase. It was written by the

“programmer” with a really bad case of NIH syndrome

``````int64_t Atime::privToI64(SYSTEMTIME * pst, int32_t * pms/*=NULL*/)
{
uint32_t i,f;
// starting from zero
int64_t itime=0;
// flag for actual leap year
i=pst->wYear; f=(((i&3)||(((i%400)==0)&&(i!=2000)))?0:1);
// non-leap 400 years
while(i>400) { itime+=12622953600000; i-=400; }
// stupid exception from exceptions (year 2000 was leap, so everything after February 2000 must be shifted by one day)
if((pst->wYear>2000) || ((pst->wYear==2000) && (pst->wMonth>=3))) itime+=86400000;
// we are done with non-leap 400-years, now leap centuries will follow
while(i>100) { itime+=3155760000000; i-=100; }
// we are at the begining of century, leap 4-years follow
while(i>4) { itime+=126230400000; i-=4; }
// we are at the begining of next 4-year cycle, adding non-leap years
while(i>1) { itime+=31536000000; i-=1; }
// we are in current year, adding time for complete months (that's why subtracting 1, in March only January and February are complete)
switch(pst->wMonth-1)
{
default:    break;
case 11:    itime+=2592000000ll;
case 10:    itime+=2678400000ll;
case 9:     itime+=2592000000ll;
case 8:     itime+=2678400000ll;
case 7:     itime+=2678400000ll;
case 6:     itime+=2592000000ll;
case 5:     itime+=2678400000ll;
case 4:     itime+=2592000000ll;
case 3:     itime+=2678400000ll;
case 2:     itime+=(f?2505600000ll:2419200000ll);   // based on actual leapness
case 1:     itime+=2678400000ll;
}
// add time for whole days
i=pst->wDay; while(i>1) { itime+=86400000; i--; }
itime+=pst->wHour*3600000;
itime+=pst->wMinute*60000;
itime+=pst->wSecond*1000;
// and finally miliseconds
itime+=pst->wMilliseconds;
m_itime = itime;
return m_itime ;
}
``````

The idea is that time is internally represented by number of (non-leap) seconds since beginning of 1 Jan 0.

The code survived 7 years in good health and is not about to be replaced any time soon, because the code only ever deals with times from it's creation until current time, so we never gave a damn about not being able to represent dates before 20th century correctly and we don't give a damn about 22nd century just yet either.

• Paging @RaceProUK...

• *responds to summons*
*drop*
*sods off again*

• `// ... so everything after February 200 ....`

Was that a transcription error, or is that what's actually in the code?...

• The missing 0 is transcription error. The rest is original

• I actually looked into this years ago when I thought writing a date library was a good idea (as opposed to using one off the shelf...). What can I say - I was younger and stupider.

Anyway, from memory, the rules are:

We have a leap year if the year is divisible by 4.
Unless it's also divisible by 100, in which case it's not a leap year.
Unless it's also divisible by 400, in which case it IS a leap year.

See - you learn something every day...

• Yes, correct. Unlike the guy who wrote the code above and mixed them up.

• ``````if year % 4 == 0:
if year % 100 == 0:
if year % 400 == 0:
return True
return False
return True
return False
``````

• ``````return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
``````

• or that if you want to abuse the ternary operator

must, get, more, caffeine..... reading fail.

• ternary operator

Which I didn't use

• Which I didn't use

appologies. i have not yet consumed my morning coffee.

• The True Python Way™ is:

``````return True if year % 400 == 0 else False if year % 100 == 0 else True if year % 4 == 0 else False
``````

• right.... forgot that True and False are capitalized for rasins in python.

• TRWTF is a ternary operator that takes its arguments in the wrong order.

• right.... forgot that True and False are capitalized for rasins in python.

Yes, `True`, `False` and `None` are special snowflakes in Python.

TRWTF is a ternary operator that takes its arguments in the wrong order.

Like `True`, `False` and `None`, the BFDL is also a special snowflake and must have something special.

• Benevolent For Dictator Life?

... Oh. My irony detector is broken. Huh.

• TRWTF is a ternary operator that takes its arguments in the wrong order.

Because right-to-left and left-to-right evaluation orders are too conventional!

• a special snowflake and must have something special

``````\$ flame to /dev/null
flame: /dev/null: No space left on device
\$
``````

• Did you check if there was space right on your device?

• there's no space in there. the nearest space is roughly a hundred klicks straight up from here.

• The idea is that time is internally represented by number of (non-leap) seconds since beginning of 1 Jan 0.

Really? The epoch is a date/time that doesn't exist? (Don't forget, boys and girls, that the year before 1 was -1.)

• And none of that matters, since the Gregorian calendar wasn't adopted until, what, AD 1753 or something like that? (In case you were wondering why SQL Server's epoch date was 1753, now you know.)

Before that date, it's nonsensical to give any dates following the Gregorian rules-- you either need to switch to the Julian rules or (much more sanely) use Julian Day Count. The Gregorian calendar can't be used to count dates that occurred before its existence.

Making things even more complicated is that not every country adopted Gregorian rules at the same time. So you have things like Russia's October Revolution that took place on November 7th. And those insane Asian calendars where you don't know the date unless you know the emperor's line of succession.

• And none of that matters, since the Gregorian calendar wasn't adopted until, what, AD 1753 or something like that? (In case you were wondering why SQL Server's epoch date was 1753, now you know.)

Before that date, it's nonsensical to give any dates following the Gregorian rules-- you either need to switch to the Julian rules or (much more sanely) use Julian Day Count. The Gregorian calendar can't be used to count dates that occurred before its existence.

Making things even more complicated is that not every country adopted Gregorian rules at the same time. So you have things like Russia's October Revolution that took place on November 7th. And those insane Asian calendars where you don't know the date unless you know the emperor's line of succession.

Never heard of a proleptic calendar eh, Blakey?

I will agree that Julian Day Count is a good idea, though.

• Dafuq? That article is so wrong.

Mayan scholars (as well as ALL scholars) use Julian Day Count. I've never seen a Proleptic Gregorian date.

I guess it's "only" 2 days off if you go back 2,000 years, but. Still. Ugh. Just sounds like a terrible idea.

• You should join the ISO 8601 committee then

• proleptic calendar

Is that where the inside parts of the calendar end up on the outside?

• I've never seen a Proleptic Gregorian date.

<joke>

• Some countries (Catholic ones) adopted the Gregorian calendar as early as 1582 (the year it was proposed). Protestant countries adopted it later and Orthodox countries only in the 20th century.

You also get weird stuff like Miguel Cervantes and William Shakespeare dying on the same date, but not the same day.

• That doesn't count leap seconds.

And the other points where we are like

"Hell, it got off again!"

• You should join the ISO 8601 committee then

I'm guessing very few people who communicate in ISO 8601 are using dates older than, say, 1900.

• Really? The epoch is a date/time that doesn't exist? (Don't forget, boys and girls, that the year before 1 was -1.)

No. Before year AD 1 (or CE 1) was a year 1 BC. That is not -1 however. It is 1 BC. Year -1 only exists in astronomical year numbering and ISO 8601 and both have 0 between -1 and 1.

• @Steve_The_Cynic said:
Really? The epoch is a date/time that doesn't exist? (Don't forget, boys and girls, that the year before 1 was -1.)

No. Before year AD 1 (or CE 1) was a year 1 BC. That is not -1 however. It is 1 BC. Year -1 only exists in astronomical year numbering and ISO 8601 and both have 0 between -1 and 1.

OK, yes, pendantically you're right, but it's reasonably safe to argue that the "AD" and "BC" (how very quaintly politically incorrect of you, I approve) are just strange ways to spell "+" and "-", like "CR" and blank in accounting. (Remember the System 370 instruction "EDIT" that had the ability to do something like this. In EBCDIC, of course.) (Yes, on System 370, `Int32.ToString(n)` was an assembly-level instruction. How's that for scary!)

Oh, and if you call 1AD by the new name 1 CE, the year before it should be 1 BCE.

• @accalia said:
ternary operator

Which I didn't use

Which was the abuse of the ternary operator. It feels abandoned. You should use it. Always.

• "AD" and "BC" are just strange ways to spell "+" and "-"

Well, except they are not, because between +1 and -1 there is 0, but there is nothing between AD 1 and 1 BC.

• Well, except they are not, because between +1 and -1 there is 0, but there is nothing between AD 1 and 1 BC.

Fortunately, 0 is nothing.

Looks like your connection to What the Daily WTF? was lost, please wait while we try to reconnect.