How to store a duration with Doctrine ODM - php

I stored documents which represent events with doctrine. Each $event has an $eventType. Each $eventType has a $duration.
For each $event I will only store the $begin but not the end as it can be calculated with the default $duration of its $eventType.
What would be the most elegant way to store the duration with each eventType?
I would choose a DateInterval annotation if it existed in doctrine. But it does not.
Save the number of seconds of the duration as Integer?
Any other possibility?
The simplified eventType Model looks like this
<?php
/**
* Class EventType
* #ODM\Document(collection="EventType")
*/
class EventType {
/**
* #var $duration int
* #ODM\Int
*/
private $duration;
}

As you mentioned, you can save the duration in seconds.
It's pretty straightforward to have a getEnd method:
/**
* return \DateTime
*/
public function getEnd()
{
$end = clone $this->begin;
return $end->add(new DateInterval('PT' . $this->duration . 'S'));
}
Or you could store the $end date, and not the duration, which you could also calculate:
/**
* return \DateInterval
*/
public function getDuration()
{
return $begin->diff($end);
}
The tricky thing with date differences is the daylight saving time: sometimes you can have
date + 3600seconds == date + 2*3600seconds
So you should think what kind of queries you will make for the end date: will you select events lasting (more or less than) a given amount of time, or will you select events ending (before/after/at) a given date?
Second option is more common, so I would store the end date.

Related

Parsing only Time using Carbon

I have a field in my DTO class which accepts start_time and end_time as "2:00 AM"
/**
* #var string
*/
#[CastWith(TimeCaster::class)]
public string $start_time; // 01:00 AM example
/**
* #var string
*/
#[CastWith(TimeCaster::class)]
public string $end_time;
Can I parse this format of time using Carbon in my Caster Class
#[\Attribute] class TimeCaster implements Caster
{
public function cast(mixed $value): mixed
{
return Carbon::parse($value)->format();
}
}
I think you use Carbon::createFromFormat
Carbon::createFromFormat('H:i A','10:00 PM')->format('Y-m-d H:i:s)
if you want to get only time using timestamp then
Carbon::parse("2021-06-26 22:00:00")->format('g:i A')

Schedule change while copying entities to next week cause a shift

I have a bug on one of my feature with timezone, let me explain.
The goal of this feature is to copy user events of a week to the next week.
I have a UserEvent model with this properties
/**
* #ORM\Entity(repositoryClass="AppBundle\Repository\UserEventRepository")
*/
class UserEvent
{
/**
* #ORM\Id()
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(type="datetime")
*/
private $start;
/**
* #ORM\Column(type="datetime")
*/
private $end;
/**
* #ORM\ManyToOne(targetEntity="UserEventType")
* #ORM\JoinColumn(nullable=false)
*/
private $type;
/**
* #ORM\ManyToOne(targetEntity="User", inversedBy="userEvents")
* #ORM\JoinColumn(nullable=false)
*/
private $user;
/* GETTERS AND SETTERS */
}
The datetime are stored in datebase in UTC timezone, and at the moment the client using the application is in the Europe/Paris timezone.
This is the logic to copy events to the next week
$newEvents = [];
foreach ($eventsToCopy as $event) {
$newEvent = clone $event;
$newEvent->getStart()->modify('+1 week');
$newEvent->getEnd()->modify('+1 week');
$newEvents[] = $newEvent;
}
The weekend are exlude from the week
The problem appear on this weeks
WEEK A: 2017-10-23 to 2017-10-27
WEEK B: 2017-10-30 to 2017-11-03
There is a time change schedules between week A and week B therefore the planified hours on the second week are shifted on hour forward.
I can't do a high level change to handling DateTime and timezone, I can only modify this code (legacy application)
I'm not very confortable with timezone, maybe I'm missing something obvious.
Please give me your magic guideline to fix this case ! :)
I solved my problem by:
Converting UTC from database to Europe/Paris
Do my stuff to modify the datetime like I want
Then I convert back my Europe/Paris datetime to UTC and save it in database
Code example
$newEvent = new UserEvent();
$newEvent->setUser($event->getUser());
$newEvent->setType($event->getType());
// Convert the datetimes of event to copy to Europe timezone
// For my case I have only one timezone to handle at the moment, so it's always about Europe/Paris <=> UTC
// In advanced use case you could convert the UTC datetime to the user's timezone
list($start, $end) = $this->timezoneFormater->convertUTCDatetimeToEuropeDatetime([$event->getStart(), $event->getEnd()]);
// Now set the date
$start->modify('+1 week');
$end->modify('+1 week');
// Then convert back to UTC timezone
list($start, $end) = $this->timezoneFormater->convertEuropeDatetimeToUTCDatetime([$start, $end]);
$newEvent->setStart($start);
$newEvent->setEnd($end);
$newEvents[] = $newEvent;

DateTime timestamp does not change when changing timezone

Alright so basically, I am a little confused about how the timestamp works in DateTime in PHP. I wish to make two methods that convert from a local time to UTC and vice versa.
I currently have this:
/**
* #param \IPS\DateTime $utcDateTime The UTC datetime.
* #param \DateTimeZone $timezone The timezone to convert the UTC time to.
* #return \IPS\DateTime New datetime object in local datetime.
* #throws \Exception when the UTC date is not in UTC format. (debugging purposes)
*/
public static function utcToLocal($utcDateTime, $timezone)
{
if ($utcDateTime->getTimezone()->getName() !== "UTC") {
throw new \Exception("Date time is not UTC!");
}
$time = new DateTime($utcDateTime, new \DateTimeZone("UTC"));
$time->setTimezone($timezone);
return $time;
}
/**
* #param \IPS\DateTime $localDateTime A datetime configured with the the user's timezone
* #return DateTime New datetime object in UTC format
* #throws \Exception When given datetime is already in UTC (for debugging purposes)
*/
public static function localToUtc($localDateTime)
{
if ($localDateTime->getTimezone()->getName() === "UTC") {
throw new \Exception("Value is already UTC");
}
$time = new DateTime($localDateTime, $localDateTime->getTimezone());
$time->setTimezone(new \DateTimeZone("UTC"));
return $time;
}
When I debug this code, at the last line return $time in localToUtc(...) my debugger shows the correct conversions:
However, when I evaluate the expression
$localDateTime->getTimestamp() === $time->getTimestamp()
it will return true.
So I am a little confused, I just want the timestamps to change when I change the timezone. I am thinking maybe I need to work with getOffset() but I want to make sure I do it in the correct way. I'd also prefer not to use any string format tricks because I feel like that is not the correct way.

How can I optimize a DateTime conversion based on a user timezone?

This is the function I have written, it works fine except for the fact that it has the potential to be called hundreds of times, causing some speed bottlenecks. I want to know if there is a way to optimize this code to be more efficient in regards to execution time.
/**
* Takes in the airing values, and then converts them to user local time, giving back the day, dayname, and a formatted timestring.
* The Day is an ISO calendar day of the week, Hour is a 24-hour format hour, and Minutes is the minutes
* #param int $airing_day The airing day (1-7)
* #param int $airing_hour The airing hour (0-23)
* #param int $airing_minutes The airing minutes (0-59)
* #return array The Array of values with keys ['day', 'dayname', 'timestring']
*/
public static function airingTimeToUserTimezone($airing_day, $airing_hour, $airing_minutes)
{
// February 1st the 2016 is a monday, perfect for conversion, since we can correlate 1 to Monday and 7 to Sunday
$AirDateTime = new DateTime('2016-2-' . $airing_day . ' ' . $airing_hour . ':' . $airing_minutes . ':00');
$AirDateTime->setTimezone(self::$user->DateTimeZone);
$toret = array();
$toret['day'] = $AirDateTime->format('N');
$toret['dayname'] = $AirDateTime->format('l');
$toret['hour'] = $AirDateTime->format('G');
$toret['minutes'] = $AirDateTime->format('i');
$toret['timestring'] = $AirDateTime->format("g:i A");
return $toret;
}
Unless this is going to executed thousands of times a second, you probably aren't going to see much of a performance hit from this function. However, one optimization I see is to call DateTime::format() only once:
public static function airingTimeToUserTimezone($airing_day, $airing_hour, $airing_minutes)
{
$AirDateTime = new DateTime("2016-02-$airing_day $airing_hour:$airing_minutes:00");
$AirDateTime->setTimezone(self::$user->DateTimeZone);
$toret = array();
list (
$toret['day'],
$toret['dayname'],
$toret['hour'],
$toret['minutes'],
$toret['timestring']
) = explode("/", $AirDateTime->format("N/l/G/i/g:i A"));
return $toret;
}

Change date after 6 hrs in php variable

I need to change the shiftdate variable after 05:30 AM. Since i need to generate data from past 24 hrs starting 05:31 AM to Next day 05:30 AM. I tried like this, but its giving previous day every time. Please help.
I want $shiftdate to use in my sql query;
Code:
<?php
if(date('H:i')>="00:00" || date('H:i')<"05:30"){
$shiftdate= date('Y-m-d',strtotime(date('Y-m-d'))-24*60*60);
}
else if(date('H:i')>"05:30" || date('H:i')<"00:00")
{
$shiftdate=date('Y-m-d');
}
echo $shiftdate;
?>
You can't just compare string like "05:30" as a number and hope for the best. You need to compare numerical value of the hour and then numerical value of the minute.
You have a race in between the first if and the else if
Also the else if doesn't cover it completely, so if it hit's the sweetspot, you can end up with $shiftdate being NULL.
Make it a function with protoype shiftdate_type_whatever_it_is fn_name(int hour, int minute);. This way you can simply unit test the function for different (think boundary) values of the date("H:i");
You can use the DateTime classes for this and encapsulate your check into a function:-
/**
* #param DateTime $checkTime
* #return string
*/
function getShiftDate(DateTime $checkTime)
{
$shiftDate = (new DateTime())->setTimestamp($checkTime->getTimestamp());
$hours = (int)$checkTime->format('H');
$minutes = (int)$checkTime->format('i');
$totalMins = $hours * 60 + $minutes;
if($totalMins < 330){
$shiftDate->modify('yesterday');
}
return $shiftDate->format('Y-m-d');
}
var_dump(getShiftDate(new DateTime()));
Obviously the input to the function may need to be modified as I don't know how you get your date/time, but that won't be a problem. Post a comment if you need help with that.

Categories