I'm trying to store chosen weekdays in one field in DB. Bitwise seem to be perfect for that, but PHP dosen't give the expected result, and i don't know why.
mon tue wnd thu fri sat sun
1 1 1 1 1 1 1
1 2 4 8 16 32 64
so to select Tue and Fri: 18 (0100100)
and for example, to check if sunday is selected: decbin(18) & decbin(64) (should return "empty"),
but the reuslts are unexpected.
That's because you need to left-pad those binary strings with leading zeroes to the same length. decbin() creates a string that discards any leading zeroes. You're anding strings like:
10010 // 18
1000000 // 64
Either use str_pad() too add leading zeroes to a fixed length, or save a function call and use sprintf to do both the base conversion and the padding in a single step
sprintf('%08b', 18) & sprintf('%08b', 64)
You don't have to work with binary strings at all to work with bit flags.
Declare your days as integers 1,2,4,8 etc.
To make a value that is Monday and Friday just binary OR the values.
To check that a number contains a day, binary AND them and check that the result is equal to the day your checking.
Although technically these are all binary operations, you don't actually have to ever see or use the binary strings.
Some examples with the dump results below... please ignore my use of extract, it was just quicker this way
<?php
// Day names
$dayNames = array(
'monday',
'tuesday',
'wednesday',
'thursday',
'friday',
'saturday',
'sunday',
);
// Bit values
$dayValues = array();
foreach($dayNames as $key => $value) {
$dayValues[$value] = 1 << $key;
}
var_dump($dayValues);
extract($dayValues);
// Monday and Tuesday
$monANDtue = $monday | $tuesday;
var_dump($monANDtue);
var_dump(decbin($monANDtue));
// Monday and Sunday
$monANDsun = $monday | $sunday;
var_dump($monANDsun);
var_dump(decbin($monANDsun));
// Is on Monday?
$isOnMonday = ($monANDsun & $monday) == $monday;
var_dump($isOnMonday);
// Is on Tuesday?
$isOnTuesday = ($monANDsun & $tuesday) == $tuesday;
var_dump($isOnTuesday);
// Is on Tuesday?
$isOnSunday = ($monANDsun & $sunday) == $sunday;
var_dump($isOnSunday);
?>
and the output
/vhost/virtual/sandbox/public/index.php:27
array(7) {
[monday] = int(1) 1
[tuesday] = int(1) 2
[wednesday] = int(1) 4
[thursday] = int(1) 8
[friday] = int(2) 16
[saturday] = int(2) 32
[sunday] = int(2) 64
}
/vhost/virtual/sandbox/public/index.php:33
int(1) 3
/vhost/virtual/sandbox/public/index.php:34
string(2) "11"
/vhost/virtual/sandbox/public/index.php:40
int(2) 65
/vhost/virtual/sandbox/public/index.php:41
string(7) "1000001"
/vhost/virtual/sandbox/public/index.php:47
bool(true)
/vhost/virtual/sandbox/public/index.php:51
bool(false)
/vhost/virtual/sandbox/public/index.php:55
bool(true)
Related
I've a case where i need to figure a formula to calculate total time between date range sets (from->to) that can overlap each other.
This is going to be used in a ticketing system where i need to calculate the total open time of all tickets (as part of an SLA agreement) without double calculating time that has been already counted.
For example, in the records below:
TicketID
Open Date
Close Date
Ticket 1
'2023-01-02 09:00:00'
'2023-01-02 14:00:00'
Ticket 2
'2023-01-02 11:00:00'
'2023-01-02 15:00:00'
Ticket 3
'2023-01-14 10:00:00'
'2023-01-14 11:00:00'
the total time i would need to have is:
from '2023-01-02 09:00:00' to '2023-01-02 15:00:00'
and from '2023-01-14 10:00:00' to '2023-01-02 11:00:00',
thus a total of 7 hours.
An ideas on where to start?
I've search for similar questions, such as this one PHP Determine when multiple(n) datetime ranges overlap each other but it is somewhat different than the one i need to have.
The following solution first compresses all overlapping intervals into one using the reduceOverlap function. This means that intervals are then available which do not overlap. These are then added using the diffTotal function. As a result, a DateInterval object is available that can be formatted as you wish with the Format method.
<?php
function reduceOverlap(array $arr){
$flag = true;
while($flag){
$flag = false;
foreach($arr as $key1 => $row1){
foreach($arr as $key2 => $row2){
if($key1 === $key2) continue;
if(($row1['open'] >= $row2['open'] && $row1['open'] <= $row2['close']) OR
($row1['close'] >= $row2['open'] && $row1['close'] <= $row2['close'])){
$arr[$key1]['open'] = min($row1['open'],$row2['open']);
$arr[$key1]['close'] = max($row1['close'],$row2['close']);
unset($arr[$key2]);
$flag = true;
break 2;
}
}
}
}
return $arr;
}
function diffTotal(array $arr){
$date = date_create('1970-01-01 00:00');
foreach($arr as $row){
$diff = date_create($row['open'])->diff(date_create($row['close']));
$date->add($diff);
}
return date_create('1970-01-01 00:00')->diff($date);
}
$times = [
['open' => '2023-01-02 09:00:00', 'close' => '2023-01-02 14:00:00'],
['open' => '2023-01-02 11:00:00', 'close' => '2023-01-02 15:00:00'],
['open' => '2023-01-14 10:00:00', 'close' => '2023-01-14 11:00:00'],
];
$arr = reduceOverlap($times);
$diffTotal = diffTotal($arr);
var_dump($diffTotal);
Output:
object(DateInterval)#2 (10) {
["y"]=>
int(0)
["m"]=>
int(0)
["d"]=>
int(0)
["h"]=>
int(7)
["i"]=>
int(0)
["s"]=>
int(0)
["f"]=>
float(0)
["invert"]=>
int(0)
["days"]=>
int(0)
["from_string"]=>
bool(false)
}
try self: https://3v4l.org/MPNPC
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"
}
This is slightly OT for SO, because I'm not trying to solve a specific problem, instead just to understand how something might be implemented. But I am after code, so let's see how it goes...
Let's say we had a checkbox for each day of the week, and we decided to store any combination of those checkboxes as a single number, such that:
0 = no days
1 = Monday
2 = Tuesday
4 = Wednesday
8 = Thursday
16 = Friday
32 = Saturday
64 = Sunday
127 = everyday
How might one go about implementing that logic in PHP so that if I submitted say, "13", PHP would know to tick only the Monday, Wednesday and Thursday checkboxes?
Bitwise ANDs:
$input = 13;
if( $input & 1 ) {
echo 'Monday';
}
if( $input & 2 ) {
echo 'Tuesday';
}
if( $input & 4 ) {
echo 'Wednesday';
}
// etc
edit
You can avoid the ifs with something like:
$input = 13;
$days = array('mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun');
for( $i=0; $i<7; $i++ ) {
$daybit = pow(2,$i);
if( $input & $daybit ) {
echo $days[$i] . ' ';
}
}
//output: mon wed thu
There's more than these two ways to skin this particular cat, but the 'best' way depends on what your result/output needs to be.
As to avoid code structure duplication (lots of similar if clauses) and introducing extra "magic" numbers (2, 7), as shown Sammitch's working suggestions, I'd prefer the following.
$daymap = array(
1 => Monday,
2 => Tuesday,
4 => Wednesday,
8 => Thursday,
16 => Friday,
32 => Saturday,
64 => Sunday
);
$input = 13;
foreach ($daymap as $code => $name) {
if ($input & $code) {
echo $name.' ';
}
}
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!
My mind seems to be going blank on this one.
I have to write something to figure out which date range todays day/month combination fits into.
I have a set amount of date ranges, which are:
$dateRanges = array(
1 => "16 January to 30 April",
2 => "1 May to 30 June",
3 => "1 July to 15 August",
4 => "16 August to 15 September",
5 => "15 September to 15 October",
6 => "16 October to 15 January"
);
All I'm trying to return is the array key of the range the current date fits into.
At the moment all thats going through my head is I'll have to set up a large if statement to look at the current date('j') and date('n') and match the results up. But surely thats pretty messy and not very efficient?
Any ideas of a better way to approach this problem would be much appreciated!
$today = time();
foreach ($dateRanges as $key => $range) {
list($start, $end) = explode(' to ', $range);
$start .= ' ' . date('Y'); // add 2011 to the string
$end .= ' ' . date('Y');
if ((strtotime($start) <= $today) && (strtotime($end) >= $today)) {
break;
}
}
$key will be either the index of the matching date range, or null/false.
This is a variation on Mark B's answer, but made more efficient by turning this into pure numeric comparisons:
function get_today () {
$dateRanges = array(
0 => 116, // 16th Jan
1 => 501, // 1st May
2 => 701, // 1st July ..etc..
3 => 816,
4 => 916,
5 => 1016
);
$today = (int) date('nd');
foreach ($dateRanges as $key => $date) {
if ($today < $date) {
$result = $key;
break;
}
}
return (empty($result)) ? 6 : $result;
}
Returns an integer matching the keys in your sample array
Create DateTime instances for the values in the array und use simple comparison operators like > and <
Just use strtotime to create a UNIX epoch, then use the inbuilt < and > operators.
http://www.php.net/manual/en/function.strtotime.php
$time_min = strtotime("17 January 2011");
$time_max = strtotime("30 April 2011");
if ($time >= $time_min && $time < $time_max)
{
echo "Time is within range!";
}
You can then just expand this to use the array of ranges you specified.