darkone (\/) (;,,,;) (\/) 11610 Posts user info edit post |
I'm using perl to, as part of a larger script, create a list of dates for an entire year formatted YYYYMMDD.
For example, if the user specifies the year 2002, it will create the following list and write it to a text file:
20020101 20020102 20020103 . . . 20021231
Obviously I have to account for pesky things like leap years. Now, I'm a perl n00b and I am not a programmer by trade. I have code that works and does what I need it to, but I'm sure that it's needlessly complicated. Therefore, my question to TWW is, given the criteria I listed above, how would you do this in perl?
[Edited on May 23, 2008 at 5:37 PM. Reason : not enough Y]5/23/2008 5:36:44 PM |
qntmfred retired 40726 Posts user info edit post |
use the DateTime module? 5/23/2008 6:35:04 PM |
Arab13 Art Vandelay 45180 Posts user info edit post |
if the year is divisible by 4 with a whole number and none extra point it to a second set of dates with leap year added
you're gonna need 2 lists for it i think, but the rest is pretty simple
^ have you tried this yet? 5/26/2008 1:49:22 PM |
agentlion All American 13936 Posts user info edit post |
two lists? what are you talking about?
all you need to do is on the fly generate a list of numbers that are always the same, then when you get to then to february 29 just run a statement like
if( 0 == $year % 4 and 0 != $year % 100 or 0 == $year % 400 ) { print "$year0229"; }
it is a leap year if it is divisible by 4 but not by 100, except if it is for 400 5/26/2008 3:40:37 PM |
agentlion All American 13936 Posts user info edit post |
without using any additional modules or any "smart" date handling (i.e. interpreting the dates purely as numbers), then something like this might work (not tested or debugged, no error checking, and assumes $year and $outFileName are provided already)
open (OUTFILE, ">$outFileName"); select OUTFILE; # select default output stream
print "Dates in $year\n"; # header
for ($month = 1; $month<=12; $month++) { $daysInMonth = 31; #default days in month is 31 because it is most frequent if ($month == 4 or $month == 6 or $month == 9 or $month == 11) $daysInMonth = 30; elseif ($month == 2) { #leap year calculation if (0 == $year % 4 and 0 != $year % 100 or 0 == $year % 400 ) $daysInMonth = 29; else $daysInMonth = 28; } for ($day=1; $day<=$daysInMonth; $day++) { print "$year"; ($month<10) ? print "0$month" : print "$month"; ($day<10) ? print "0$day" : print "$day\n"; } } close OUTFILE
5/26/2008 3:58:40 PM |
darkone (\/) (;,,,;) (\/) 11610 Posts user info edit post |
I feel like a dumb ass for not thinking to do this:
"0$day"
My initial stumbling block when first coding my solution was the leading zero on months and days. I was like, "shit, I can't just have a month variable be an interger and go from there because I need the leading zero. I guess I'll use strings."
^That's a more elegant solution that the solution I finally coded. I'll probably clean that up a bit a swap out my needlessly complicated algorithm.
Maybe when I get back to the office, I'll post my original code so you guys can have a laugh.5/28/2008 10:36:21 PM |
agentlion All American 13936 Posts user info edit post |
Quote : | "I can't just have a month variable be an interger and go from there because I need the leading zero. I guess I'll use strings." |
yeah, more or less all the same in Perl - integer, string, numbers, whatever - Perl is smart enough to know the difference (how long till i'm corrected by the Perl gods telling me that "NUH UH - PERL VARIABLES HAVE TYPES!")5/28/2008 11:03:59 PM |
darkone (\/) (;,,,;) (\/) 11610 Posts user info edit post |
My code: (don't laugh too much... it does actually work)
@listMonths = ("01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"); @listDay_28 = ("01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28"); @listDay_29 = ("01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29"); @listDay_30 = ("01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30"); @listDay_31 = ("01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31");
foreach $Month (@listMonths) { if ($Month eq "01" or $Month eq "03" or $Month eq "05" or $Month eq "07" or $Month eq "08" or $Month eq "10" or $Month eq "12") { foreach $Day (@listDay_31) { $Date="$Year$Month$Day"; system("echo \'$Date\' >> \'$DateList\'"); } } elsif ($Month eq "04" or $Month eq "06" or $Month eq "09" or $Month eq "11") { foreach $Day (@listDay_30) { $Date="$Year$Month$Day"; system("echo \'$Date\' >> \'$DateList\'"); } } else { if ($Year eq "2000" or $Year eq "2004" or $Year eq "1998" or $Year eq "2008" or $Year eq "2012") #all leap years in the TRMM dataset { foreach $Day (@listDay_29) { $Date="$Year$Month$Day"; system("echo \'$Date\' >> \'$DateList\'"); } } else { foreach $Day (@listDay_28) { $Date="$Year$Month$Day"; system("echo \'$Date\' >> \'$DateList\'"); } } } }
I hard coded in the leap year dates because I'm working with data from a satellite that was launched in 1997 and will run out of fuel in 2012.
[Edited on May 30, 2008 at 4:34 PM. Reason : leap year info]5/30/2008 4:33:23 PM |
agentlion All American 13936 Posts user info edit post |
a colleague just reminded me of the printf function, which will format your output, for example, with leading zeros. The printing can be further simplified to
for ($day=1; $day<=$daysInMonth; $day++) { print "\n$year"; printf '%02s', $month; printf '%02s', $day; } 6/2/2008 1:10:56 PM |
agentlion All American 13936 Posts user info edit post |
a little more optimization - this is all you need in the for loop
for ($day=1; $day<=$daysInMonth; $day++) sprint '%04s%02s%02s\n', $year, $month, $day;
6/2/2008 1:40:42 PM |
agentlion All American 13936 Posts user info edit post |
ok, now i have Perl geeks at work doing what Perl geeks do best - making the program shorter
this will take care of all the year, month and day printing, including leap years
$month = 1; foreach (31,28,31,30,31,30,31,31,30,31,30,31) { $daysInMonth=$_; $month++; if ($daysInMonth == 28 && !($year%4) && ($year%100) || !($year%400) ) $daysInMonth = 29; for ($day=1; $day<=$daysInMonth; $day++) printf "%04s%02s%02s\n",$year,$month,$day; }
[Edited on June 2, 2008 at 4:10 PM. Reason : .]6/2/2008 4:09:32 PM |
darkone (\/) (;,,,;) (\/) 11610 Posts user info edit post |
^wow... I <3 perl 6/2/2008 8:47:51 PM |
Scary Larry Suspended 644 Posts user info edit post |
I don't mean to butt in with my opinion, but date handling should pretty much never be done by loop or list, and it really sounds like this is going to be used as a work around for a proper implementation of date and time logic. If that's the case you should probably realize how terribly wrong it is to difference dates by file line numbers. Sure it may work, but it's so far from having predictable behavior or utilizing resources efficiently that I'd just err on the side of caution and call it flat out wrong. 6/2/2008 8:59:51 PM |
darkone (\/) (;,,,;) (\/) 11610 Posts user info edit post |
^ read the intended goal and comment again 6/2/2008 9:02:09 PM |
Scary Larry Suspended 644 Posts user info edit post |
I read the intended goal, and I honestly can't think of a good reason to generate such a text file. The point I was trying to convey was that the need for such a text in your application implied a greater problem with date handling than simply generating the text file.
Again, if I'm making assumptions about your application I apologize. I've seen too many good script ideas rendered useless in implementation, as hacks were unnecessarily used to treat computer science challenges in the same manner as API challenges. 6/2/2008 9:42:19 PM |
qntmfred retired 40726 Posts user info edit post |
you know, that reminds me of a problem i was trying to solve at work today
given a start and end DateTime (we're talking c# now), generate a List representing the range of dates between the start and end date separated by a given amount of time. the only way i could think to do it was a loop
List dates = new List<DateTime>(); DateTime curDate = start; while (curDate<=end) { dates.Add(curDate); curDate.AddMonths(1); }
it seemed like there should be a better way, but i couldn't think of anything simpler than this] 6/2/2008 10:27:44 PM |
darkone (\/) (;,,,;) (\/) 11610 Posts user info edit post |
^^ The file generated is used as input for a called script. 6/2/2008 10:43:56 PM |
LimpyNuts All American 16859 Posts user info edit post |
Quote : | "$month = 1; foreach (31,28,31,30,31,30,31,31,30,31,30,31) { $daysInMonth=$_; $month++; if ($daysInMonth == 28 && !($year%4) && ($year%100) || !($year%400) ) $daysInMonth = 29; for ($day=1; $day<=$daysInMonth; $day++) printf "%04s%02s%02s\n",$year,$month,$day; } " |
Any particular reason you're starting the months at 2? $month++ should go after the output loop.6/4/2008 10:04:48 PM |
agentlion All American 13936 Posts user info edit post |
Quote : | "not tested or debugged, no error checking" |
6/4/2008 10:06:18 PM |
|