Page 1 of 1

Pure DOS valid Date checker

Posted: 30 Dec 2013 00:49
by carlsomo
**EDIT: See my post on Sun, Jan 26, 2014 for latest update / correction for older dates **

I often write routines that allow a user to input a date. This is very often very problematic to say the least.
Users often do not care about which format is expected and routines that may call this routine may have
been programmed with a specific date format peculiar to the country or machine in use. This routine
chooses to ignore the 'Tue 12/24/2013' and '24-Dec-13' formats since users never input that way. These
formats are typically generated by accessing %date% in these peculiar formats and thus are correct dates.
This flexible routine determines if a date passed is a valid date (AD) and accepts multiple date formats.
It does not rely on any outside programs such as xcopy, WMIC, etc. Nor does it access the registry.
Leap years are processed appropriately, including years divisible by 100 with no leap year unless
divisible by 400, like the year 2000, which had a leap year. Please report any bugs if found.

Code: Select all

@echo off&goto :start
:Valid_Date Date
echo.:::----------------------------------------------------------:::
echo.::: Syntax: Valid_Date 'Date'                                :::
echo.:::                                                          :::
echo.::: Date may be passed as a value or as a reference variable :::
echo.::: Checks for validity of the Date value, which may be in   :::
echo.::: M-D-Y, MM-DD-YYYY, YYYYMMDD or Julian (7 digit) format   :::
echo.::: Non English YYYY.MM.DD is valid if year is gtr 2 digits  :::
echo.::: Year must be at least year 0 AD to be considered valid   :::
echo.::: 1 or 2 digit years in M-D-Y format are considered 2000+  :::
echo.::: For example the year 11 A.D. should be passed as 0011    :::
echo.::: The Valid date parameter separators are: '/' '-' and '.' :::
echo.::: Returns 0 in errorlevel if date is valid, else non zero  :::
echo.:::                                                          :::
echo.:::----------------------------------------------------------:::
exit /b 1

:start
if "%~1"=="" goto :Valid_Date Syntax
if "%~1"=="/?" goto :Valid_Date Syntax
SetLocal EnableDelayedExpansion
if defined %~1 (set "dte=!%~1!") else (set "dte=%~1")
set "invalid="
set "sep="
set/a err=1
for /f "tokens=1* delims=0123456789-/." %%a in ("!dte!") do set "err=%%a"
if /i !err! neq 1 set/a err=1&goto :end
for /f "tokens=1-2* delims=0123456789" %%a in ("!dte!") do (
  set "err=%%a"&set "sep=%%b"&set "invalid=%%c"
)
call :strln dte len
if /i !err! equ 1 ( REM string is numeric only
  if /i !len! lss 7 goto :end
  if /i !len! gtr 8 goto :end
) else ( REM valid separators found in string
  set/a err=2
  if defined invalid goto :end
  if not defined sep goto :end
  if /i !len! lss 5 goto :end
  if /i !len! gtr 10 goto :end
)
set "dte=%dte:/=-%"
set "dte=%dte:.=-%"
echo !dte!|Find "--">nul
if !errorlevel! equ 0 goto :end
set "ymd=%dte:-=%"
if /i !err! equ 1 ( REM process numeric only string
  if /i !len! equ 7 (call :gdate ymd &set/a err=0)
  set "dte=!ymd:~4,2!-!ymd:~6,2!-!ymd:~0,4!"
)
if !err! neq 0 ( REM process separated string
  for /f "tokens=1-3 delims=-" %%A in ("!dte!") do (
    set mm=%%A&set dd=%%B& set "yy=%%C"
    call :strln mm mlen
    if /i !mlen! gtr 2 if /i "!sep!" equ "." (
      set yy=%%A&set mm=%%B&set "dd=%%C"
      if not defined dd goto :end
    )
  )
  if not defined yy goto :end
  call :strln yy ylen
  if /i !dd! gtr 31 goto :end
  if /i !mm! gtr 12 goto :end
  set/a yy=10000!yy! %% 10000,mm=100!mm! %% 100,dd=100!dd! %% 100
  if !yy! LSS 100 if /i !ylen! lss 3 set /a yy+=2000
  call :jdate JD yy mm dd
  if /i 1!mm! lss 20 set "mm=0!mm!"
  if /i 1!dd! lss 20 set "dd=0!dd!"
  set "ymd=!yy!!mm!!dd!"
  call :Gdate JD
  if /i !JD! equ !ymd! set/a err=0
)
:end
EndLocal&exit /b %err%

:gdate
setlocal enabledelayedexpansion
set "p=!%~1!"
set /a p      = p + 68569
set /a q      = 4 * %p% / 146097
set /a r      = %p% - ( 146097 * %q% +3 ) / 4
set /a s      = 4000 * ( %r% + 1 ) / 1461001
set /a t      = %r% - 1461 * %s% / 4 + 31
set /a u      = 80 * %t% / 2447
set /a v      = %u% / 11
set /a GYear  = 100 * ( %q% - 49 ) + %s% + %v%
set /a GMonth = %u% + 2 - 12 * %v%
set /a GDay   = %t% - 2447 * %u% / 80
if /i 1%GMonth% lss 20 set "GMonth=0%GMonth%"
if /i 1%GDay%   lss 20 set "GDay=0%GDay%"
endlocal&(
  set "%~1=%GYear%%GMonth%%GDay%"
)&exit/b %GYear%%GMonth%%GDay%

:jdate
setlocal enabledelayedexpansion
set/a yy=!%~2!,mm=!%~3!,dd=!%~4!
set /a JD=dd-32075+1461*(yy+4800+(mm-14)/12)/4+367*(mm-2-(mm-14)/12*12)/12-3*((yy+4900+(mm-14)/12)/100)/4
endlocal&set/a %~1=%JD%&exit/b

:Strln
( SetLocal
  set "str=A!%~1!"
  set "len=0"
  for /L %%A in (12,-1,0) do (
    set /a "len|=1<<%%A"
    for %%B in (!len!) do if "!str:~%%B,1!"=="" set /a "len&=~1<<%%A"
  )
)
EndLocal&(
  if "%~2" NEQ "" set/a %~2=%len%
)&exit /b %len%

Re: Pure DOS valid Date checker

Posted: 30 Dec 2013 01:40
by berserker
carlsomo wrote:This routine
chooses to ignore the 'Tue 12/24/2013' and '24-Dec-13' formats since users never input that way. These
formats are typically generated by accessing %date% in these peculiar formats and thus are correct dates.

if you look regional and language settings in control panel you can see various date/time settings for various countries. South africa has got this YYYY/MM/DD.
The slash "/" format is quite common. you might want to consider including all possible formats if this routine is going to be super "flexible"

Re: Pure DOS valid Date checker

Posted: 30 Dec 2013 02:15
by carlsomo
OK, good point.
I suggest you may modify the routine to accept South African convention and then post it for the South Africans who may be disprivileged. As I see it, one must make compromises for the more numerous Germans who use the '.' separator with the Year first
as well as Americans who always use m/d/y and abbreviate the year to two digits. I suppose the Somalians and Ethiopians may use another convention that may not fit. In any case the Syntax section at the top of the routine outlines the parameters that work for this routine. Please feel free to make modifications that work for other formats and post it here.

Re: Pure DOS valid Date checker

Posted: 30 Dec 2013 02:23
by berserker
carlsomo wrote:OK, good point.
I suggest you may modify the routine to accept South African convention and then post it for the South Africans who may be disprivileged. As I see it, one must make compromises for the more numerous Germans who use the '.' separator with the Year first
as well as Americans who always use m/d/y and abbreviate the year to two digits. I suppose the Somalians and Ethiopians may use another convention that may not fit. In any case the Syntax section at the top of the routine outlines the parameters that work for this routine. Please feel free to make modifications that work for other formats and post it here.


you are the one who writes this
carlsomo wrote:This flexible routine determines ......
....
Please report any bugs if found


now I am reporting to you some things you can consider if you want it to be more flexible. why should I write the code for you? I am not going to use it. Its the people whom you are going to distribute this code to who are going to use it. Its not even my concern whether you want to follow my suggestion or not. I merely just did what you asked, report bugs. that's all.

Re: Pure DOS valid Date checker

Posted: 30 Dec 2013 02:39
by carlsomo
OK, here is the solution to all the ills of South African date checkers and thanks for the input BTW
I just hope this solution doesn't screw up the Somalians:

Code: Select all

@echo off&goto :start
:Valid_Date Date
echo.:::----------------------------------------------------------:::
echo.::: Syntax: Valid_Date 'Date'                                :::
echo.:::                                                          :::
echo.::: Date may be passed as a value or as a reference variable :::
echo.::: Checks for validity of the Date value, which may be in   :::
echo.::: M-D-Y, MM-DD-YYYY, YYYYMMDD or Julian (7 digit) format   :::
echo.::: Non English YYYY.MM.DD is valid if year is gtr 2 digits  :::
echo.::: Year must be at least year 0 AD to be considered valid   :::
echo.::: 1 or 2 digit years in M-D-Y format are considered 2000+  :::
echo.::: For example the year 11 A.D. should be passed as 0011    :::
echo.::: The Valid date parameter separators are: '/' '-' and '.' :::
echo.::: Returns 0 in errorlevel if date is valid, else non zero  :::
echo.:::                                                          :::
echo.:::----------------------------------------------------------:::
exit /b 1

:start
if "%~1"=="" goto :Valid_Date Syntax
if "%~1"=="/?" goto :Valid_Date Syntax
SetLocal EnableDelayedExpansion
if defined %~1 (set "dte=!%~1!") else (set "dte=%~1")
set "invalid="
set "sep="
set/a err=1
for /f "tokens=1* delims=0123456789-/." %%a in ("!dte!") do set "err=%%a"
if /i !err! neq 1 set/a err=1&goto :end
for /f "tokens=1-2* delims=0123456789" %%a in ("!dte!") do (
  set "err=%%a"&set "sep=%%b"&set "invalid=%%c"
)
call :strln dte len
if /i !err! equ 1 ( REM string is numeric only
  if /i !len! lss 7 goto :end
  if /i !len! gtr 8 goto :end
) else ( REM valid separators found in string
  set/a err=2
  if defined invalid goto :end
  if not defined sep goto :end
  if /i !len! lss 5 goto :end
  if /i !len! gtr 10 goto :end
)
set "dte=%dte:/=-%"
set "dte=%dte:.=-%"
echo !dte!|Find "--">nul
if !errorlevel! equ 0 goto :end
set "ymd=%dte:-=%"
if /i !err! equ 1 ( REM process numeric only string
  if /i !len! equ 7 (call :gdate ymd &set/a err=0)
  set "dte=!ymd:~4,2!-!ymd:~6,2!-!ymd:~0,4!"
)
if !err! neq 0 ( REM process separated string
  for /f "tokens=1-3 delims=-" %%A in ("!dte!") do (
    set mm=%%A&set dd=%%B& set "yy=%%C"
    call :strln mm mlen
    if /i !mlen! gtr 2  (
      set yy=%%A&set mm=%%B&set "dd=%%C"
      if not defined dd goto :end
    )
  )
  if not defined yy goto :end
  call :strln yy ylen
  if /i !dd! gtr 31 goto :end
  if /i !mm! gtr 12 goto :end
  set/a yy=10000!yy! %% 10000,mm=100!mm! %% 100,dd=100!dd! %% 100
  if !yy! LSS 100 if /i !ylen! lss 3 set /a yy+=2000
  call :jdate JD yy mm dd
  if /i 1!mm! lss 20 set "mm=0!mm!"
  if /i 1!dd! lss 20 set "dd=0!dd!"
  set "ymd=!yy!!mm!!dd!"
  call :Gdate JD
  if /i !JD! equ !ymd! set/a err=0
)
:end
EndLocal&exit /b %err%

:gdate
setlocal enabledelayedexpansion
set "p=!%~1!"
set /a p      = p + 68569
set /a q      = 4 * %p% / 146097
set /a r      = %p% - ( 146097 * %q% +3 ) / 4
set /a s      = 4000 * ( %r% + 1 ) / 1461001
set /a t      = %r% - 1461 * %s% / 4 + 31
set /a u      = 80 * %t% / 2447
set /a v      = %u% / 11
set /a GYear  = 100 * ( %q% - 49 ) + %s% + %v%
set /a GMonth = %u% + 2 - 12 * %v%
set /a GDay   = %t% - 2447 * %u% / 80
if /i 1%GMonth% lss 20 set "GMonth=0%GMonth%"
if /i 1%GDay%   lss 20 set "GDay=0%GDay%"
endlocal&(
  set "%~1=%GYear%%GMonth%%GDay%"
)&exit/b %GYear%%GMonth%%GDay%

:jdate
setlocal enabledelayedexpansion
set/a yy=!%~2!,mm=!%~3!,dd=!%~4!
set /a JD=dd-32075+1461*(yy+4800+(mm-14)/12)/4+367*(mm-2-(mm-14)/12*12)/12-3*((yy+4900+(mm-14)/12)/100)/4
endlocal&set/a %~1=%JD%&exit/b

:Strln
( SetLocal
  set "str=A!%~1!"
  set "len=0"
  for /L %%A in (12,-1,0) do (
    set /a "len|=1<<%%A"
    for %%B in (!len!) do if "!str:~%%B,1!"=="" set /a "len&=~1<<%%A"
  )
)
EndLocal&(
  if "%~2" NEQ "" set/a %~2=%len%
)&exit /b %len%

Re: Pure DOS valid Date checker

Posted: 26 Jan 2014 16:24
by carlsomo
After a little more research....

Prior to Pope Gregory's new Calendar that we now use the Julian Calendar was in force. This calendar had leap years for every year divisible by 4 with no remainder. This led to a 10 day difference in the spring equinox from where the earth was actually in position with the Sun and kept pushing Easter closer to Summer. The solution was to declare Friday, October 15th, 1582 as the date that follows Thursday, October 4th, 1582. From then on leap years did not occur on century dates, such as in February 1700, unless divisible by 400 so years 1600 and 2000 are leap years. In the Julian Calendar there is no year 0. The date at 12 Noon, December 31st, Year 1 BCE (Julian 1721423.0), is followed by the date, 12 Noon January 1st, 1 AD (Julian 1721424.0). Julian day 0.0 is 12 Noon, January 1st, 4713 BCE (Before Common Era)

The plot thickens....
Basically only devout Catholic nations followed the Pope's lead on October 15, 1582 and many protestant nations and English colonies (like the USA) did not adopt the Gregorian calendar until the 1700's. The various exact dates that specific countries chose to update to the Gregorian calendar are too numerous to account for in a simple script such as this. This modified Valid_Date batch script assumes October 15th, 1582 (one day after October 4th, 1582 on the Julian calendar) as the start of the Gregorian era for purposes of simplicity. This also follows the convention of the US Naval Observatory website and many others capable of converting older dates to Julian day. That being said this script could easily be modified to be 'Country specific' if one knows the conversion date for any given nation. The following website allows the user to toggle between Julian and Gregorian calendars to convert specific dates in question for dates that fall into the nebulous zone after October 4th, 1582:

http://bowie.gsfc.nasa.gov/time/julian.html?delta=1199160

Valid_Date.bat

Code: Select all

@echo off&goto :start
:Valid_Date Date
echo.        :::----------------------------------------------------------:::
echo.        :::                                                          :::
echo.        ::: Syntax: Valid_Date.cmd 'Date' ^(variable or string^)       :::
echo.        :::                                                          :::
echo.        ::: Date may be passed as a value or as a reference variable :::
echo.        ::: Checks for validity of the Date value, which may be in   :::
echo.        ::: M-D-Y, MM-DD-YYYY, YYYYMMDD or Julian (7 digit) format   :::
echo.        ::: Julian dates correspond to 12 noon on calendar dates     :::
echo.        ::: Julian dates must be 7 digits, ie. 0000000 for day 0     :::
echo.        ::: that corresponds to 12 Noon on January 1st, 4713 BC      :::
echo.        ::: Non English YYYY.MM.DD is valid if year is gtr 2 digits  :::
echo.        ::: 1 or 2 digit years in M-D-Y format are considered 2000+  :::
echo.        ::: For B.C. years a '-' must immediately predcede the year  :::
echo.        ::: For example the year 11 B.C. should be passed as -0011   :::
echo.        ::: The year 45 A.D. should be represented as 0045            :::
echo.        ::: The valid date parameter separators are: '/' '-' and '.' :::
echo.        ::: The Gregorian calendar is used after Oct 4th, 1582       :::
echo.        ::: The Julian Calendar is used for Oct 4, 1582 and ealier   :::
echo.        ::: Returns 0 in errorlevel if date is valid, else non zero  :::
echo.        ::: Variable 'Valid_Date.ymd' will be set to YYYYMMDD format :::
echo.        ::: Variable 'Valid_Date.jul' will be set to Julian number   :::
echo.        :::                                                          :::
echo.        :::----------------------------------------------------------:::
exit /b 1

:start
if "%~1"=="" goto :Valid_Date Syntax
if "%~1"=="/?" goto :Valid_Date Syntax
SetLocal EnableDelayedExpansion
if defined %~1 (set "dte=!%~1!") else (
  set "dte=%~1 %~2"
  if "!dte:~-1!" equ " " set "dte=!dte:~0,-1!"
)
set "invalid="
set "sep="
set "ymd="
set "Julian="
set/a err=1
if /i "!dte:~0,1!" gtr "9" set "dte=!dte:* =!"
if /i "!dte:~0,1!" neq "-" (set "sign=") else (
  set "sign=-"
  set "dte=!dte:~1!"
)
for /f "tokens=1* delims=0123456789-/." %%a in ("!dte!") do set "err=%%a"
if /i !err! neq 1 set/a err=1&goto :end
for /f "tokens=1-2* delims=0123456789" %%a in ("!dte!") do (
  set "err=%%a"&set "sep=%%b"&set "invalid=%%c"
  if "!sep:~-1!" equ "-" if not defined sign (set "sign=-"&set "err=3")
)
call :strln dte len
if /i !err! equ 1 ( REM string is numeric only
  if /i !len! lss 7 goto :end
  if /i !len! gtr 8 goto :end
) else ( REM valid separators found in string
  if !err! neq 3 set/a err=2
  if defined invalid goto :end
  if not defined sep goto :end
  if /i !len! lss 5 goto :end
  if !err! neq 3 (
    if /i !len! gtr 10 goto :end
  ) else if /i !len! gtr 11 goto :end
)
set "dte=%dte:/=.%"
set "dte=%dte:-=.%"
echo !dte!|Find "..">nul
if !errorlevel! equ 0 if !err! neq 3 goto :end
set "ymd=%dte:.=%"
if /i !err! equ 1 ( REM process numeric only string
  if /i !len! equ 7 (
    set "ymd=!sign!!ymd!"
    set "djd=!ymd!"
    call :gdate ymd
    set/a err=0
  )
  set "dte=!ymd:~4,2!.!ymd:~6,2!.!ymd:~0,4!"
)
if !err! neq 0 ( REM process separated string
  for /f "tokens=1-3 delims=." %%A in ("!dte!") do (
    set mm=%%A&set dd=%%B& set "yy=%%C"
    call :strln mm mlen
    if /i !mlen! gtr 2 (
      set yy=%%A&set mm=%%B&set "dd=%%C"
      if not defined dd goto :end
    )
  )
  if not defined yy goto :end
  call :strln yy ylen
  if /i !dd! gtr 31 goto :end
  if /i !mm! gtr 12 goto :end
  set/a yy=!sign!10000!yy! %% 10000,mm=100!mm! %% 100,dd=100!dd! %% 100
  if !yy! geq 0 if !yy! lss 100 if /i !ylen! lss 3 set /a yy+=2000
  if /i !yy! equ 0 set/a err=1&goto :end
  if !yy!!mm!!dd! gtr 15821004 if !yy!!mm!!dd! lss 15821015 goto :end
  call :jdate JD yy mm dd
  if /i !errorlevel! neq 0 goto :end
  if /i 1!mm! lss 20 set "mm=0!mm!"
  if /i 1!dd! lss 20 set "dd=0!dd!"
  set "ymd=!yy!!mm!!dd!"
  set/a djd=JD
  call :Gdate JD
  if /i !JD! equ !ymd! set/a err=0
)
:end
if /i !err! equ 0 (call :format ymd 8&call :format djd 7) else (set "ymd="&set "dJD=")
EndLocal&set "Valid_Date.ymd=%ymd%"&set "Valid_Date.jul=%dJD%"&exit/b %err%

:format int length
if "%~2"=="" exit/b
if not defined %~1 exit /b
setlocal enabledelayedexpansion
set "int=!%~1!"
set "sign=!int:~0,1!"
if "!sign!" equ "-" (set "int=!int:~1!") else set "sign="
:format_loop
call :strln int len
if %len% geq %~2 endlocal&set "%~1=%sign%%int%"&exit/b
set "int=0%int%"&goto :format_loop

:jdate
setlocal enabledelayedexpansion
set/a yy=!%~2!,mm=!%~3!,dd=!%~4!
if /i !yy! lss 0 set/a yy+=1
set/a a=(14 - mm)/12
set/a m=mm + 12*a - 3
set/a y=yy+4800-a
set/a JDGregorian=dd + (153*m + 2)/5 + 365*y + y/4 - y/100 + y/400 - 32045
set/a JDJulian=dd + (153*m + 2)/5 + 365*y + y/4 - 32083
if /i %JDGregorian% geq 2299161 (set/a JD=%JDGregorian%) else (set/a JD=%JDJulian%)
rem echo JD=%JD%  JDJ=%JDJulian%   JDG=%JDGregorian%
endlocal&set/a %~1=%JD%&exit/b 0

:Strln
( SetLocal
  set "str=A!%~1!"
  set "len=0"
  for /L %%A in (12,-1,0) do (
    set /a "len|=1<<%%A"
    for %%B in (!len!) do if "!str:~%%B,1!"=="" set /a "len&=~1<<%%A"
  )
)
EndLocal&(
  if "%~2" NEQ "" set/a %~2=%len%
)&exit /b %len%

:gdate
setlocal enabledelayedexpansion
set "p=!%~1!"
if "!p:~0,1!" equ "-" (
  set "p=!p:~1!"
  set "sign=-"
) else set "sign="
if "!p!" neq "0" if "!p:~0,1!" equ "0" (set/a "p=!sign!1!p! %% 1000000") else set "p=!sign!!p!"
if !p! geq 2299161 goto :Gregorian_Calendar
:Julian_Calendar
  set/a b=0
  set/a c=p + 32082
  goto :Calc
:Gregorian_Calendar
  set/a a=p + 32044
  set/a b=(4*a + 3)/146097
  set/a c=a - (146097*b)/4
:Calc
set/a d= (4*c + 3)/1461
set/a e=c - 1461*d/4
set/a m=(5*e + 2)/153
set/a GDay=e - (153*m + 2)/5 + 1
set/a GMonth=m + 3 - 12*(m/10)
set/a GYear=100*b + d - 4800 + m/10
if /i %GYear% leq 0 set/a GYear-=1
if /i 1%GMonth% lss 20 set "GMonth=0%GMonth%"
if /i 1%GDay%   lss 20 set "GDay=0%GDay%"
endlocal&(
  set "%~1=%GYear%%GMonth%%GDay%"
)&exit/b %GYear%%GMonth%%GDay%