5
votes

I have a php code as shown below in which I want to display anything in between two calendar days of the week.

The values coming inside $data->{"select_start_day"}; $data->{"start_time"}; $data->{"select_end_day"}; and $data->{"end_time"}; is controlled by the user.

PHP Code:

    if (file_exists('feeds/ptp-ess_landing.json')) {
    $data = json_decode(file_get_contents('feeds/ptp-ess_landing.json'));
    }

    date_default_timezone_set('America/Toronto');
    $arradate = strtolower(date('D'));
    $nowtime = (int)date('His');


    $start_day=$data->{"select_start_day"};
    $start_time=$data->{"start_time"};

    $end_day=$data->{"select_end_day"};
    $end_time=$data->{"end_time"};

For example, let us suppose the user enter $start_day as sun $start_time as 143400 $end_day as wed $end_time as 140000

The above time duration means we are in range and it should display anything we want to display until tomorrow 2pm as tomorrow is wednesday. I am in EST.

I am using the following code in order to pull the current day of the week and time:

date_default_timezone_set('America/Toronto');
$arradate = strtolower(date('D'));
$nowtime = (int)date('His');

Problem Statement:

I am wondering what if logic I need to use so that its print anything Sunday 143400 and Wednesday 140000.

if()    {
    echo "Its in range";
}

Cases:

  1. If the override is supposed to apply from Monday at 8am to Monday at 6pm and today is Wednesday then the override doesn't apply.

  2. If its Monday 6 pm and Friday 6 pm then the override will work for 6 hours on Monday, Tuesday whole day, Wednesday whole day, Thursday whole day, Friday upto 6pm

  3. If it Sunday 6pm and Monday 6pm then the override will work for 6 hours on Monday and 18 hours on Monday.

  4. If its Tuesday 6pm and Friday 6pm and today is Tuesday 9pm then the override will apply.

  5. if its Thursday 6pm and Wednesday 6pm then the override will work for 6 hours on thursday, 24 hours on Friday, 24 hours on Saturday, 24 hours on Sunday, 24 hours on Monday, 24 hours on Tuesday, 24 hours on Wednesday and 18 hours on Thursday.

7
That is coming from a JSON. Its a separate admin portal. I am adding more code to make it clear.flash
Yeah, maybe a little more sense, but why do you use braces? Why not $data->select_start_day?Dharman
It just became a habit. I agree we can use without braces.flash
When does your week start? Can the range be Thursday-Wednesday?Dharman
I expounded on the answer.Ususipse

7 Answers

2
votes

The difficult part of this question lies in handling ranges that 'wrap' around the end of the week, i.e. your example case 5

I'd suggest setting up a reference array of days that covers two weeks

$days = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun'];
$days = array_merge($days, $days);

Slice it so that it starts at the day of the start point (it will be reindexed from 0)

$days = array_slice($days, array_search($startDay, $days));

You can then build a reference integer for both now and the end point

$nowRef = (int) (array_search($nowDay, $days) . $nowTime);
$endRef = (int) (array_search($endDay, $days) . $endTime);

Note that you could do the same for the start point, but as the days array starts with $startDay (index 0) this is equivalent to $startTime

Your if condition then simply becomes

if ($nowRef >= $startTime && $nowRef <= $endRef) {
   // IN RANGE
}

N.B. This assumes that your user inputs have been validated, and that if the start day and end day are the same then the end time is greater than the start time


Your naming convention is a bit inconsistent, so I have renamed some of your variables and switched to camel case for readability

$nowDay = $arradate;
$nowTime = $nowtime;

$startDay = $start_day;
$startTime = $start_time;

$endDay = $end_day;
$endTime = $end_time;
1
votes

Here is my stab at it, using a mapping table and string concatenation. It doesn't work if the days are in reversed order. e.g. If today is Sunday and the value of select_start_day is Fri and the value of select_end_day is Mon, then it won't work.

<?php

$arr = (object) [
    'select_start_day' => 'wed',
    'start_time' => 143400,
    'select_end_day' => 'wed',
    'end_time' => 220000
];

$map_daysToNumbers = ['sun'=>1, 'mon'=>2, 'tue'=>3, 'wed'=>4, 'thu'=>5, 'fri'=>6, 'sat'=>7];

$startString = $map_daysToNumbers[$arr->select_start_day] . str_pad($arr->start_time, 6, '0', STR_PAD_LEFT);
$endString = $map_daysToNumbers[$arr->select_end_day] . str_pad($arr->end_time, 6, '0', STR_PAD_LEFT);

$tz = new \DateTimeZone('America/Toronto');

$today = new \DateTime('now', $tz);

$todayString = ($today->format('w')+1) . $today->format('His');

if($startString <= $todayString && $todayString <= $endString){
    echo 'In range';
}

or date-based solution. Neither of them is guaranteed to fulfil your needs.

$arr = (object) [
    'select_start_day' => 'tue',
    'start_time' => 143400,
    'select_end_day' => 'wed',
    'end_time' => 220000
];

$tz = new \DateTimeZone('America/Toronto');

// extrapolate the start date looking 7 days back
$sDate = new \DateTime('tomorrow midnight', $tz);
$sDate->modify('last '.$arr->select_start_day);
$sDate->setTime(...str_split(str_pad($arr->start_time, 6, '0', STR_PAD_LEFT), 2));
// or bound the start date to be between last sunday and next saturday
$sDate = new \DateTime('Saturday last week', $tz);
$sDate->modify('next '.$arr->select_start_day);
$sDate->setTime(...str_split(str_pad($arr->start_time, 6, '0', STR_PAD_LEFT), 2));

// extrapolate the end date
$eDate =  clone $sDate;
$eDate->modify('yesterday'); // workaround to consider the same day possibility
$eDate->modify('next '.$arr->select_end_day);
$eDate->setTime(...str_split(str_pad($arr->end_time, 6, '0', STR_PAD_LEFT), 2));

// Test against today
$today = new \DateTime('now', $tz);

var_dump($sDate);
var_dump($eDate);
var_dump($today);

if($sDate <= $today && $today <= $eDate){
    echo 'In range';
}

The first way will always start in the past and depending on your range it might include today or not. The second will always be bound to the current week, which I believe is what you wanted.

As @mickmackusa and I said in the comments, the requirements given to you are vague and imprecise. You either need more rigid rules or a date based solution, i.e. you are given two precise dates (timestamps) and then you compare if a date falls between them. This is what I tried to do in my second option, but It is unknown if the date should be in the past or future.

1
votes

At first, I can recommend you use Object-oriented programming to better structuration of your code and decomposition of the task. You can create an abstraction to work with the weekday time. For example:

class WeekDayTime
{
    /** @var string[] map of the name of days and their number */
    const DAY_MAP = [
        'Mon' => 1,
        'Tue' => 2,
        'Wed' => 3,
        'Thu' => 4,
        'Fri' => 5,
        'Sat' => 6,
        'Sun' => 7
    ];

    /** @var int number of the day */
    private $dayNumber;

    /** @var int amount of hours */
    private $hours;

    /** @var int amount of minutes */
    private $minutes;

    /** @var int amount of seconds */
    private $seconds;

    /**
     * Constuctor
     * @param string $day number of the day
     * @param int $hours amount of hours
     * @param int $minutes amount of minutes
     * @param int $seconds amount of seconds
     */ 
    public function __construct(string $day, int $hours, int $minutes, int $seconds)
    {
        assert(array_key_exists($day, static::DAY_MAP), 'The day is incorrect');
        assert($hours < 24, 'The hours must be less than 24');
        assert($minutes < 60, 'The hours must be less than 60');
        assert($seconds < 60, 'The hours must be less than 60');
        $this->dayNumber = static::DAY_MAP[$day];
        $this->hours = $hours;
        $this->minutes = $minutes;
        $this->seconds = $seconds;
    }

    /**
     * Get number of the day
     * @return int number of the day
     */
    public function getDayNumber(): int
    {
        return $this->dayNumber;
    }

    /**
     * Get amount of hours
     * @return int amount of hours
     */
    public function getHours(): int
    {
        return $this->hours;
    }

    /**
     * Get amount of minutes
     * @return int amount of minutes
     */
    public function getMinutes(): int
    {
        return $this->minutes;
    }

     /**
     * Get amount of seconds
     * @return int amount of seconds
     */
    public function getSeconds(): int
    {
        return $this->seconds;
    }

    /**
     * Check if the current week day time is less the a denined week day time
     * @param WeekDayTime $value value which will be compared
     * @return bool status of the checking
     */
    public function isLessOrEqual(WeekDayTime $value): bool
    {
        $isLess = $this->dayNumber < $value->dayNumber;
        $isLessOrEqual = $this->dayNumber === $value->getDayNumber()
            && $this->hours <= $value->getHours()
            && $this->minutes <= $value->getMinutes()
            && $this->seconds <= $value->getSeconds();
        return $isLess || $isLessOrEqual;
    }

    /**
     * Check if the current week day time is greater the a denined week day time
     * @param WeekDayTime $value value which will be compared
     * @return bool status of the checking
     */
    public function isGreaterOrEqual(WeekDayTime $value): bool
    {
        $isGreater = $this->dayNumber > $value->dayNumber;
        $isGreaterOrEqual = $this->dayNumber === $value->getDayNumber()
            && $this->hours >= $value->getHours()
            && $this->minutes >= $value->getMinutes()
            && $this->seconds >= $value->getSeconds();
        return $isGreater || $isGreaterOrEqual;
    }
}

It will be the object-value which will have information about the day of week and time and methods to compare objects of this class. After it, you can create a class to contain a range of weekday time. For example:

class WeekDayTimeRange
{
    /** WeekDayTime range start */
    private $start;

    /** WeekDayTime range end */
    private $end;

    /**
     * Constuctor
     * @param WeekDayTime $start range start
     * @param WeekDayTime $end range end
     */
    public function __construct(WeekDayTime $start, WeekDayTime $end)
    {
        $this->start = $start;
        $this->end = $end;
    }

    /**
     * Check if a date-time occurs into the range
     * @param DateTimeInterface the date-time which will be checked
     * @return bool status of the checking
     */
    public function inRange(DateTimeInterface $dateTime): bool
    {}
}

As can you see this class has information about range start, range end and method to check the occurrence of any date-time into the range. If you want to check the occurrence into a range which has start value less then end value (for example from Monday to Friday) you can do the following implementation of inRange method:

public function inRange(DateTimeInterface $dateTime): bool
{
    $day = $dateTime->format('D');
    $hours = $dateTime->format('H');
    $minutes = $dateTime->format('i');
    $seconds = $dateTime->format('s');
    $weekDayTime = new WeekDayTime($day, $hours, $minutes, $seconds);

    return $this->start->isLessOrEqual($weekDayTime) && $this->end->isGreaterOrEqual($weekDayTime);
}

But if you want to check the occurrence into a range which has start value greater then end value (for example from Friday to Monday) you should break range to two ranges: from range start to week end and from week start to range end and to check the occurrence of the date-time into both ranges. For example:

public function inRange(DateTimeInterface $dateTime): bool
{
    $day = $dateTime->format('D');
    $hours = $dateTime->format('H');
    $minutes = $dateTime->format('i');
    $seconds = $dateTime->format('s');
    $weekDayTime = new WeekDayTime($day, $hours, $minutes, $seconds);

    // if the range end is less then range start we break the current range to two range
    if ($this->end->isLessOrEqual($this->start))  {
        $range1 = new WeekDayTimeRange($this->start, new WeekDayTime('Sun', 23,59,59));
        $range2 = new WeekDayTimeRange(new WeekDayTime('Mon', 0,0,0), $this->end);
        return $range1->inRange($dateTime) || $range2->inRange($dateTime);
    }

    return $this->start->isLessOrEqual($weekDayTime) && $this->end->isGreaterOrEqual($weekDayTime);
}

Example of using:

// Date occurs into the range from Tuesday to Friday
$start = new WeekDayTime('Tue', 10, 0,0);
$end = new WeekDayTime('Fri', 14, 0,0);
$range = new WeekDayTimeRange($start, $end);
$range->inRange(DateTime::createFromFormat('Y-m-d H:i:s', '2019-10-03 10:00:00'));

// Date doesn't occur into the range from Tuesday to Friday
$start = new WeekDayTime('Tue', 10, 0,0);
$end = new WeekDayTime('Fri', 14, 0,0);
$range = new WeekDayTimeRange($start, $end);
$range->inRange(DateTime::createFromFormat('Y-m-d H:i:s', '2019-10-05 10:00:00'));

// Date doesn't occur into the range from Friday to Tuesday
$start = new WeekDayTime('Fri', 14, 0,0);
$end = new WeekDayTime('Tue', 10, 0,0);
$range = new WeekDayTimeRange($start, $end);
$range->inRange(DateTime::createFromFormat('Y-m-d H:i:s', '2019-10-03 10:00:00'));

// Date occurs into the range from Friday to Tuesday
$start = new WeekDayTime('Fri', 14, 0,0);
$end = new WeekDayTime('Tue', 10, 0,0);
$range->inRange(DateTime::createFromFormat('Y-m-d H:i:s', '2019-10-05 10:00:00'));

You can see demo at sandbox

0
votes

Try This:

$dowMap = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];

$arradate = date('D');

$arradateNum = array_search($arradate, $dowMap);

$start_dayNum = array_search($start_day, $dowMap);

$end_dayNum = array_search($end_day, $dowMap);

if(
    ($arradateNum >= $start_dayNum && $nowtime>=$data->{"start_time"}) && 
    ($arradateNum <= $end_dayNum && $nowtime <= $data->{"end_time"})
) {
    echo "Show Anything";  
}

0
votes

You should thoroughly go through the PHP Manual and see how to create DateTime Objects and various date formats available.

First of all you need to convert the User Input to DateTimeObjects. Since the user input contains only Day (represented by D) and Time (represented by His), you could use following code to get these Objects.

date_default_timezone_set('America/Toronto'); //Setting the Timezone
//These are the User Inputs
$start_day = 'tue';
$start_time  = 181300;

$end_day = 'fri';
$end_time  = 134500;
//User Input Ends

//Create the Date Time Object for Start and End Date
$startDate = DateTime::createFromFormat ( 'D His', $start_day . ' '. $start_time);
$endDate = DateTime::createFromFormat ( 'D His', $end_day . ' '. $end_time);

You can check various options available for createFromFormat in the Manual

Now that you have the Date Time Objects you could get any value from them. Lets get the Day of the Week in Numeric Format as well as Hour in Numeric Format for both Start and End Date.

$startHour = $startDate->format('G');  //24-hour format of an hour without leading zeros
$startDay = $startDate->format('N');  //numeric representation of the day of the week

$endHour = $endDate->format('G'); //24-hour format of an hour without leading zeros
$endDay = $endDate->format('N');  //numeric representation of the day of the week

You can check about G and N format as well as other options available in Manual

We Need to check the time is between Tuesday 6PM to Friday 6PM.

First of all we will check whether the Day is between Tuesday and Friday. We have the $startDay and $endDay variables above to determine that. For Tuesday, the corresponding numeric value is 2 and for Friday it is 5.

For Time Range, we have the $startHour and $endHour variables. 6PM in the format is represented as 18 ( 12 + 6).

So we can check this condition using the following code:

if ( $startDay >= 2 && $endDay <= 5 && $startHour >= 18 and $endHour <= 18 ) {
    echo 'In Range';
} else {
    echo 'out of Range';
}

This way you can check any complex condition. You can see the Online Demo at 3v4l

0
votes

I would keep all the dates in the same format. Unix time is a great way to store the time because you can easily run calculations.

In your example you are calculating using both strings and numbers. In fact you have 3 different ways of expressing the date. I suggest using just date() to save and compare all of the values and only format that Unix timestamp when displaying to the end user.

↓↓↓↓ new edit ↓↓↓

Look carefully at what your code is returning:

$arradate = strtolower(date('D')); //echos the day ex: sun
$nowtime = (int)date('His'); //echos a different format ex: 25019

And have a good read of the date function's docs, specifically the arguments you chose for nowtime which are 'His' which will return:

H 24-hour format of an hour with leading zeros 00 through 23

i Minutes with leading zeros 00 to 59

s Seconds with leading zeros 00 through 59

Now consider that date() returns a Unix timestamp and you will see the failure. You simply need to remove the formatting of the date in all the code save for elements that display to the end user. The computer gets no benefit from knowing it is Wednesday. The computer only counts up from the year 1970.

So you just need one argument to call the current date. Either will work:

$arradate = date(); 
$nowtime = date(); 

Finally after reading about Unix time you will see that the dates you have stored are backwards. You end_time is day 0 and your start time is two days after that.

0
votes

Here is the Example of displaying anything between time duration

Add any time of Toronto in start time and end time..you will be get output between this day and time duration in every week.

date_default_timezone_set('America/Toronto');
$now     = new DateTime(); 
$date    = $now->format('Y-m-d');
$time    = $now->format('h:i A');
$dayname = strtolower(date('l', strtotime($date))); //it will give you today's day name


$start_day = "monday"; //lets consider your data will be come as startday="monday" from $data->{"select_start_day"};
$start_time = '07:50 AM'; //lets consider your data will be come as starttime="07:50 AM" from $data->{"start_time"}; 
$end_day = "tuesday"; //lets consider your data will be come as endday="monday" from $data->{"select_end_day"};
$end_time = '08:26 AM'; //lets consider your data will be come as endtime="08:26 AM" from $data->{"end_time"}; 

$todays_date_time    = strtotime($dayname);
$start_day_time      = strtotime($start_day);
$end_day_time        = strtotime($end_day);
$timeconvert         = strtotime($time);
$timeconvertstart    = strtotime($start_time);
$timeconvertend      = strtotime($end_time);

if($todays_date_time >= $start_day_time && $todays_date_time <= $end_day_time ) 
 {
  if ($timeconvert >= $timeconvertstart && $timeconvert <= $timeconvertend) { 
     echo "test";
 }
}

I Hope this one is help you!