# Idle thoughts...Can I make this leap year function more elegant/efficient?

25 views

### Fritz Knack

May 21, 1998, 3:00:00 AM5/21/98
to

I compacted this one as much as *I* could, but I couldn't help but
wonder if there was a "better way" or something that may use an idiom
with which I'm not familiar.

I'm not terribly worried about detecting past leap years; in
particular, the year we started using the Gregorian calendar doesn't
matter in the context in which this function lives. Given that, a leap
year is any year divisible by 4 unless it's a 100 year, in which case
it must also be divisible by 400. (1800, 1900, 2100, 2200 aren't leap
years, but 2000 is.)

#---------------------------------------------
sub is_leap {
my \$year = shift;
return (
!(\$year % 4) &&
(!(\$year % 100) ? !(\$year % 400) : 1)
);
}
#---------------------------------------------

Thoughts and comments are welcome.

Thanks,
Fritz

-------------------------
Sorry 'bout the nospam in the From field. You know how those 'bots are.

### Belg4mit

May 21, 1998, 3:00:00 AM5/21/98
to

looks good to me

### Tom Rokicki

May 21, 1998, 3:00:00 AM5/21/98
to

This is what I might use:

sub ly { my \$y = shift ; !(\$y % 4) && \$y % 100 || !(\$y % 400) }

-tom

### Mark-Jason Dominus

May 21, 1998, 3:00:00 AM5/21/98
to

In article <356749eb...@cnews.newsguy.com>,

Fritz Knack <fritz...@POPULUS.net> wrote:
>I compacted this one as much as *I* could,

There's plenty of benefit in leaving it readable to people instead of
trying to compact it. If you're trying to speed it up, maybe
memoization is a better option.

Still, if you want something cute, how about:

sub is_leap {
my \$y = shift;
!(\$y % 400 xor \$y % 100 xor \$y % 4);
}

Maybe it bothers you that you alsways do three divisions. In that
case use

sub is_leap {
my \$y = shift;
!(\$y%4 or (\$y%100 xor \$y%400));
}

Or maybe
!( \$year%4 || !(\$year%100) && \$year%400 )

I would never use ``!(X%Y)''. It's hard to understand.
I'd prefer (X%Y==0) instead:

!( \$year%4 || (\$year%100==0) && \$year%400 )

Reversing the sense of the test might (or might not) make it more
understandable?

sub regular_year {
my \$year = shift;
\$year%4 || (\$year%100==0) && \$year%400
}

There's always the fighting-without-fighting approach:

%is_leap = map {(\$_ => 1)} (1201 .. 2399);
map {\$is_leap{\$_*100} = 0} (13..15, 17..19, 21..23);

sub is_leap {
\$is_leap{\$_[0]};
}

You know, this whole exercise seems like a waste of time. Who cares
if your leap year function is efficient? Is it a bottleneck?

Maybe this is a good opportunity to point out that the point of leap
years is to make the length of the calendar year more closely
approximate the length of the solar year, and that all intercalation
schemes are inaccurate. The Gregorian Calendar was devised when
people had been using the Julian system for long enough to notice the
inaccuracies. But there's a rule that we could adopt that is simpler
than the Gregorian system *and* more accurate. Under the Dominus
Calendar, a year is a leap year if and only if:

sub is_leap { \$_[0] % 33 % 4 == 0 }

Now is the perfect time to institute this change. The switchover will
be easy: This rule coincides with the Gregorian rule until 2012, so we
have 13.5 years to change all our software. (2013 is a leap year in
the Dominus Calendar, so we'd have to have everything changed by
then.)

### Tom Phoenix

May 21, 1998, 3:00:00 AM5/21/98
to Mark-Jason Dominus

On 21 May 1998, Mark-Jason Dominus wrote:

> Now is the perfect time to institute this change. The switchover will
> be easy: This rule coincides with the Gregorian rule until 2012, so we
> have 13.5 years to change all our software. (2013 is a leap year in
> the Dominus Calendar, so we'd have to have everything changed by
> then.)

Is Perl Y2013 compatible? :-)

--
Tom Phoenix Perl Training and Hacking Esperanto
Randal Schwartz Case: http://www.rahul.net/jeffrey/ovs/

### Craig Berry

May 21, 1998, 3:00:00 AM5/21/98
to

Tom Phoenix (root...@teleport.com) wrote:

: On 21 May 1998, Mark-Jason Dominus wrote:
:
: > Now is the perfect time to institute this change. The switchover will
: > be easy: This rule coincides with the Gregorian rule until 2012, so we
: > have 13.5 years to change all our software. (2013 is a leap year in
: > the Dominus Calendar, so we'd have to have everything changed by
: > then.)
:
: Is Perl Y2013 compatible? :-)

Only versions 4.036 and earlier.

---------------------------------------------------------------------
| Craig Berry - cbe...@cinenet.net
| Member of The HTML Writers Guild: http://www.hwg.org/
"Every man and every woman is a star."

### Abigail

May 21, 1998, 3:00:00 AM5/21/98
to

Mark-Jason Dominus (m...@op.net) wrote on MDCCXXIV September MCMXCIII in
<URL: news:6k21vk\$6k9\$1...@monet.op.net>:
++
++
++ There's always the fighting-without-fighting approach:
++
++ %is_leap = map {(\$_ => 1)} (1201 .. 2399);

This should be
%is_leap = map {(4 * \$_ => 1)} (401 .. 799);

++ map {\$is_leap{\$_*100} = 0} (13..15, 17..19, 21..23);
++
++ sub is_leap {
++ \$is_leap{\$_[0]};
++ }
++
++ You know, this whole exercise seems like a waste of time. Who cares
++ if your leap year function is efficient? Is it a bottleneck?

sub is_leap {!((grep {\$_ [0] % \$_} (4, 100, 400)) % 2);}

Abigail
--
perl -pwle '\$_ .= reverse'

### Greg Bacon

May 21, 1998, 3:00:00 AM5/21/98
to

In article <6k21vk\$6k9\$1...@monet.op.net>,
m...@op.net (Mark-Jason Dominus) writes:
: But there's a rule that we could adopt that is simpler

: than the Gregorian system *and* more accurate.

kill \$my, @calendar and die;

:-)
--
open(G,"|gzip -dc");\$_=<<EOF;s/[0-9a-f]+/print G pack("h*",\$&)/eg
f1b88000b620f22320303fa2d2e21584ccbcf29c84d2258084
d2ac158c84c4ece4d22d1000118a8d5491000000
EOF

### Steffen Beyer

May 21, 1998, 3:00:00 AM5/21/98
to

Fritz Knack <fritz...@nospam.POPULUS.net> wrote:

> I compacted this one as much as *I* could, but I couldn't help but
> wonder if there was a "better way" or something that may use an idiom
> with which I'm not familiar.

> I'm not terribly worried about detecting past leap years; in
> particular, the year we started using the Gregorian calendar doesn't
> matter in the context in which this function lives. Given that, a leap
> year is any year divisible by 4 unless it's a 100 year, in which case
> it must also be divisible by 400. (1800, 1900, 2100, 2200 aren't leap
> years, but 2000 is.)

> #---------------------------------------------
> sub is_leap {
> my \$year = shift;
> return (
> !(\$year % 4) &&
> (!(\$year % 100) ? !(\$year % 400) : 1)
> );
> }
> #---------------------------------------------

> Thoughts and comments are welcome.
> Thanks, Fritz

boolean DateCalc_leap_year(Z_int year)
{
Z_int yy;

return( ((year AND 0x03) == 0) and
( (((yy = (Z_int) (year / 100)) * 100) != year) or
((yy AND 0x03) == 0) ) );
}

That makes one division instead of three.

Yours,
--
Steffen Beyer <s...@engelschall.com>
"Perl is like sex: If you never had it, you wonder what the fuss is all
about. Once you had it, you never want to be without it again." (unknown)

### Fritz Knack

May 22, 1998, 3:00:00 AM5/22/98
to

On 21 May 1998 16:14:44 -0400, m...@op.net (Mark-Jason Dominus) wrote:

>
>In article <356749eb...@cnews.newsguy.com>,

>Fritz Knack <fritz...@POPULUS.net> wrote:
>>I compacted this one as much as *I* could,
>

>You know, this whole exercise seems like a waste of time. Who cares

>if your leap year function is efficient? Is it a bottleneck?
>

It's not a waste as an exercise for its own sake. If I can learn to
better code the little things, the big ones will come more naturally.
Efficiency of my leap year function is probably the least of my
worries. :)

### Ken Williams

May 22, 1998, 3:00:00 AM5/22/98
to

In article <356749eb...@cnews.newsguy.com>, fritz...@POPULUS.net wrote:

>I compacted this one as much as *I* could, but I couldn't help but
>wonder if there was a "better way" or something that may use an idiom
>with which I'm not familiar.
>
>I'm not terribly worried about detecting past leap years; in
>particular, the year we started using the Gregorian calendar doesn't
>matter in the context in which this function lives. Given that, a leap
>year is any year divisible by 4 unless it's a 100 year, in which case
>it must also be divisible by 400. (1800, 1900, 2100, 2200 aren't leap
>years, but 2000 is.)
>
>
>#---------------------------------------------
>sub is_leap {
> my \$year = shift;
> return (
> !(\$year % 4) &&
> (!(\$year % 100) ? !(\$year % 400) : 1)
> );
>}
>#---------------------------------------------

You can do a little boolean algebra on it to get rid of the !'s. Here are
the simplification steps:

!(\$year % 4) &&
(!(\$year % 100) ? !(\$year % 400) : 1)

not not(

!(\$year % 4) &&
(!(\$year % 100) ? !(\$year % 400) : 1)
)

not (
(\$year % 4) ||
!(!(\$year % 100) ? !(\$year % 400) : 1)
)

not (
(\$year % 4) ||
(!(\$year % 100) ? (\$year % 400) : 0)
)

not (
(\$year % 4) ||
((\$year % 100) ? 0 : (\$year % 400))
)

not (\$year % 4 or (\$year % 100) ? 0 : (\$year % 400))

In general, whenever you've got a construct like this:
!\$condition ? \$result1 : \$result2

You could rewrite it like this:
\$condition ? \$result2 : \$result1

That ? 0 : is nagging me though, I think we might be able to get rid of that...

not (\$year % 4 or !(\$year % 100) ? (\$year % 400) : 0)

not (\$year % 4 or !(\$year % 100) and (\$year % 400))

Which I see is totally identical to Tom Rokicki's solution, distributing
the not:

not \$year % 4 and not !(\$year % 100) or not (\$year % 400)

not \$year % 4 and (\$year % 100) or not (\$year % 400)

!(\$year % 4) and (\$year % 100) or !(\$year % 400)

Ah well...