Using DateTime to produce different date strings in PHP - php

There's something ugly about this way of producing 2 date strings a year apart
$obj_now = new DateTime();
$obj_year_ago = new DateTime();
$obj_year_ago->modify( '- 1 year');
$string_now = $obj_now->format('Y-m-d') ;
$string_year_ago = $obj_year_ago->format('Y-m-d') ;
It works but seems a clunky to me (and therefore probably inefficient).
To frame the question; if I have to produce dates based on a reference date (these are used in different ways in different forms). Do I need one object to each date or can I perform calculations to produce my strings from one DateTime object?
The difficulty I see is if I use $obj_now->modify()` it's no longer 'now'

Modify changes the DateTime object, but not the format method. Here's how you can do the following:
$curDate = new DateTime('Now');
$dates = [];
for($i=0;$i<3;$i++){
$dates[] = $curDate->format('Y-m-d');
$curDate->modify('-1 year');
}
var_dump($dates);
//array(3) { [0]=> string(10) "2021-11-04" [1]=> string(10) "2020-11-04" [2]=> string(10) "2019-11-04" }
If you don't want the modify to change the original object, you can use DateTimeImmutable. To get the same result as above, the code for DateTimeImmutable is like this:
$date = new DateTimeImmutable('Now');
$dates = [];
for($i=0;$i<3;$i++){
$curDate = $date->modify('-'.$i.' year');
$dates[] = $curDate->format('Y-m-d');
}
var_dump($dates);
For more information see DateTimeImmutable vs DateTime

Obviously, after modify() the datetime has changed and it's no longer 'now'.
But you can do your job using just one object simply changing your instructions order:
$obj = new DateTime();
$string_now = $obj->format('Y-m-d');
$obj->modify('- 1 year');
$string_year_ago = $obj->format('Y-m-d');

Edit: As mentioned by the user IMSoP (Thank you!) the date object parameter gets modified. Therefore I changed the function to use a copy of the object to prevent this.
I would prefere to use a function for it.
First of all you can, but not have to declare an date object. Second, the object you declared before the function calls doesn't get modified only to get the information you need.
<?php
/**
* #param DateTime $dt_date
* #param String $s_format
* #param String $s_modifier
* #return String
*/
function dateGetFormatedStringModified($dt_date, $s_format, $s_modifier = ''){
$dt_temp = clone $dt_date;
if(!empty($s_modifier)){
$dt_temp->modify($s_modifier);
}
return $dt_temp->format($s_format);
}
$string_year_ago = dateGetFormatedStringModified(new DateTime(), 'Y-m-d', '- 1 year');
$string_now = dateGetFormatedStringModified(new DateTime(), 'Y-m-d');
echo $string_now; // 2021-11-04
echo $string_year_ago; // 2020-11-04
?>
Different approach would be DateTimeImmutable. This prevents the date getting changed if modify is used.
$date = new DateTimeImmutable();
$date_last_year = $date->modify('-1 year');
echo $date->format('Y-m-d');
echo $date_last_year->format('Y-m-d');
You also can combine modify with format within one line
$date = new DateTimeImmutable();
echo $date->format('Y-m-d');
echo $date->modify('-1 year')->format('Y-m-d');

Related

How to correctly add a date and a time (string) in PHP?

What is the "cleanest" way to add a date and a time string in PHP?
Albeit having read that DateTime::add expects a DateInterval, I tried
$date = new \DateTime('17.03.2016');
$time = new \DateTime('20:20');
$result = $date->add($time);
Which was no good and returned nothing to $result.
To make a DateInterval from '20:20', I only found very complex solutions...
Maybe I should use timestamps?
$date = strtotime($datestring);
$timeObj = new \DateTime($timestring);
// quirk to only get time in seconds from string date
$time = $timeObj->format('H') * 3600 + $timeObj->format('i') * 60 + $timeObj->format('s');
$datetime = $date+$time;
$result = new \DateTime;
$result->setTimestamp($datetime);
In my case, this returns the desired result, with the correct timezone offset. But what do you think, is this robust? Is there a better way?
If you want to add 20 hours and 20 minutes to a DateTime:
$date = new \DateTime('17.03.2016');
$date->add($new \DateInterval('PT20H20M'));
You do not need to get the result of add(), calling add() on a DateTime object will change it. The return value of add() is the DateTime object itself so you can chain methods.
See DateInterval::__construct to see how to set the intervals.
DateTime (and DateTimeImmutable) has a modify method which you could leverage to modify the time by adding 20 hours and 20 minutes.
Updated
I've included examples for both DateTime and DateTimeImmutable as per the comment made, you don't need to assign the outcome of modify to a variable because it mutates the original object. Whereas DateTimeImmutable creates a new instance and doesn't mutate the original object.
DateTime
<?php
$start = new DateTimeImmutable('2018-10-23 00:00:00');
echo $start->modify('+20 hours +20 minutes')->format('Y-m-d H:i:s');
// 2018-10-23 20:20:00
Using DateTime: https://3v4l.org/6eon8
DateTimeImmutable
<?php
$start = new DateTimeImmutable('2018-10-23 00:00:00');
$datetime = $start->modify('+20 hours +20 minutes');
var_dump($start->format('Y-m-d H:i:s'));
var_dump($datetime->format('Y-m-d H:i:s'));
Output
string(19) "2018-10-23 00:00:00"
string(19) "2018-10-23 20:20:00"
Using DateTimeImmutable: https://3v4l.org/oRehh

How to add on to formatted date in PHP

this is what I have got:
$_SESSION["chosen_exam_start_time"] = new DateTime();
$schedule_date = $_SESSION["chosen_exam_start_time"];
$schedule_date->setTimeZone(new DateTimeZone('Europe/London'));
$triggerOn = $schedule_date->format('Y-m-d H:i:s');
so I have a date passed within the session called "chosen_exam_start_time".
Now what I am struggling to do is add on $triggerOn.
So currently if I do
die(var_dump($triggerOn));
I get :
string(19) "2016-03-28 17:17:57"
What I want to do is if I have another variable which tells me a value like 10, how would I be able to add that value on to the $triggerOn. So that it will be:
string(19) "2016-03-28 17:27:57"
What I have done up to now is :
$_SESSION["chosen_exam_start_time"] = new DateTime();
$schedule_date = $_SESSION["chosen_exam_start_time"];
$schedule_date->setTimeZone(new DateTimeZone('Europe/London'));
$triggerOn = $schedule_date->format('Y-m-d H:i:s');
$value = 10;
$triggerOn->modify((int) $value.' minute');
die(var_dump($triggerOn));
However this die is not printing anything out now
I doesn't make sense to make any sort of calculation using strings. Just imagine you had this:
$price = 'Thirty five point nine';
$discount = 'Twenty percent';
... and you wanted to apply the discount ;-)
If your current architecture makes it impossible to keep the original DateTime object (I don't know, I don't have enough information for that) you'd better create a new one:
<?php
$triggerOn = '2016-03-28 17:17:57';
$minutes_to_add = 10;
$triggerOnDateTime = new DateTime($triggerOn, new DateTimeZone('Europe/London'));
$triggerOnDateTime->modify( sprintf('%d minutes', $minutes_to_add) );
$triggerOn = $triggerOnDateTime->format('Y-m-d H:i:s');
You are looking for the modify function on a datetime object.
$trigger->modify((int) $value.' minute');
Note I just pulled this from the Carbon Library, which is a very useful wrapper for DateTime giving you a good bit of syntactic sugar such as:
$trigger->addMinutes(10);

Create new datetime object each time

I have created different vars using date_format from one datetime object $date_obj.
$date_obj = date_create($date);
$year = date_format($date_obj, 'Y');
$month = date_format($date_obj, 'm');
$day = date_format($date_obj, 'd');
However I have read (lost source) that this is bad practice? Instead I should create a new datetime object each time because the original object isn't referenced, but directly manipulated.
$date_obj_1 = date_create($date);
$year = date_format($date_obj_1, 'Y');
$date_obj_2 = date_create($date);
$month = date_format($date_obj_2, 'm');
$date_obj_3 = date_create($date);
$day = date_format($date_obj_3, 'd');
Is this true?
The DateTime object is an object, and therefore is passed by reference.
In your example above this won't matter, because you only format a date, you do not manipulate it. However, if you use a DateTime object as an argument in a function and inside this function you manipulate the object, your changes will be visible outside of that function:
function addDays($date,$days){
$date->add(new DateInterval($days.' days'));
}
$date_obj_1 = date_create($date);
$formatedDate1 = date_format($date_obj_1, 'Y-m-d');
addDays($date_obj_1,10);
$formatedDate2 = date_format($date_obj_1, 'Y-m-d');
In the above example $formatedDate1 is different from $formatedDate1 because $date_obj_1 was passed by reference
EDIT: for a detailed explanation on my above snipped look at the comments section. #Xatoo explained it pretty good.
date_format isn't manipulating the DateTime object. What you do is the equivalent of:
$dateObject = new DateTime($date);
$year = $dateObject->format('Y');
$month = $dateObject->format('m');
$day = $dateObject->format('d');
This is absolutely fine, dateObject is not changed by calling the format method on it.

PHP: DateTime '-1 day'

I want to get the date of yesterday of the current date in a specifc time zone.
I tried like this, but it is not working:
$date = new DateTime(NULL, new DateTimeZone('Pacific/Wake'));
$yesterday = $date->modify( '-1 day' );
$yesterday = $yesterday->format('Y-m-d');
I am still getting today's date.
This problem, according to the documentation for the modify() method, seems to entirely depend on which version of php you're using. In this case, method chaining(which is what you're attempting to do is called), is only available on php version 5.3.0 and up, according to the changelog on the previously linked docs.
That in mind, it explains why your code didn't work, and #Deryck's did. If you ever do upgrade your php version, or get your host to upgrade it, you could likely reduce those three lines to two:
$date = new DateTime(NULL, new DateTimeZone('Pacific/Wake'));
$date = $date->modify( '-1 day' )->format('Y-m-d');
Not much of an improvement, I realize, but there's your reason for why it failed to work.
Below are two of the methods I see of getting around this; one is creation of a class.. which seems like overkill to me unless this is apart of something grander... the other is a creation of a function. Both shove the extra lines into something that takes up less space, in a sense.
class DT {
private $now; //can be null
private $timezone; //DateTimeZone object
public function __construct($tz_str, $now = NULL) {
$this->now = $now;
$this->timezone = new DateTimeZone($tz_str);;
}
public function mod($mod,$format) {
$dt = new DateTime($this->now, $this->timezone);
$dt->modify($mod);
return $dt->format($format);
}
}
function mod_formatted_zone($tz_str, $mod, $format, $now = NULL) {
$timezone = new DateTimeZone($tz_str);
$dt = new DateTime($now,$timezone);
$dt->modify($mod);
return $dt->format($format);
}
The use of either is simple; in the case of the class, it'd be something like..
$dt = new DT('Pacific/Wake');
echo $dt->mod('-1 day', 'Y-m-d');
While in the case of the function, it'd simply be..
echo mod_formatted_zone('Pacific/Wake', '-1 day', 'Y-m-d');
Seems to work once you don't re-assign the $date variable unnecessarily. See below:
<?php
$date = new DateTime(NULL, new DateTimeZone('Pacific/Wake'));
$date->modify("-1 day");
$date = $date->format("Y-m-d");
// echo $date; // just in case you wanna echo - ya dig
?>
View demo
FYI:
Wake Island Time Zone (UTC+12:00)
Which means 1 day before is actually today (for me at least, on the western hemisphere).
I want to get the date of yesterday of the current date in a specifc time zone.
You can specify relative dates in the DateTime constructor. This will work for you:-
$yesterday = new DateTime('- 1 day', new DateTimeZone('Pacific/Wake'));
var_dump($yesterday);
Proof!.

PHP DateTime __construct() Failed to parse time string (xxxxxxxx) at position x

I had this construction error when trying to creating a new DateTime object using a timestamp:
Exception: DateTime::_construct(): Failed to parse time string (1372622987) at position 8 (8): Unexpected character in DateTime->_construct()
The object creation code is:
$start_date = new DateTime( "#{$dbResult->db_timestamp}" );
Where $dbResult->db_timestamp is a valid unix timestamp taken from a database. The timestamp in question was:
1372622987
I would understand this error for invalid formats being passed, but this is a genuine timestamp.
The reason this is very strange: I since ran a script to create a new DateTime object with the timestamp passed in as a hard coded value, and it reported no errors.
This seems to have been a one off, but I need an explanation if there is one, as I can't afford for this to happen again.
You should use setTimestamp instead, if you hardcode it:
$start_date = new DateTime();
$start_date->setTimestamp(1372622987);
in your case
$start_date = new DateTime();
$start_date->setTimestamp($dbResult->db_timestamp);
Use the createFromFormat method:
$start_date = DateTime::createFromFormat("U", $dbResult->db_timestamp);
UPDATE
I now recommend the use of Carbon
change your code to this
$start_date = new DateTime( "#" . $dbResult->db_timestamp );
and it will work fine
This worked for me.
/**
* return date in specific format, given a timestamp.
*
* #param timestamp $datetime
* #return string
*/
public static function showDateString($timestamp)
{
if ($timestamp !== NULL) {
$date = new DateTime();
$date->setTimestamp(intval($timestamp));
return $date->format("d-m-Y");
}
return '';
}
$start_date = new DateTime();
$start_date->setTimestamp($dbResult->db_timestamp);

Categories