How to mock date in php unit test? - php

I'm new to php unit testing. How do I mock the date in the function below. Currently it is getting the current date. But I want to change the date in the mock to the first day of a month.
function changeStartEndDate() {
if (date('j', strtotime("now")) === '1') {
$this->startDate = date("Y-n-j", strtotime("first day of previous month"));
$this->endDate = date("Y-n-j", strtotime("last day of previous month")) . ')';
} else {
$this->startDate = date("Y-n-j", strtotime(date("Y-m-01")));
$this->endDate = date("Y-n-j", strtotime("yesterday"));
}
}
I've tried doing this but its not working.
public function testServicesChangeStartEndDate() {
$mock = $this->getMockBuilder('CoreFunctions')
->setMethods(array('changeStartEndDate'))
->getMock();
$mock->method('changeStartEndDate')
->with(date("Y-n-j", strtotime(date("Y-m-01"))));
$this->assertSame(
'1',
$this->core->changeStartEndDate()
);
}

Unit testing works best by avoiding side effects. Both date and strtotime are depending on an external state defined on your host system, namely the current time.
One way to deal with that is to make current time an injectable property allowing you to "freeze" it or to set it to a specific value.
If you look at at the definition to strtotime it allows setting current time:
strtotime ( string $time [, int $now = time() ] ) : int
Same with date:
date ( string $format [, int $timestamp = time() ] ) : string
So always inject that value from your function to decouple the results of your code from your host's state.
function changeStartEndDate($now) {
if (date('j', strtotime("now", $now), $now) === '1') {
...
$this->startDate = date("Y-n-j", strtotime(date("Y-m-01", $now), $now));
$this->endDate = date("Y-n-j", strtotime("yesterday", $now), $now);
}
Is your function part of a class? I would then make the $now part of the constructor, and have it default to time(). In your test cases, you can always inject a fixed number, and it will always return the same output.
class MyClassDealingWithTime {
private $now;
public function __construct($now = time()) {
$this->now = $now;
}
private customDate($format) {
return date($format, $this->now);
}
private customStringToTime($timeSring) {
return strtotime($timeStrimg, $this->now);
}
}
In your test cases you then set $now to the value you need, e.g. via
$firstDayOfAMonth = (new DateTime('2017-06-01'))->getTimestamp();
$testInstance = new MyClassDealingWithTime(firstDayOfAMonth);
$actual = $testInstance->publicMethodYouWantTotest();
...

Disclaimer: I wrote the library mentioned on this answer.
I'm adding an answer to provide an alternative way that works with zero modifications to your code, and no need to inject the current time.
If you can afford to install the php uopz extension, then you can use https://github.com/slope-it/clock-mock.
You can then use ClockMock::freeze and ClockMock::reset to "move" the internal php clock to a specific point in time during your tests.

Related

PHP Wrong time after setting timezone

I stumbled onto a really weird problem:
For my task I wrote a method that would give me the current date in a specific format,
another method would then extract the date so I could compare it with dates in the database.
public static function getCurrentDate()
{
return date("Y-m-d H:i:s a");
}
public static function extractDate($date)
{
return date("Y-m-d", strtotime($date));
}
Because I've noticed that the time wasn't right, I've set the default timezone at the beginning of the script like this:
date_default_timezone_set('Europe/Berlin');
Running this script, I've noticed that it gives the wrong output, maybe you could already help me here.
$currentDate = getCurrentDate();
echo $currentDate."\n";
$extractedDate = extractDate($currentDate );
echo $extractedDate."\n";
Output:
2020-08-25 21:58:13 pm
1970-01-01
Then I tried it in another way with DateTime, where it still produced the wrong output
public static function extractDate($date)
{
$timezone = new DateTimeZone('Europe/Berlin');
$dt = DateTime::createFromFormat("Y-m-d H:i:s a", $date, $timezone);
return $dt->format("Y-m-d");
}
Output:
2020-08-25 21:58:13 pm
2020-08-26
I would understand if there was an error so it would lead to the Unix epoch again, but this time it somehow added a day.
It would be nice if you knew where my error at the first approach was and I'm also really interested to hear why PHP behaves like that in the second approach
Change:
public static function getCurrentDate()
{
return date("Y-m-d H:i:s a");
}
to
public static function getCurrentDate()
{
return date("Y-m-d H:i:s");
}
and you are all set.
In 24h time you do not need the a or am/pm, which makes the datetime unrecognizable via strtotime, it cannot convert it , and it returns false, thus the invalid date 1970-01-01which is qeuivalent tounixtime = 0`

Is there a better way to get the days since 1.1.1970

I need a function which returns the number of days since 1.1.1970 but depending on the timezone.
I wrote the following function but was wondering if there is a better way to do it (some better way to determinate the offset from gmt would be a good thing).
function getDaysSinceUnix($time = null){
if($time === null){$time = time();}
$day = 24*60*60;
$offset = intval(substr(date('O'),0,-2))*60*60;
return intval(floor(($time+$offset)/$day));
}
If not, is there something you would add that could give this function more stability ?
Using DateTime:
function getDaysSinceUnix( $time = 0, $timeZone = Null )
{
$time = "#$time";
$tz = new DateTimeZone( $timeZone ?: date_default_timezone_get() );
return date_create( $time )->setTimeZone( $tz )->diff( date_create( '1970-01-01', $tz ) )->days;
}
If no Time Zone is passed, current Time Zone is used.
The Carbon library is pretty much the gold standard for PHP date/time work.
Carbon\Carbon::createFromTimestamp(0)->diffInDays();

Converting UTC to a different time zone in php

I'm using the below method to convert the UTC time to an other time zone. But the below method seems to returning back the UTC time. Will any of you be kind enough to point out whats wrong in the method I'm using?
static function formatDateMerchantTimeZone($t, $tz) {
if (isset($t)) {
return date('Y-m-d H:i:s', strtotime($t , $tz));
} else {
return null;
}
}
$t is the datetime I pass
$tz is the time zone such as America/Los_Angeles
It surprises me that many people are unaware of, or do not use the DateTime classes. They make tasks like this almost trivial.
I have assumed that the date string you pass to the function is in the UTC timezone.
function formatDateMerchantTimeZone($t, $tz)
{
$date = new \DateTime($t, new \DateTimeZone('UTC'));
$date->setTimezone(new \DateTimeZone($tz));
return $date->format('Y-m-d H:i:s');
}
See it working
Strtotime converts a timestamp in string format to a valid date time like '09-29-2013 07:00:00' as second parameter, it does not convert a timezone to a time. php has numerous functions for timezones such as timezone_offset that does calculate the difference between two timezones. take a look in the documentation for more info:
http://php.net/manual/en/function.timezone-offset-get.php
static function formatDateMerchantTimeZone($t, $tz) {
if (isset($t)) {
date_default_timezone_set($tz);
return date('Y-m-d H:i:s', strtotime($t));
} else {
return null;
}
}
From php.net first comment.
To avoid frustrating confusion I recommend always calling
date_default_timezone_set('UTC') before using strtotime().
Because the UNIX Epoch is always in UTC; you will most likely output the wrong time if you do not do this.
try this:
<?php
/** Returns the offset from the origin timezone to the remote timezone, in seconds.
* #param $remote_tz;
* #param $origin_tz; If null the servers current timezone is used as the origin.
* #return int;
*/
function get_timezone_offset($remote_tz, $origin_tz = null) {
if($origin_tz === null) {
if(!is_string($origin_tz = date_default_timezone_get())) {
return false; // A UTC timestamp was returned -- bail out!
}
}
$origin_dtz = new DateTimeZone($origin_tz);
$remote_dtz = new DateTimeZone($remote_tz);
$origin_dt = new DateTime("now", $origin_dtz);
$remote_dt = new DateTime("now", $remote_dtz);
$offset = $origin_dtz->getOffset($origin_dt) - $remote_dtz->getOffset($remote_dt);
return $offset;
}
?>
Examples:
<?php
// This will return 10800 (3 hours) ...
$offset = get_timezone_offset('America/Los_Angeles','America/New_York');
// or, if your server time is already set to 'America/New_York'...
$offset = get_timezone_offset('America/Los_Angeles');
// You can then take $offset and adjust your timestamp.
$offset_time = time() + $offset;
?>

Easier way for PHP date calculations?

I'm trying to determine when a user has last logged on. My current method works but is there an easier way of determining this so that I could determine last X hours etc?
This is what I currently use:
$last_login_di = getdate($last_login);
$now = time();
$now_di = getdate($now);
$today = mktime(0,0,0,$now_di['mon'],$now_di['mday'], $now_di['year']);
if ($last_login > $today) {
return 'Online Today';
}
$yesterday = $now-86400;
$yesterday_di = getdate($yesterday);
$yesterday = mktime(0,0,0,$yesterday_di['mon'],$yesterday_di['mday'], $yesterday_di['year']);
if ($last_login > $yesterday) {
return 'Online Yesterday';
}
if (($now - $last_login < 604800) ) {
return 'Online This Week';
}
....
Try strtotime() (see relative formats it accepts) or better yet, the DateTime, DateInterval classes.
For example, the $yesterday variable creation is prone errors near datetime savings. strtotime() handles this properly with:
$yesterday = strtotime('-1 day');
While the $last_login check can be written like:
if (strtotime('-1 week') < $last_login) {
// ...
}
If you need to support different timezones you probably better of with the DateTime objects though.
Have a look at the DateTime and related classes DateTime Book on php.net. The DateInterval class may be of particular use to you.
How do you get the date? Using MySQL? Use UNIX_TIMESTAMP for dates, eg SELECT UNIX_TIMESTAMP(last_login) AS last_login_timestamp FROM ... Then you can better calculate in PHP (using date_diff)
I think your code's fine. But the $yesterday var is wrong.
It should be:
$yesterday = $today - 86400;
In your code $yesterday means $a_day_ago.
The same for the last week.
You should heavily use the date objects built-in with PHP.
$now = new DateTime();
$yesterday = new DateTime('yesterday');
$lastWeek = new DateTime('last week');
Now you are able to to any comparison logic you want, using the basic comparison operators:
if ($last_login > $now) {
...
} else if ($last_login > $yesterday) {
...
} else if ($last_login > $lastWeek) {
...
} else {
...
}
If you choose not to use the objects, try to avoid the time() function. That makes unit testing impossible. Tests should never depend on environment.
Use $_SERVER['REQUEST_TIME'] instead so you can mock it later.

Bug with Zend_Date calculating subtraction two date

I write this function:
public function calcDifferentDate($dateStart, $dateEnd = false, $output = Zend_Date::DAY)
{
$dateEnd = $dateEnd ? $dateEnd : Zend_Date::now()->toString('YYYY-MM-dd');
$dateStartZD = new Zend_Date($dateStart, 'YYYY-MM-dd');
$dateEndZD = new Zend_Date($dateEnd, 'YYYY-MM-dd');
return $dateEndZD->sub($dateStartZD)->toString($output);
}
If call this:
echo calcDifferentDate('2011-11-10');
and today is: '2011-11-14'
the output returned is 05 and not 04
why? where am I doing wrong?
P.S. I use ZF 1.11.11 version
I found the solution
this work right! :D
public function calcDaysDiffDate($dateStart, $dateEnd = '')
{
$dateEnd = !empty($dateEnd) ? $dateEnd : Zend_Date::now()->toString('YYYY-MM-dd');
$dateStartZD = new Zend_Date($dateStart, 'YYYY-MM-dd');
$dateEndZD = new Zend_Date($dateEnd, 'YYYY-MM-dd');
$dateStartZD->sub($dateEndZD);
return $dateStartZD->getTimestamp() / (60 * 60 * 24);
}
Try returning this instead:
$newDate = new Zend_Date($dateEndZD->sub($dateStartZD), 'YYYY-MM-dd');
return $newDate->get($output);
The calculations are incorrect, I will try to get to that later. But for now, you'll need your logic to be similar to that, because like I said in my comment, your method was resulting in a fatal error due to the fact that your date subtraction was returning an integer instead of a Zend_Date object from which to call toString().
Edit
Sorry about my presumptuous, not well-thought-out previous answer. After more careful testing I believe I found your issue. The sub() function accepts an optional second param $part which is the part of the date will be returned from the resulting date subtraction. No need to call a toString() now even if you could.
So without further adieu, here it is with the fixed return statement:
public function calcDifferentDate($dateStart, $dateEnd = false, $output = Zend_Date::DAY)
{
$dateEnd = $dateEnd ? $dateEnd : Zend_Date::now()->toString('YYYY-MM-dd');
$dateStartZD = new Zend_Date($dateStart, 'YYYY-MM-dd');
$dateEndZD = new Zend_Date($dateEnd, 'YYYY-MM-dd');
return $dateEndZD->sub($dateStartZD, $output); // <-- fixed
}
Second Edit
After chatting with OP, it appears that my solution will not work for ZF 1.11.x due to the differences in the Zend_Date::sub() method.
The accepted answer for this question: How to compare the date parts of two Zend_Date objects? recommends using DateTime instead of Zend_Date in the following way (I've modified the code a bit to suit your needs):
$date1 = new DateTime('2011-11-14');
$date2 = new DateTime('2011-11-10');
$diffDays = $date1->diff($date2)->days;
I've tried it and it seems to return the correct result. It could be a good alternative to Zend_Date, if you are not absolutely required to use it.
Hope that helps,
I find solution:
public function calcDaysDiffDate($dateStart, $dateEnd = '')
{
$dateEnd = !empty($dateEnd) ? $dateEnd : Zend_Date::now()->toString('YYYY-MM-dd');
$dateStartZD = new Zend_Date($dateStart, 'YYYY-MM-dd');
$dateEndZD = new Zend_Date($dateEnd, 'YYYY-MM-dd');
$dateStartZD->sub($dateEndZD);
return $dateStartZD->getTimestamp() / (60 * 60 * 24);
}

Categories