<?php
// *** Script by Yossi Beck ***
// finds the following invalid dates:
// - impossible dates (31 apr 2003, 30 feb 2003, 29 feb 2003 (not a leap year!), 43 mar 2003)
// - years in the future (31 apr 2020)
// - partial dates (1 feb,  mar)
// - invalid GEDCOM year entries ( 1820? )
// - invalid GEDCOM month entries (12 april 2003, 23 feb. 2003, 12 december 2003)
// - prefixes before last (or only) date that are not BEF, AFT, ABT, EST, CAL, INT, AND, TO or valid combinations like EST ABT
// - lack of BET or FROM in string if AND or TO (respectively) where found
// - invalid GEDCOM entries such as use of dash (1845-1847) or slash (23/4/1990) etc
// - and of course any junk that was entered in the date field instead of elsewhere...   ;-)
// - does not (yet) validate the first date in a "BET ... AND ..." or "FROM ... TO ..." two-date string. Maybe we'll add that later

namespace Genealogy\Include;

class ValidateDate
{
    function check_date($date)
    {
        // *** Remove B.C. (before christ) addition, for further tests of date ***
        //if (substr($date,-3)==' BC' OR substr($date,-5)==' B.C.') return "finished";
        if (substr($date, -3) === ' BC' || substr($date, -5) === ' B.C.') {
            $date = str_replace(" BC", "", $date);
            $date = str_replace(" B.C.", "", $date);
        }

        $year = $this->check_year($date);
        if ($year === null) {
            return null;
        } elseif (strpos($year, "/") === false) {
            if ($year > 999) {
                $strlen = 4;
            } elseif ($year > 99) {
                $strlen = 3;
            } elseif ($year > 9) {
                $strlen = 2;
            } elseif ($year > 0) {
                $strlen = 1;
            } else {
                return null;
            }
        } else {
            // we've got a 1741/42 case
            $strlen = strlen($year);
        }
        if (strlen($date) === $strlen) {
            return "finished";
        } elseif ($this->check_month($date) === null) {
            return null;
        }

        if (strlen($date) == $strlen + 4) {
            return "finished";
        } elseif ($this->check_day($date) === null) {
            return null;
        }

        return 1;
    }

    function check_month($date)
    {
        $year = $this->check_year($date);
        $strlen = strlen($year);
        $month = substr($date, - ($strlen + 4), 3);

        if (
            $month === "JAN" || $month === "FEB" || $month === "MAR" || $month === "APR" || $month === "MAY" || $month === "JUN" || $month === "JUL" || $month === "AUG" || $month === "SEP" || $month === "OCT" || $month === "NOV" || $month === "DEC"
        ) {
            return "month" . $month; // flags valid month
        } elseif ($month == "EST" or $month == "CAL") {
            if (strlen($date) > ($strlen + 4)) {
                // EST and CAL should not have anything in front of them!
                return null;
            } else {
                // flags valid EST or CAL with nothing in front
                return $month;
            }
        } elseif ($month == "BEF" or $month == "AFT" or $month == "ABT" or $month == "INT") {
            if (strlen($date) == $strlen + 8 and (substr($date, 0, 3) == "EST" or substr($date, 0, 3) == "CAL")) return $month; //valid "EST ABT" etc.
            elseif (strlen($date) > ($strlen + 4)) return null; // these texts should not have anything in front of them except EST or CAL!
            else {
                // flags valid 3 letter text with nothing in front
                return $month;
            }
        } elseif ($month == " TO") {
            if (substr($date, 0, 4) != "FROM") return null; // TO must have FROM up front!
            else {
                return " TO";
            }
        } elseif ($month == "AND") {
            if (substr($date, 0, 3) != "BET") return null; // AND must have BET up front
            else {
                return "AND";
            }
        } else {
            // if we found "BET" or "FROM" that is also invalid - they can't occur before the last date!
            return null;
        }
    }

    function check_day($date)
    {
        $year = $this->check_year($date);
        $month = $this->check_month($date);
        $strlen = strlen($year) + 6;
        /*
        if($year >999) { $strlen=10; }
        elseif($year >99) { $strlen=9; }
        */
        $day_len = 1; // to be added to strlen later. if day is "8" (and not "12" or "08") $day_len will be set to 0

        if (substr($month, 0, 5) === "month") {
            $day = "";
            if (strlen($date) > $strlen) {
                // 12 sep 2002 or 08 sep 2002 or ABT 8 sep 2002 or ABT sep 2002
                $day = substr($date, - ($strlen + 1), 2); // gets "12" or "08" or " 8" or "BT" in above examples
                if (substr($day, 0, 1) === "0") {
                    // 08 aug 2002
                    $day = substr($day, 1, 1); // turns $day from "08" into "8"
                } elseif (substr($day, 0, 1) === " ") {
                    $day_len = 0;
                }
            } elseif (strlen($date) == $strlen) {
                // 8 aug 2002
                $day = substr($date, - ($strlen), 1); // gets "8"  maybe just: $day=substr($date, 0, 1);
                $day_len = 0;
            }

            if ($day) {
                if (is_numeric($day)) {
                    //in above examples will accept "12", "8", " 8" but will not accept "BT"
                    $day = (int)$day;
                    // check if max day fits month
                    $max = 31;
                    if (substr($month, 5, 3) === "FEB") {
                        // check for leap year
                        if ($year % 400 == 0) {
                            $max = 29;
                        } elseif ($year % 100 == 0) {
                            $max = 28;
                        } elseif ($year % 4 == 0) {
                            $max = 29;
                        } else {
                            $max = 28;
                        }
                    } elseif (
                        substr($month, 5, 3) === "APR" || substr($month, 5) === "JUN" || substr($month, 5) === "SEP" || substr($month, 5) === "NOV"
                    ) {
                        $max = 30;
                    }

                    if ($day > 0 && $day <= $max) {
                        $strlen += $day_len;
                        if ($strlen == strlen($date)) {
                            // nothing before the day digit(s)
                            return $day;
                        }
                    } else {
                        return null;
                    }
                } else {
                    // not numeric for ex. in "ABT FEB 1950", $day will be "BT". We have a case of a month with a prefix
                    $strlen -= 2; // we have to search back from beginning of month name
                }
            }

            if (strlen($date) > $strlen) {
                // now search for text in front of day or month: BEF 10 FEB 1935 or BEF FEB 1935
                $text = substr($date, - ($strlen + 4), 3);
                if ($text == "EST" or $text == "CAL") {
                    if (strlen($date) > ($strlen + 4)) return null; // EST and CAL should not have anything in front of them!
                    else {
                        return $text;
                    } // flags valid EST or CAL has nothing in front
                } elseif ($text == "BEF" or $text == "AFT" or $text == "ABT" or $text == "INT") {
                    if (strlen($date) == $strlen + 8 and (substr($date, 0, 3) == "EST" or substr($date, 0, 3) == "CAL")) return $text;
                    //valid "EST ABT" etc.
                    elseif (strlen($date) > ($strlen + 4)) return null;
                    // these texts should not have anything in front except CAL or EST!
                    else {
                        return $text;
                    }
                    // flags valid 3 letter text has nothing in front 
                } elseif ($text == " TO") {
                    if (substr($date, 0, 4) != "FROM") return null; // TO must have FROM up front
                    else {
                        // must have text in front of it (FROM ... TO ...)
                        return " TO";
                    }
                } elseif ($text == "AND") {
                    if (substr($date, 0, 3) != "BET") return null; // AND must have BET in front!
                    else {
                        return "AND";
                    }
                }
            }
            return null;
        } elseif ($month == " TO") {
            if (substr($date, 0, 5) === "FROM ") {
                return $month;
            } else {
                return null;
            }
        } elseif ($month == "AND") {
            if (substr($date, 0, 4) === "BET ") {
                return $month;
            } else {
                return null;
            }
        } else {
            return 1;
        }
    }

    function check_year($date)
    {
        $year = substr($date, -4, 4);
        // If only year is given, this will work with any year from 0 till today.
        // If year >= 100 and month is given this will also work.
        // HOWEVER, if year is <100 and month is given this will go wrong: "2 mar 24" will give substr "r 24" and "2 mar 6" will give: "ar 6".
        // Therefore:
        if (substr($year, -2, 1) === " " || substr($year, -3, 1) === " ") {
            $temp = explode(" ", $year);
            $year = $temp[1];
        }

        // Now take care of 1741/42 cases (= valid GEDCOM)
        // This can only happen with dates after 1500 so we don't have to check for years <1000
        if (strpos($year, "/") !== false) {
            // date is "4 mar 1741/42", so substr became "1/42" or date is "4 mar 1741/2" so substr became "41/2"
            $year_string = str_replace(" ", "", substr($date, -7));
            // "1741/42" or if " 1741/2" becomes "1741/2"
            $year_arr = explode("/", $year_string);
            $year_part = $year_arr[0];
            // 1741
            if (!is_numeric($year_part) || $year_part > date("Y")) {
                return null;
            } else {
                return $year_string;
            }
        } elseif (!is_numeric($year) || $year > date("Y")) {
            //if (!is_numeric($year) OR $year > date("Y") OR $year<100) { return null; }
            return null;
        } else {
            return (int)$year;
        }
    }
}
