ical file recurring events - php

I have created a PHP script to read an ical file from my mailserver. This script is used to plan events in my PBX so when people call and I'm out of office it automatically redirects them to voicemail. This works perfectly for one time events, but I would like it to work for recurring events.
This is the result of the script:
[BEGIN] => VEVENT
[DTSTAMP] => 20170920T120736Z
[UID] => 80462778A326E04EBD831336D01F2A2F179EBCBCC8BD7A45954DE9CF
[DESCRIPTION] => \n
[PRIORITY] => 5
[SUMMARY] => Summary
[CLASS] => PUBLIC
[LOCATION] => Place
[X-ALARM-TRIGGER] => -PT30M
[DTSTART] => 20170912T083000
[DTEND] => 20170912T173000
[RRULE] => FREQ=WEEKLY;BYDAY=TU
[END] => VEVENT
I can recognize a recurring event by the "RRULE" parameter, but thinking it through the script would have to calculate the recurring date and that would take a lot of CPU I guess. Especially since my ical file already has over 1800 events and these would all have to be checked. Then there is also the question on how to check this, because recurring events can be daily, weekly, monthly, yearly, and that is without intervals, e.g. every other week.
Any idea on how to go about this?

Give something like this a try
<?php
$recur = 'FREQ=WEEKLY;BYDAY=TU';
$ex = explode(';', $recur);
$freq = str_replace('FREQ=','',$ex[0]);
$day = str_replace('BYDAY=','',$ex[1]);
$dates = [];
switch ($freq) {
case 'WEEKLY':
$date = new DateTime(); // set to correct day obvs
$dates[] = $date;
for ($x = 0; $x <= 52; $x ++) {
$date = clone $date;
$date->modify('+1 week');
$dates[] = $date;
}
break;
}
foreach($dates as $date) {
echo $date->format('Y-m-d')."\n";
}
You'll need to code the rest yourself, but this generates a bunch of dates one week apart from each other.
See it working here https://3v4l.org/Dgriv

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')

How to calculate hour intervals from two datetimes in PHP?

I am creating an online booking system. when user clicks on a date in calendar, it returns two datetimes (start and end) for that date. I am trying to calculate all the hours from start to end which I was able to do, but I need to display the hours in intervals.
Lets say user has added available time tomorrow from 10.00-14.00 then I need to display the times like this:
10.00-11.00
11.00-12.00
12.00-13.00
13.00-14.00
for the specific day.
What I have so far.
public function getTimes()
{
$user_id = Input::get("id"); //get the user id
$selectedDay = Input::get('selectedDay'); // We get the data from AJAX for the day selected, then we get all available times for that day
$availableTimes = Nanny_availability::where('user_id', $user_id)->get();
// We will now create an array of all booking datetimes that belong to the selected day
// WE WILL NOT filter this in the query because we want to maintain compatibility with every database (ideally)
// For each available time...
foreach($availableTimes as $t => $value) {
$startTime = new DateTime($value->booking_datetime);
if ($startTime->format("Y-m-d") == $selectedDay) {
$endTime = new DateTime($value->booking_datetime);
date_add($endTime, DateInterval::createFromDateString('3600 seconds'));
// Try to grab any appointments between the start time and end time
$result = Nanny_bookings::timeBetween($startTime->format("Y-m-d H:i"), $endTime->format("Y-m-d H:i"));
// If no records are returned, the time is okay, if not, we must remove it from the array
if($result->first()) {
unset($availableTimes[$t]);
}
} else {
unset($availableTimes[$t]);
}
}
return response()->json($availableTimes);
}
How can I get the intervals?
Assuming the hour difference between start and end is 1 as per your question, you could use DateInterval and DatePeriod, to iterate over the times like:
$startDate = new DateTime( '2017-07-18 10:15:00' );
$endDate = new DateTime( '2017-07-18 14:15:00' );
$interval = new DateInterval('PT1H'); //interval of 1 hour
$daterange = new DatePeriod($startDate, $interval ,$endDate);
$times = [];
foreach($daterange as $date){
$times[] = $date->format("H:i") . " -- "
. $date->add(new DateInterval("PT1H"))->format("H:i");
}
echo "<pre>"; print_r($times);
//gives
Array
(
[0] => 10:15 -- 11:15
[1] => 11:15 -- 12:15
[2] => 12:15 -- 13:15
[3] => 13:15 -- 14:15
)
Update
You could use json_encode() in order to return the json times data, as:
$jsonTimes = json_encode($times);

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 total the number of days taken as leave within a yearly period

Am struggling about how to go about implementing this. I am fairly new with this language so any help would be greatly appreciated.
I have developed functionality whereby users are able to log annual leave, by selcting date to and date from etc. What I’d like to be able to do is be able to display how much leave is remaining / set a limit on logging leave they go over the 25 day limit within the yearly leave period.
I have looked at various php functions like date interval and date time, but can’t figure out how I would use them.
I would do something like this to tally up the total days taken.
<?php
$time_off_requests = array(0 => array('start_date' => '2015-01-01', 'end_date' => '2015-01-14'), array('start_date' => '2015-12-20', 'end_date' => '2016-01-02');
$total_days = 0;
foreach($time_off_requests as $time_out) {
$datetime1 = new DateTime($time_out['start_date']);
$datetime2 = new DateTime($time_out['end_date']);
$interval = $datetime1->diff($datetime2);
$total_days += (int)$interval->format('%a');
}
echo 'Out for a total of '.$total_days.' days this period.';
However if your data is in a MySQL database I would suggest doing that in the SQL Query unless you have a good reason not to.
$maxHolidays = 25;
$holidaysTaken = 0; //populate this from a database, Past holidays
$start = new DateTime('2015-07-26');
$end = new DateTime('2015-08-26');
$diff = $dStart->diff($dEnd);
if ($holidaysTaken + $diff < $maxHolidays) {
// Add the holiday
//add to the database
} else {
//reject the holiday
}
Something like that should work,
In reality though half day holidays are possible
You will likely have to check for holidays crossing years etc also depending on requirements

PHP Get recurring week from specific start date

I'm looking into trying to set up and array that will look something like this:
$dates = array(
[0] => "07/11/2013",
[1] => "14/11/2013",
[2] => "21/11/2013",
[3] => "28/11/2013",
[4] => "05/12/2013",
[5] => "12/12/2013");
I'm willing to use this, but as I want this to reoccur again next year I'd prefer to have PHP do this and enter it into an array for me. I know how to limit it to a specific amount that I want, but I don't know how to add a week onto the current date or specific date if I wanted to start 08/11/2013 for example.
I've had a quick look and I can't seem to find anything that does this.
I just need a script to add a week to the current date, at the moment this is every Thursday, and then add that to the array.
My only problem is I'm not sure how to specify a date, and then add a week every time. I assume a for loop would be best here.
Use DateTime class. DateInterval and DatePeriod classes were introduced in PHP 5.3.0, so the below solution works for only PHP >= 5.3.0:
$start = new DateTime('2013-11-07');
$end = new DateTime('2013-12-31');
$interval = new DateInterval('P1W'); // one week
$p = new DatePeriod($start, $interval, $end);
foreach ($p as $w) {
$weeks[] = $w->format('d-m-Y');
}
Demo!
As Glavic notes in the comments below, this can also be done in previous versions of PHP using the modify() method:
$start = new DateTime('2013-11-07');
$end = new DateTime('2013-12-31');
$weeks = array();
while ($start < $end) {
$weeks[] = $start->format('d-m-Y');
$start->modify('+1 week');
}
Demo.
You can use strtotime('+1 week', $unixTimestamp) for this:
<?php
$startDate = '2013-11-07';
$endDate = '2013-12-31';
$startDateUnix = strtotime($startDate);
$endDateUnix = strtotime($endDate);
$dates = array();
while ($startDateUnix < $endDateUnix) {
$dates[] = date('Y-m-d', $startDateUnix);
$startDateUnix = strtotime('+1 week', $startDateUnix);
}
print_r($dates);
?>
Outputs:
Array
(
[0] => 2013-11-07
[1] => 2013-11-14
[2] => 2013-11-21
[3] => 2013-11-28
[4] => 2013-12-05
[5] => 2013-12-12
[6] => 2013-12-19
[7] => 2013-12-26
)
DEMO
(format the date() call in any way you want to get the format you want).
strtotime does what you need
$nextWeek = strtotime('08/11/2013 + 1 week');
If you need that 8 times, loop it 8 times. You can make a function with $start and $numWeek to return an array with $numWeeks+1 values (the start added)
function createDateList($start, $numWeeks){
$dates = array($start);// add first date
// create a loop with $numWeeks illiterations:
for($i=1;$<=$numWeeks; $i++){
// Add the weeks, take the first value and add $i weeks to it
$time = strtotime($dates[0].' + '.$i.' week'); // get epoch value
$dates[] = date("d/M/Y", $time); // set to prefered date format
}
return $dates;
}
would the strtotime() function work here?
$nextweek = strtotime('thursday next week');
$date = date('d/m/Y', $nextweek);
To create a 5 element array containing today (or this thursday) and the next 4:
for ($a = 0; $a < 5; $a++)
{
$thur = date('d/m/Y', strtotime("thursday this week + $a weeks"));
$dates[] = $thur;
}

Categories