I was today tasked with produceing code which took a date in the format CCYYMMDD, printing out a message if it was not the last day of a month. For the real job I used a well notated sub routine but wondered how short I could make a stand alone program. With a little help from the vaults of The Monks, the leap year bit( I used the Date::leapyear module), I came up with this
my ($y, $m, $d) = (substr($ARGV[0],0,4),substr($ARGV[0],4,2),substr($A
+RGV[0],6,2));
my $ld = (31,28,31,30,31,30,31,31,30,31,30,31)[$m-1];
if (($m-1) == 1){$ld++ if(!($y%100) && !($y%400))||(($y % 100)&&!($y%4
+))}
print "$ARGV[0] is NOT a month end day\n" unless $d eq $ld;
I'm sure that some of the Monks, Saints and Gods around the monestery could make this look like War & Peace but it gave a warm feeling of satisfaction. I'd also like to thank everyone in the monestery for there continuing help
Re: Last day of the month. Any shorter
by Abigail-II (Bishop) on May 19, 2004 at 13:18 UTC
|
perl -MDate::Manip -le 'print "Last day" if UnixDate (ParseDate ($ARGV
+ [0]), "%B") ne UnixDate DateCalc ($ARGV [0], "1day"), "%B"' YYYYMMDD
Abigail | [reply] [d/l] |
|
Sadly working on a windows server, weep weep.
| [reply] |
|
Yes, and? Date::Manip is written in 100% Perl.
Abigail
| [reply] |
Re: Last day of the month. Any shorter
by japhy (Canon) on May 19, 2004 at 13:01 UTC
|
use Time::Local;
### this will get me the Unix time
### for the last day in May
# get today's month and year
my ($m, $y) = (localtime)[4,5];
# get next month's month and year
my ($nm, $ny) = ($m == 11) ? (0, $y+1) : ($m+1, $y);
# get noon on the first of next month
my $next_month_noon = timelocal(0,0,12, 1,$nm,$ny);
# subtract a day from it (86400 seconds)
my $month_last_day = $next_month_noon - 86400;
Now I have a time value in $month_last_day that I can pass to localtime() if I want. I can use this code, then, to determine if a given date is the last day of its month:
# my code above, as a function
sub lastday_time {
use Time::Local; # I never do this in real code
# I put module uses at the top of
# the entire program, not each sub
# arguments are assumed to be in "human" terms
my ($m, $y) = @_;
$m--, $y -= 1900; # perl-ify them
my ($nm, $ny) = ($m == 11) ? (0,$y+1) : ($m+1,$y);
# return the first of next month, minus one day
return timelocal(0,0,12, 1,$nm,$ny) - 86400;
}
sub is_last_of_month {
# arguments are "human"
# lastday_time() subtracts 1 from $m and 1900 from $y
my ($d, $m, $y) = @_;
my $last_day = (localtime lastday_time($m,$y))[3];
# fixed that last line -- I had $d, meant $m
return $d == $last_day;
}
There, the is_last_of_month() function returns true if the given date (like (31,5,2004)) is the last of that month, and false otherwise.
_____________________________________________________
Jeff [japhy]Pinyan:
Perl,
regex,
and perl
hacker, who'd like a job (NYC-area)
s++=END;++y(;-P)}y js++=;shajsj<++y(p-q)}?print:??;
| [reply] [d/l] [select] |
Re: Last day of the month. Any shorter
by eserte (Deacon) on May 19, 2004 at 13:27 UTC
|
| [reply] [d/l] |
Re: Last day of the month. Any shorter
by Ven'Tatsu (Deacon) on May 19, 2004 at 13:25 UTC
|
You can save some characters and avoid some repetition by replacing the first line with
my ($y, $m, $d) = unpack('A4A2A2', $ARGV[0]);
| [reply] [d/l] |
|
I'd like to if I understood what is happening here.
| [reply] |
|
unpack extracts values of various types from a string based on a format, in this case A4A2A2, and returns them as a list.
A is a space padded sting of bytes, the number after it is the number of bytes in each value.
So this format will return a string of the first 4 bytes (stripping away any trailing spaces if there were any) then the next 2 bytes and then the next 2 bytes.
The end result is identical to the 3 calls to substr, but a bit more concise.
| [reply] [d/l] [select] |
Re: Last day of the month. Any shorter
by BrowserUk (Patriarch) on May 19, 2004 at 13:29 UTC
|
$ARGV[ 0 ] =~ m[^(\d{4})(\d{2})(\d{2})] or die "Bad date: $ARGV[ 0 ]";
print "$ARGV[0] is NOT a month end day"
if $3-28-substr(' 303232332323',$2,1)-($2==2and not$1%4xor$1%100xo
+r$1%400);
Examine what is said, not who speaks.
"Efficiency is intelligent laziness." -David Dunham
"Think for yourself!" - Abigail
| [reply] [d/l] |
|
Very, Very good 2 months ago I would have sratched and gone back to bed but at least I know what I'm looking at now. Thanks.
| [reply] |
Re: Last day of the month. Any shorter
by eserte (Deacon) on May 19, 2004 at 13:23 UTC
|
The leapyear check could be made somewhat shorter:
($year % 4 == 0 && (($year % 100 != 0) || ($year % 400 == 0)))
| [reply] [d/l] |
Re: Last day of the month. Any shorter
by johndageek (Hermit) on May 19, 2004 at 15:38 UTC
|
use Time::Local;
print "last day" if (((localtime(time()+60*60*24))[3]) < 2);
Just a bit of fun.
| [reply] [d/l] |
Re: Last day of the month. Any shorter
by jdporter (Paladin) on May 19, 2004 at 13:32 UTC
|
I think you're taking a risk by trying to implement your own leap-year calculation.
use Time::Local;
sub is_last_day_of_month
{
my($y,$m,$d) = $_[0] =~ /(.{2,4})(..)(..)/;
(gmtime(timegm(0,0,12,$d,$m-1,$y)+24*60*60))[4] != $m-1
}
my $a = shift;
print is_last_day_of_month($a)
? "$a: is last day of month\n"
: "$a: is NOT last day of month\n";
| [reply] [d/l] |
|
"I think you're taking a risk by trying to implement your own leap-year calculation."
Why? It's a simple and well documented algorithm, you can write it in a line of Perl. What's the risk?
Personally I keep hoping that a rudimentary date time module (maybe DateTime.pm or a subset) will be included with Perl core, then using the "comes with" will be easier than cutting and pasting the line of perl around :)
-- Do not seek to follow in the footsteps of the wise. Seek what they sought. -Basho
| [reply] |
|
Still not sure why you'd want to. Just doing the leap-year check by hand is longer than some of the entire solutions presented.
And also:
use Time::Local;
sub is_leapyear {
1 == (gmtime(timegm(0,0,1,28,1,$_[0])+24*60*60))[4]
}
(But this has severe limitations on the range of valid dates input.)
| [reply] [d/l] |
|
|
The code thats gone into production uses something very similer to your example here. I'm only a contractor here and the full time staff(at the moment not perl experts) have to be able to maintain the code after I've gone, so the shortest version in this case is not the best. I just wonted to give myself a bit of a test and see where my skills where compared to the rest of the Monks. Still pretty low I'm afriad.
| [reply] |
Re: Last day of the month. Any shorter
by zude (Scribe) on May 19, 2004 at 21:45 UTC
|
my ($y,$m,$d) = unpack "A4A2A2", $ARGV[0]; # ven'tatsu!
$d == 30+($m&1^$m>7)-($m==2)*(2-!($y%4||(!($y%100)&&$y%400)))
or print "not month end day\n";
------- ~%{${@_[0]}}->{0}&&+++ NO CARRIER
| [reply] [d/l] |
Re: Last day of the month. Any shorter
by jeffa (Bishop) on May 19, 2004 at 20:08 UTC
|
TIMTWOTDI. This one via Time::Piece, which relies on localtime:
perl -MTime::Piece -le'$t=localtime->strptime(shift,"%Y%m%d");print "l
+ast day" if $t->mday == $t->month_last_day' 20010731
Haven't tested this, but this one-liner should work for Win32 systems:
perl -MTime::Piece -le"$t=localtime->strptime(shift,'%Y%m%d');print 'l
+ast day' if $t->mday == $t->month_last_day" 20010731
| [reply] [d/l] [select] |
Re: Last day of the month. Any shorter
by Paulster2 (Priest) on May 20, 2004 at 10:50 UTC
|
While most, if not all of the nodes already giving feedback to your question have very good ideas, Abigail-II is on the money in my book. Date::Manip is probably the best date manipulation (as the name implies) module that I have come accross. It is a little large, but it works VERY well. It handles almost any form of manipulate, also uses regular speech paterns to decipher what you want to do (such as "last day of the month"). Also, the documentation is NOT lacking. This module will not only make your code shorter (as in the number of lines to produce the same result), it also makes your code a lot easier for someone else to read. It will definitly make life a lot easier where dates are concerned.
Paulster2 You're so sly, but so am I. - From the movie Manhunter.
| [reply] |
|
|