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

I am making a subscription software billing reminder. We have software that we have like 400 users of, and we bill them on the anniversary of their signing up. We need to just remind them the day their bill is due, the same day every month.
the problem is that just changing 01/31/07 to 02/31/07 is just that, there is no 02/31/07.
Does perl have a way to just add one month to a date and be calander aware?

If so, how does it know on 02/28/07 when the cron job runs and sees that date, that next month it needs to set it not at 03/28/07, but to 03/31/07?

Thank you very much for your insight.

RJEnterprises

Replies are listed 'Best First'.
Re: Adding to dates
by GrandFather (Saint) on Aug 22, 2006 at 23:53 UTC

    There are a slew of modules available to do date calculations. The Swiss army chainsaw date module is Date::Manip. Date::Calc however provides Add_Delta_YM which "can be used to add a year and/or month offset to a given date" and "this function does no ``wrapping'' into the next month ... it simply truncates the day to the last possible day of the resulting month".

    Store the start date and add n months to get the next update date to avoid the truncation issue.


    DWIM is Perl's answer to Gödel
Re: Adding to dates
by explorer (Chaplain) on Aug 23, 2006 at 02:08 UTC

    Solved with Date::Manip, for example:

    #!/usr/bin/perl -l use Date::Manip; my $date = ParseDate("01/31/07"); my @dates = ParseRecur("0:1:0*-1:0:0:0",$date,$date,"01/31/08"); foreach ( @dates ) { print UnixDate $_,"%D"; }
    Output:
    01/31/07
    02/28/07
    03/31/07
    04/30/07
    05/31/07
    06/30/07
    07/31/07
    08/31/07
    09/30/07
    10/31/07
    11/30/07
    12/31/07
Re: Adding to dates
by explorer (Chaplain) on Aug 23, 2006 at 02:29 UTC

    And this is a DateTime solution:

    #!/usr/bin/perl -l use DateTime; use DateTime::Duration; my $date = DateTime->new( year => 2006, month => 12, day => 31, locale => 'en_US', ); my $duration = DateTime::Duration->new( months => 1, end_of_month => 'preserve', ); for ( 1..12 ) { $date->add( $duration ); # One month more print $date->mdy('/'); # And show it }
    Updated: With Ponky advice, rest:
    use DateTime; my $date = DateTime->last_day_of_month( year => 2006, month => 12 ); print $date->add( months => 1, end_of_month => 'preserve' )->mdy('/') +foreach 1..12;
      A little improvement is to include the duration definition in the add call:
      $date->add( months => 1, end_of_month => 'preserve' );
      Then you don't need the use DateTime::Duration either.
Re: Adding to dates
by jdtoronto (Prior) on Aug 23, 2006 at 01:58 UTC
    Over the years I have used Date::Manip as GrandFather has already suggested, currently much of my code uses Time::Piece. Others may suggest DateTime, all of these work well and each has its own advantages. If you are doing lots of commercial type calculations then Date::Calc may be what you need. Date::Manip is by far the heavy lifting champion offering far more than any of the others. But it is BIG, and by the authors own admission, slow. In fact he suggests you probably shouldn't be using it, but yu need to read the doc's just to be certain.

    jdtoronto

      Thank you! That worked. Here is what I did:
      # $_bill is set as a fetchrow_hashref from the mysql db. ($_billUser_month,$_billUser_day,$_billUser_Year) = split /\//, $_bill +->{orig_day}, 3; $_add_Months = $_bill->{months_active} + 1; ($_billUser_Year,$_billUser_month,$_billUser_day) = Add_Delta_YM($ +_billUser_Year,$_billUser_month,$_billUser_day, 0,$_add_Months); ($_billUser_Year,$_billUser_month,$_billUser_day) = Add_Delta_Days($_b +illUser_Year,$_billUser_month,$_billUser_day, -5); $_nextBillingDate = sprintf("%02d/%02d/%02d",$_billUser_month,$_billUs +er_day,$_billUser_Year); $dbh->do(qq{UPDATE `subscription_users` SET `day` = ?, months_active = + months_active + 1 WHERE `id` = ?}, undef, $_nextBillingDate, $_billU +ser_id);
      That way I am always basing the billing day off the original month. That way the billing DAY don't ever change, like going from 31 one month, to 30 then it would stay 30 because the next date is based on 30... Anyhow, thank you so much!!


      Thanks again!
      RJEnterprises