Search code examples
laravellaravel-validation

laravel how to validate time between two values that crosses midnight (15:00 > time > 01:00)


I have a request that looks like the following

"date" => "2022-05-25"
"shift" => "2"
"attendance" => array:1 [▼
  4 => array:5 [▼
    "present" => "on"
    "employee_id" => "4"
    "time_in" => "15:00"
    "time_out" => "01:00"
    "note" => null
  ]

knowing that each shift has its own starting and leaving time, also each shift has the option to be across midnight. for this case, we have the shift like this

#attributes: array:6 [▼
    "id" => 2
    "title" => "Evening Shift"
    "starting_time" => "15:00"
    "leaving_time" => "01:00"
    "across_midnight" => 1
    "user_id" => 1
  ]

what I need is to validate the time_in and time_out to be between starting_time and leaving_time

for example, here (in this case) the valid values can be 17:00, 18:00, 22:00, 00:30 While 14::00 is not a valid value

this is my validation rules for now that works well if the shift doesn't cross midnight

public function rules()
    {
        $shift = Shift::find($this->shift);
        $rules = [
            'date' => 'required|date|date_format:Y-m-d',
            'shift' => 'required|exists:shifts,id,user_id,' . auth()->id(),
            'attendance' => 'required|array',
            'attendance.*.employee_id' => 'required|exists:employees,id,user_id,' . auth()->id(),
        ];

        foreach ($this->attendance as $key => $Value) {
            $rules['attendance.' . $key . '.time_in'] = [Rule::requiredIf($this->has('attendance.' . $key . '.present')), 'date_format:H:i', 'nullable', 'after_or_equal:' . $shift?->starting_time, 'before_or_equal:' . $shift?->leaving_time];
            $rules['attendance.' . $key . '.time_out'] = [Rule::requiredIf($this->has('attendance.' . $key . '.present')), 'date_format:H:i', 'nullable', 'after_or_equal:' . $shift?->starting_time, 'before_or_equal:' . $shift?->leaving_time];
        }

        return $rules;
    }

here is what I get if the shift crosses midnight

The Time In must be a date before or equal to 01:00.

The Time Out must be a date after or equal to 15:00.

Solution

  • I finally found a solution for this.

    I've created a rule using the command 'php artisan make:rule CrossMidnightTimeValidation'

    inside the created file

    <?php
    
    namespace App\Rules;
    
    use Illuminate\Contracts\Validation\Rule;
    
    class CrossMidnightTimeValidation implements Rule
    {
        private $starting_time,$end_time;
    
        public function __construct($starting_time, $end_time)
        {
            $this->starting_time = $starting_time;
            $this->end_time = $end_time;
    
        }
    
        /**
         * Determine if the validation rule passes.
         *
         * @param  string  $attribute
         * @param  mixed  $value
         * @return bool
         */
        public function passes($attribute,  $value)
        {
            return ($value >= $this->starting_time && $value <= now()->endOfDay()->format('H:i'))
                || (($value >= now()->startOfDay()->format('H:i') && $value <= $this->end_time));
        }
    
        /**
         * Get the validation error message.
         *
         * @return string
         */
        public function message()
        {
            return 'Invalid Time (out of the shift time).';
        }
    }
    

    for my rules.

    public function rules()
        {
            $shift = Shift::find($this->shift);
            $rules = [
                'date' => 'required|date|date_format:Y-m-d',
                'shift' => 'required|exists:shifts,id,user_id,' . auth()->id(),
                'attendance' => 'required|array',
                'attendance.*.employee_id' => 'required|exists:employees,id,user_id,' . auth()->id(),
            ];
            if ($shift->across_midnight) {
                foreach ($this->attendance as $key => $value) {
                    $rules['attendance.' . $key . '.time_in'] = [Rule::requiredIf($this->has('attendance.' . $key . '.present')), 'date_format:H:i', 'nullable', new CrossMidnightTimeValidation($shift?->starting_time, $shift?->leaving_time)];
                    $rules['attendance.' . $key . '.time_out'] = [Rule::requiredIf($this->has('attendance.' . $key . '.present')), 'date_format:H:i', 'nullable', new CrossMidnightTimeValidation($shift?->starting_time, $shift?->leaving_time)];
                }
            }
            else {
                foreach ($this->attendance as $key => $value) {
                    $rules['attendance.' . $key . '.time_in'] = [Rule::requiredIf($this->has('attendance.' . $key . '.present')), 'date_format:H:i', 'nullable', 'before_or_equal:attendance.' . $key . '.time_out', 'after_or_equal:' . $shift?->starting_time];
                    $rules['attendance.' . $key . '.time_out'] = [Rule::requiredIf($this->has('attendance.' . $key . '.present')), 'date_format:H:i', 'nullable', 'before_or_equal:' . $shift?->leaving_time, 'after_or_equal:' . $shift?->starting_time];
                }
            }
            return $rules;
        }