Show next "DateInterval" value depending on actual time - php

I use a "DateInterval" with an interval of 3 hours to get all the dates between a start and end time. The result looks like that:
15:00 | 18:00 | 21:00 | 00:00 | 03:00 (and so on...)
Now I'm searching a solution that only the next "DateInterval" value gets shown (depending on the actual time), and not all of them.
Example: if the actual time is 19:29 the shown result should be 21:00.
My code so far:
$start = new DateTime('2022-12-18 15:00:00');
$interval = DateInterval::createFromDateString('3 hours');
$end = new DateTime('2022-12-31 15:00:00');
$occurrences = new DatePeriod($start, $interval, $end);
foreach ($occurrences as $occurrence) {
echo $occurrence->format('H:i') . PHP_EOL;
}

No need to iterate through the period. Just manipulate current hour number.
This probably might be reworked with plain unix timestamps for even simplier code:
<?php
// This is just for test, in the code below
// should be replaced with plain `(new DateTime)`
$now = new DateTime('2022-12-18 19:29:00');
// Set the next fraction-3 hour
$result = ( new DateTime )
->setTime(
// Divide current hour by 3, take integer part,
// add one, multiply by 3, take 24 modulo
// (to get '0' instead of '24' and '3' instead of '27')
(((int)($now->format('H') / 3) + 1 ) * 3) % 24,
0
);
// The result
// Note, the date is current here
print $result->format('H:i');
UPDATE: for dynamic start time, when the hour is not x3
<?php
# The alternative start time
// $start = new DateTime('2022-12-18 13:00:00');
$start = new DateTime('2022-12-18 15:00:00');
// Moving magic number out of the calculations
$period_hours = 3;
// This is just for test, in the below code
// should be replaced with plain `(new DateTime)`
$now = new DateTime('2022-12-18 19:29:00');
// Set the next fraction-period hour
$result = ( new DateTime )
->setTime(
// Divide current hour by the period, take integer part,
// add one, multiply by period, add start DT shift,
// and at last take 24 modulo
// (to get '0' instead of '24', '3' instead of '27' etc)
(
(((int)($now->format('H') / $period_hours) + 1 ) * $period_hours)
+ $start->format('H') % $period_hours
)
% 24,
0
);
// The result
// Note, the day is current here
print $result->format('H:i');

The following will properly take into account the start time and return an object with the same timezone and other properties as its input.
function intervalToSeconds(DateInterval $interval) {
$d1 = new DateTimeImmutable('', new DateTimezone('UTC'));
$d2 = $d1->add($interval);
return $d2->getTimestamp() - $d1->getTimestamp();
}
function getNextDateTime(DateTime $target, DateTime $start, DateInterval $interval) {
$t_start = $start->getTimestamp();
$t_target = $target->getTimestamp();
$t_since = $t_target - $t_start;
$t_interval = intervalToSeconds($interval);
$t_next = ( intdiv($t_since, $t_interval) + 1 ) * $t_interval + $t_start;
return (clone $target)->setTimestamp($t_next);
}
$start = new DateTime('2022-12-18 16:00:00', new DateTimezone('UTC'));
$interval = DateInterval::createFromDateString('3 hours');
$now = new DateTime('2022-12-20 23:45:01', new DateTimezone('America/Vancouver'));
var_dump(
$start, $interval,
getNextDateTime($now, $start, $interval)
);
Output:
object(DateTime)#1 (3) {
["date"]=>
string(26) "2022-12-18 16:00:00.000000"
["timezone_type"]=>
int(3)
["timezone"]=>
string(3) "UTC"
}
object(DateInterval)#2 (2) {
["from_string"]=>
bool(true)
["date_string"]=>
string(7) "3 hours"
}
object(DateTime)#5 (3) {
["date"]=>
string(26) "2022-12-21 02:00:00.000000"
["timezone_type"]=>
int(3)
["timezone"]=>
string(17) "America/Vancouver"
}

Related

How to make a code that finds sundays when date picked a start date and end date in PHP

I'm currently working on a project (membership system) with a start date and expiration date. For example the user chose the 30 days membership and his starting date is Sep, 05,2022.
So 09/05/2022 + 30 days is equals to expiration date.
But I want to skip sundays when adding the 30 days in the starting date.
How can I do that in PHP?
Edit: Sorry I'm a begginer, I tried all of your recommendation but it doesn't match to what I want. These are my code in computing the expiredate.
date_default_timezone_set('Asia/Manila');
$startDate = date('Y-m-d');
$many_days = 30;//the value of this comes from database but let's assume that its 30
$expireDate = date('Y-m-d', strtotime('+'.$many_days.' day'));//expiredate
I would use a loop. It's also a great start if you're going to extend that to holidays. Otherwise, it is an overkill but I really don't think you'd suffer performance issues.
So here we go:
$start_date = "2022-09-05";
$start = new DateTime($start_date);
$days = 30;
while ($days) {
// P1D means a period of 1 day
$start->add(new DateInterval('P1D'));
$day = $start->format('N');
// 7 = sunday
if ($day != 7) {
$days--;
}
}
print_r($start);
// output:
// [date] => 2022-10-10 00:00:00.000000
Full weeks should be pre-calculated for better performance. The following function allows you to name one or more days of the week that are not counted.
/**
* Add days without specific days of the week
*
* #param DateTime $startDate start Date
* #param int $days number of days
* #param array $withoutDays array with elements "Mon", "Tue", "Wed", "Thu", "Fri", "Sat","Sun"
* #return DateTime
*/
function addDaysWithout(DateTime $startDate ,int $days, array $withoutDays = []) : DateTime
{
//validate $withoutDays
$validWeekDays = 7 - count($withoutDays);
$validIdentifiers = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat","Sun"];
if($validWeekDays <= 0 OR
$withoutDays != array_intersect($withoutDays,$validIdentifiers)){
$msg = 'Invalid Argument "'.implode(',',$withoutDays).'" in withoutDays';
throw new \InvalidArgumentException($msg);
}
$start = clone $startDate;
$fullWeeks = (int)($days/$validWeekDays)-1;
if($fullWeeks > 0){
$start ->modify($fullWeeks.' weeks');
$days -= $fullWeeks * $validWeekDays;
}
while($days){
$start->modify('+1 Day');
if(!in_array($start->format('D'),$withoutDays)){
--$days;
}
}
return $start;
}
Application examples:
$start = date_create('2022-09-05');
$dt = addDaysWithout($start,30,["Sun"]);
var_dump($dt);
//object(DateTime)#3 (3) { ["date"]=> string(26) "2022-10-10 00:00:00.000000"
$start = date_create('2022-09-05');
$dt = addDaysWithout($start,30,["Mon","Sun"]);
var_dump($dt);
//object(DateTime)#3 (3) { ["date"]=> string(26) "2022-10-15
Demo: https://3v4l.org/AZX0E

Get financial registry every 15 days

I'm working on a system that controls the finances of the company. One of the features is to schedule fixed income/expenses that happens every 15 days.
I already have a function that schedules incomes/expenses on a weekly basis, and I tried to copy that to make it work every 15 days. This is the code:
$registry_list = array();
foreach ($list as $item) {
/**
* Day of month 01-31 of the income/expense.
* This is the day the user created the first registered item.
*/
$firstDay = $item->day;
// How many days there is on the month
$daysOnMonth = cal_days_in_month(CAL_GREGORIAN, $params['month'], $params['year']);
// We check if the current month/year is greater than the first registry
// expire_date is always day 01. The day itself is on the property 'day'
if ($item->expire_date < $params['year'].$params['month'].'-01') {
$newDate = new \DateTime($item->expire_date);
$newYear = $newDate->format('Y');
$newMonth = $newDate->format('m');
$newDate = $newDate->setDate($newYear, $newMonth, $firstDay);
$newDate = $newDate->format('l');
$firstDay = strtotime("first ".$newDate." ".$params['year']."-".$params['month']);
$firstDay = date('d', $firstDay);
}
// First registry
$newItem = clone($item);
$newItem->expire_date = $params['year'].'-'.$params['month'].'-'.$firstDay;
$newItem = new Registry($newItem); // Just to format some data
array_push($registry_list, $newItem);
while ($firstDay < $daysOnMonth) {
$firstDay = $firstDay + 14; // 14 because the day itself count as +1
if ($firstDay <= $daysOnMonth) {
$newItem=clone($item);
$newItem->expire_date = $params['year'].'-'.$params['month'].'-'.$firstDay;
$newItem = new Registry($newItem);
array_push($registry_list, $newItem);
}
}
}
return $registry_list;
This code runs just fine when the month/year is the same as when the registry was created. But as it goes to the next month, sometimes it's not correct. For example, if I start it on 02/08/2022 (d/m/Y) the schedules for that month are:
02/08/2022
16/08/2022
30/08/2022
Which is correct. However, when September starts it messes with the list. Since I'm starting on the first weekday (based on the first registry) it's not always correct. So September list is as follow:
06/09/2022
20/09/2022
Which is incorrect. This should be the correct dates:
13/09/2022
27/09/2022
Because the last one from August was 30/08/2022.
I just don't know how to identify when I need to skip the first week of the month based on the last month. I also don't want to always start counting from the date the registry was created, otherwise I would go a long way to find the correct date when we pass a year or 2.
Since it's business financial control, they tend to look further on the calendar.
Is there a way to fix this issue?
This is the kind of problem that DateInterval can solve pretty easily:
/*
This function returns all dates between two dates at the given interval
Interval syntax: https://www.php.net/manual/en/dateinterval.construct.php
Date format syntax: https://www.php.net/manual/en/datetime.format.php
*/
function get_schedule_dates($start_date, $end_date, $interval, $format = 'Y-m-d')
{
$date = new DateTime($start_date);
$end = new DateTime($end_date);
$schedule = [$date->format($format)];
while ($date->add(new DateInterval($interval)) <= $end)
$schedule[] = $date->format($format);
return $schedule;
}
var_dump(get_schedule_dates('2022-08-02', '2022-10-03', 'P14D', 'd/m/Y'));
Yields
array(5) {
[0]=>
string(10) "02/08/2022"
[1]=>
string(10) "16/08/2022"
[2]=>
string(10) "30/08/2022"
[3]=>
string(10) "13/09/2022"
[4]=>
string(10) "27/09/2022"
}
What you could do is get the expiry_date of the first item as the start date, then calculate a date in advance, say 2 years, based on that, and use that as the end date.
You could then iterate through the scheduled dates and clone and create new Registry objects, pushing to $registry_list

How to find date difference excluding specific dates in array - PHP

I know how to calculate date difference using PHP like;
$newdate = "01-03-2013";
$olddate = "01-06-2013";
$date_diff = abs(strtotime($olddate)-strtotime($newdate)) / 86400;
echo $date_diff;
But suppose, if I have some dates in an array like;
$datesarray = array(10-05-2013, 20-05-2013, 12-08-2013);
etc., holding some specific dates, is it possible to calculate date difference excluding the dates in array along with the Sundays, if they lie in between the start and end dates?
just loop through the $datesarray and check for each one if it's between the $olddate and $newdate. If so, increase a $counter variable (which starts at 0, obviously).
Then $date_diff - $counter will give you the expected result.
I would use the DateTime class in a custom function like this:
function dates_between(DateTime $start, DateTime $end, $format = 'm-d-Y') {
$date = $start;
$dates = array();
$oneDay = new DateInterval('P1D');
// push all dates between start and end to the result
while(($date = $date->add($oneDay)) < $end) {
$dates []= $date->format($format);
}
return $dates;
}
Example usage:
$now = new DateTime();
$nextWeek = new DateTime('+1 week');
var_dump(dates_between($now, $nextWeek));
Output:
array(6) {
[0] =>
string(10) "07-12-2013"
[1] =>
string(10) "07-13-2013"
[2] =>
string(10) "07-14-2013"
[3] =>
string(10) "07-15-2013"
[4] =>
string(10) "07-16-2013"
[5] =>
string(10) "07-17-2013"
}
The following script creates and array of timestamps from your array of UK dates and then calculates the max and min timestamps to calculate the days difference.
If the timestamp defaults to 0, it is not added to the timestamp array, avoiding huge results for one bad date defaulting to the epoch
I.e. When date is invalid or pre epoch 1/1/1970
<?php
$datesarray = array('10-05-2013', '20-05-2013', '12-08-2013');
$date_diff=0; // default for 0 or 1 dates
if( (is_array($datesarray)) && (sizeof($datesarray)>1) )
{
$timestampsarray=array();
reset($datesarray);
while(list($key,$value)=each($datesarray))
{
$timestamp=timestamp_from_UK($value);
if($timestamp!=0) $timestampsarray[$key]=$timestamp;
}
$date_diff = abs(max($timestampsarray)-min($timestampsarray)) / 86400;
}
echo $date_diff;
function timestamp_from_UK($ukdatetime)
{
// where PHP is processing UK dates d-m-y correctly
$ukdatetime=str_replace('/', '-', $ukdatetime);
if(date("d", strtotime("1-2-1970"))==1) return strtotime($ukdatetime);
// Fallback script for when PHP is NOT processing UK dates
$success=false;
if(!$success) $success=preg_match("/([0-9]{1,2})[^0-9]([0-9]{1,2})[^0-9]([0-9]{2,4})[^0-9]([0-9]{1,2})[^0-9]([0-9]{1,2})[^0-9]([0-9]{1,2})/", $ukdatetime, $matches);
if(!$success) $success=preg_match("/([0-9]{1,2})[^0-9]([0-9]{1,2})[^0-9]([0-9]{2,4})[^0-9]([0-9]{1,2})[^0-9]([0-9]{1,2})/", $ukdatetime, $matches);
if(!$success) $success=preg_match("/([0-9]{1,2})[^0-9]([0-9]{1,2})[^0-9]([0-9]{2,4})/", $ukdatetime, $matches);
if(!$success) return 0;
// ensure all values are set - to avoid invalid offset
for($i=4;$i<=6;$i++)
{
if(!isset($matches[$i])) $matches[$i]=0;
}
// $matches[0] is the full matched string
return mktime($matches[4], $matches[5], $matches[6], $matches[2], $matches[1], $matches[3]);
}
?>

Random time and date between 2 date values

I'm trying to write a php script (or line of code) to echo a random time and date between 2 dates, eg
2012-12-24 13:03
which would be between my chosen dates of 1st October 2012 and 1st Jan 2013.
Any ideas how best to do this? Thanks in advance.
Easy :) Just choose 2 random dates, convert to EPOCH, and random between these 2 values :)
EPOCH - The time since 1/1/1970, in seconds.
You can use the strtotime() function to make date-strings turn into epoch time, and the date() function to make it the other way back.
function rand_date($min_date, $max_date) {
/* Gets 2 dates as string, earlier and later date.
Returns date in between them.
*/
$min_epoch = strtotime($min_date);
$max_epoch = strtotime($max_date);
$rand_epoch = rand($min_epoch, $max_epoch);
return date('Y-m-d H:i:s', $rand_epoch);
}
You probably want to define a resolution, for example one minute, or three minutes or 15 seconds or one and a half day or what not. The randomness should be applied on the whole period, I've choosen one minute here for exemplary purposes (there are 132480 minutes in your period).
$start = new Datetime('1st October 2012');
$end = new Datetime('1st Jan 2013');
$interval = new DateInterval('PT1M'); // Resolution: 1 Minute
$period = new DatePeriod($start, $interval, $end);
$random = new RandomIterator($period);
list($result) = iterator_to_array($random, false) ? : [null];
This for example gives:
class DateTime#7 (3) {
public $date =>
string(19) "2012-10-16 02:06:00"
public $timezone_type =>
int(3)
public $timezone =>
string(13) "Europe/Berlin"
}
You can find the RandomIterator here. Without it, it will take a little longer (ca. 1.5 the number of iterations compared to the example above) using:
$count = iterator_count($period);
$random = rand(1, $count);
$limited = new LimitIterator(new IteratorIterator($period), $random - 1, 1);
$limited->rewind();
$result = $limited->current();
I also tried with seconds, but that would take quite long. You probably want first to find a random day (92 days), and then some random time in it.
Also I've run some tests and I could not find any benefit in using DatePeriod so far as long as you're on common resolutions like seconds:
$start = new Datetime('1st October 2012');
$end = new Datetime('1st Jan 2013');
$random = new DateTime('#' . mt_rand($start->getTimestamp(), $end->getTimestamp()));
or minutes:
/**
* #param DateTime $start
* #param DateTime $end
* #param int|DateInterval $resolution in Seconds or as DateInterval
* #return DateTime
*/
$randomTime = function (DateTime $start, DateTime $end, $resolution = 1) {
if ($resolution instanceof DateInterval) {
$interval = $resolution;
$resolution = ($interval->m * 2.62974e6 + $interval->d) * 86400 + $interval->h * 60 + $interval->s;
}
$startValue = floor($start->getTimestamp() / $resolution);
$endValue = ceil($end->getTimestamp() / $resolution);
$random = mt_rand($startValue, $endValue) * $resolution;
return new DateTime('#' . $random);
};
$random = $randomTime($start, $end, 60);
Assuming you want to include October 1st, but not include Jan 1st...
$start = strtotime("2012-10-01 00:00:00");
$end = strtotime("2012-12-31 23:59:59");
$randomDate = date("Y-m-d H:i:s", rand($start, $end));
echo $randomDate;
so crazy it just may worK
function randomDate($start_date, $end_date)
{
//make timetamps
$min = strtotime($start_date);
$max = strtotime($end_date);
//random date
$rand_date = rand($min, $max);
//format it
return date('Y-m-d H:i:s', $rand_date);
}
Here's some code to accomplish this:
$randDate=date('Y-m-d', mt_rand(strtotime('2012-10-01'), strtotime('2013-01-01')));
Okay, here's something
$date_start = strtotime('1 October 2012');
$date_end = strtotime('1 January 2013');
$rand_date = rand($date_start, $date_end);
echo(date('d.m.Y H:i', $rand_date));

Function to determine wether the current datetime is within a user set date and time in PHP [duplicate]

This question already has answers here:
How to check if a date is in a given range?
(10 answers)
Finding whether time is in a defined range [duplicate]
(5 answers)
Closed 8 years ago.
I've been working at this code off and on for the past few days and can't figure it out.
What I need to do is return from a function either a 0 or 1 depending on if the current time is within the times set by a user. If the time and date is within a 4 value array set by the user, then return 1, if not, return 0. The user can set multiple arrays for multiple periods of times.
I've been trying to work with this code for a while:
functions.php:
function determineWoE($woe) {
$curDayWeek = date('N');
$curTime = date('H:i');
$amountWoE = count($woe['WoEDayTimes']); // Determine how many WoE times we have.
if ( $amountWoE == 0 ) {
return 0; // There are no WoE's set! WoE can't be on!
}
for ( $i=0; $i < $amountWoE; $i++ ) {
if ( $woe['WoEDayTimes'][$i][0] == $curDayWeek && $woe['WoEDayTimes'][$i][2] == $curDayWeek ) { // Check the day of the week.
if ( $woe['WoEDayTimes'][$i][1] >= $curTime && $woe['WoEDayTimes'][$i][3] <= $curTime ) { // Check current time of day.
// WoE is active
return 1;
}
else {
// WoE is not active
return 0;
}
}
else {
// WoE is not active
return 0;
}
}
}
And...where the user sets as many periods of time for this feature that they want:
$woe = array( // Configuration options for WoE and times.
// -- WoE days and times --
// First parameter: Starding day 1=Monday / 2=Tuesday / 3=Wednesday / 4=Thursday / 5=Friday / 6=Saturday / 7=Sunday
// Second parameter: Starting hour in 24-hr format.
// Third paramter: Ending day (possible value is same or different as starting day).
// Fourth (final) parameter: Ending hour in 24-hr format.
'WoEDayTimes' => array(
array(6, '18:00', 6, '19:00'), // Example: Starts Saturday 6:00 PM and ends Saturday 7:00 PM
array(3, '14:00', 3, '15:00') // Example: Starts Wednesday 2:00 PM and ends Wednesday 3:00 PM
),
);
But, no matter what I do...the function determineWoE always returns 0.
Am I needing a foreach in the function instead of a for? How do I get determineWoE to return 1 if the time is within the user settable times?
Tried changing the for to a foreach:
foreach ( $woe['WoEDayTimes'] as $i ) {
And now I get error:
Warning: Illegal offset type in /var/www/jemstuff.com/htdocs/ero/functions.php on line 76
...which I have no idea why I would be getting that error. Line 76 is:
if ( $woe['WoEDayTimes'][$i][0] == $curDayWeek && $woe['WoEDayTimes'][$i][2] == $curDayWeek ) { // Check the day of the week.
In functions.php
var_dump($woe)
array(2) { ["WhoOnline"]=> string(2) "no" ["WoEDayTimes"]=> array(2) { [0]=> array(4) { [0]=> int(6) [1]=> string(5) "18:00" [2]=> int(6) [3]=> string(5) "19:00" } [1]=> array(4) { [0]=> int(3) [1]=> string(5) "14:00" [2]=> int(3) [3]=> string(5) "15:00" } } }
Thanks for any help you can provide to me. :)
A couple minor points:
A foreach loop and a for loop would both work fine, but you might find the foreach more covenient, since you wouldn't have to count() the days/times to check for.
You should return boolean true or false instead of 1 or 0.
I'm not sure why you're getting that error, but the bigger problem I see is how you compare the times. You cast the string times to numeric types, and that won't convert entirely like you think it will. For example...
"14:00" < "14:59"
...Would be false, because it casts both strings to 14. Thus, the first string actually equals the second.
You might be better off converting the strings to Unix Timestamps (which are the seconds since 1/1/1970), and comparing those.
Here's a rough idea of how I would do it:
// Function to help get a timestamp, when only given a day and a time
// $today is the current integer day
// $str should be 'last <day>', 'next <day>', or 'today'
// $time should be a time in the form of hh:mm
function specialStrtotime($today, $day, $time) {
// An array to turn integer days into textual days
static $days = array(
1 => 'Monday',
2 => 'Tuesday',
3 => 'Wednesday',
4 => 'Thursday',
5 => 'Friday',
6 => 'Saturday',
7 => 'Sunday'
);
// Determine if the day (this week) is in the past, future, or today
if ($day < $today) {
$str = 'last ' . $days[$day];
} else if ($day > $today) {
$str = 'next ' . $days[$day];
} else {
$str = 'today';
}
// Get the day, at 00:00
$r = strtotime($str);
// Add the amount of seconds the time represents
$time = explode(':', $time);
$r += ($time[0] * 3600) + ($time[1] * 60);
// Return the timestamp
return $;
}
// Your function, modified
function determineWoE($timeNow, $woe) {
$dayNow = (int) date('N', $timeNow);
foreach ($woe as $a) {
// Determine current day
// Determine the first timestamp
$timeFirst = specialStrtotime($dayNow, $a[0], $a[1]);
// Determine the second timestamp
$timeSecond = specialStrtotime($dayNow, $a[2], $a[3]);
// See if current time is within the two timestamps
if ($timeNow > $timeFirst && $timeNow < $timeSecond) {
return true;
}
}
return false;
}
// Example of usage
$timeNow = time();
if (determineWoE($timeNow, $woe['WoEDayTimes'])) {
echo 'Yes!';
} else {
echo 'No!';
}
Good luck!

Categories