/* dtchk.sas Daniel Brockman 070721 Check dates */ %macro dtchk (iset,yr,mo,dy) ; /* * dtchk prints an error message in the log if it detects an invalid * date in iset. * * iset = name of data set to consider. * yr = name of year variable stored in iset. * The variable contains an integer between 1 and 9999 inclusive. * 1 or 2 digits represents a year not more than 50 years from * this year. * 3 or 4 digits represents the year number unambiguously. * mo = name of month variable stored in iset. * The variable contains an integer between 1 and 12 inclusive. * dy = name of day variable stored in iset. * The variable contains an integer between 1 and 31. * * Notes: * 1. The above limits on the numbers in yr, mo and dy apply for valid * dates. The numbers for invalid dates may not comply with these limits. * 2. You may have to create the 3 variables yr, mo and dy in the data * set you want to check. To maintain generality, dtchk makes no assumptions * about the data set except that it contains variables representing year, * month and day as described above. * 3. dtchk executes a data step. * 4. dtchk writes nothing in the log when it encounters a valid date. * 5. a date 50 years from this year is an ambiguous case for a 2-digit year. * * Example: * * * data mydataset ; * * input somekindadate ; * * yr = int(somekindadate/10000) ; * mo = int((somekindadate - 10000*yr)/100) ; * dy = somekindadate - 10000*yr -100*mo ; * * cards; * 19000229 * 19000228 * 20000229 * 20000228 * 000229 * 000228 * 14921012 * 920431 * August 15, 1914 * 920430 * 929230 * 920229 * 920228 * 570712 * 580712 * 560712 * 5 * 25 * 225 * 1225 * 01225 * 101225 * 101225.5678 * 0101225 * 20101225 * 220101225 * . * 0 * notanumber * -88 * 3.1416 * 11:15 * 20070132 * 20070229 * 20070332 * 20070431 * 20070532 * 20070631 * 20070732 * 20070832 * 20070931 * 20071032 * 20071131 * 20071232 * 20070131 * 20070228 * 20070331 * 20070430 * 20070531 * 20070630 * 20070731 * 20070831 * 20070930 * 20071031 * 20071130 * 20071231 * * run; * * %dtchk(mydataset,yr,mo,dy); * */ data _NULL_ ; set &iset ; * number of days in each month of non-leap year ; * days per month ; a1=31; a2=28 ; a3=31 ; a4=30 ; a5=31 ; a6=30 ; a7=31 ; a8=31 ; a9=30 ; a10=31 ; a11=30 ; a12=31 ; array dpm(12) a1-a12; good=1; * starting assumption: date is good ; if (&dy ne int(&dy)) then good=0; * yr not an integer? ; else if (&mo ne int(&mo)) then good=0; * yr not an integer? ; else if (&yr ne int(&yr)) then good=0; * yr not an integer? ; else do; * all integers ? ; if (&yr<100) then do; * 2 digit year? ; tyyyy = year(today()); * this year ; tcc = int(tyyyy/100) ; * this century ; tcc&yr = tcc*100 + &yr ; * this year if yr is in this century ; if (tcc&yr - tyyyy > 50) then tcc=tcc-1 ; * yr must be prev century ; if (tyyyy - tcc&yr >=50) then tcc=tcc+1 ; * yr must be next century ; &yr = tcc*100 + &yr ; * we will work with this year number ; end ; * 2 digit year ; if (&yr<1 or &yr>9999) then good=0; * yr out of range?; else if (&mo<1 or &mo>12) then good=0; * mo out of range?; else if (&dy<1 or &dy>31) then good=0; * dy out of range?; else if (&mo = 2) then do; * February?? ; * discover whether leap year ; leap=0 ; * assume not a leap year; if (mod(&yr,400)=0) then leap=1; * yr divides by 400? ; else if (mod(&yr,100)=0) then leap = 0 ; * century yr? ; else if (mod(&yr,4)=0) then leap=1 ; * yr divides by 4? ; else leap = 0 ; * regular year ; if (leap and &dy>29) then good=0; * >29 days in leap yr? ; else if ((not leap) and &dy>28) then good=0; * >28 days in regular yr? ; end; * February ; else do ; * some other month? ; * too many days? ; if (&dy>dpm(&mo)) then good=0; * too many days? ; * if (&dy>30 and (&mo=9 or &mo=4 or &mo=6 or &mo=11)) then good=0; end; * some other month ; end; * all integers ; if (not good) then put 'ERROR BAD DATE ' _N_= ' ' &yr.= ' ' &mo= ' ' &dy= ; %mend dtchk ; * --------------------------------------------------------- ;