简体   繁体   中英

Maturity date with PHP

Edit :

Maturity date, what is it ?

I'm not native english speaker. Sorry about that. I think the best we can do, is to define Maturity Date.

A maturity date is a date that indicates the deadline for the payment of an invoice. In BtoB, the maturity date indicates when the customer wants to pay us, defined during the contract it is usually a later date between 1 and 3 months after the publishing of the bill. It's a calculated data, and implemented in my application with the following code.

I have this code in my Invoice module I extracted it for you can tests :

<?php
class Config
{
    protected $amountDelayedDays;
    protected $paymentDay;
    protected $paymentCondition;

    public function getAmountDelayedDays()
    {
        return $this->amountDelayedDays;
    }

    public function getPaymentCondition()
    {
        return $this->paymentCondition;
    }

    public function getPaymentDay()
    {
        return $this->paymentDay;
    }


    public function setAmountDelayedDays($days)
    {
        $this->amountDelayedDays = $days;
        return $this;
    }

    public function setPaymentDay($days)
    {
        $this->paymentDay = $days;
        return $this;
    }

    public function setPaymentCondition($condition)
    {
        $this->paymentCondition = $days;
        return $this;
    }

}

class Test {
    /**
     * @param DateTime $dateInvoice
     * @param Config $config
     * @return DateTime
     */
    public function calcMaturityDate(\DateTime $dateInvoice, $config)
    {
        if ($config->getPaymentCondition() == 'delayed') {
            $dateMaturity = clone $dateInvoice;
            $startDay = $dateMaturity->format('j');

            $dateMaturity->modify("+{$config->getAmountDelayedDays()} days");
            $endDay = $dateMaturity->format('j');

            if ($startDay != $endDay && $endDay < 30) {
                $dateMaturity->modify('last day of last month');
            } else {
                $dateMaturity->modify('last day of this month');
            }
            if ($config->getPaymentDay() != 0) {
                $dateMaturity->modify('+' . $config->getPaymentDay() . 'days');
            }
            return $dateMaturity;
        } else {
            return $dateInvoice;
        }
    }
}

$config = new Config;
$config->setPaymentDay(15);
$config->setAmountDelayedDays(60);
$config->setPaymentCondition('delayed');

$test = new Test;
$date = $test->calcMaturityDate(new DateTime('2015-01-31'), $config);

var_dump($date);

?>

I have to calculate a maturity date from the date of invoice.

If my invoice is dated at 2014-11-30 and my client is configured to be charged 2 month later & on the 15'(=60days + 15 ), I have to produce a maturity date like this :

'2015-02-15'

For doing this I have to variables in my Config class :

$config->getAmountDelayedDays() and $config->getPaymentDay()

My code is not perfect to handle all problems. February changing years, custom value of days... Jumped month...

I think the problem is in

if ($startDay != $endDay && $endDay < 30) {
                $dateMaturity->modify('last day of last month');
            } else {
                $dateMaturity->modify('last day of this month');
            }

It's too simple to handle all cases, maybe it's wrong. I can't make my mind clear about this...

Tests case

I have units test testing this function I'm not passing

/**
     * Tests MaturityDate
     * 
     */
    public function testCanGiveCorrectMaturityDate()
    {
        $config = $this->parser->setConfig();
        $config->setAmountDelayedDays(60);
        $config->setPaymentDay(15);
        $config->setPaymentCondition('delayed');

        // From February Ok ?
        $dateInvoice = new \Datetime('2015-02-28');
        $maturityDate = $this->mock->calcMaturityDate($dateInvoice , $config);
        $this->assertEquals('15-05-2015', $maturityDate->format('d-m-Y'));

        // From February ok ?
        $dateInvoice = new \Datetime('2015-02-28');
        $config->setAmountDelayedDays(30);
        $config->setPaymentDay(0);
        $maturityDate = $this->mock->calcMaturityDate($dateInvoice , $config);
        $this->assertEquals('31-03-2015', $maturityDate->format('d-m-Y'));

        // New years and pass february
        $config->setAmountDelayedDays(90);
        $config->setPaymentDay(15);
        $dateInvoice = new \Datetime('2014-11-30');
        $maturityDate = $this->mock->calcMaturityDate($dateInvoice , $config);
        $this->assertEquals('15-03-2015', $maturityDate->format('d-m-Y'));

        // No delayed
        $config->setPaymentCondition('standard');
        $dateInvoice = new \Datetime('2014-11-30');
        $maturityDate = $this->mock->calcMaturityDate($dateInvoice , $config);
        $this->assertEquals('30-11-2014', $maturityDate->format('d-m-Y'));
    }

If I get it right, you expect the paymentDay to be always 15th day of month of maturity, or next month, when date of maturity is later than 15th.

With this assumption your class would be:

public function calcMaturityDate(\DateTime $dateInvoice, $config)
{
        $dateMaturity = clone $dateInvoice;
        $dateMaturity->add(new \DateInterval("P{$config->getAmountDelayedDays()}D"));

        $payDay = $config->getPaymentDay();

        // patch 0 payDay to last day of month
        if (0 == $payDay) {
            $payDay = $dateMaturity->format('t');
        }

        if ($dateMaturity->format('j') > $payDay) {                
            $dateMaturity->modify('next month');                
        }
        $dateMaturity->setDate(
            $dateMaturity->format('Y'), 
            $dateMaturity->format('m'), 
            $payDay
        );

        return $dateMaturity;
}

Few notes:

  • the class name is a bit misleading, as it returns payment date, not maturity date;
  • maturity interval is 60 days, not 2 months;

For the last point I would recommend to refactor your config class to return \\DateInterval for maturity interval instead of integer, so you will have flexibility to define 2 month interval as "P2M" or 60 days as "P60D" depending on business requirements:
If instead of

public function getAmountDelayedDays()
{
    return $this->amountDelayedDays;
}

you have

/**
* @return \DateInterval
*/ 
public function getDelayInterval()
{
    return $this->delayInterval;
}

the ugly line from above

$dateMaturity->add(new \DateInterval("P{$config->getAmountDelayedDays()}D"));

turns to elegant

$dateMaturity->add($config->getDelayInterval());

In these cases a always convert the "friendly date" to a timestamp; do the math; then convert back to a friendly date. Something like this:

$invoice_date = "2014-11-30";
$ts_invoice_date = time($invoice_date);
$ts_maturity_month = $ts_invoice_date + (60*24*60*60); //60 days*24hrs*60mins*60secs
$maturity_date = date("Y-m-15 00:00:00", $ts_maturity_month);

From documentation: Date and Time

date('Ym-d', strtotime('+1 week'))

strtotime documentation

Your question:

I have to calculate a maturity date from the date of invoice.

You can use date('Ym-d', strtotime('+60 days')) and that will handle odd/even number of days in month, as well as fix to hours (if you need that precision)..

A date 2014-11-30 with date('Ym-d', strtotime('+ $amountDelayedDays days')) yelds the correct date.

I think i get what your trying to do and I have had to do something similar. You basically want something to determine if the current day of the month is less than 15 and if it is it remains the 15th of this month else if its say the 20th of the same month then date is 15th of next month?. I think this is how I am reading this. If this is the case then you need to compare the day of the invoice to the 15th and if less than its the same month or if its greater than then its the next month. It then gets interesting as are you allowing a leverage period of say 5 days before the due date. A factor on what I was working on I used. This could be the case if the date of the invoice was the 14th and you set payment date on the 15th. With the logic the client would only have a day to pay the invoice. I think we went with the 15th minus 5 days.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM