How to create a function to validate time in PHP

Asked

Viewed 1,493 times

8

I am trying to validate a time field in PHP, and with the help of this topic on an external site I came to this script:

function validaHoras($campo){
    if (preg_match('/^[0-9]{2}:[0-9]{2}$/', $campo)) {
        $horas = substr($campo, 0, 2);
        $minutos = substr($campo, 3, 2);
        if (($horas > "23") OR ($minutos > "59")) {
            $campo = false;
        }
    }
}

The input must receive the field in format '00:00', but has no validation in JS, and the user can enter an invalid time.

So what I want is to change the value of the variable to false when it comes with a wrong time (type 99:99), but it is not working. Testing the script below with the input 99:99, the value of the variable is not changed...

function validaHoras($campo){
    if (preg_match('/^[0-9]{2}:[0-9]{2}$/', $campo)) {
        $horas = substr($campo, 0, 2);
        $minutos = substr($campo, 3, 2);
        if (($horas > "23") OR ($minutos > "59")) {
            $campo = false;
        }
    }
}

// SEGUNDA

$val1 = isset($_POST["Tsegs"]) && !empty($_POST["Tsegs"]) ? $_POST["Tsegs"] : false;

validaHoras($val1);
echo $val1;

//saida: 99:99
//saida desejada: false

5 answers

10

It is possible to validate the format and contents only with regular expressions if you wish. ^(0[0-9]|1[0-9]|2[0-3]):([0-5][0-9]).

^(0[0-9]|1[0-9]|2[0-3]) => The first group captures the time in the format (00-23) it defines three types 'categories' the first indentifies values from 00 to 09, the second from 10 to 19 and the last from 20 to 23.

([0-5][0-9]) => The second group covers the minutes which has a slightly different logic than the hours. Captures a digit that can be between zero and five and another that can be between zero and nine, which covers 00 to 59

function validaHoras($campo){
    return preg_match('/^(0[0-9]|1[0-9]|2[0-3]):([0-5][0-9])/', $campo) ? $campo : false;
}


$arr = array('00:00', '19:59', '20:34', '24:33', '28:64', '07:59', '000:30', '30:50');

foreach($arr as $item) var_dump(validaHoras($item)) .'<br>';

Example - ideone

  • Very good! Thanks a lot!

6


There is a mistake in the call of the validateHoras function, it is passing the parameter but does not return anything after the execution. The variable you print is the same one you pass as parameter (by value), it will not change. Try this way:

<?php

function validaHoras($campo){
    if (preg_match('/^[0-9]{2}:[0-9]{2}$/', $campo)) {
        $horas = substr($campo, 0, 2);
        $minutos = substr($campo, 3, 2);
        if (($horas > "23") OR ($minutos > "59")) {
            return false;
        }

        return true;
    }

    return false;
}

// SEGUNDA
$val1 = isset($_POST["Tsegs"]) && !empty($_POST["Tsegs"]) ? $_POST["Tsegs"] : false;

if(validaHoras($val1) === true){
    echo 'hora valida ' . $val1;
}else {
    echo 'hora invalida ' . $val1;
}
  • 1

    $hours > "23" ?? Why not convert the return of the regex to the whole and compare? Comparison of larger with string is strange, even more giving room for fallacies with php..

  • 1

    @Marlysson if it was just a string, "1A:2X" would be true in this case, but since you filtered through Regex before, it still does, I think. Can be done with string very efficiently (posted an alternative right here) but has to test byte by byte. The advantage is that we do each test only once, totally dispensing with the need for Regex and any kind of numerical conversion or string modification.

3

It follows a very succinct "prototype", using the most basic of the basic, which is validation with if.

(no need for Regex, conversions or even operations modifying string)

function pareceHora($str)
{
   return strlen($str) == 5
          && $str[2] == ':'
          && $str[0] >= '0' && $str[0] <= '2'
          && $str[1] >= '0' && $str[1] <= ($str[0]=='2'?'3':'9')
          && $str[3] >= '0' && $str[3] <= '5'
          && $str[4] >= '0' && $str[4] <= '9';
}

See working: IDEONE

  • The syntax $string[n] accesses the number byte n of string directly

  • in the 3rd row we make sure the size is 5 characters

  • on the 4th of April : in the middle

  • In the 5th and 6th lines we checked the time between 00 and 23, using ASCII comparison. One digit is done at a time so that a string '1A' is not considered valid (if we tested '00' to '23' it would give these side effects)

  • on the 7th and 8th lines is the same thing, only for minutes.

This way, we validate the 5 characters of the string completely (range and shape) with basic and efficient operations.

3

An option with sanitization.

Routine sanitizes user input by removing anything that is not a number, but preserves the character :.

It also converts full-width numeric characters (zenkaku) and accepts hour and minute with 1 digit. Example: 1:10, 1:6 (01:10, 01:06)

Sanitization makes validation more user-friendly.

function numbers_only($str, $exception = '')
{
    return preg_replace('#[^0-9'.$exception.']#', '', mb_convert_kana($str, 'n'));
}

function valid_hour($n)
{
    $n = str_replace(':', ':', $n);
    $n = numbers_only($n, ':');

    $arr = explode(':', $n);
    if (count($arr) == 2) {
        $h = intval($arr[0]);
        $m = intval($arr[1]);
        if ($h <= 23 && $h >= 0 && $m <= 59 && $m >= 0) {
            return str_pad($h, 2, '0', STR_PAD_LEFT).':'.str_pad($m, 2, '0', STR_PAD_LEFT);
        }
    }
    return false;
}

$str[0] = '0000'; // (inválido pois é necessário o separador : )
$str[1] = '00:09'; // caracteres zenkaku (válido)
$str[2] = '00:00'; // (válido)
$str[3] = '0:06'; // (válido)
$str[4] = '0:0'; // (válido)
$str[5] = '00:0'; // (válido)
$str[6] = '01:60'; // (inválido pois os minutos ultrapassaram o limite)
$str[7] = '01:6.'; // (válido, pois o ponto no final é removido na sanitização)

/*
Quando for inválido, retorna booleano false.
Quando for válido, retorna a string do usuário sanitizada e formatada como hh:mm, mesmo que o usuário insira apenas 1 dígito para hora ou minuto.
*/

var_dump(valid_hour($str[7]));
  • Very good Daniel, and still solves several recurring problems of information of invalid schedules... excellent indeed.

2

If you are considering only hours of 00:00 and 23:59, you can simply use the method DateTime::createFromFormat or function, which is the nickname for this first, date_create_from_format

Thus:

date_create_from_format('!H:i', $valor);

Return DateTime, is a valid value. If you return false, is invalid.

But we have a problem here. This function accepts values like 24:99 as valid time, converting its values to correct times when passed. In the case of 24:99, would be converted to 1970-01-02 01:39:00.

To get around this, we could use the function that creates the date from the format and then, using the same format for the output, compare with the input value.

Behold:

function validar_hora($hora) 
{
   $formato = 'H:i';

   $data = date_create_from_format('!'. $formato, $hora);

   return $data && $data->format($formato) === $hora;
}


validar_hora('23:55'); // true
validar_hora('24:00'); // false

This is because:

When applying 'H:i' in '24:00' is converted to '1970-01-02 00:00:00', which is converted to 00:00 when calling 'H:i' to format it.

In the case of '23:55', is converted to '1970-01-01 23:55:00', which is converted to '23:55' when calling format.

See working on IDEONE.

This is an alternative solution if you do not want to use REGEX.

Note:: The !, according to the documentation, redefine all fields for the Unix "epoch" (1970-01-01)

Updating

Still thinking of a solution that costs less lines than the first one I presented, I applied the "same fundamentals" to the functions date and strtotime. The function strtotime converts a string to timestamp. Already the function date returns the formatted date from the timestamp, when passed the second parameter.

We can do it that way:

function validar_hora($hora)
{
    return date('H:i', strtotime($hora)) == $hora;
}

https://ideone.com/4dWLzv

Browser other questions tagged

You are not signed in. Login or sign up in order to post.