Impossible arithmetic

See this code:
<?php
// email_reminders.php
// overnight process to remind subscribers
include "db_connect.php";
$qry = "SELECT * from subscribers where subscribe_type = 'YR' or subscribe_type = '90'";
$res = mysqli_query($link, $qry);
$ns = mysqli_num_rows($res);
$thirty = 30 * 86400;
$fifteen = 15 * 86400;
$ten = 864000;
$five = 5 * 86400;
$two = 2 * 86400;
$one = 86400;
echo "mo = " . date("n") . ", day = " . date('j') . ", year = " . date("Y") . "<br>";
$today_secs = mktime (0, 0, 0, date("n"), date("j"), date("Y"));
if ($ns != 0) {
	for ($i = 0; $i < $ns; $i++) {
		$s = mysqli_fetch_array($res,MYSQLI_ASSOC);
		$exdt = $s['exp_date'];
		echo "exp date = " . $exdt . "<br>";
		echo "mo exp date = " . substr($exdt,5,2) . "<br>";
		echo "day exp date = " . substr($exdt,8,2) . "<br>";
		echo "yr exp date = " . substr($exdt,0,4) . "<br>";
		$expsec = mktime (0, 0, 0, substr($exdt,5,2), substr($exdt,8,2), substr($exdt,0,4));
		$remsec = $expsec - $today_secs;
		echo "remsecs for " , $s['uid'] . " = " . $remsec . "<br>";
		echo "thirty = " . $thirty . "<br>";
		$msg = "";
		switch($remsec) {
			case ($thirty):
				$msg = "30 days";
				break;
			case ($fifteen):
				$msg = "15 days";
				break;
			case ($ten):
				$msg = "100 days";
				break;
			case ($five):
				$msg = "30 days";
				break;
			case ($two):
				$msg = "2 days";
				break;
			case ($one):
				$msg = "1 day";
				break;
			default:
				break;
		}
		if ($msg != "") {
			// send email
			$qryu = "SELECT * from users where uid = " . $s['uid'];
			$resu = mysqli_query($link, $qryu);
			$u = mysqli_fetch_array($resu,MYSQLI_ASSOC);
			$link = "<a href='http://backflowtestreport.com/subscribe.php?uid=" . $s['uid'] . "'>Subscribe</a>";
			$headers  = 'MIME-Version: 1.0' . "\r\n";
			$headers .= 'Content-type: text/html; charset=iso-8859-1' . "\r\n";
			$to = $u['email'];
			$subj = "Backflow Test Report Subscription Expiration";
			$body = "Your subscription to the Backflow Test Report Web app will expire in " . $msg . "<br><br>";
			$body = $body . "To renew, click this link " . $link . "<br><br>";
			$body = $body . "Thank you for your interest.";
			echo "to: " . $to . "<br>";
			echo "body = " . $body . "<br>";
	//echo "subject = " . $subj . "<br>";
			//$mr = mail($to, $subj, $body, $headers);
		}
	}
}
?>

Open in new window


This code, run today 10/16/2016, produces this:

mo = 10, day = 16, year = 2016
exp date = 2016-11-14
mo exp date = 11
day exp date = 14
yr exp date = 2016
remsecs for 1 = 2509200
thirty = 2592000
exp date = 2016-11-15
mo exp date = 11
day exp date = 15
yr exp date = 2016
remsecs for 2 = 2595600
thirty = 2592000

This is impossible.

The remsecs for 1 is 2509200, which in days is 29.04166666666667, which is wrong.

The actual difference is EXACTLY 30 days.

What am I doing wrong?
Richard KortsAsked:
Who is Participating?
 
Kim WalkerWeb Programmer/TechnicianCommented:
Actually, remsecs for 1 should be 29 days: the 16th of October to the 14th of November is 29 days. The reason the calculation is not exactly 29 days is daylight savings time.

Take a look at this which deals in days instead of seconds and calculates relative to GMT based on whatever timezone you choose. I've chosen America/New_York for my base timezone.
<?php

// WHEN WE ARE DEBUGGING OUR CODE, WE WANT TO SEE ALL THE ERRORS!
error_reporting(E_ALL);
ini_set('display_errors','on');

date_default_timezone_set('America/New_York');

$gmt = new DateTimeZone('+0000');
$today_new = new DateTime(date("Y-n-j"),$gmt);
echo '$today_new = '.$today_new->format('r').'<br>';
$s = array('exp_date' => "2016-11-14", "uid" => "1");
$exdt_new = new DateTime(date($s['exp_date']),$gmt);
echo '$exdt_new = '.$exdt_new->format('r').'<br>';
$remsec_new = $exdt_new->diff($today_new);
echo "remdays for " . $s['uid'] . " = " . $remsec_new->d . " days<br>";

?>

Open in new window

Output:
$today_new = Mon, 17 Oct 2016 00:00:00 +0000
$exdt_new = Mon, 14 Nov 2016 00:00:00 +0000
remdays for 1 = 28 days

Open in new window

1
 
Kim WalkerWeb Programmer/TechnicianCommented:
What is the default time zone for the PHP server?
0
 
Kim WalkerWeb Programmer/TechnicianCommented:
Never mind that. All calculations should be relative to the same time zone regardless of which time zone that is. I'm still looking.
0
Introducing Cloud Class® training courses

Tech changes fast. You can learn faster. That’s why we’re bringing professional training courses to Experts Exchange. With a subscription, you can access all the Cloud Class® courses to expand your education, prep for certifications, and get top-notch instructions.

 
Ray PaseurCommented:
As a general rule, it's not a good idea to write your own date/time calculations.  The "home-grown" computations tend to fail over leap year (a rare, super-annual occurrence), and the boundaries of daylight-savings time (a common, twice-a-year occurrence).  I haven't got time to plow this field again right now, but my guess is that home-grown computations made at this time of year might incorrectly assume that all days have 86,400 seconds.  When the computations span the boundaries of daylight savings time, that assumption is untrue, leading to results that look a lot like what you're finding here.

Try this search.  It looks like we are less than a month away from the boundary.  Might be worth checking.
https://www.google.com/?q=Daylight+savings+time

This looks suspicious.  The difference here is 3,600, or exactly the number of seconds in an hour.  Like the hour adjustment for daylight savings time.
remsecs for 2 = 2595600
thirty = 2592000

It might also be a typo?  This looks incongruous:
remsecs for 1 = 2509200 /*** maybe this should be 2592000 ? ***/
thirty = 2592000

The principles for handling date/time values in PHP are described in these two articles.

Procedural
https://www.experts-exchange.com/articles/201/Handling-Date-and-Time-in-PHP-and-MySQL-Procedural-Version.html

Object-oriented
https://www.experts-exchange.com/articles/20920/Handling-Time-and-Date-in-PHP-and-MySQL-OOP-Version.html
0
 
Richard KortsAuthor Commented:
I see the problem is that a daylight savings time change occurs during that period.

Is there a way to use DateDiff in procedural (rather than object) notation? Does Date Diff take into consideration changes in DST?

I cannot understand the usage of DateDiff in object notation.

What I am trying to do is to a calculation that reminds subscribers that there subscription will expire in 30 days, 15 days, 10 days etc.

I think what I have will work if there is NOT a daylight savings time change in the middle. I think mine will work over "leap day" because I think php mktime is aware of that.
0
 
Ray PaseurCommented:
If you want procedural code, this article describes how it's done.
https://www.experts-exchange.com/articles/201/Handling-Date-and-Time-in-PHP-and-MySQL-Procedural-Version.html

Here's an example...
https://iconoun.com/demo/temp_rkorts.php
<?php // demo/temp_rkorts.php
/**
 * https://www.experts-exchange.com/questions/28976753/Impossible-arithmetic.html#a41846831
 *
 * REQUIRED READING!
 * https://www.experts-exchange.com/articles/201/Handling-Date-and-Time-in-PHP-and-MySQL-Procedural-Version.html
 */
error_reporting(E_ALL);
echo '<pre>';

// GET TODAY IN ISO-8601 FORMAT
$now = date('c');

// GET A FUTURE DATE IN ISO-8601 FORMAT
$days = '15 days';
$then = date('c', strtotime("$now + $days"));

// SHOW THE WORK PRODUCTS
echo PHP_EOL . $now;
echo PHP_EOL . $then;

Open in new window

0
 
Ray PaseurCommented:
For anyone coming across this question in the future, the take-away message is this: Do not write your own date calculations.  Do not use seconds when what you really want is days.  Instead use the PHP built-in functions to your greatest advantage.
1
 
Kim WalkerWeb Programmer/TechnicianCommented:
Same code with comments. (I really should have commented the original code. Sorry.)
<?php

// WHEN WE ARE DEBUGGING OUR CODE, WE WANT TO SEE ALL THE ERRORS!
error_reporting(E_ALL);
ini_set('display_errors','on');

// set the default time zone to match the time zone of dates in database
// reason: GMT (+0000) could be a day ahead of the viewer
date_default_timezone_set('America/New_York');

// create date/time zone object for GMT (+0000)
$gmt = new DateTimeZone('+0000');

// create a date/time object for today's date in the default time zone
// but use GMT to avoid daylight savings time
$today_new = new DateTime(date("Y-n-j"),$gmt);
echo '$today_new = '.$today_new->format('r').'<br>';

// mimic data from database query
$s = array('exp_date' => "2016-11-14", "uid" => "1");

// create another date/time object for the expiration date
// from the database using GMT to avoid daylight savings time
$exdt_new = new DateTime(date($s['exp_date']),$gmt);
echo '$exdt_new = '.$exdt_new->format('r').'<br>';

// calculate difference between today and expiration date
$remsec_new = $exdt_new->diff($today_new);
// NOTE: the variable $remsec_new contains an array of values for
// years, months, days, hours, minutes, etc.

// echo the number of days from the difference array using the "d" key
echo "remdays for " . $s['uid'] . " = " . $remsec_new->d . " days<br>";

?>

Open in new window

0
Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.

All Courses

From novice to tech pro — start learning today.