Go Premium for a chance to win a PS4. Enter to Win

x
?
Solved

Impossible arithmetic

Posted on 2016-10-16
8
Medium Priority
?
72 Views
Last Modified: 2016-10-17
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?
0
Comment
Question by:Richard Korts
  • 4
  • 3
8 Comments
 
LVL 22

Expert Comment

by:Kim Walker
ID: 41846059
What is the default time zone for the PHP server?
0
 
LVL 22

Expert Comment

by:Kim Walker
ID: 41846066
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
 
LVL 111

Expert Comment

by:Ray Paseur
ID: 41846085
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
Concerto's Cloud Advisory Services

Want to avoid the missteps to gaining all the benefits of the cloud? Learn more about the different assessment options from our Cloud Advisory team.

 
LVL 22

Accepted Solution

by:
Kim Walker earned 2000 total points
ID: 41846117
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
 

Author Comment

by:Richard Korts
ID: 41846831
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
 
LVL 111

Expert Comment

by:Ray Paseur
ID: 41846928
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
 
LVL 111

Expert Comment

by:Ray Paseur
ID: 41847151
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
 
LVL 22

Expert Comment

by:Kim Walker
ID: 41847496
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

Featured Post

Important Lessons on Recovering from Petya

In their most recent webinar, Skyport Systems explores ways to isolate and protect critical databases to keep the core of your company safe from harm.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Things That Drive Us Nuts Have you noticed the use of the reCaptcha feature at EE and other web sites?  It wants you to read and retype something that looks like this. Insanity!  It's not EE's fault - that's just the way reCaptcha works.  But it i…
Originally, this post was published on Monitis Blog, you can check it here . In business circles, we sometimes hear that today is the “age of the customer.” And so it is. Thanks to the enormous advances over the past few years in consumer techno…
Learn how to match and substitute tagged data using PHP regular expressions. Demonstrated on Windows 7, but also applies to other operating systems. Demonstrated technique applies to PHP (all versions) and Firefox, but very similar techniques will w…
The viewer will learn how to dynamically set the form action using jQuery.
Suggested Courses

824 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question