Calculating Date

I’m trying to get estimated delivery dates for various products but rather than working it out for each product it adds the previous delivery time onto the next product before working it out.

This is what I’ve got so far


public function getDeliveryDate() {
       $ndd = $this->getPhysicalOrderProducts();
        $arrive = $this->getPostDate();
        $arrive->add(new DateInterval('P'.$ndd[0]->getDeliveryTime().'D'));
        return $arrive;
      }

I want it get a product, add the DeliveryTime to PostDate then return that as $date I then want it to go on to the next product and do the same thing. At the moment what’s happening is it’s getting the date, adding the delivery time. Then with the next date it’s adding the previous delivery time to the result of $date from the previous product.

Is there anyway to get it to recalculate it from fresh for every product?

The problem is that objects are passed by reference, so when you do $arrive = $this->getPostDate(); then the variable $arrive is now pointing to the same DateTime object that is being used for the post date elsewhere in your object.

What you want to do is clone the original object, so that when you add the delivery time to the clone it doesn’t affect the original:


$arrive = clone $this->getPostDate();

It might be a good idea to do the cloning within the getPostDate() method instead, as this will prevent the original post date from being modified by accident by other code.

(Note that if you’re using PHP 5.5, you can use the DateTimeImmutable class)

Thank you for your help I tried to use clone and it worked better, it showed both dates as the same which is an improvement. What I want though is if I add 3 days to first product and 1 to the next it should add that to the original date. When I added clone it added 3 days to the original date for both products.

Also I’m running PHP version 5.4.24 if that helps.

I’m a little confused as to how it works. Could you share the code that loops and calls getDeliveryDate() for each product to build up the result?

The site is built using Silex so the only code that calls the getDeliveryDate() is in the twig file, but to do that I use this

{% for op in products %}
        <li>{{ entry.getDeliveryDate|date("d/m/Y") }}</li>
        {%  endfor %}

Also I don’t know if it helps but the code that get getPhysicalOrderProducts() is this:

public function getPhysicalOrderProducts()
  {
    $products = array();
    foreach($this->getOrderProducts() as $ndd) {
      if($ndd->getProduct()->isPostalable()) {
        $products[] = $ndd; 
      }
    }

    return $products;
  }

Thank you for helping me with this.

This seems a little odd, as you’re iterating over products but not actually doing anything with each item. You’re calling entry.getDeliveryDate() each time, but surely the return value will be the same each time, as it’s the same object?

Not always but often an order will have multiple products in it each with different delivery time. When somebody places an order I want the receipt to show the order along with the expected delivery date for each product. This is how I’m trying to do it.

Unfortunately because different products ship from different places the delivery time will be different for each product.

I used var_dump($arrive) to get the results and this is what I got:

object(DateTime)#149 (3) { [“arrive”]=> string(19) “2017-07-28 00:00:00” [“timezone_type”]=> int(3) [“timezone”]=> string(13) “Europe/London” }

It doesn’t really make any difference to me but I don’t know if that sheds any light on why it’s not working

What I mean is, in this code:


{% for op in products %}
        <li>{{ entry.getDeliveryDate|date("d/m/Y") }}</li>
{%  endfor %}

you’re looping all the products in your order, and for each iteration of the loop op is the current product. So shouldn’t it be calling op.getDeliveryDate? Currently, if there are 5 products in products then the loop will obviously run 5 times, but each time entry is still the same object. Or have I misunderstood how your code works?

No I think you’ve said makes perfect sense, I’m just not 100% sure how to do it

The ops in product is using this to get that:

public*function*getPhysicalOrderProducts()
**{
****$products*=*array();
****foreach($this->getOrderProducts()*as*$ops)*{
******if($ops->getProduct()->isPostalable())*{
********$products[]*=*$ops;*
******}
****}

****return*$products;
**}*

while the the

{% for op in products %}
        <li>{{ entry.getDeliveryDate|date("d/m/Y") }}</li>
{%  endfor %}

uses this to get the results


public*function*getDeliveryDate()*{ 
*******$ops*=*$this->getPhysicalOrderProducts(); 
********$arrive*=*$this->getPostDate(); 
********$arrive->add(new*DateInterval('P'.$ops[0]->getDeliveryTime().'D')); 
********return*$arrive; 
******}* 

I did try changing it to op.getDeliveryDate but that gave me this error:

(“Call to undefined method: getDeliveryDate.”)

OK. Could you show me the code where you set the values of entry and products for your view (I’d like to see where those values come from)?

What normally happens is when somebody places an order the results go though various functions to do things like push the order to Zendesk, to database etc.

The code that sets the values should be these though:

public function getDeliveryDate() {
       $ops = $this->getPhysicalOrderProducts();
        $date = clone $this->getPostDate();
        $date->add(new \\DateInterval('P'.$ops[0]->getDeliveryTime().'M'));
        return $date;
      }

and


  public function getPhysicalOrderProducts()
  {
    $products = array();
    foreach($this->getOrderProducts() as $op) {
      if($op->getProduct()->isPostable()) {
        $products[] = $op; 
      }
    }

    return $products;
  }

Although the actual file (a pdf which is generated from the page where the results are is this:

public function generateReceipt($app)
    {
	$products = $this->getPhysicalOrderProducts();
	if (count($products) > 0) {
      $filename = $this->getOrderReceiptFilename($app);
      if (!file_exists($filename)) {
        // Generate HTML
        $html =  $app['twig']->render('order/receipt.twig', array(
          'entry'           => $this,
          'products'        => $products,
          'content_height'  => intval( (8*100*count($products))/95 )+1,
          'domain'          => $app['config']['domain'],
        ));
        // Generate PDF
        $snappy = new Pdf($app['wkhtmltox_bin']);
        $snappy->setOption('orientation', 'landscape');
        $snappy->generateFromHtml(utf8_decode($html), $filename);
      }
    }
}

If it would be more helpful I could send you the files? Unfortunately it’s an intranet site so I can’t give you access to it

OK, so something like this might work.

In your controller action, add a new variable to the view:


$html =  $app['twig']->render('order/receipt.twig', array(
    'entry'           => $this,
    'products'        => $products,
    'delivery_dates'  => $this->getDeliveryDates(), // Add this line
    'content_height'  => intval( (8*100*count($products))/95 )+1,
    'domain'          => $app['config']['domain'],
 ));

add a new method to your order class:


public function getDeliveryDates() {
	$ops = $this->getPhysicalOrderProducts();
	$dates = array();

	foreach ($ops as $op) {
		$date = clone $this->getPostDate();
		$date->add(new \\DateInterval('P'.$op->getDeliveryTime().'M'));

		$dates[] = $date;
	}
	
	return $dates;
}

and then lastly, in receipt.twig:


{% for dd in delivery_dates %}
    <li>{{ dd|date("d/m/Y") }}</li>
{%  endfor %}

Thank you so much, I can’t thank you enough. That works perfectly now. :slight_smile:

Thank you