0

I am looking for a versatile method for validating dates.

What I want, is some way of achieving validation rules such as:

First monday of month

monday or friday

Fourth day of month

Is there anything that can handle such requirements? Or does anyone have any pointers as to how i could achieve this?

Edit: I appreciate the answers, but heres what i ended up using for now:

Thanks for all the answers. What i actually ended up using (At least till i find the perfect solution) is roughly this function:

function isDateValid($rule,$date){
    $DATE = (int) $date;

    /* Make it safe for eval */
    preg_match_all('/:\w:|&&|\|\||<|=|>|!|\(|\)|\d+/i',$rule,$filteredRule);
    $rule = implode('',$filteredRule[0]);

    //replace rule notations
    $f = function($str){
        return "date('$str[1]',\$DATE)";
    };
    //Or if php < 5.3: $f = create_function('$str', 'return "date(\'$str[1]\',\$DATE)";');

    $rule = preg_replace_callback('/:(\w):/i', $f, $rule);

    //Evaluate expression
    return @eval("return $rule;");
}

//Example of usage testing first of month:
var_dump( isDateValid(':d:==1',time()) );

This lets me handle all the requirements i have in, well PHP code. The people who use this is very limited (<5 persons) and most of them have some degree of php experience. Now i can make rules such as:

first monday of month : :w:==1 && :d: <= 7

monday or friday : :w:==1 || :w:==5

Fourth day of month : :d: == 4

I would appreciate an even better more flexible solution or feedback regarding security (Can this be injected with mallicious php code?)

EJTH
  • 2,178
  • 1
  • 20
  • 25
  • http://php.net/manual/en/function.date.php – sskoko Sep 26 '13 at 09:01
  • Have you looked at the [strtotime()](http://php.net/manual/en/function.strtotime.php) function or [DateTime object constructor](http://www.php.net/manual/en/datetime.construct.php)? While `Monday or Friday` is pretty meaningless even to me, both should handle your other two examples – Mark Baker Sep 26 '13 at 09:02
  • It is not meaningless say if something is done every monday & friday.. I guess what im looking for is something like a crontab parser. – EJTH Sep 26 '13 at 09:05
  • You can use http://www.php.net/manual/en/function.date.php using maaaaaaany parameters to know if the day is monday, if it's the first day of the month and so on. If you want to check if the date exists first, use http://php.net/manual/en/function.checkdate.php . Moreover, you may directly use the datetime constructor: http://www.php.net/manual/en/datetime.construct.php . Just use some fantasy, everything you need is already included in many PHP libraries! – briosheje Sep 26 '13 at 09:05
  • I could easily accomplish this in PHP code, problem is that these "rules" is specified by users, and i certainly don't want them to write php. – EJTH Sep 26 '13 at 09:07
  • 1
    Why don't you just allow users to enter cronjob syntax? Validating that = http://stackoverflow.com/questions/235504/validating-crontab-entries-w-php – Glavić Sep 26 '13 at 09:15

2 Answers2

0

I would define a set of rules something like:

$ordinals = array("First", "Second"...);
$days = array("Monday", "Tuesday"...);

$rules = array(
    array("%1 %2 of month", $ordinals, $days),
    array("%1 or %1", $days),
    etc
)

Then, do a foreach(rule) and generate all possible allowed strings

$alloweds = array();
foreach($rules as $rule){
    $str = $rule[0];
    for($i = 1; $i < sizeof($rule); $i++){
        for($a = 0; $a < sizeof($rule[$i]); $a++){
            $alloweds[] = str_replace($str, "%$i", $rule[$i][$a]);
        }
    }
}

Then do a foreach(allowed string) compare to what we have

foreach($alloweds as $allowed){
    if(preg_match($allowed, $input){
        //its valid
    }
}

you could set up your rules to be quite complicated - however its worth noting that the more complex they are the longer this will take - in its current form its clearly an exponential time algorithm, but its a starting point.

Zack Newsham
  • 2,810
  • 1
  • 23
  • 43
0

Not the most versatile solution, but this actually does EXACTLY what you want to do:

Class:

<?php
class validate {
    private static $month;
    private static $year;
    private static $day;
    private static $day_literal;

    function __construct() {
        self::$year = date("Y");
        self::$month = date("m");
        self::$day = date("d");
        self::$day_literal = date("l");
    }

    public function firstInMonth($required,$day,$month,$year) {
        $firstday;
        for ($i = 1; $i < 31; $i++) {
            if (checkdate($month,$i,$year)) {
                if (trim(strtolower(date("l",mktime(0,0,0,$month,$i,$year)))) == trim(strtolower(trim($required)))) {
                    $firstday = $i;
                    break;
                }
                else {
                    continue;
                }
            }
        }

        if (strtotime(date("Y-m-d",mktime(0,0,0,$month,$i,$year))) == strtotime(date("Y-m-d",mktime(0,0,0,$month,$day,$year)))) {
            return "{$year}/{$month}/{$day} is the first {$required} of the month.";
        }
        else {
            return "Nope.";
        }
    }

    public function checkDayLiteral($literal,$day,$month,$year) {
        if (trim(strtolower(date("l",mktime(0,0,0,$month,$day,$year)))) == trim(strtolower($literal))) {
            return true;
        }
        else {
            return false;
        }
    }

    public function dayPosition($day,$month,$year) {
        return date("jS",mktime(0,0,0,$month,$day,$year));
    }
}
?>

I don't know what's the purpose of your checking, so I've also implemented the construct method to compile the "today" values, which are NOT used in the code. If you need them, feel free to call them using self::

How to use:

<?php
$validate = new validate;

echo $validate->firstInMonth("Monday",2,9,2013);
echo "<br />";
echo $validate->checkDayLiteral("Monday",2,9,2013);
echo "<br />";
echo $validate->dayPosition(2,9,2013);
?>

Change the class according to WHAT you need to get (I mean: edit the returns I've set).

In this case, the above code returns:

2013/9/2 is the first Monday of the month.

1

2nd

while this:

echo $validate->firstInMonth("Tuesday",2,9,2013);
echo "<br />";
echo $validate->checkDayLiteral("Wednesday",15,9,2013);
echo "<br />";
echo $validate->dayPosition(24,9,2013);

returns this:

Nope.

24th

Of course, this is not as versatile as whatever you want, but this works.

Hope this helps.

If it does not do exactly what you want I'm sorry, just take this as an idea, I think it's pretty clear how you can get whatever information you want from this code.

briosheje
  • 7,356
  • 2
  • 32
  • 54