PHP - Check time period against another time period - php

Let's just say, for the sake of simplicity, I have two arrays, in the first is a time range in which I am wanting to go to sleep. I must sleep for the entire two hour period without any interruption, so the bed must be available during that time. The function needs to check if the bed will be available when I want to sleep.
$aSleeping = array(
'start' => '6:00 AM',
'end' => '10:00 AM'
);
$aBedAvail = array(
'start' => '10:00 AM',
'end' => '12:00 PM'
);
I have tried this, it worked on one time range, but not another, any help to improve on this is greatly appreciated.
function checkRange($aSleeping,$aBedAvail){
if(
strtotime($aSleeping['start']) >= strtotime($aBedAvail['start']) &&
strtotime($aSleeping['end']) <= strtotime($aBedAvail['end'])
){
return true;
}else{
return false;
}
}

Because a user can start sleeping at 11h59pm in one day and ends in next day, you must consider using the day information in comparison. PHP DateTime helps enable other date capabilities, in case you need perform other actions with date vars:
$aSleeping = array(
'start' => new DateTime('2020-03-10 05:00:00'),
'end' => new DateTime('2020-03-10 12:00:00')
);
$aBedAvail = array(
'start' => new DateTime('2020-03-10 05:00:00'),
'end' => new DateTime('2020-03-10 12:00:00')
);
function checkRange($aSleeping,$aBedAvail){
return ($aSleeping['start'] >= $aBedAvail['start'] && $aSleeping['end'] <= $aBedAvail['end']) ? true : false;
}
var_dump(checkRange($aSleeping,$aBedAvail));

Your function looks like it would work just fine unless either of the ranges has an end time earlier than the start time (an evening to morning range). You can correct that by checking for that condition and moving the end time to the next day if it is before the start time.
function checkRange(array $outer, array $inner) {
// create DateTimes
$outer = array_map('date_create', $outer);
$inner = array_map('date_create', $inner);
// Correct earlier end times
$outer['start'] > $outer['end'] && $outer['end']->modify('+24 hours');
$inner['start'] > $inner['end'] && $inner['end']->modify('+24 hours');
// compare ranges and return result
return $outer['start'] >= $inner['start'] && $outer['end'] <= $inner['end'];
}

Related

Laravel: Skip time slots from Carbon\CarbonPeriod some time ranges

I'm developing a booking system for a various packages having different durations for each. The customer should get available time slots from a selected date. The date may contain different package bookings with different time intervals. So, What I'm trying to do is to extract available time slots on the date by skipping all booked time slots.
Expected output is:
08:00 AM
10:30 AM // 09:00 AM has a booking. and its duration is 1.5 hours
11:30 AM // The current package has 1 hour duration
... until the shop closes for eg 08:00 PM
I'm using CarbonPeriod to get list of time slots. But, I can't apply the filters to skip all booked time slots.
$hours = new CarbonPeriod(
$opening,
$this->duration() . ' minutes',
$closing->subMinutes($this->duration())
);
$hours->filter(function ($date) use ($booked) {
$toSkip = [];
foreach ($booked as $bookedItem) {
$bookingTime = Carbon::parse($bookedItem->time);
$completingTime = Carbon::parse($bookedItem->time)->addMinutes($bookedItem->package->duration());
if ($date->isBetween($bookingTime, $completingTime)) {
array_push($toSkip, $date);
}
}
return !in_array($date, $toSkip);
});
Any help would be highly appreciated!
If you don't want to reinvent the wheel, this sounds like a job for spatie/opening-hours or the version dedicated to Carbon: cmixin/business-time
Definition looks like:
BusinessTime::enable(Carbon::class, [
'monday' => ['09:00-12:00', '13:00-18:00'],
'tuesday' => ['09:00-12:00', '13:00-18:00'],
'wednesday' => ['09:00-12:00'],
'thursday' => ['09:00-12:00', '13:00-18:00'],
'friday' => ['09:00-12:00', '13:00-20:00'],
'saturday' => ['09:00-12:00', '13:00-16:00'],
]);
Then with CarbonPeriod, use the custom step:
$period = CarbonPeriod::create(
$opening,
static fn ($date) => $date->nextOpen(),
$closing,
);
foreach ($period as $slot) {
echo "$slot\n";
}
You also can easily convert AM/PM string to h0-23 format with:
Carbon::parse('11:20 PM')->format('H:i')

PHP: Generate Carbon array with opening and closing times (per day)

The desired result
I would like to have an associative array that contains a range of times (between opening and closing times) with an interval of 15 minutes. For example:
[
'2017-01-16' => [ // Start of the week (Monday)
'08:00', // Opening time
'08:15',
'08:30',
// Etc..
'18:00', // Closing time
],
'2017-01-17' => [ // Tuesday
'10:00', // Opening time
'10:15',
'10:30',
// Etc..
'22:00', // Closing time
],
// For every day in the week.
];
Another thing I would like to be able to do, is: Take a range of times (e.g. 09:00 - 10:00) and remove it from the array (at a specific date key)
The steps I made (so far)
I have an array that looks just like the one above. But.. it starts with 00:00 and ends at 23:45. With the following code (mainly from another question at Stackoverflow):
private function generateDateRange(Carbon $start_date, Carbon $end_date)
{
$dates = [];
while ($start_date->lte($end_date)) {
if(! array_key_exists($start_date->format('Y-m-d'), $dates)) {
$dates[$start_date->format('Y-m-d')] = [];
} else {
array_push($dates[$start_date->format('Y-m-d')], $start_date->format('H:i'));
if(in_array($start_date->format('H:i'), $dates[$start_date->format('Y-m-d')])) {
$start_date->addMinutes(15);
} else {
$start_date->addDay();
}
}
}
return $dates;
}
$start = Carbon::now()->startOfWeek();
$end = Carbon::now()->endOfWeek();
$range = $this->generateDateRange($start, $end);
My question
How can I do this in PHP (Laravel)? I am planning to make this (more) dynamic by using a database. But first I want to have a working basic.
Does someone know what I could do to reach the desired result?
Try this:
private function generateDateRange(Carbon $start_date, Carbon $end_date,$slot_duration = 15)
{
$dates = [];
$slots = $start_date->diffInMinutes($end_date)/$slot_duration;
//first unchanged time
$dates[$start_date->toDateString()][]=$start_date->toTimeString();
for($s = 1;$s <=$slots;$s++){
$dates[$start_date->toDateString()][]=$start_date->addMinute($slot_duration)->toTimeString();
}
return $dates;
}

PHP: How to check the season of the year and set a class accordingly

I have a website which uses 4 different background images for the header area which visually corresponds to the season of the ear (summer, autumn etc.) – for the summer timeframe I use one image, for the autumn – another one and so on. The problem is that I have to manually change those images once the season of the year changes.
Maybe someone could show how would it be possible to check the current time / season of the year and then print the corresponding classes to the header element (.summer, .autumn etc.)?
I assume using PHP would be the way.
As I stated in the comments, this is an interesting challenge because the dates of seasons are always changing and different depending what part of the world you live in. Your server time and the website visitor's local time are also a factor.
Since you've stated you're just interested in a simple example based on server time and you're not concerned with it being exact, this should get you rolling:
// get today's date
$today = new DateTime();
echo 'Today is: ' . $today->format('m-d-Y') . '<br />';
// get the season dates
$spring = new DateTime('March 20');
$summer = new DateTime('June 20');
$fall = new DateTime('September 22');
$winter = new DateTime('December 21');
switch(true) {
case $today >= $spring && $today < $summer:
echo 'It\'s Spring!';
break;
case $today >= $summer && $today < $fall:
echo 'It\'s Summer!';
break;
case $today >= $fall && $today < $winter:
echo 'It\'s Fall!';
break;
default:
echo 'It must be Winter!';
}
This will output:
Today is: 11-30-2016
It's Fall!
One might consider this necromancy, Yet when I was looking for ready to use method that does this I've got here.
Mister Martin answer was good assuming the year will not changes, here is my snippet, might be useful for someone in future:
private function timestampToSeason(\DateTime $dateTime): string{
$dayOfTheYear = $dateTime->format('z');
if($dayOfTheYear < 80 || $dayOfTheYear > 356){
return 'Winter';
}
if($dayOfTheYear < 173){
return 'Spring';
}
if($dayOfTheYear < 266){
return 'Summer';
}
return 'Fall';
}
cheers!
I know this question is quite old, but none of the answers on this or any of the other questions that ask this question account for timezones, hemispheres, and different calendars in the same function, and since this came up first when I Googled for this, here's what I ended up writing myself.
If you want to get fancier you could calculate the actual Astronomical dates, geolocate the user, internationalize the season name output, etc., but that's beyond the scope of this question anyway.
function get_season( string $date = '', string $timezone = 'Europe/London', string $hemisphere = 'northern', string $calendar = 'meteorological' ): string {
// Create the calendars that you want to use for each hemisphere.
$seasons = array(
'northern' => array(
'astronomical' => array(
'spring' => '03-20', // mm-dd
'summer' => '06-21',
'autumn' => '09-22',
'winter' => '12-21',
),
'meteorological' => array(
'spring' => '03-01',
'summer' => '06-01',
'autumn' => '09-01',
'winter' => '12-01',
),
),
'southern' => array(
'astronomical' => array(
'spring' => '09-22',
'summer' => '12-21',
'autumn' => '03-20',
'winter' => '06-21',
),
'meteorological' => array(
'spring' => '09-01',
'summer' => '12-01',
'autumn' => '03-01',
'winter' => '06-01',
),
),
);
// Set $date to today if no date specified.
if ( empty( $date ) ) {
$date = new \DateTimeImmutable( 'now', new \DateTimeZone( $timezone ) );
} else {
$date = new \DateTimeImmutable( $date, new \DateTimeZone( $timezone ) );
}
// Set the relevant defaults.
$seasons = $seasons[ strtolower( $hemisphere ) ][ strtolower( $calendar ) ];
$current_season = array_key_last( $seasons );
$year = $date->format( 'Y' );
// Return the season based on whether its date has passed $date.
foreach ( $seasons as $season => $start_date ) {
$start_date = new \DateTimeImmutable( $year . '-' . $start_date, new \DateTimeZone( $timezone ) );
if ( $date < $start_date ) {
return $current_season;
}
$current_season = $season;
}
return $current_season;
}
Usage
// Pass nothing to get current meteorological season in the Northern hemisphere.
echo get_season();
// Pre-PHP 8.0, though I've only tested it on 7.4.
echo get_season( 'December 18', 'Europe/London', 'northern', 'astronomical' );
// After PHP 8.0 you can use named arguments to skip the defaults.
echo get_season( 'December 18', calendar: 'astronomical' );
Outputs
winter // Current season.
autumn
autumn // If PHP 8.0, otherwise error.
I personally like that it lets me add calendars if I discover something unique, like for example, the beginning of Summer is always the first Thursday after April 18th in Iceland.

PHP - Calcute unavailable hours in a calendar/agenda

I have a problem that seems quite hard to solve so I hope someone will have a good solution for me:)
I have a PHP program with a list of reservation for many conference rooms of an hotel. I need to calculate how much time during the day at least 1 conference room is in use.
Here is an exemple:
Room 1: 10h 00 to 13h 00
Room 2: 11h 00 to 14h 00
Room 3: 15h 00 to 16h 00
With theses numbers, I need to calculate that the hotel is used during 5 hours (10h to 14h and 15h to 16h).
In the end, this tells me how many hours the hotel has to pay someone to check the rooms in case someone has a problem.
If you don't have a library that could help me, an algorithm could be a good start.
Algorithm outline:
Order you intervals by starting time.
Go through them and join neighbors that intersect. The intersection condition will be that the second interval's start time is before or equal to the first interval's end time. Because of the ordering only one loop will be needed.
At this point you have only disjoint intervals and you can sum their spans to get the total span.
And here is an implementation of that algorithm:
<?php
// assuming hh:mm format for all the dates //
$intervals = array(
array(
'start' => '15:30',
'end' => '16:00',
),
array(
'start' => '10:00',
'end' => '13:00',
),
array(
'start' => '15:00',
'end' => '16:09',
),
array(
'start' => '11:00',
'end' => '14:00',
),
);
// 1. sort the intervals by start date //
function mySortIntervals($a, $b){
return $a > $b;
}
usort($intervals, 'mySortIntervals');
// 2. merge adjoining intervals //
$active = 0;
$current = 1;
$length = count($intervals);
while($current < $length){
if($intervals[ $current ]['start'] <= $intervals[ $active ]['end']){
$intervals[ $active ]['end'] = max($intervals[ $active ]['end'], $intervals[ $current ]['end']);
unset($intervals[ $current ]);
}
else{
$active = $current;
}
$current++;
}
// 3. cout the total time //
$time = 0;
foreach($intervals as $interval){
$time += strtotime($interval['end']) - strtotime($interval['start']);
}
// output //
echo str_pad((int) ($time/60/60), 2, '0', STR_PAD_LEFT).':';
echo str_pad((int) (($time/60)%60), 2, '0', STR_PAD_LEFT);
?>
First of all, you need to define your interval or time span, for example every 15 minutes.
Why this? because you need to control the gaps caused by room that finish before time, or for some other reason.
The time span can NOT be more than 15 minutes.(Time is money).
Order a room (time span). It must check avaliable time span every time a request happen.
Once the cell (time span) is checked that time span will not be available anymore.
To order a room just select the hour (00 til 23) and the quarter (00; 15; 30; 45;)
It worked to me quite well for an ISP business. I think is not different for a hotel. Because the idea of time span is the same.
But of course you can look for some class if you don't want to write your code at all.

Algorithm to look up the date range based on event date

I'm writing a PHP function that would use a table of sorts to look up which DB shard the application should go to, based on the datestamp I have.
The shard configuration is something like this (pseudo-code): the first column is the date of the event I'm looking for and the 2nd is the shard the event resides in.
pre-2008 -> shard1
2008-2009 -> shard2
2009_01-2009_06 -> shard3
2009_07 -> shard4
2009_08 -> shard5
2009_09 and up -> shard6
As you can see, the configuration I want is pretty flexible - it can take any date range, however small or big and map to a shard.
I am looking for the quickest way to do a lookup based on a given date.
For example, if my date is 2009-05-02, the shard I am after is shard3. If the date is 2007-08-01, then it's shard1.
Bonus points for actual PHP code, as the application is in PHP.
Thank you.
I am guessing that you don't want to have holes in date ranges, so I propose that you
just need to specify an end date for each shard, and explicitly name one Default Shard
which holds everything that is too new to fit into one of the other shards.
// configure shards
$SHARDS = array(
// <end date> => <shard number>
'2007-12-31' => 'shard1', // shard1 - up to end of 2007
'2008-12-31' => 'shard2', // shard2 - up to end of 2008
'2009-06-30' => 'shard3', // shard3 - up to end of June 09
'2009-07-31' => 'shard4', // shard4 - up to end of July 2009
'2009-08-31' => 'shard5', // shard4 - up to end of August 2009
'DEFAULT' => 'shard6', // everything else in shard 6
);
This makes it easy to get the dates right, and the code for finding a shard based on date is simple:
function findShardByDate($date) {
static $default = false;
static $sorted = false;
if($sorted === false) {
// copy of global $SHARDS
$SHARDS = $GLOBALS['SHARDS'];
$default = $SHARDS['DEFAULT'];
unset($SHARDS['DEFAULT']);
// make sure $SHARDS is sorted
ksort($SHARDS);
$sorted = $SHARDS;
unset($SHARDS);
}
// find the first shard which would contain that date
foreach($sorted as $endDate => $shardName)
if($endDate >= $date)
return $shardName;
// no shard found - use the default shard
return $default;
}
Edit: Used static variables so that sorting is only done once.
<?php
function get_shard($datetime)
{
$timestamp = strtotime($datetime);
$shards = array(array('start' => null, 'end' => '2007-12-31'),
array('start' => '2008-01-01', 'end' => '2008-12-31'),
array('start' => '2009-01-01', 'end' => '2009-06-30'),
array('start' => '2009-07-01', 'end' => '2009-07-31'),
array('start' => '2009-08-01', 'end' => '2009-08-31'),
array('start' => '2009-09-01', 'end' => null),
);
foreach ($shards as $key => $range) {
$start = strtotime($range['start']);
$end = strtotime($range['end']);
if ($timestamp >= $start && $timestamp <= $end) {
return $key + 1;
}
if ($timestamp >= $start && $end === false) {
return $key + 1;
}
}
}
$datetime = '2007-08-01';
echo 'shard' . get_shard($datetime) . "\n";
$datetime = '2009-05-02';
echo 'shard' . get_shard($datetime) . "\n";
$datetime = '2010-01-01';
echo 'shard' . get_shard($datetime) . "\n";
?>
Outputs:
shard1
shard3
shard6
Since your shards can be strictly ordered, it seems like storing them in a binary tree and then simply running a binary search through that tree would give you the fastest results.

Categories