0

I am trying to show the hours and minutes remaining from the current time and 13:00/1pm.

We have two conditions:

  1. If the current hour is < 13:00 (1 pm), the delivery is 7 weekdays and the user is shown how much time is left until the 1 pm deadline (e.g. 1 hour 22 minutes at 11:38).

  2. If the current hour is > 13:00 (1 pm), the delivery is 8 weekdays and the user should be shown how much time is left until the next day at 1 pm (e.g. 23 hours 59 minutes at 13:01).

The second if-statement below shows nothing in the browser (html echo).

Prior to today reaching 13:00 (1 pm), the first if-statement worked and showed e.g. 2 hours remaining at 11:00 (11 am).

add_action( 'woocommerce_after_add_to_cart_form', 'delivery_info' );

function delivery_info() 
{
    date_default_timezone_set( 'Europe/London' );  

    // if 1 pm 
    if ( date('H') < 13 )  {
        $del_day = date( 'l jS F' , strtotime ( '7 weekdays' ) );
        
        $today = strtotime('today 13:00');
        $tomorrow = strtotime('tomorrow 13:00');
        $now = time();
        $timeLeft = ($now > $today ? $tomorrow : $today) - $now;
        $timeResult = (gmdate("H:i:s", $timeLeft));
        
        $hour = date('H', $timeLeft);
        $min = date('i', $timeLeft);
    } 
        
    // if after 1PM
    elseif ( date('H') > 13 )  {
        $del_day = date( 'l jS F' , strtotime ( '8 weekdays' ) );
        
        $today = strtotime('today 13:00');
        $tomorrow = strtotime('tomorrow 13:00');
        $now = time();
        $timeLeft = ($now > $today ? $tomorrow : $today) - $now;
        $timeResult = (gmdate("H:i:s", $timeLeft));
        
        $hour = date('H', $timeLeft);
        $min = date('i', $timeLeft);
    } 
     
    // HTML output
    echo <<<HTML
        <br>
        <div class="woocommerce-message est-delivery"
             style="white-space: pre-line;
                    text-align: left; 
                    display: inline-block;
                    ">
            <h4>Standard Delivery</h4>
            <br>
            <span style='color:#000000'>
                Order arrives <b>{$del_day}</b>,
            </span>
            <br/>
            <span style="color:#47bab5">
                order within {$hour}hrs {$min}mins
            </span>
        </div>
    HTML;
}

What have I done wrong? And is there a better way to achieve the above?

LoicTheAztec
  • 229,944
  • 23
  • 356
  • 399
richag
  • 133
  • 13
  • What have you tried to resolve the problem? Where are you stuck? Are you sure this problem is in any way related to WooCommerce? – Nico Haase Aug 11 '23 at 13:27
  • @richag Updated my answer to also resolve the distance calculation inaccuracies and provide an alternaitve. – Will B. Aug 11 '23 at 18:29

2 Answers2

3

Remove the elseif and use else instead, since there would be an hour of time (13:00:00 - 13:59:59) between < 13 and > 13.

Additionally, the only code that needs to be in the condition is $del_day =, both of the other blocks are identical, and can be moved outside the if/else condition.

function delivery_info() {
   date_default_timezone_set( 'Europe/London' );  
    
   if ( date('H') < 13 )  {
      // if before 1PM
      $del_time = strtotime( '7 weekdays' );
   } else {
      // if 1PM or later
      $del_time = strtotime( '8 weekdays' );
   } 

   $del_day = date( 'l jS F', $del_time);
   $today = strtotime('today 13:00');
   $tomorrow = strtotime('tomorrow 13:00');
   $now = time();
   $timeLeft = ($now >= $today ? $tomorrow : $today) - $now;

   $hour = date('H', $timeLeft);
   $min = date('i', $timeLeft);
 
   echo "<br><div class='woocommerce-message est-delivery' style='white-space: pre-line;text-align:left;display: inline-block;'><h4>Standard Delivery</h4><br><span style='color:#000000'> Order arrives <b>{$del_day}</b>,</span><br/><span style='color:#47bab5'>order within {$hour}hrs {$min}mins</span></div>";
}

Example: https://3v4l.org/7AEPS

Results at 2023-08-11 12:01:00 (should be 0 hours and 59 minutes)

Order Arrives Tuesday 22nd August order within 01 hrs and 59 mins

Results at 2023-08-11 13:01:00 (should be 23 hours and 59 minute)

Order Arrives Wednesday 23rd August order within 00 hrs and 59 mins

Unix Timestamp Inaccuracies

There are some additional issues with using a Unix timestamp and date() to display the hours and minutes, since the resulting value of date('H', $timeLeft) would not produce the expected results, and is reliant on the currently set time zone.

For example, the following would produce an Epoch date of 1970-01-02 00:59 not the expected 23 hours and 59 minutes.

Example: https://3v4l.org/RGsuq

$t = strtotime('2023-08-12 13:00:00') - strtotime('2023-08-11 13:01:00');
date('H:i', $t); // 00:59

Instead, to obtain the minutes and hours of the distance between the two dates, arithmetic would need to be used from the highest denominator and would still be subject to daylight savings, and the number of days in a month/year inaccuracies.

$days = intval($timeLeft / 86400);
$hour = (intval($timeLeft / 3600) % 24) + ($days * 24);
$min = intval($timeLeft / 60) % 60;

Example: https://3v4l.org/UeS8k

Results at 2023-08-11 12:01:00

Order Arrives Tuesday 22nd August order within 0 hrs and 59 mins

Results at 2023-08-11 13:01:00

Order Arrives Wednesday 23rd August order within 23 hrs and 59 mins

DateTimeInterface Alternative

To avoid the inaccuracies when using the Unix timestamp with arithmetic operations and make the code much more readable, a DateTimeInterface object can be used instead.

function delivery_info() {
    $dateNow = (new DateTimeImmutable())
        ->setTimezone(new DateTimeZone('Europe/London'));
    
    $dateCutOff = $dateNow->setTime(13, 0, 0); // cutoff time of 1PM today
    $dateDelivery = $dateNow->modify('+7 weekdays'); // standard delivery duration before cutoff
    $diffCutOff = $dateNow->diff($dateCutOff); // distance from now to 1PM cutoff time today
    
    if ($dateNow >= $dateCutOff)  {
        // if 1PM or later
        $dateDelivery = $dateDelivery->modify('+1 weekdays'); // add one weekday to the delivery date
        $diffCutOff = $dateNow->diff($dateCutOff->add(new DateInterval('P1D'))); // distance from now to 1PM cutoff time the next day
    }
    
    $del_day = $dateDelivery->format('l jS F');
    $hour = $diffCutOff->format('%h') + ($diffCutOff->format('%a') * 24);
    $min = $diffCutOff->format('%i');
 
    echo "<br><div class='woocommerce-message est-delivery' style='white-space: pre-line;text-align:left;display: inline-block;'><h4>Standard Delivery</h4><br><span style='color:#000000'> Order arrives <b>{$del_day}</b>,</span><br/><span style='color:#47bab5'>order within {$hour}hrs {$min}mins</span></div>";
}

Example: https://3v4l.org/Q0Mif

Results at 2023-08-11 12:01:00

Order Arrives Tuesday 22nd August order within 0 hrs and 59 mins

Results at 2023-08-11 13:01:00

Order Arrives Wednesday 23rd August order within 23 hrs and 59 mins
Will B.
  • 17,883
  • 4
  • 67
  • 69
1

Prior to today reaching 13:00 (1 pm), the first if-statement worked and showed e.g. 2 hours remaining at 11:00 (11 am).

What have I done wrong?

There is indeed a mistake that manifests exactly when you reach 13:00 at that minute. If we zoom out on the if clauses:

    // if 1 pm 
    if ( date('H') < 13 )  {
        # ...
    } 
        
    // if after 1PM
    elseif ( date('H') > 13 )  {
        # ...
    } 

We can see that the comments do not reflect the implementation. The first condition is correct according to your question "before 1 pm", but the comment is wrong because it tells "at 1 pm" (bold by me).

As Will B. already suggested, you can use an if/else instead to not fall into the one hour (!) gap you have here.

For the full 1 pm hour (13 o'clock), you would not do any calculation.

And is there a better way to achieve the above?

Certainly, there are always better ways, but the main point is that you get your code functional first. However, if I may give a couple of pointers:

Current Time is an Input Parameter

The current time is an input parameter to your routine, but you don't make it explicitly visible.

Better than relying on date() without the second parameter or time() (which is also implicitly the second parameter if you leave it out) at multiple places in between it changes, is to make the current date and time a DateTime object and work with it.

Same like with calls to date_default_timezone_set(), better have the timezone explicitly with the time value already, not from the runtime environment.

// Get the current day and time with a timezone
$currentDateTime = date_create_immutable("@$_SERVER[REQUEST_TIME]")
    ->setTimezone(new DateTimeZone('Europe/London'));

In the example PHP code, the variable $currentDateTime is set to the time of the request (similar to time(), but from the PHP SAPI, not the system/kernel) and then given the timezone you also have for display and calculation purposes.

Not only has using a variable (parameter) now the benefit you always rely to the same time exactly, it also offers you to better handle changes (e.g. distributing functionality over different functions and methods).

You also get the full interface of DateTime which includes range/distance calculation.

You can also be more clever like: When this is about a minute that can make a difference, why not add one minute before doing the checks? The browser will need to send the reply and the user will need to read it and this all will take time, and if the precision is minutes, submitting the request at 12:59:59 will likely not have the order 13:00 (1 pm) any longer. Cf. "Adding minutes to date time in PHP".

Also your main problem to format the distance in hours and minutes is possible, too, without re-inventing the wheel. Cf. "How to calculate the difference between two dates using PHP?"

hakre
  • 193,403
  • 52
  • 435
  • 836