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

Hey guys! I have a perl script that I use generate billing reports that gets ran and emailed every month via cronjob to report on some metrics. The perl script runs fine (I originally got this working because of some help from the wonderful monks!) For security reasons I cannot post the script itself but I can post how the current date filter works:
y $START_DATE = `date '+%Y-%m-%d' -d "-1 month"`; my $END_DATE = `date '+%Y-%m-%d'`; chomp $START_DATE; chomp $END_DATE; my $URL = "http://url/url/url/dump?end_date=$END_DATE&start_date=$STAR +T_DATE&type=csv";
What I need to do is setup this script to generate a monthly report every month for past 2 years. Is there any easy way to do this with the $START_DATE and $END_DATE? Can anyone help me do this?

Replies are listed 'Best First'.
Re: Generate a report for every month for a year
by thanos1983 (Parson) on Jun 23, 2017 at 20:36 UTC

    Hello younggrasshopper13,

    I think you are looking for that Get previous date. There are plenty of examples and easy to understand.

    From there just make a subroutine loop for every month that will return new start and end date. :D

    Update: Simple example using DateTime.

    #!usr/bin/perl use say; use strict; use warnings; use DateTime; my $time_now = DateTime ->now( time_zone => 'local' ); say $time_now->strftime("%Y, %m, %d"); my $one_month_ago = DateTime ->now( time_zone => 'local' ) ->subtract( days => 30 ); say $one_month_ago->strftime("%Y, %m, %d"); __END__ $ perl test.pl 2017, 06, 23 2017, 05, 24

    Hope this helps, BR

    Seeking for Perl wisdom...on the process of learning...not there...yet!
      my $one_month_ago = DateTime ->now( time_zone => 'local' ) ->subtract( days => 30 );

      I agree that DateTime is a good choice, but note that "one month ago" is not the same as "30 days ago", and date math works the same as DateTime in this case:

      use DateTime; my $dt = DateTime->new(year=>2017,month=>3,day=>1); print $dt->ymd, "\n"; print "-30 days: ", $dt->clone->subtract(days=>30)->ymd, " "; system('date','+%Y-%m-%d','-d 2017-03-01 -30 days')==0 or die $?; print "-1 month: ", $dt->clone->subtract(months=>1)->ymd, " "; system('date','+%Y-%m-%d','-d 2017-03-01 -1 month')==0 or die $?; __END__ 2017-03-01 -30 days: 2017-01-30 2017-01-30 -1 month: 2017-02-01 2017-02-01

      For younggrasshopper13: If you want to get the past 24 months starting with the current, here's one way:

      use DateTime; my $dt = DateTime->now->truncate(to=>'month'); for (1..24) { my $last = $dt->clone->add(months=>1)->subtract(days=>1); print $dt->ymd," to ",$last->ymd,"\n"; $dt->subtract(months=>1); } __END__ 2017-06-01 to 2017-06-30 2017-05-01 to 2017-05-31 2017-04-01 to 2017-04-30 ... etc.

      And just for the sake of TIMTOWTDI, another, although not exactly the same:

      use DateTime; my $now = DateTime->now; for ( my $dt = $now->clone->truncate(to=>'month') ->subtract(years=>2)->add(months=>1); $dt < $now; $dt->add(months=>1) ) { my $last = DateTime->last_day_of_month(year=>$dt->year, month=>$dt->month,time_zone=>$dt->time_zone); print $dt->ymd," to ",$last->ymd,"\n"; } __END__ 2015-07-01 to 2015-07-31 2015-08-01 to 2015-08-31 2015-09-01 to 2015-09-30 ...

      Note that DateTime->now defaults to using UTC, so if you want something else, specifying DateTime->now(time_zone=>...) like thanos1983 showed is not a bad idea. But keep in mind that time_zone=>'local' will make it depend on the current configuration of the local machine, so if you want consistency, consider using a specific time zone like time_zone=>'America/Chicago'.

      Update: Changed the last example to use a for(;;) loop, just to add some variety.

Re: Generate a report for every month for a year
by huck (Prior) on Jun 23, 2017 at 22:16 UTC

    A Core method

    # a core method determine start/end dates of prior months use strict; use warnings; use Time::Local; my @startend; # [0] is current noncompleted month others are full mo +nths back # in each of those the sub elements are # [0] yyyy mm # [1] is start yyyy mm dd # [2] is end yyyy mm dd my $date; my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) =localtime( +time) ; # move to noon so DST changes dont kill ya my $now_fix12=timelocal( 0, 0, 12, $mday, $mon, $year); $date=$now_fix12; my $back=24; for my $m (0..$back) {push @startend,['']}; my $todo=0; my $doing; my ($ym,$ymd); $doing=$startend[$todo]; ($date,$ym,$ymd)=getparts($date); startup($doing,$ym,$ymd); while (1) { ($date,$ym,$ymd)=getparts($date); # print $ymd."\n"; unless ($doing->[0] eq $ym) { $todo++; last if ($todo>$back); $doing=$startend[$todo]; startup($doing,$ym,$ymd); } else {$doing->[1]=$ymd; } } # while for my $doing (@startend) { print join(' | ',@$doing)."\n"; } exit; sub startup { my $doing=shift; my $ym=shift; my $ymd=shift; $doing->[0]=$ym; $doing->[1]=$ymd; $doing->[2]=$ymd; } # startup sub getparts { my $date=shift; my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) =localtime( +$date) ; my $ymd=sprintf('%4d %02d %02d',$year+1900,$mon+1,$mday); my $ym=substr($ymd,0,7); $date=$date-24*60*60; return ($date,$ym,$ymd); }
    Result
    2017 06 | 2017 06 01 | 2017 06 23 2017 05 | 2017 05 01 | 2017 05 31 2017 04 | 2017 04 01 | 2017 04 30 2017 03 | 2017 03 01 | 2017 03 31 2017 02 | 2017 02 01 | 2017 02 28 2017 01 | 2017 01 01 | 2017 01 31 2016 12 | 2016 12 01 | 2016 12 31 2016 11 | 2016 11 01 | 2016 11 30 2016 10 | 2016 10 01 | 2016 10 31 2016 09 | 2016 09 01 | 2016 09 30 2016 08 | 2016 08 01 | 2016 08 31 2016 07 | 2016 07 01 | 2016 07 31 2016 06 | 2016 06 01 | 2016 06 30 2016 05 | 2016 05 01 | 2016 05 31 2016 04 | 2016 04 01 | 2016 04 30 2016 03 | 2016 03 01 | 2016 03 31 2016 02 | 2016 02 01 | 2016 02 29 2016 01 | 2016 01 01 | 2016 01 31 2015 12 | 2015 12 01 | 2015 12 31 2015 11 | 2015 11 01 | 2015 11 30 2015 10 | 2015 10 01 | 2015 10 31 2015 09 | 2015 09 01 | 2015 09 30 2015 08 | 2015 08 01 | 2015 08 31 2015 07 | 2015 07 01 | 2015 07 31 2015 06 | 2015 06 01 | 2015 06 30
    Note that element 0 in @startup is the current non completed month, others are full months back

    If you are just dealing with days, $date=$date-24*60*60; is fine if $date starts at noon.

Re: Generate a report for every month for a year
by anonymized user 468275 (Curate) on Jun 25, 2017 at 07:53 UTC
    As horrible as the code is to a seasoned perl developer, the task appears to be to modify it rather than rewrite it and when modifying, we are usually supposed to maintain the existing style even though we might hate it. On the other hand, there is something slightly wrong with starting one month ago and ending today because if the start date is the 1st of the month then so is the end date and likewise if the end-date is the last day of a month then so is the start date. So I would check whether the report is duplicating data for the first or last day of the month before using it as a basis for generating a two year report - it certainly looks that way. What to do next - rewrite or modify depends on that although it might not be your decision which way to go.

    One world, one people

A reply falls below the community's threshold of quality. You may see it by logging in.