Bulletproof date validation routine, subroutine or function.

GIVEN: An input string in the format of: YYYYMMDD

where:

YYYY is the year (past, present or future: from 1600 to 3999)

MM is the month

DD is the day

CHALLENGE: Read the date input string and make absolutely sure that the represented date is valid.

VALIDITY: To be valid:

* The year must be from 1600 to 3999)

* The month must be from 01 to 12

* The day must be from 01 to the last day of the above month, considering leap year.

* Obviously, if the date input string is not 8 bytes long, the date is invalid.

OUTPUT: The message "VALID" or "INVALID".

If you haven't needed this routine yet, for sure you will need it someday.

I will test most entries, and tell you if it works, or on what date it fails.

*****

Here's my entry. It's pretty straightforward. The DinM% formula looks pretty beastly, but I've triple-checked it, and it does the same thing as several lines of code that check the requirements for leap year. Lemme know if it needs further explanation.

*peace*

Meg.

Code:

`FUNCTION Validate$ (d$) `

'ASSUME TRUE UNLESS PROVEN FALSE

'%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

Validate$ = "VALID"

'CHECK STRING LENGTH

'%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

IF LEN(d$) <> 8 THEN Validate$ = "INVALID": EXIT FUNCTION

'CHECK NUMERIC, LEADING ZERO, "-", "D" NOT A PROB

'%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

IF INSTR(UCASE$(d$), "D") OR INSTR(d$, "-") THEN Validate$ = "INVALID": EXIT FUNCTION

IF LTRIM$(RTRIM$(STR$(VAL(d$)))) <> d$ THEN Validate$ = "INVALID": EXIT FUNCTION

'EXTRACT YEAR, MONTH, DAY

'%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

y% = VAL(LEFT$(d$, 4))

m% = VAL(MID$(d$, 5, 2))

d% = VAL(RIGHT$(d$, 2))

'GET DAYS IN MONTH. IF FEB, CHECK LEAP YEAR

'%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

DInM% = 31 + (m% MOD 2) * (m% > 7) + (m% MOD 2 XOR 1) * (m% < 8)

IF m% = 2 THEN DInM% = 28 - ((y% MOD 4 = 0) + (y% MOD 100 = 0) * NOT (y% MOD 400 = 0))

'CHECK THE RANGES

'%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

IF y% < 1800 OR y% > 3999 THEN Validate$ = "INVALID": EXIT FUNCTION

IF m% < 1 OR m% > 12 THEN Validate$ = "INVALID": EXIT FUNCTION

IF d% < 1 OR d% > DInM% THEN Validate$ = "INVALID": EXIT FUNCTION

END FUNCTION

-=-EDITS-=-

10/12/2004: included check for "-" and "D". Changed date from 1600 to 1800.

:o

wow... Thats... short... I'd imagine lots of more code, then again, i didn't even try to do it

Moneo made the challenge a lot easier by only allowing the years 1600-3999. If he included earlier dates, the challenge gets harder because you have to take into account all the calendar shifts with respect to leap years.

In thta case you must consider the country you are on, as the calendar shifts did'nt take place in ths same day.

For example UNESCO has declared 23 April the World Book day, because in that day of 1616 both William Shakespeare and Miguel de Cervantes died. In fact they died with twenty-one days of interval. Spain was using then the Gregorian (modern) calender and England was using the Julian calender....

Antoni has a valid point. My first choice of the "from" date was 1800. Maybe we should change the 1600 to 1800 to cover the differences mentioned by Antoni.

MEG, you're solution looks pretty good except for the VAL statement. My experience has been that certain values containing a "D" make QB think it is an exponential value. I have to look up an old validation routine where the "D" made it fail.

Give me a few days to thoroughly check out your routine. I intend putting it inside a loop to check all the date combinations against a routine that has been working for me for years.

*****

Code:

`If INSTR(d$, "D") Then Validate$ = "INVALID"`

If INSTR(d$, "-") Then Validate$ = "INVALID"

Moneo: Yes, you're right. The program snags on "200D1010" with an overflow error. I added a line to cover this, and changed the date from 1600 to 1800, as noted.

Meg, I ran your validation logic side by side with mine in the same program, in a loop from years 1800 to 3999, months from 1 to 12, and days from, 1 to 31. The days going to 31 will generate "invalid" dates.

Well, your logic and mine agreed 100% with invalids and valids.

Excellent work, Meg. :bounce:

*****

Meg,

I can't understand the DinM logic. Could you explain it? Did you derive this or did you "lift" it from somewhere?

I get the number of days in a month from a little table which has 28 for February, month 2. Then if month 2 and leap year, I add one. Not elegant, but easy to understand.

A suggestion on the next line that tests if month 2 and adds 1 if it's leap year. The leap year logic here is embedded and specifically tailored to adding 1 if leap year. I suggest that you have one and only one function for determining leap year, and that you always use it regardless of the program's needs. Otherwise, you have different implementations of the leap year logic depending on the program, which increases the risk of error.

The following is the leap year function I've been using for about 15 years. The result is -1 (true) or 0 (false).

Code:

`' ====================== ISLEAPYEAR ==========================`

' Determines if a year is a leap year or not.

' ============================================================

'

FUNCTION IsLeapYear (Z) STATIC

' If the year is evenly divisible by 4 and not divisible

' by 100, or if the year is evenly divisible by 400, then

' it's a leap year:

IsLeapYear = (Z MOD 4 = 0 AND Z MOD 100 <> 0) OR (Z MOD 400 = 0)

END FUNCTION

*****