I'd like to include some text on my website that states whether or not a shop is open based on its opening times. If the shop is open, it says when it's open until. If it's not open, it says when it's next open.
I already have the opening times stored in the following variable:
$opening_times = [
'Monday' => ['09:00' => '17:00'],
'Tuesday' => ['09:00' => '17:00'],
'Wednesday' => ['09:00' => '12:00'],
'Thursday' => ['09:00' => '17:00'],
'Friday' => ['09:00' => '17:00'],
'Saturday' => ['09:30' => '17:00']
];
The shop is closed on Sunday.
Please could someone guide me as to how I can do this? I've already looked at this example but I'm unsure how to handle showing the time the shop is open next and what to do when it's a Sunday.
I'm hoping to finish with something that displays the next time the shop's open, whether that's on the same day or not. For example, at 5.30pm on Saturday, I'd like the message to say that the shop's next open at 9am on Monday.
I had previously attempted this by storing the next open day and time with each day in the $opening_times variable but I was wondering if there was a more elegant solution.
Using the answer here: Determine If Business Is Open/Closed Based On Business Hours as a guide.
This takes into account opening later and not opening today at all.
UPDATE: Tells you when it is next open. (Untested because work servers use PHP 5.3 :()
<?php
$storeSchedule = [
'Sun' => [['12:00' => '01:00', '09:00' => '12:00']],
'Mon' => [['09:00' => '12:00']],
'Tue' => [['09:00' => '12:00']],
'Wed' => [['09:00' => '12:00']],
'Thu' => [['09:00' => '12:00'], ['22:50' => '23:00']],
'Fri' => [['09:00' => '12:00']],
'Sat' => [['12:00' => '01:00', '09:00' => '12:00']]
];
// current or user supplied UNIX timestamp
$timestamp = time();
// default status
$open = false;
// Open later at
$openAt = false;
// get current time object
$currentTime = (new DateTime())->setTimestamp($timestamp);
// Current day
$currentDay = date('D', $timestamp);
if(isset($storeSchedule[$currentDay])){
// loop through time ranges for current day
foreach ($storeSchedule[$currentDay] as $key => $dateGroup) {
$startTime = current(array_keys($dateGroup));
$endTime = current(array_values($dateGroup));
// create time objects from start/end times
$startTime = DateTime::createFromFormat('H:i', $startTime);
$endTime = DateTime::createFromFormat('H:i', $endTime);
// check if current time is within a range
if (($startTime < $currentTime) && ($currentTime < $endTime)) {
$open = true;
break;
}elseif($currentTime < $startTime){
// Opening Later
$openAt = $startTime;
}
}
}else{
// Not open because day is not in array
}
if($open){
echo "We are open";
}else{
if($openAt){
echo "We open later at " . $openAt->format('H:i');
}else{
// Get next open
$arrayDays = array_keys($storeSchedule); // Get an array of the days
$arrayTimes = array_values($storeSchedule); // Get an array of times
$dayIndex = array_search($currentDay, $arrayDays); // Find out what day we are in in the array. To see if there are more this week
$nextDay = ($dayIndex + 1) >= count($arrayDays) ? $arrayTimes[0] : $arrayTimes[$dayIndex + 1]; // If there are no more this week, take the first day, else take the next day.
$nextOpenTime = current(array_keys($nextDay)); // Take the first set of times from this day as the start time
$nextOpenDay = $arrayDays[$dayIndex + 1]; // Get the day key name
echo "We are not open";
echo "We open next on " . $nextOpenDay . " at " . $nextOpenTime->format('H:i');
}
}
?>
Related
I'm trying to make an calendar scheduler for student teacher for their future lessons together.
I'm trying to add day, time and DATE(most important). Right now I added day and time in each of their following meetings. For ex. every Monday at 08:00 and every Wednesday at 10:00. But what I cannot add is Date . Here is some of my coding.
$student = Auth::user()->student;
$teacher = Teacher::where('id',$id)->first();
$lessons = $request->lessons_booked;
$times = $request->times;
$timesArrLength = count($times);
$stuTeachArr = array();
for($i = 1;$i<= $lessons;$i++){
$studentTeacher = StudentTeacher::create([
'student_id' => $student->id,
'teacher_id' => $teacher->id,
'subject_id' => $request->subject_id,
'place_of_tutoring_id' => $request->place_of_tutoring_id,
'lessons_booked' => $lessons,
'lesson_nr' => $i,
'time_id' => $times[($i-1) % $timesArrLength]['id'],
'day' => $times[($i-1) % $timesArrLength]['day'],
'date' => '2021-01-01', // add date here
'lessons_left' => $request->lessons_booked - $i,
'status' => 'active'
]);
array_push($stuTeachArr,$studentTeacher);
}
// $stuTeachArr['booked_teacher'] = $studentTeacher;
return $stuTeachArr;
What I want is to add DATE for every lesson that will happen from now until they finish with their lessons(For ex. every Monday and every Wednesday as mentioned before).
Any suggestion to change the way I'm doing would be welcomed.
I have a php script which I finally got with the help of friends and stackoverflow, where I am trying to display the hours of operation of a store such as it displays the current status whether it is open or not and the next staus as well. That means if the store is not open currently then when it will open next also needs to display. The script code is given below: it works some time when store is not opened currently and will open same day again (in case of Sunday, Thursday and Saturday when it opens twice a day), but it never works when it is currently closed and will now open on next day some time. It gives some error on last line of scrip and the error is :
Fatal error: Call to a member function format() on a non-object in E:\xampp\htdocs\karnalguide\operation-hrs-test.php on line 73
I am making some mistake which I am not able to figure out - Please help me guys to fix this issue - check the error in script and help me with the fix. I am not much expert in php object oriented programming. So please explain me with full fix of this issue. Thanks in advance!
<?php
/* Script for displaying operation times of a store and will be used in https://www.karnalguide.com */
/* array of store opening timings */
$storeSchedule = [
'Sun' => [['12:00' => '01:00', '10:00' => '12:00']],
'Mon' => [['09:00' => '12:00']],
'Tue' => [['09:00' => '12:00']],
'Wed' => [['09:00' => '12:00']],
'Thu' => [['09:00' => '12:00'], ['22:50' => '23:00']],
'Fri' => [['09:00' => '12:00']],
'Sat' => [['12:00' => '01:00', '09:00' => '12:00']]
];
// current or user supplied UNIX timestamp
$timestamp = time();
// default status
$open = false;
// Open later at
$openAt = false;
// get current time object
$currentTime = (new DateTime())->setTimestamp($timestamp);
// Current day
$currentDay = date('D', $timestamp);
if(isset($storeSchedule[$currentDay])){
// loop through time ranges for current day
foreach ($storeSchedule[$currentDay] as $key => $dateGroup) {
$startTime = current(array_keys($dateGroup));
$endTime = current(array_values($dateGroup));
// create time objects from start/end times
$startTime = DateTime::createFromFormat('H:i', $startTime);
$endTime = DateTime::createFromFormat('H:i', $endTime);
// check if current time is within a range
if (($startTime < $currentTime) && ($currentTime < $endTime)) {
$open = true;
break;
}elseif($currentTime < $startTime){
// Opening Later
$openAt = $startTime;
}
}
}else{
// Not open because day is not in array
}
if($open){
echo "We are open";
}else{
if($openAt){
echo "We open later at " . $openAt->format('H:i');
}else{
// Get next open
$arrayDays = array_keys($storeSchedule); // Get an array of the days
$arrayTimes = array_values($storeSchedule); // Get an array of times
$dayIndex = array_search($currentDay, $arrayDays); // Find out what day we are in in the array. To see if there are more this week
$nextDay = ($dayIndex + 1) >= count($arrayDays) ? $arrayTimes[0] : $arrayTimes[$dayIndex + 1]; // If there are no more this week, take the first day, else take the next day.
$nextOpenTime = current(array_keys($nextDay)); // Take the first set of times from this day as the start time
$nextOpenDay = $arrayDays[$dayIndex + 1]; // Get the day key name
echo "We are not open";
echo "We open next on " . $nextOpenDay . " at " . $nextOpenTime->format('H:i');
}
}
?>
I believe this code will do the same in a shorter way.
I loop today and see if it's open or will open today.
If it's not open I dump the next day but that can easily be replaced with a echo.
$storeSchedule = [
'Sun' => [['12:00' => '01:00', '10:00' => '12:00']],
'Mon' => [['09:00' => '12:00']],
'Tue' => [['09:00' => '12:00']],
'Wed' => [['09:00' => '12:00']],
'Thu' => [['09:00' => '12:00'], ['22:50' => '23:00']],
'Fri' => [['09:00' => '12:00']],
'Sat' => [['12:00' => '01:00', '09:00' => '12:00']]
];
$today = date("D");
$closed = Null;
foreach($storeSchedule[$today] as $opentimes){
foreach($opentimes as $open => $close){
$now = time();
if($now > strtotime(date("Y-m-d" . $open)) && $now < strtotime(date("Y-m-d" . $close))){
echo "store open now";
$closed = false;
}else if($now < strtotime(date("Y-m-d" . $open))){
echo "store will open at " . date("Y-m-d" . $open);
$closed = false;
}else{
// Store is closed
if($closed !== false) $closed = true;
}
}
}
if($closed){
// Dump the next open time
var_dump($storeSchedule[date("D",strtotime($today)+86400)]);
}
https://3v4l.org/DAvmo
I'm having a hell of a time trying to solve the following problem:
It's a calendar program where given a set of available datetime sets from multiple people, I need to figure out what datetime ranges everyone is available in PHP
Availability Sets:
p1: start: "2016-04-30 12:00", end: "2016-05-01 03:00"
p2: start: "2016-04-30 03:00", end: "2016-05-01 03:00"
p3: start: "2016-04-30 03:00", end: "2016-04-30 13:31"
start: "2016-04-30 15:26", end: "2016-05-01 03:00"
I'm looking for a function that I can call that will tell me what datetime ranges all (p) people are available at the same time.
In the above example the answer should be:
2016-04-30 12:00 -> 2016-04-30 13:31
2016-04-30 15:26 -> 2016-05-01 03:00
I did find this similar question and answer
Datetime -Determine whether multiple(n) datetime ranges overlap each other in R
But I have no idea what language that is, and have to unable to translate the logic in the answer.
Well that was fun. There's probably a more elegant way of doing this than looping over every minute, but I don't know if PHP is the language for it. Note that this currently needs to manage the start and end times to search separately, although it would be fairly trivial to calculate them based on the available shifts.
<?php
$availability = [
'Alex' => [
[
'start' => new DateTime('2016-04-30 12:00'),
'end' => new DateTime('2016-05-01 03:00'),
],
],
'Ben' => [
[
'start' => new DateTime('2016-04-30 03:00'),
'end' => new DateTime('2016-05-01 03:00'),
],
],
'Chris' => [
[
'start' => new DateTime('2016-04-30 03:00'),
'end' => new DateTime('2016-04-30 13:31')
],
[
'start' => new DateTime('2016-04-30 15:26'),
'end' => new DateTime('2016-05-01 03:00')
],
],
];
$start = new DateTime('2016-04-30 00:00');
$end = new DateTime('2016-05-01 23:59');
$tick = DateInterval::createFromDateString('1 minute');
$period = new DatePeriod($start, $tick, $end);
$overlaps = [];
$overlapStart = $overlapUntil = null;
foreach ($period as $minute)
{
$peopleAvailable = 0;
// Find out how many people are available for the current minute
foreach ($availability as $name => $shifts)
{
foreach ($shifts as $shift)
{
if ($shift['start'] <= $minute && $shift['end'] >= $minute)
{
// If any shift matches, this person is available
$peopleAvailable++;
break;
}
}
}
// If everyone is available...
if ($peopleAvailable == count($availability))
{
// ... either start a new period...
if (!$overlapStart)
{
$overlapStart = $minute;
}
// ... or track an existing one
else
{
$overlapUntil = $minute;
}
}
// If not and we were previously in a period of overlap, end it
elseif ($overlapStart)
{
$overlaps[] = [
'start' => $overlapStart,
'end' => $overlapUntil,
];
$overlapStart = null;
}
}
foreach ($overlaps as $overlap)
{
echo $overlap['start']->format('Y-m-d H:i:s'), ' -> ', $overlap['end']->format('Y-m-d H:i:s'), PHP_EOL;
}
There are some bugs with this implementation, see the comments. I'm unable to delete it as it's the accepted answer. Please use iainn or fusion3k's very good answers until I get around to fixing it.
There's actually no need to use any date/time handling to solve this
problem. You can exploit the fact that dates in this format are in alphabetical as well as chronological order.
I'm not sure this makes the solution any less complex. It's probably less
readable this way. But it's considerably faster than iterating over every minute so you might choose it if performance is a concern.
You also get to use
every
single
array
function
out there, which is nice.
Of course, because I haven't used any date/time functions, it might not work if Daylight Savings Time or users in different time zones need dealing with.
$availability = [
[
["2016-04-30 12:00", "2016-05-01 03:00"]
],
[
["2016-04-30 03:00", "2016-05-01 03:00"]
],
[
["2016-04-30 03:00", "2016-04-30 13:31"],
["2016-04-30 15:26", "2016-05-01 03:00"]
]
];
// Placeholder array to contain the periods when everyone is available.
$periods = [];
// Loop until one of the people has no periods left.
while (count($availability) &&
count(array_filter($availability)) == count($availability)) {
// Select every person's earliest date, then choose the latest of these
// dates.
$start = array_reduce($availability, function($carry, $ranges) {
$start = array_reduce($ranges, function($carry, $range) {
// This person's earliest start date.
return !$carry ? $range[0] : min($range[0], $carry);
});
// The latest of all the start dates.
return !$carry ? $start : max($start, $carry);
});
// Select each person's range which contains this date.
$matching_ranges = array_filter(array_map(function($ranges) use($start) {
return current(array_filter($ranges, function($range) use($start) {
// The range starts before and ends after the start date.
return $range[0] <= $start && $range[1] >= $start;
}));
}, $availability));
// Find the earliest of the ranges' end dates, and this completes our
// first period that everyone can attend.
$end = array_reduce($matching_ranges, function($carry, $range) {
return !$carry ? $range[1] : min($range[1], $carry);
});
// Add it to our list of periods.
$periods[] = [$start, $end];
// Remove any availability periods which finish before the end of this
// new period.
array_walk($availability, function(&$ranges) use ($end) {
$ranges = array_filter($ranges, function($range) use($end) {
return $range[1] > $end;
});
});
}
// Output the answer in the specified format.
foreach ($periods as $period) {
echo "$period[0] -> $period[1]\n";
}
/**
* Output:
*
* 2016-04-30 12:00 -> 2016-04-30 13:31
* 2016-04-30 15:26 -> 2016-05-01 03:00
*/
A different approach to your question is to use bitwise operators. The benefits of this solution are memory usage, speed and short code. The handicap is that — in your case — we can not use php integer, because we work with large numbers (1 day in minutes is 224*60), so we have to use GMP Extension, that is not available by default in most php distribution. However, if you use apt-get or any other packages manager, the installation is very simple.
To better understand my approach, I will use an array with a total period of 30 minutes to simplify binary representation:
$calendar =
[
'p1' => [
['start' => '2016-04-30 12:00', 'end' => '2016-04-30 12:28']
],
'p2' => [
['start' => '2016-04-30 12:10', 'end' => '2016-04-30 12:16'],
['start' => '2016-04-30 12:22', 'end' => '2016-05-01 12:30']
]
];
First of all, we find min and max dates of all array elements, then we init the free (time) variable with the difference in minutes between max and min. In above example (30 minutes), we obtain 230-20=1,073,741,823, that is a binary with 30 ‘1’ (or with 30 bits set):
111111111111111111111111111111
Now, for each person, we create the corresponding free-time variable with the same method. For the first person is easy (we have only one time interval): the difference between start and min is 0, the difference between end and min is 28, so we have 228-20=268435455, that is:
001111111111111111111111111111
At this point, we update global free time with a AND bitwise operation between global free time itself and person free time. The OR operator set bits if they are set in both compared values:
111111111111111111111111111111 global free time
001111111111111111111111111111 person free time
==============================
001111111111111111111111111111 new global free time
For the second person, we have two time intervals: we calculate each time interval with know method, then we compone global person free time using OR operator, that set bits if they are set in either first or second value:
000000000000001111110000000000 12:10 - 12:16
111111110000000000000000000000 12:22 - 12:30
==============================
111111110000001111110000000000 person total free time
Now we update global free time with the same method used for first person (AND operator):
001111111111111111111111111111 previous global free time
111111110000001111110000000000 person total free time
==============================
001111110000001111110000000000 new global free time
└────┘ └────┘
:28-:22 :16-:10
As you can see, at the end we have an integer with bits set only in minutes when everyone is available (you have to count starting from right). Now, you can convert back this integer to datetimes. Fortunately, GMP extension has a method to find 1/0 offset, so we can avoid to perform a for/foreach loop through all digits (that in real case are many more than 30).
Let's see the complete code to apply this concept to your array:
$calendar =
[
'p1' => [
['start' => '2016-04-30 12:00', 'end' => '2016-05-01 03:00']
],
'p2' => [
['start' => '2016-04-30 03:00', 'end' => '2016-05-01 03:00']
],
'p3' => [
['start' => '2016-04-30 03:00', 'end' => '2016-04-30 13:31'],
['start' => '2016-04-30 15:26', 'end' => '2016-05-01 03:00']
]
];
/* Get active TimeZone, then calculate min and max dates in minutes: */
$tz = new DateTimeZone( date_default_timezone_get() );
$flat = call_user_func_array( 'array_merge', $calendar );
$min = date_create( min( array_column( $flat, 'start' ) ) )->getTimestamp()/60;
$max = date_create( max( array_column( $flat, 'end' ) ) )->getTimestamp()/60;
/* Init global free time (initially all-free): */
$free = gmp_sub( gmp_pow( 2, $max-$min ), gmp_pow( 2, 0 ) );
/* Process free time(s) for each person: */
foreach( $calendar as $p )
{
$pf = gmp_init( 0 );
foreach( $p as $time )
{
$start = date_create( $time['start'] )->getTimestamp()/60;
$end = date_create( $time['end'] )->getTimestamp()/60;
$pf = gmp_or( $pf, gmp_sub( gmp_pow( 2, $end-$min ), gmp_pow( 2, $start-$min ) ) );
}
$free = gmp_and( $free, $pf );
}
$result = [];
$start = $end = 0;
/* Create resulting array: */
while( ($start = gmp_scan1( $free, $end )) >= 0 )
{
$end = gmp_scan0( $free, $start );
if( $end === False) $end = strlen( gmp_strval( $free, 2 ) )-1;
$result[] =
[
'start' => date_create( '#'.($start+$min)*60 )->setTimezone( $tz )->format( 'Y-m-d H:i:s' ),
'end' => date_create( '#'.($end+$min)*60 )->setTimezone( $tz )->format( 'Y-m-d H:i:s' )
];
}
print_r( $result );
Output:
Array
(
[0] => Array
(
[start] => 2016-04-30 12:00:00
[end] => 2016-04-30 13:31:00
)
[1] => Array
(
[start] => 2016-04-30 15:26:00
[end] => 2016-05-01 03:00:00
)
)
3v4l.org demo
Some additional notes:
At the start, we set $tz to current timezone: we will use it later, at the end, when we create final dates from timestamps. Dates created from timestamps are in UTC, so we have to set correct timezone.
To retrieve initial $min and $max values in minutes, firstly we flat original array, then we retrieve min and max date using array_column.
gmp_sub subtract second argument from first argument, gmp_pow raise number (arg 1) into power (arg 2).
In the final while loop, we use gmp_scan1 and gmp_scan0 to retrieve each ‘111....’ interval, then we create returning array elements using gmp_scan1 position for start key and gmp_scan0 position for end key.
I have an offset from UTC stored in minutes: e.g -240
I'm trying to find the corresponding UNIX timestamp of midnight of the current day for this particular offset.
I found similar information in questions like this one: How do I get the UTC time of "midnight" for a given timezone?
However, I don't have the city name/timezone jurisdiction, just a minute offset. I think this should be fine since for my purposes I don't need to account for daylight savings, it can be off by an hour and still be fine.
Examples
Offset: -420
Midnight on 7/12/2014: 1405148400 (unix TS)
With UTC, I would have to first tell if it's the next day or same day as the TZ because it may have a different "last midnight".
While this solution looks a little ugly it does do what I think you're asking for! This example uses -180 minutes as the offset.
date_default_timezone_set('UTC');
// Work out which day the time zone is in
$day = strtotime('-180 minutes');
// Strip of the time part of the day, to give UTC midnight on the correct day
$utcMidnight = strtotime('midnight', $day);
// Now apply the offset in reverse to give the zone's midnight
$zoneMidnight = strtotime('+180 minutes', $utcMidnight);
You could use date_default_timezone_set to make all time-related functions acknowledge the shift. First thing to do is to convert those minutes into hours, since the UTC gap is 1 hour between n and n+1.
$hours = $minutes / 60;
I would also recommend that you check the minutes values first :
if($minutes % 60 == 0) // We're good.
Now, if you want to convert the UTC offset to a timezone, you can create your function :
<?php
function offsetToTimezone($offset){
$timezones = array(
"-12" => "Pacific/Kwajalein",
"-11" => "Pacific/Samoa",
"-10" => "Pacific/Honolulu",
"-9" => "America/Juneau",
"-8" => "America/Los_Angeles",
"-7" => "America/Denver",
"-6" => "America/Mexico_City",
"-5" => "America/New_York",
"-4" => "America/Caracas",
"-3.5" => "America/St_Johns",
"-3" => "America/Argentina/Buenos_Aires",
"-2" => "Atlantic/Azores",
"-1" => "Atlantic/Azores",
"0" => "Europe/London",
"1" => "Europe/Paris",
"2" => "Europe/Helsinki",
"3" => "Europe/Moscow",
"3.5" => "Asia/Tehran",
"4" => "Asia/Baku",
"4.5" => "Asia/Kabul",
"5" => "Asia/Karachi",
"5.5" => "Asia/Calcutta",
"6" => "Asia/Colombo",
"7" => "Asia/Bangkok",
"8" => "Asia/Singapore",
"9" => "Asia/Tokyo",
"9.5" => "Australia/Darwin",
"10" => "Pacific/Guam",
"11" => "Asia/Magadan",
"12" => "Asia/Kamchatka"
);
return $timezones[$offset];
}
?>
... and use if for conversion :
date_default_timezone_set(offsetToTimezone($hours));
By the way, I suggest you have a look at this answer, which provides you with a more elegant way to achieve the work of offsetToTimezone.
Now that your script if configured on the correct timezone, just ask for a timestamp :
$timestamp = mktime(0, 0, 0);
If at some time, you need to reset to timezone to default, you might need date_default_timezone_get to save it :
$timezone = date_default_timezone_get();
// Change to another timezone based on your offset.
// Get your timestamp.
date_default_timezone_set($timezone);
I had to think through it quite a bit, but I think this was the solution I was looking for. Let me know if you think this algorithm is incorrect.
function getLastMidnightForOffset( $minuteOffset ) {
$today = mktime( 0, 0, 0 );
$tomorrow = $today + 86400;
$yesterday = $today - 86400;
$offset = $minuteOffset * 60;
if ( time() + $offset >= $tomorrow ) {
$localMidnight = $tomorrow - $offset;
} elseif ( time() + $offset >= $today ) {
$localMidnight = $today - $offset;
} else {
$localMidnight = $yesterday - $offset;
}
return $localMidnight;
}
I am building a small class combination to calculate the precise date of the beginning of a semester. The rules for determining the beginning of the semester goes as follow :
The monday of week number ## and after dd-mm-yyyy date
ie: for winter its week number 2 and it must be after the january 8th of that year
I am building a resource class that contain these data for all the semesters (4 in total). But now I am facing an issue based on the public holidays. Since some of those might be on a Monday, in those cases I need to get the date of the Tuesday.
The issue I am currently working on is the following :
The target semester begins on or after august 30 and must be on week 35.
I also have to take account of a public holiday which happen on the first monday of september.
The condition in PHP terms is the following
if (date('m', myDate) == 9 // if the month is september
&& date('w', myDate) == 1 // if the day of the week is monday
&& date('d', myDate) < 7 // if we are in the first 7 days of september
)
What would be the best way to "word" this as a condition and store it in an array?
EDIT
I might not have been clear enough, finding the date is not the problem here. The actual problem is storing a condition in a configuration array that looks like the following :
$_ressources = array(
1 => array(
'dateMin' => '08-01-%',
'weekNumber' => 2,
'name' => 'Winter',
'conditions' => array()
),
2 => array(
'dateMin' => '30-04-%',
'weekNumber' => 18,
'name' => 'Spring',
'conditions' => array()
),
3 => array(
'dateMin' => '02-07-%',
'weekNumber' => 27,
'name' => 'Summer',
'conditions' => array()
),
4 => array(
'dateMin' => '30-08-%',
'weekNumber' => 35,
'name' => 'Autumn',
'conditions' => array("date('m', %date%) == 9 && date('w', %date%) == 1 && date('d', %date%) < 7")
)
);
The issue I have with the way it's presented now, is that I will have to use the eval() function, which I would rather not to.
You said:
The target semester begins on or after august 30 and must be on week 35.
If that's the case you can simple check for week number.
if(date('W', myDate) == 35)
Or if your testing condition is correct then you should compare day number till 7 as it starts from 1.
if((date('m', myDate) == 9 // september
&& date('w', myDate) == 1 // monday
&& date('d', myDate) <= 7 // first 7 days of september
)
And then in the if statement, once you have found the monday which would be OK IF its not a public holiday, do this
if(...){
while(!array_search (myDate, aray_of_public_holidays))
date_add($myDate, date_interval_create_from_date_string('1 days'));
}
Here the array_of_public_holidays contains the list of public holidays.
Update with Code
Following code should work for your purposes
<?php
// array with public holidays
$public_holidays = array(/* public holidays */);
// start on 30th august
$myDate = new DateTime('August 30');
// loop till week number does not cross 35
while($myDate->format('W') <= 35){
// if its a monday
if($myDate->format('w') == 1){
// find the next date not a public holiday
while(array_search($myDate, $public_holidays))
$myDate->add(date_interval_create_from_date_string('1 days'));
// now myDate stores the valid semester start date so exit loop
break;
}
// next date
$myDate->add(date_interval_create_from_date_string('1 days'));
}
// now myDate is the semester start date
?>
Update according to updated question
Following code should work for your needs. You do not need to store the condition in your array as PHP code. The following code shows how it can be done
// semester conditions
$sem_conditions = array(
1 => array(
'dateMin' => '08-01-%',
'weekNumber' => 2,
'name' => 'Winter'
),
2 => array(
'dateMin' => '30-04-%',
'weekNumber' => 18,
'name' => 'Spring'
),
3 => array(
'dateMin' => '02-07-%',
'weekNumber' => 27,
'name' => 'Summer'
),
4 => array(
'dateMin' => '30-08-%',
'weekNumber' => 35,
'name' => 'Autumn'
)
);
// array with public holidays format (d-M)
$public_holidays = array('05-09', '10-01');
// store sem starts
$sem_starts = array();
// for each semester
foreach($sem_conditions as $sem){
// start date
$myDate = date_create_from_format('d-m', substr($sem['dateMin'], 0, -2));
// loop till week number does not cross $sem['weekNumber']
while($myDate->format('W') <= $sem['weekNumber']){
// if its a monday
if($myDate->format('w') == 1){
// find the next date not a public holiday
while(array_search($myDate->format('d-m'), $public_holidays) !== false)
$myDate->add(date_interval_create_from_date_string('1 days'));
// now myDate stores the valid semester start date so exit loop
break;
}
// next date
$myDate->add(date_interval_create_from_date_string('1 days'));
}
// add to sem starts
$sem_start[$sem['name']] = $myDate->format('d-m-Y');
}
var_dump($sem_start);
The target semester begins on or after august 30 and must be on week 35
The start of the semester is the minimal date between week 35 and August 30:
$week35 = new DateTime("January 1 + 35 weeks");
$august30 = new DateTime("August 30");
$start = min($week35, $august30);
Alternatively:
$start = min(date_create("January 1 + 52 weeks"), date_create("August 30"));