#!/usr/bin/perl

use warnings;
use strict;

#we generally place all subroutines at the end of file, but
# here we intend to have ONLY the subroutine and place the 
# test code on the end, temporarily 
sub validateDate { #returns the date in yyyyMMdd form if a valid date
    # 0 otherwise.
    #
    # The date is expected in international 31-Dec-2020 or US 12/31/20
    # format.  The year may be two or four digits and is limited to
    # this century.  We check that the date is valid, so 30-Feb is not
    # permitted
    my $toCheck = shift; #arguments arrive in subs in the array
        # @_, so get the first one into a local scalar.  Since no list 
        # is specified  to the shift function we get from @_ as is the 
        # Perl default when no variable is named
    $toCheck =~ s/^s*//;    #remove any leading whitespace
    $toCheck =~ s/\s*$//;   # and trailing
    $toCheck =~ s/20(\d\d)$/$1/; #if it ends 20xx then remove the 20

    my @date;       #place for the parsed data, will be
                    # day, month, year-2000

    my $months = <<EOT; #month names to number conversion string
1jan
2feb
3mar
4apr
5may
6jun
7jul
8aug
9sep
10oct
11nov
12dec
EOT
    #each entry in the string is of the form:
    #  <monthNumber><monthName><newLine>
    #   we certainly could have saved this list in one line, 
    #   but it seems easier to read as a multi line value

    my @daysInM = (31,28,31,30,31,30,31,31,30,31,30,31); #days in month
    # we will modify the value for Feb when in a leap year

    if (@date = $toCheck =~ /^(\d\d?)-([a-z]{3})-(\d\d)$/i) { #intern'l
        # format,  we are doing three things:
        #  the last clause, between the / is the regular expression, to
        #   look for and since it ends /i we will ignore case in 
        #   the search
        #  the =~ instructs Perl to apply the regular expression to the 
        #   variable to see if $toCheck matches.  Remember that =~ 
        #   has high precedence so this is performed before the 
        #   = (assignment) 
        #  since we evaluated in array context, as indicated by the 
        #   @date, the values of the matched capture groups are 
        #   returned as a list and copied into @date if we match.  The
        #   condition will be true when a match since values are copied
        #
        # the =~ is both an equality test and generates the list for 
        #   assignment!

        #we seem to have an international format date
        if ($months =~ /(\d+)$date[1]/i) { #does this month name exist
            # test is there is a number followed by the month entered 
            # in the pre-set $months (which is a list of months with
            # their numbers).  If so the =~ will return true and will 
            # have set $1 to the capture group for the month number
            $date[1] = $1;	#set the month num in place of the name
        }
        else {
            return 0;   #did not recognize the month name
        }
    } #we are done with international format

    elsif (@date = $toCheck =~ m{^(\d\d?)/(\d\d?)/(\d\d?)$}) { #US form
        #we have month,day,year - swap the sequence to day,mon,year 
        ($date[0],$date[1]) = ($date[1],$date[0]); 
    }	    
    else {
        return 0;	#not US form either
    }

    #arrival here means that we did recognize the form and have it in 
    # @date check that the month is a valid number and the day is 
    # allowed in the mon
    if ($date[1] > 12 or $date[1] < 1) {return 0} #months are 1..12

    if (!($date[0] % 4)) {$daysInM[1]++} #a leap year, feb allows 29
    # $date[0] is the year, what is its modulus 4 value, zero when 
    # leap year the ! inverts the condition so leap years are true 
    # and change feb to allow 29 days
    if ($date[0] > $daysInM[$date[1]-1] or !$date[0]) {
        return 0; #day number too high or 0
    }

    #any detected error has already returned 0
    return sprintf("20%02d%02d%02d",reverse @date); #20201231 format
    #sprintf is 'string returning'(s), print (print), formatted(f) 
    # function that most modern languages support.  For those 
    # unfamiliar with sprintf from other languages, see the next 
    # section and just accept that it converts the three numbers, of 
    # year,month and day into a fixed format string.  sprintf is like 
    # printf except it returns a string rather than printing it
    #
    #You will remember that @date is the list list in day,month,year 
    # sequence, so we reverse this list before passing it to the 
    # sprintf() reverse() should be self explanatory
} #end of our function

#while in development, so that we can test the function without the 
# need of an additional file, to import this one, we will add some test 
# code 

if (!caller()) { #caller() returns true IF we are called by other code
    #since here we must be in test mode, not being called() by require
    print "? ";	#prompt the user
    my $y = <<>>; #get some kbd input
    my $as = validateDate($y);    #generate a YYYYmmDD type version
    if ($as) {
        print "Validated as $as\n";
    }
    else {
        print "Can't recognize date\n";
    }
}

1;	#so that when we do "require" it the necessary true is returned
