silent11 has asked for the wisdom of the Perl Monks concerning the following question:

I'm writing a reporting tool that draws information out of a database. Each time the report is run, I will only need the new items that were added the half-month before the current.....let me explain. Say today is january 25, so I'd like to see all of the items in my DB added from Jan 1 thru Jan 15. If Today were Jan 9 I would want to report on the entries added from Dec 16 thru the last day of December. So if I'm in the last half of the month, I'd like to report on the first half, if I'm in the first half of the month, I'd like to report on the last half of last month.

I need these dates in YYYYMMDD format. ( TIMESTAMP(8) )

Here is what I have written so far, I tried to do it using Date::Time, but I'm not familliar with the module. I'm afraid that this code may have errors since I didn't use a CPAN module specific to the task. Do any of you monks see a better way of doing this, or do you see any shortcomings of this code?

sub newrelease { print " New Releases \n"; my $start_day; my $end_day; (my $day,my $month,my $year) = (localtime)[3,4,5]; if ($day > 15) { $start_day = sprintf("%04d%02d%02d", $year + 1900, $month + 1, 0); $end_day = sprintf("%04d%02d%02d", $year + 1900, $month + 1, 15) +; } if ($day <= 15) { $start_day = sprintf("%04d%02d%02d", $year + 1900, $month, 15); $end_day = sprintf("%04d%02d%02d", $year + 1900, $month , 31); } print "New Releases from $start_day to $end_day \n"; # pass datestamp off to my &sql sub outside of this block of code &sql("stamp > $start_day and stamp =< $end_day"); }
Thanks,

-Silent11

Replies are listed 'Best First'.
Re: date span in YYYYMMDD
by pfaut (Priest) on Jan 25, 2003 at 15:35 UTC

    This appears to do what you want.

    #!/usr/bin/perl -w use strict; # Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec my @days = ( 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31); my ($d1,$m,$y) = (localtime)[3..5]; $y += 1900; my $d2; if ($d1 > 15) { $d1 = 1; $d2 = 15; } else { if (--$m < 0) { $m = 11; $y--; } $d1 = 16; $d2 = $days[$m]; } $m++; printf "%04d%02d%02d-%04d%02d%02d\n",$y,$m,$d1,$y,$m,$d2;
    --- print map { my ($m)=1<<hex($_)&11?' ':''; $m.=substr('AHJPacehklnorstu',hex($_),1) } split //,'2fde0abe76c36c914586c';
      To deal with leap years, you could make the @days array like this:

      my @days = ( [ qw(31 28 31 30 31 30 31 31 30 31 30 31) ], # non-leap [ qw(31 29 31 30 31 30 31 31 30 31 30 31) ] # leap )

      A function to return 0 when a gives year is not a leap year, and 1 if it is:

      sub leap { my $y = shift; return ($y % 4 == 0 && $y % 100 != 0) || $y % 400 == 0; }
      and you can use it like
      my $leap = leap($y); ... $days[$leap][$m]

      Arjen

Re: date span in YYYYMMDD
by BazB (Priest) on Jan 25, 2003 at 16:07 UTC

    Here's an example using Date::Calc.

    This code gets the current day, month and year using Date::Calc::Localtime, rather than the built in localtime().

    This code will figure out the number of days in each month.
    Leap years are handled correctly.

    You could always modify this code so that you can get those numbers yourself outside of the subroutine and have the sub just do the calculations.

    #!/usr/bin/perl use strict; use warnings; use Date::Calc qw/Localtime Days_in_Month/; sub give_dates { my ($year, $month, $day) = (Localtime())[0,1,2]; my $days_in_mon = Days_in_Month($year, $month); my $cutoff = int($days_in_mon / 2); my ($start, $end); if ( $day <= $cutoff) { # First half of month $start = $year . sprintf("%02d", $month) . '01'; $end = $year . sprintf("%02d", $month) . $cutoff; } else { # Second half of month $start = $year . sprintf("%02d", $month) . $cutoff; $end = $year . sprintf("%02d", $month) . $days_in_mon; } return ($start, $end); } print join " ", give_dates(), "\n";

    Cheers.

    BazB

    Update: Corrected code. Initial version gave dates in wrong format (YYYYMMMDD not YYYYMMDD).


    If the information in this post is inaccurate, or just plain wrong, don't just downvote - please post explaining what's wrong.
    That way everyone learns.

Re: date span in YYYYMMDD
by poj (Abbot) on Jan 25, 2003 at 17:20 UTC
    If the current date is in the first half of January, then presumably you want the previous year.
    For these kind of problems I find it useful to write a test routine that checks across the boundary conditions like this
    use warnings; use strict; # test routine for my $y (2001..2002){ for my $m (1,6,12){ for my $d (1,15,16,31){ print "$d\t$m\t$y = ".newrelease($d,$m-1,$y-1900)."\n"; } } print "\n"; } sub newrelease { #my ($day,$month,$year) = (localtime)[3,4,5]; my ($day,$month,$year) = @_; # testing # make normal $year+=1900; $month+=1; # return values my ($start_date,$end_date); # logic if ($day > 15){ # easy one same month $month = sprintf "%02d",$month; $start_date = $year.$month."01"; $end_date = $year.$month."15"; } else { # day is 15 or less use previous month # if now jan then prev month is dec last year if ($month == 1){ --$year; $month=12; } else { $month = sprintf "%02d",$month-1; } $start_date = $year.$month."16"; $end_date = $year.$month."31"; } return "$start_date to $end_date"; }
    poj
Re: date span in YYYYMMDD
by Aragorn (Curate) on Jan 25, 2003 at 15:40 UTC
    Basically, I think the code is correct, but what about the lengths of the months? (30, 31, 29, or 28 days). Maybe it doesn't matter for your application, but if it does you have to add code for dealing with those special cases.

    Here is what I have written so far, I tried to do it using Date::Time, but I'm not familliar with the module.
    Hmm. Maybe you should do some experimentation with Date::Time to see what it can do. Or look for other Date:: modules. Date::Calc looks promising for your problem.

    Not being familiar with a module is not really a good reason to not use it. Experiment with it and maybe someday a problem comes by which you can solve with that module because you knew it existed and played around with it.

    Arjen

Re: date span in YYYYMMDD
by Dr. Mu (Hermit) on Jan 25, 2003 at 20:05 UTC
    To take advantage of Perl's wonderful syntactic shortcuts, you might try something like:
    use strict; my ($day, $month, $year) = (localtime) [3, 4, 5]; $year += ($month += ($day = $day > 15 ? 0 : 15) ? 11 : 0) == 11 ? 1899 + : 1900; my ($start_day, $end_day) = map {sprintf("%4.4d%2.2d%2.2d", $year, $mo +nth % 12 + 1, $_)} ($day, $day ? 31 : 15); print "New Releases from $start_day to $end_day \n"
    This makes heavy use of the conditional operator (?:) to back the date up to the beginning of the previous half-month, meanwhile putting the year in the proper century. Then map is used to generate the similarly-formatted $start_day and $end_day, while also correcting the month value. Note that to subtract one from the month, we are adding 11, and that the month correction takes this value modulo 12 before doing the final increment. Also, no account is made of months shorter than 31 days. Since this is used only in an expression for comparing real dates from a file, it shouldn't matter.

    Of course, Perl's shortcuts sometimes do make code hard to read, and I may have crossed the line somewhat into obfuscation territory to make my point. But used carefully, they support a brevity that can actually foster comprehension by stripping a script of redundancy.

    As with any code you get from the Monks, "try it before you buy it". I may have overlooked something. :-)

Re: date span in YYYYMMDD
by l2kashe (Deacon) on Jan 27, 2003 at 07:38 UTC
    Ahhh the joys of figuring out what the date is, or what yesterday was....

    Anywho.. I like all the suggestions so far, but I have a little something to add. Your data is already in a database, so personally I think you should leverage the power of the database for you like so,
    %num = ( Jan => '01', Feb => '02', Mar => '03', Apr => '04', May => '05', Jun => '06', Jul => '07', Aug => '08', Sep => '09', Oct => '10', Nov => '11', Dec => '12' ); ($day,$mth,$year) = (localtime)[3,4,5]; if ($mth =~ /Jan/) { # If its jan we need last year to this year $s_year = $year - 1; $e_year = $year; } else { $s_year = $year; $e_year = $year; } if ($day <= 14) { # Get last half of last month $s_day = '16'; $e_day = '01'; $s_month = $num{$last{$mth}}; $e_mth = $num{$mth}; } else { # Get the beginning of this month $s_day = '01'; $e_day = '15'; $s_month = $num{$mth}; $e_month = $num{$mth}; } $query = 'SELECT * from newreleases '; $query = 'WHERE rel_date >= "$s_year-$s_mth-$s_day" AND rel_date <= "$ +e_year-$e_mth-$s_day";'
    I jumped through more hoops than I like there, and there is some redundancy, but I don't need an exact date range, as the DB will know how to compute it. Also I don't need to keep track of leap years, or other idioms like the last day of any particular month.

    All the suggestions above where good, I just thought that there was a simpler way. I mean the DB has to be able to compare stuff right? I just say start here, and go till there.

    /* And the Creator, against his better judgement, wrote man.c */