!fndatetime [176] - various date/time functions
!------------------------------------------------------------------------
!EDIT HISTORY
!Version 1.0:-
! [176] 06-Apr-26 / jdm / Fn'Date'To'YYMMDD$() wasn't working for JulianY2K and FILETIME args 
! [175] 24-Jun-25 / jdm / Standardize T_FILETIME time zone offset logic to fix discrepancies between to/from conversions;
!                           add VERSYS resource; fix bug in Fn'Date'To'SQL'DateTime$ with FILETIME (time was always now) 
! [174] 27-Apr-25 / jdm / Add test of Fn'Time'To'Secs(); extend Fn'Time'To'Secs() to support > 24 hours
! [173] 16-Apr-25 / jdm / Fn'Date'To'SQL'DateTime$() now handles binary time properly 
! [172] 15-Apr-25 / jdm / Fix bug in Fn'Date'Plus'Days'To'MM'DD'YY$() confusing CC with YY 
! [171] 10-Apr-25 / jdm / Upgrade Date'Plus'Days... to handle Julian  
! [170] 02-Apr-25 / jdm / Add Fn'Secs'Ago(); 'to'FILETIME now supports "now" for time 
! [169] 27-Mar-25 / jdm / Clean up/remove the dup & numeric Fn'Date'Earliest() / 'Latest()
! [168] 26-Mar-25 / jdm / Fix issues with DMY ldf format not defaulting properly 
! [167] 25-Mar-25 / jdm / Add weekdays option to Fn'Days'Ago()
! [166] 16-Aug-24 / jdm / Add DOW_xxx definitions
! [165] 24-Feb-24 / jdm / Add Fn'Date'Earliest$() and Fn'Date'Latest$()
! [164] 06-Sep-23 / jdm / Add keywords, include fndtoffset.bsi (Fn'DateTimeOffset$)
! [163] 05-Sep-23 / jdm / Add keywords (for SOSFUNCIDX)
! [162] 27-Aug-23 / jdm / Upgrade FILETIME functions to use T_FILETIME48
! [161] 30-Apr-23 / jdm / Minor refinement to Fn'Date'In'Range 
! [160] 03-Apr-23 / jdm / Fix Fn'FILETIME'To'SQL'DateTime$(0) to return "" 
! [159] 31-Mar-23 / jdm / Treat mm/dd as mm/dd/yy (current yy) 
! [158] 25-Mar-23 / jdm / Refinement to Fn'Date'To'SQL'DateTime$() 
! [157] 08-Mar-23 / jdm / Add Fn'FILETIME'To'SQL'DATETIME$() alias
! [156] 27-Nov-22 / jdm / Add Fn'Date'To'Weekday'Name$(date), Fn'DOW'To'Weekday'Name$(dow) 
! [155] 17-Nov-22 / jdm / Add Fn'Msecs'To'SQL'Time$(msecs)
! [154] 03-Nov-22 / jdm / Minor enhancement to test routine handling of Fn'Date'Time'To'FILETIME()
! [153] 16-Aug-22 / jdm / Fn'Date'To'SQL'DateTime$() allows combining time with date in 1 arg
! [152] 18-Apr-22 / jdm / Minor tweak to year <= this year when
!                           yymmdd vs mmddyy is ambiguous
! [151] 05-Apr-22 / jdm / Add Fn'Date'Plus'Days'To'MM'DD'YY$(date,days);
!                           add to test routines
! [150] 14-Mar-22 / jdm / Add Fn'YYMMDD'To'MM'DD'YY$(), Fn'SQL'Date'To'MMDDYY$()
! [149] 18-Feb-22 / jdm / Minor refinements to Fn'Days'Ago()
! [148] 18-Jan-22 / jdm / Fix Fn'Date'To'CCYYMMDD$(juldate)
! [147] 15-Nov-21 / jdm / Add Fn'FILETIME'To'CCYYMMDDhhmmss$(), Fn'SQL'DateTime'To'FILETIME()

! [146] 01-Nov-21 / jdm / Add Fn'Date'In'Range()
! [145] 12-Sep-21 / jdm / Add Fn'Time'To'SYSTIME(time$) - same as Fn'Time'To'Secs()
! [144] 25-Aug-21 / jdm / Fix Fn'Date'To'SQL'Date$ date order problem
! [143] 19-Aug-21 / jdm / Add Fn'SQL'Date'To'CCYYMMDD
! [142] 15-Aug-21 / jdm / Convert 0 values of julian and filetime to ""
! [141] 11-Jul-21 / jdm / Include T_JULIANY2K and T_FILETIME in T_FLEX_DATE; 
!                           add Fn'Date'To'JulianY2K()
! [140] 05-Jun-21 / jdm / Add Fn'Date'To'SQL'DateTime$()
! [139] 18-May-21 / jdm / Add Fn'SQL'DateTime'To'CCYYMMDDhhmmss$()
! [138] 01-Apr-21 / jdm / Add Fn'Date'To'SQL'Date$()
! [137] 19-Mar-21 / jdm / Add Fn'Month'Name$(mm)
! [136] 14-Feb-21 / jdm / Fn'Date'Time'To'FILETIME() now allows date & time in one arg
! [135] 22-Jan-21 / jdm / Chg debug.print to debug.print for 6.4 backward compatibility
! [134] 26-Aug-20 / jdm / Expand Fn'Days'Ago() to allow as asof date /jd
! [133] 19-Jun-20 / jdm / Add Fn'Date'To'MM_DD_YY$() (with dashes)
! [132] 06-Mar-20 / jdm / Add Fn'FILETIME'To'Formatted'DateTime$() 
! [131] 10-Jan-20 / jdm / Revise Fn'FILETIME'To'ODTIM$() to return "" for 0
! [130] 22-Nov-19 / jdm / Add Fn'Date'To'YYMMDD$()
! [129] 13-Oct-19 / jdm / Fix bug in Fn'Date'Time'To'FILETIME
! [128] 11-Oct-19 / jdm / FILETIME'To'ODTIM$()
! [127] 25-Sep-19 / jdm / Add Fn'Date'Is'Valid() 
! [126] 23-Sep-19 / jdm / Support today+# and today-# aliases 
! [125] 20-Sep-19 / jdm / Support ccyy-mm-dd format for flexdates
! [124] 16-Sep-19 / jdm / Add flexdate support for format: "Mon 09-16-19 00:45 AM"
! [123] 04-Sep-19 / jdm / Add Fn'CCYYMMDDHHMMSS'ODTIM$(datetime,flags) 
! [122] 23-Aug-19 / jdm / Fixes to Fn'Date'Time'To'FILETIME(); 
!                           qualify several trace messages
! [121] 09-Aug-19 / jdm / Add "now" to aliases; Add Fn'Date'Time'To'FILETIME(),
!                           Fn'Time'To'Secs()
! [120] 07-Aug-19 / jdm / Add Fn'Date'To'MMDDYY$() 
! [119] 16-Jul-19 / jdm / Fix support for long date format; modernize tracing 
! [118] 17-Jun-19 / jdm / Support RFC822 date format in T_FLEX_DATE; clean up others
! [117] 17-Jun-19 / jdm / Fix yymmdd confusion in Date'to'...
! [116] 09-Jun-19 / jdm / Define T_FLEX_DATE, use for all generic date args;
!                           add Fn'Date'ODTIM$(); Fn'Date'To'Sdate();
!                           clean up notes, arg lists
! [115] 20-May-19 / jdm / Fn'Date'To'xxx now assumes input order of month and day
!                           based on LDF; output based on function name 
!                           (with option to override);  enhance test routines;
!                           add format override option to Fn'Date'Alias'to'MM'DD'CCYY$;
!                           add Fn'Date'To'DD'MM'CCYY$(); add Fn'Date'Alias'to'DD'MM'CCYY$;
!                           add Fn'Date'To'DD'MM'YY$(), Fn'Date'To'DOW()
! [114] 18-May-19 / jdm / Add Fn'Sdate'to'MMDDYY$()
! [113] 08-May-19 / jdm / Consolidate LDF copy in private; minor fix to test routine
! [112] 02-May-19 / jdm / Fn'Date'To'xxx now recognize YYMMDD, CCYYMMDD 
! [111] 02-May-19 / jdm / Add Fn'CCYYMMDD'To'MM'DD'YY$() alias
! [110] 29-Apr-19 / jdm / Add Fn'YYMMDD'To'MM'DD'YY$() 
! [109] 25-Apr-19 / jdm / Support "Tuesday, March 21, 2019" fmt in Fn'Date'To'xxx
! [108] 24-Apr-19 / jdm / Support "Fri Jan 25, 2019 01:12 PM" fmt in Fn'Date'To'xxx
! [107] 18-Mar-19 / jdm / Add Fn'DOW'From'Date()
! [106] 18-Mar-19 / jdm / Add Fn'Date'To'MM'DD'YY$()
! [105] 17-May-18 / jdm / Add Fn'TimeStamp'GMT$(idate,itime,offset)
! [104] 14-Jan-18 / jdm / Add Fn'Date'Alias'to'MM'DD'CCYY$; use in Fn'Date'To'xxx functions
! [103] 27-Dec-17 / jdm / Add Fn'Date'To'CCYYMMDD$ /jdm
! [102] 06-Sep-16 / jdm / Add test routine (compile with /C:FNDATETIME_BSI_TEST=1) 
! [101] 13-Apr-16 / jdm / Add Fn'Date'To'MM'DD'CCYY$
! [100] 19-Dec-15 / jdm / created
!------------------------------------------------------------------------
!PUBLIC ROUTINES:
! Fn'Date'To'MM'DD'CCYY$(flexdate${,format}) - convert flex date to MM/DD/CCYY
! Fn'Date'To'DD'MM'CCYY$(flexdate$) - DD/MM/CCYY version of Fn'Date'To'MM'DD'CCYY$()
! Fn'Date'To'MM'DD'YY$(flexdate$) - convert flex date formats to MM/DD/YY
! Fn'YYMMDD'To'MM'DD'YY$(T_YYMMDD) 
! Fn'Date'To'MM_DD_YY$(flexdate$) - convert flex date formats to MM-DD-YY [133]
! Fn'Date'To'MMDDYY$(flexdate$) - convert flex date formats to MMDDYY   [120]
! Fn'Date'To'CCYYMMDD$(flexdate$) - convert flex date formats to CCYYMMDD [103]
! Fn'Date'To'YYMMDD$(flexdate$) - convert flex date formats to YYMMDD [130]
! Fn'Date'Earliest$(flexdate1$,flexdate2$) - return earliest of the dates passed (no format chg) [165]
! Fn'Date'Latest$(flexdate1$,flexdate2$) - return latest of the dates passed (no format chg) [165]
! Fn'Date'To'SQL'Date$(flexdate$) - convert flex date to CCYY-MM-DD [138]
! Fn'Date'To'SQL'DateTime$(flexdate$,flextime$) - convert flex date to CCYY-MM-DD hh:mm:ss [140]
! Fn'Msecs'To'SQL'Time$(msecs) - convert msecs since midnight to hh:mm:ss.xxx [155] 
! Fn'Date'To'Sdate(flexdate${,format}) - convert flexdate to system separated date [116]
! Fn'Date'ODTIM$(flexdate$,flags)  - wrapper for ODTIM() for flexdates$ [116]
!
! Fn'SQL'DateTime'To'CCYYMMDDhhmmss$(sqltime) - convert CCYY-MM-DD hh:mm:ss{.ttt} to CCYYMMDDhhmm{ssttt}
! Fn'SQL'Date'To'CCYYMMDD$(sqldate) - convert CCYY-MM-DD to CCYYMMDD
! Fn'SQL'Date'To'MMDDYY$(sqldate) - convert CCYY-MM-DD to MMDDYY
! Fn'SQL'Date'To'FILETIME(sqldate) - convert CCYY-MM-DD to T_FILETIME [147]

! Fn'Date'Alias'To'MM'DD'CCYY$(alias${,format}) - convert date aliases 
!           (e.g. yesterday, first of month, etc.) to mm/dd/ccyy [104]
! Fn'Date'Alias'To'DD'MM'CCYY$(alias$) - DD/MM/CCYY version of Fn'Date'Alias'To'MM'DD'CCYY()
! Fn'TimeStamp'GMT$(idate,itime,offset) - e.g. Tue, 15 Nov 1994 08:12:31 GMT
! Fn'DOW'From'Date(flexdate$) - return DOW for specified date (or today) [107]
! Fn'Date'To'Weekday'Name$(date) - return weekday name from date [156]
! Fn'DOW'To'Weekday'Name$(dow) - return weekday name from DOW # [156]
! Fn'Days'Ago(flexdate${,asofdate$}) - return # days flexdate$ (flex date) is ago (as of today or asofdate) [134]
! Fn'Date'Plus'Days'To'MM'DD'YY$(flexdate$,days) - return mm/dd/yy for date days after flexdate$ [151]
! Fn'Sdate'To'CCYYMMDD$(sepdate) - convert separated date (b4) to ccyymmdd [114] /jdm
! Fn'YYMMDD'To'MM'DD'YY$(flexdate$) - convert {CC}YYMMDD to MM/DD/YY
! Fn'Mon'To'Num(month$ as s20) - convert month name/abbr to number
! Fn'Parse'Date'Time(subject$, patno, day,mon,yr,hh,mm,ss) - check if
!			subject is date and/or time and if so, parse it
! Fn'CCYYMMDDHHMMSS'ODTIM$(datetime,flags) - ODTIM() wrapper for CCYYMMDDhhmmss [123]
! Fn'Date'Time'To'FILETIME(flexdate${,flextime$}) - convert date/time to T_FILETIME format [121]
! Fn'FILETIME'To'ODTIM$(T_FILETIME,odtim'flags) - output T_FILETIME using ODTIM flags [128]
! Fn'Time'To'Secs(flextime$)    - secs since midnight [121]
! Fn'Time'To'SYSTIME(flextime$) - same as Fn'Time'To'Secs() [14]

! Fn'Date'Is'Valid(flexdate$) - returns .TRUE if valid [127]
! Fn'FILETIME'To'Formatted'DateTime$(T_FILETIME) -> CCYY-MM-DD HH:MM:SS [132]
! Fn'FILETIME'To'SQL'DateTime$(T_FILETIME) (alias) [157]
! Fn'FILETIME'To'CCYYMMDDHHMMSS$(T_FILETIME) -> CCYYMMDDhhmmss [147]
! Fn'Month'Name$(mm) - return name of month # [137]
! Fn'Date'In'Range(testdate,sdate,edate) - test if testdate is within range (inclusive) [146]
!
! Fn'DateTimeOffset$(idate,itime,offdays,offhrs,odate,otime,flags) - return new timestamp offset from old [164]
!           (actualling part of fndtoffset.bsi)
!
!PRIVATE FUNCTIONS
! fn'juliany2k'to'mm'dd'yy$(juldate) - convert juliany2k to MM/DD/YY [141]
! fn'juliany2k'to'ccyymmdd$(juldate) - convert juliany2k to CCYYMMDD [141]
! fn'filetime'timezone'offset() - T_FILETIME conversion offset for local timezone [175]

!------------------------------------------------------------------------
! Keywords: julian conversion 
!
! Also see:
!   fnfileage.bsi  (file age functions)
!   fndtoffset.bsi (date offset by days/hours) (now included)
!------------------------------------------------------------------------
! Date Format Notes: [116]
!   Functions with 'Date' in the name accept a T_FLEX_DATE input type,
!   which supports the following formats:
!
!   1.  {#}#-aaa-##{##}     e.g. 3-may-19, 21-AUG-2019  
!                           (month names from LDF; case insensitive)
!
!   2.  mmdd{cc}yy
!       {cc}yymmdd          
!                           Warning: the above formats are potentially
!                           ambiguous unless we know that {cc}yy will be
!                           in range of 1913-2000 or 2013-2099 and the 
!                           length is either 8 digits or the LDF puts
!                           the month before the day. (That way if first
!                           ## > 12, we can assume it's CC or YY, depending
!                           on if 6 or 8 digits long.)  For ###### format
!                           in day-before-month LDF environments, we
!                           assume ddmmyy unless first two digits > 31.
!
!    3. Anything supported by IDTIM, i.e mm/dd/{cc}yy or dd/mm/{cc}yy depending
!           on order in LDF; separator may be dash or slash
!
!    4. Any alias supported by Fn'Date'Alias'To'CCYYMMDD$() (NOT case sensitive), e.g.
!                           "First of Last Month"
!                           "First of Last Year"
!                           "First of This Month"
!                           "First of This Year"
!                           "Today"
!                           "Yesterday"
!                           "Now" [121] equivalent to current time for time contexts
!                           "Today+#"  ![126] # days forward from today
!                           "Today-#"  ![126] # days back from today ("today-1" equals "yesterday")
!
!    5. Ddd Mmm dd, ccyy hh:mm ?M      e.g. Fri Jan 25, 2019 01:12 PM [108] (odtim flags &hCE2)
!    6. Dayname, Month dd, ccyy        e.g. Friday, March 01, 2019 [109]
!    7. RFC822 format                  e.g. Mon, 17 Jun 2019 {17:40:32 -0700}  [118]
!    8. T_JULIANY2K                    (days since 1/1/2000) [141]
!    9. T_FILETIME                     (UNIX seconds since 'epoch' 1/1/1970) [141]
!   10. T_SQL_DATE, T_SQL_DATETIME     e.g. CCYY-MM-DD{ hh.mm.ss...)
!-------------------------------------------------------------------------

++ifndef INC_FNDATETIME_BSI_

define INC_FNDATETIME_BSI_ = 1

map1 VERSYS_FNDATETIME_BSI,s,40,">>@VERSYS(1)->>fndatetime.bsi[175]"

++include'once ashinc:ashell.def
++include'once ashinc:ashell.sdf    ! [114]
++include'once ashinc:types.def     ! [107]
++include'once ashinc:regex.def
++include'once ashinc:gtlang.sdf    ! [113]
++include'once sosfunc:fnistype.bsi
++include'once sosfunc:fndec2hex.bsi
++include'once sosfunc:fnfileage.bsi    ! [121]
++include'once sosfunc:fnextch.bsi      ! [121]
++include'once sosfunc:fndtoffset.bsi   ! [164]

![166] DOW values
define DOW_MON = 0
define DOW_TUE = 1
define DOW_WED = 2
define DOW_THU = 3
define DOW_FRI = 4
define DOW_SAT = 5
define DOW_SUN = 6

deftype T_FLEX_DATE = s,50       ! [116] (see Date Format Notes above)
deftype T_FLEX_TIME = s,12       ! [121] various time formats, including "now"

define EPOCH_1900_OFFSET = 25567    ! [121] # days from 1/1/1900 to 1/1/1970
define SECS_PER_DAY = 86400         ! [171]
    
++pragma PRIVATE_BEGIN              ! [113] 
    map1 Datetime'Privates
        map2 Ldf, ST_GTLANG
        map2 DebugBuffer,s,100,"zzz"
        map2 Status,f
        
    xcall GTLANG, Status, Ldf           
++pragma PRIVATE_END


defalias Fn'CCYYMMDD'To'MM'DD'YY$() = Fn'YYMMDD'To'MM'DD'YY$()  ! [111]
defalias Fn'Date'To'DOW() = Fn'DOW'From'Date()                  ! [115]
defalias Fn'FILETIME'To'SQL'DateTime$() = Fn'FILETIME'To'Formatted'DateTime$()  ![157]
!======================================================================
! Simple test code: 
!   .COMPIL FNDATETIME.BSI/X:2/PX/C:FNDATETIME_BSI_TEST=1 
!   .RUN FNDATETIME
!======================================================================
++ifdef FNDATETIME_BSI_TEST

    ++message Compiling FNDATETIME.BSI test routine...
    
    ++include'once sosfunc:fndec2hex.bsi    ! [121]

    significance 11
    
    map1 testvars
        map2 test,b,1
        map2 flexdate$,T_FLEX_DATE
        map2 flexdate2$,T_FLEX_DATE     ! [165]
        map2 flextime$,T_FLEX_TIME      ! [121]
        map2 month$,s,20
        map2 day,b,2
        map2 mon,b,2
        map2 yr,b,2
        map2 hh,b,2
        map2 mm,b,2
        map2 ss,b,2
		map2 itime,b,4
		map2 offset,i,2
        map2 format$,s,3
        map2 flags,b,4
        map2 filetime,T_FILETIME48      ! [121][162]
        map2 strtime$,T_FLEX_DATE       ! [121]
        map2 ccyymmddhhmmss$,s,14       ! [123]
        map2 juldate,T_JULIANY2K        ! [141]
        map2 asofdate$,T_FLEX_DATE      ! [151] 
        map2 days,i,2
        map2 msecs,b,4                  ! [155]
        map2 bweekdays,BOOLEAN          ! [167]
        
	map1 sepdatex
		map2 s'm,b,1
		map2 s'd,b,1
		map2 s'y,b,1
		map2 d'dow,b,1
	map1 sepdate,b,4,@sepdatex
        
    ? "Testing FNDATETIME.BSI routines ..."
    ? "1)Fn'Date'To'?,2)Fn'Mon'To'Num,3)Fn'Parse'Date'Time,4)GMT,5)ODTIM,6)ftime "
    ? "7)Julian, 8)Fn'Days'Ago, 9)Fn'Date'Plus'Days'To'MM'DD'YY, 10)Msecs "
    input "11)Fn'Date'Earliest$ Fn'Date'Latest$, 12)Time to Secs : ",test   ! [116][121][151][155][165]
    switch test
        case 1
            input line "flexdate$ (flex format): ",flexdate$          ! [113]
            input "optional format (md, dm, ldf): ",format$         ! [115]
            ? "Fn'Date'To'MM'DD'CCYY$(";flexdate$;") = ";Fn'Date'To'MM'DD'CCYY$(flexdate$,format$)
            ? "Fn'Date'To'MM'DD'YY$(";flexdate$;") = ";Fn'Date'To'MM'DD'YY$(flexdate$,format$)
            ? "Fn'Date'To'MMDDYY$(";flexdate$;") = ";Fn'Date'To'MMDDYY$(flexdate$,format$)   ! [120]
            ? "Fn'Date'To'DD'MM'YY$(";flexdate$;") = ";Fn'Date'To'DD'MM'YY$(flexdate$)
            ? "Fn'Date'To'DD'MM'CCYY$(";flexdate$;") = ";Fn'Date'To'DD'MM'CCYY$(flexdate$)
            ? "Fn'Date'To'CCYYMMDD$(";flexdate$;") = ";Fn'Date'To'CCYYMMDD$(flexdate$)
            ? "Fn'Days'Ago(";flexdate$;") = ";Fn'Days'Ago(flexdate$)
            ? "Fn'Date'To'DOW(";flexdate$;") = ";Fn'Date'To'DOW(flexdate$);" (0=Mon, 6=Sun)"
            juldate = Fn'Date'To'JulianY2K(flexdate$)           ! [141]
            ? "Fn'Date'To'JulianY2K(";flexdate$;") = ";juldate  ! [141]
            ? "Fn'Date'To'MM'DD'YY$(";juldate;"as T_JULIANY2K) = ";Fn'Date'To'MM'DD'YY$(juldate)   ! [141]
            ? "Fn'Date'To'MM'DD'CCYY$(";juldate;"as T_JULIANY2K,";format$;") = ";Fn'Date'To'MM'DD'CCYY$(juldate,format$)   ! [141]
            if instr(1,flexdate$,":") then      ! [154] assume contains time
                filetime = Fn'Date'Time'To'FILETIME(flexdate$)           ! [154]
                ? "Fn'Date'Time'To'FILETIME(";flexdate$;") = ";filetime  ! [154]
            else                                ! [154] use 'now' for time
                filetime = Fn'Date'Time'To'FILETIME(flexdate$,"now")           ! [141]
                ? "Fn'Date'Time'To'FILETIME(";flexdate$;",now) = ";filetime  ! [141]
            endif
            ? "Fn'Date'To'MM'DD'YY$(";filetime;"as T_FILETIME48) = ";Fn'Date'To'MM'DD'YY$(filetime)   ! [141][162]
            ? "Fn'Date'To'SQL'Date$(";flexdate$;") = ";Fn'Date'To'SQL'Date$(flexdate$)      ! [144]
            ? "Fn'Date'To'SQL'DateTime$(";flexdate$;") = ";Fn'Date'To'SQL'DateTime$(flexdate$)      ! [158]
            ? "Fn'Date'To'Weekday'Name$(";flexdate$;") = ";Fn'Date'To'Weekday'Name$(flexdate$)      ! [156]
            ? "Fn'FILETIME'To'SQL'Date'Time$(0) = ";Fn'FILETIME'To'SQL'DateTime$(0)
            exit
        case 2
            input "month name or abbr: ",month$
            ? "Fn'Mon'To'Num(";month$;") = ";Fn'Mon'To'Num(month$)
            exit
        case 3
		case 4
            input line "input date/time (various formats): ",flexdate$     ! [113]
            ? "Fn'Parse'Date'Time("+flexdate$+",0,day,mon,yr,hh,mm,ss) = ";Fn'Parse'Date'Time(flexdate$,0,day,mon,yr,hh,mm,ss)
            ? "day=";day;", mon=";mon;", yr=";yr;", hh=";hh;", mm=";mm;" ss=";ss
			if test = 4 then			! 
				input "GMT offset: ",offset
				s'm = mon
				s'd = day
				s'y = yr - 1900
				itime = (hh * 3600) + (mm * 60) + ss
				? "Fn'TimeStamp'GMT$: ";Fn'TimeStamp'GMT$(sepdate, itime, offset)
			endif
            exit
	    case 5:                                                     ! [115]
            input line "flexdate$ (flex format): ",flexdate$        
            input "optional format (md, dm, ldf): ",format$         
            input "ODTIM flags: ",flags
            ? "Fn'Date'ODTIM$: ";Fn'Date'ODTIM$(flexdate$,flags,format$)
            
            input "CCYYMMDDHHMMSS: ",ccyymmddhhmmss$                                        ! [123]
            input "ODTIM flags: ",flags                                                     ! [123]
            ? "Fn'CCYYMMDDHHMMSS'ODTIM$: ";Fn'CCYYMMDDHHMMSS'ODTIM$(ccyymmddhhmmss$,flags)  ! [123]
            exit
        case 6:             ! [121]
            input line "flexdate$ (flex format): ",flexdate$        
            input line "flextime$ ('now', hh:mm, etc) ",flextime$
            filetime = Fn'Date'Time'To'FILETIME(flexdate$,flextime$)
            ? "filetime = ";filetime;" (";Fn'Dec2Hex$(filetime);")"
            xcall MIAMEX, MX_FTFORMAT, filetime, strtime$
            ? "MX_FTFORMAT: ";strtime$
            ? "Fn'FILETIME'To'ODTIM$(";filetime;") = ";Fn'FILETIME'To'ODTIM$(filetime)
            ? "Fn'Time'To'SYSTIME(";flextime$;") = ";Fn'Time'To'SYSTIME(flextime$)      ! [145]
            open #1, "test.lst", output
            close #1
            filetime = Fn'FileAge("test.lst")
            ? "Fn'FileAge(<file created now>) = ";filetime;" (";Fn'Dec2Hex$(filetime);")"
        case 7:
            input "T_JULIANY2K: ",juldate
            !juldate = Fn'Date'To'MMDDYY$(juldate)
            ? "Fn'Date'To'MM'DD'CCYY$(";juldate;") = ";Fn'Date'To'MM'DD'CCYY$(juldate)
            ? "Fn'Date'To'MM'DD'YY$(";juldate;") = ";Fn'Date'To'MM'DD'YY$(juldate)
            ? "Fn'Date'To'MMDDYY$(";juldate;") = ";Fn'Date'To'MMDDYY$(juldate)
            ? "Fn'Date'To'DD'MM'YY$(";juldate;") = ";Fn'Date'To'DD'MM'YY$(juldate)
            ? "Fn'Date'To'DD'MM'CCYY$(";juldate;") = ";Fn'Date'To'DD'MM'CCYY$(juldate)
            ? "Fn'Date'To'CCYYMMDD$(";juldate;") = ";Fn'Date'To'CCYYMMDD$(juldate)
            
            exit
        case 8:     ! [151]
            input line "flexdate$ (flex format): ",flexdate$ 
            input line "asofdate$ (blank for today): ",asofdate$
            input line "0) all days, 1) week days only: ",bweekdays
            bweekdays = ifelse(bweekdays,.TRUE,.FALSE)
            ? "Fn'Days'Ago(";flexdate$;",";asofdate$;") = ";Fn'Days'Ago(flexdate$,asofdate$=asofdate$,bweekdays=bweekdays)    ! [167]
            ? "For seconds ago..."                                  ! [170]
            input line "flexdate$ (flex format date {time}): ",flexdate$ 
            input line "asofdate$ (blank for today now): ",asofdate$
            ? "Fn'Secs'Ago(";flexdate$;",";asofdate$") = ";Fn'Secs'Ago(flexdate$,asofdate$=asofdate$)   ! [170] 
            exit
        case 9:     ! [151]
            input line "flexdate$ (flex format): ",flexdate$                    
            input line "days (after): ",days
            ? "Fn'Date'Plus'Days'To'MM'DD'YY$(";flexdate$;",";days;") = ";Fn'Date'Plus'Days'To'MM'DD'YY$(flexdate$,days=days)
            exit
        case 10:    ! [155]
            input "Msecs since midnight: ",msecs
            ? "Fn'Msecs'To'SQL'Time$(";msecs;") = ";Fn'Msecs'To'SQL'Time$(msecs)
            exit
        case 11     ! [165]
            input line "flexdate1$ (flex format): ",flexdate$                    
            input line "flexdate2$ (flex format): ",flexdate2$                    
            ? "Fn'Date'Earliest$(";flexdate$;",";flexdate2$;") = ";Fn'Date'Earliest$(flexdate$,flexdate2$)   
            ? "Fn'Date'Latest$(";flexdate$;",";flexdate2$;") = ";Fn'Date'Latest$(flexdate$,flexdate2$)   
            exit
        case 12     ! [174]
            input "time (###:##{:##}: ", flextime$
            ? "Fn'Time'To'Secs(";flextime$;") = ";Fn'Time'To'Secs(flextime$)
            exit
        default
            exit
    endswitch
    end
++endif         ! end of test routine


![165]
!---------------------------------------------------------------------
!Function:
!   Return earliest of the dates passed
!Params:
!   flexdate1$  (T_FLEX_DATE) [in] - first date
!   flexdate2$  (T_FLEX_DATE) [in] - second date
!Returns:
!Returns:
!   flexdate1$ or flexdate2$ whichever earlier; no format change 
!       unless input format was binary in which case output is CCYYMMDD
!Globals:
!Notes:
!---------------------------------------------------------------------
Function Fn'Date'Earliest$(flexdate1$ as T_FLEX_DATE:inputonly, &
                          flexdate2$ as T_FLEX_DATE:inputonly) as T_FLEX_DATE
    map1 locals
        map2 ccyymmdd1$,s,8
        map2 ccyymmdd2$,s,8
        
    ccyymmdd1$ = Fn'Date'To'CCYYMMDD$(flexdate1$)
    ccyymmdd2$ = Fn'Date'To'CCYYMMDD$(flexdate2$)
    
    if ccyymmdd1$ <= ccyymmdd2$ then
        .fn = ifelse$((.argtyp(@flexdate1$) and ARGTYP_MASK) = ARGTYP_B,ccyymmdd1$, flexdate1$)
    else
        .fn = ifelse$((.argtyp(@flexdate2$) and ARGTYP_MASK) = ARGTYP_B,ccyymmdd2$, flexdate2$)
    endif

EndFunction

![165]
!---------------------------------------------------------------------
!Function:
!   Return latest of the dates passed
!Params:
!   flexdate1$  (T_FLEX_DATE) [in] - first date
!   flexdate2$  (T_FLEX_DATE) [in] - second date
!Returns:
!   flexdate1$ or flexdate2$ whichever earlier; no format change 
!       unless input format was binary in which case output is CCYYMMDD
!Globals:
!Notes:
!---------------------------------------------------------------------
Function Fn'Date'Latest$(flexdate1$ as T_FLEX_DATE:inputonly, &
                          flexdate2$ as T_FLEX_DATE:inputonly) as T_FLEX_DATE
    map1 locals
        map2 ccyymmdd1$,s,8
        map2 ccyymmdd2$,s,8
        
    ccyymmdd1$ = Fn'Date'To'CCYYMMDD$(flexdate1$)
    ccyymmdd2$ = Fn'Date'To'CCYYMMDD$(flexdate2$)
    
    if ccyymmdd1$ >= ccyymmdd2$ then
        .fn = ifelse$((.argtyp(@flexdate1$) and ARGTYP_MASK) = ARGTYP_B,ccyymmdd1$, flexdate1$)
    else
        .fn = ifelse$((.argtyp(@flexdate2$) and ARGTYP_MASK) = ARGTYP_B,ccyymmdd2$, flexdate2$)
    endif

EndFunction

!---------------------------------------------------------------------
!Function:
!   convert flex date formats to CCYYMMDD
!Params:
!   flexdate$ (T_FLEX_DATE) [in] - see Date Format Notes at top of file
!Returns:
!   ccyymmdd if input date valid, else ""
!Globals:
!Notes:
!   Wraps around Fn'Date'To'MM'DD'CCYY$()
!   if input "", output is also ""
!---------------------------------------------------------------------

Function Fn'Date'To'CCYYMMDD$(flexdate$ as T_FLEX_DATE:inputonly) as s8
debug.print (99,"fndatetime") "Fn'Date'To'CCYYMMDD$() flexdate$=["+flexdate$+"]"

    map1 locals
        map2 mm'dd'ccyy$,s,10
        map2 juldate,T_JULIANY2K        ! [148]
        map2 filedate,T_FILETIME48      ! [148][162]
        
    ! [148] detect if caller passed us a B2 or B4, and if so,
    ! [148] we need to pass same type to Fn'Date'To'MM'DD'CCYY$
    if (.argtyp(@flexdate$) and ARGTYP_MASK) = ARGTYP_B then    ! 
        if .argsiz(@flexdate$) = 2 then                         ! T_JULIANY2K (B,2)
            if val(flexdate$) > 0 then                          ! only if not 0
                .fn = fn'juliany2k'to'ccyymmdd$(flexdate$)      ! CCYYMMDD
            endif
        elseif .argsiz(@flexdate$) >= 4 then                    ! T_FILETIME (B,4 [162] or T_FILETIME48 (B,6)
            if val(flexdate$) > 0 then                          ! only if not 0
                .fn = Fn'FILETIME'To'ODTIM$(flexdate$,&h00200268)   ! MMDDCCYY
                .fn = .fn[5;4] + .fn[1,4]                           ! CCYYMMDD
            endif
        endif
    else
        mm'dd'ccyy$ = Fn'Date'To'MM'DD'CCYY$(flexdate$)
        if mm'dd'ccyy$ # "" then
            .fn = mm'dd'ccyy$[7;4] + mm'dd'ccyy$[1;2] + mm'dd'ccyy$[4;2]
        endif
    endif
EndFunction

!---------------------------------------------------------------------
!Function:
!   convert flex date formats to YYMMDD
!Params:
!   flexdate$ (T_FLEX_DATE) [in] - see Date Format Notes at top of file
!Returns:
!   yymmdd if input date valid, else ""
!Globals:
!Notes:
!   Wraps around Fn'Date'To'CCYYMMDD$()
!   if input "", output is also ""
!---------------------------------------------------------------------

Function Fn'Date'To'YYMMDD$(flexdate$ as T_FLEX_DATE:inputonly) as s8
debug.print (99,"fndatetime") "Fn'Date'To'YYMMDD$() flexdate$=["+flexdate$+"]"

    ! [176] have to handle binary case separate here else the arg type is lost in 
    ! [176] secondary call to Fn'Date'To'CCYYMMDD$()
    ![176] .fn = Fn'Date'To'CCYYMMDD$(flexdate$)[3;6]    

    map1 locals
        map2 mm'dd'ccyy$,s,10
        map2 juldate,T_JULIANY2K 
        map2 filedate,T_FILETIME48
    
    if (.argtyp(@flexdate$) and ARGTYP_MASK) = ARGTYP_B then    ! 
        if .argsiz(@flexdate$) = 2 then                         ! T_JULIANY2K (B,2)
            if val(flexdate$) > 0 then                          ! only if not 0
                .fn = fn'juliany2k'to'ccyymmdd$(flexdate$)[3;6] ! YYMMDD
            endif
        elseif .argsiz(@flexdate$) >= 4 then                    ! T_FILETIME (B,4 [162] or T_FILETIME48 (B,6)
            if val(flexdate$) > 0 then                          ! only if not 0
                .fn = Fn'FILETIME'To'ODTIM$(flexdate$,&h00200268)   ! MMDDCCYY
                .fn = .fn[7;2] + .fn[1,4]                           ! YYMMDD
            endif
        endif
    else
        mm'dd'ccyy$ = Fn'Date'To'MM'DD'CCYY$(flexdate$)
        if mm'dd'ccyy$ # "" then
            .fn = mm'dd'ccyy$[9;2] + mm'dd'ccyy$[1;2] + mm'dd'ccyy$[4;2]    ! YYMMDD
        endif
    endif
    
EndFunction

!---------------------------------------------------------------------
!Function:
!   convert various date formats to MM/DD/CCYY
!Params:
!   flexdate$ !   flexdate$ (T_FLEX_DATE) [in] - see Date Format Notes at top of file
!   mon'day'order$ (str) [in] - (optional) flag controlling output order of month and day [115]
!                               "" or "md" = mm/dd/ccyy
!                               "dm"       = dd/mm/ccyy
!                               "ldf"      = (according to LDF) 
!Returns:
!   mm/dd/ccyy or dd/mm/ccyy (see mon'day'order$) if input date valid, else ""

!Globals:
!Notes:
!   The output format is always mm/dd/ccyy regardless of the LDF.
!   The dd/mm/ccyy version should probably be implemented as a wrapper,
!   possibly with an optional flag passed to this routine.
!---------------------------------------------------------------------

Function Fn'Date'To'MM'DD'CCYY$(flexdate$ as T_FLEX_DATE:inputonly, &
                                mon'day'order$ as s3:inputonly) as s10
                                
debug.print (99,"fndatetime") ABC_CURRENT_ROUTINE$, flexdate$, mon'day'order$

    map1 locals
        map2 mon$,s,3
        map2 monnum,b,1
        map2 idate2$,s,20
        map2 idate,f,6
        map2 itime,f,6
        map2 status,f
        map2 i,i,2
        map2 j,i,2
        map2 flags,T_BITFLAGS32
        map2 flexday$,s,20               ! [119]
        map2 suffix$,s,12                ! [124]
        
    static map1 Ldf'Day'Abbr'List$,s,28  ! [113] "Mon Tue Wed Thu Fri Sat Sun "
    static map1 Ldf'Day'Name'List$,s,60  ! [113] "Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday,"

    if Ldf'Day'Abbr'List$ = "" then      ! [113] if not yet set up, do it from the private module Ldf
        Ldf'Day'Abbr'List$ = Ldf.MON[1,3] + " " + Ldf.TUE[1,3] + " " + Ldf.WED[1,3] + " " &
                + Ldf.THU[1,3] + " " + Ldf.FRI[1,3] + " " + Ldf.SAT[1,3] + " " + Ldf.SUN[1,3] + " " 
        Ldf'Day'Name'List$ = Ldf.MON + "," + Ldf.TUE + "," + Ldf.WED + "," &
                + Ldf.THU + "," + Ldf.FRI + "," + Ldf.SAT + "," + Ldf.SUN + "," 
    endif
    
    if flexdate$ # "" then

        ! [141] handle T_JULIANY2K, T_FILETIME to CCYYMMDD 
        if (.argtyp(@flexdate$) and ARGTYP_MASK) = ARGTYP_B then
            if .argsiz(@flexdate$) = 2 then                 ! T_JULIANY2K (B,2)
                if val(flexdate$) > 0 then                          ! [142] only if not 0
                    .fn = fn'juliany2k'to'ccyymmdd$(flexdate$)          ! CCYYMMDD
                endif
            elseif .argsiz(@flexdate$) >= 4 then             ! T_FILETIME (B,4) [162] or T_FILETIME48 (B,6)
                if val(flexdate$) > 0 then                          ! [142] only if not 0
                    .fn = Fn'FILETIME'To'ODTIM$(flexdate$,&h00200268)   ! MMDDCCYY
                    .fn = .fn[5;4] + .fn[1,4]                           ! CCYYMMDD
                endif
            endif
            debug.print (99,"fndatetime") "julian: ",.fn
            if .fn # "" then        ! now convert to either MM/DD/CCYY or DD/MM/CCYY
                if val(flexdate$) = 0 then      ! binary 0 returns ""
                    .fn = ""
                else
                    if mon'day'order$ = "dm" or Ldf.DATE'FORMAT = 1 then        ! dd/mm/ccyy
                        .fn = .fn[7;2] + "/" + .fn[5;2] + "/" + .fn[1;4]
                    else
                        .fn = .fn[5;2] + "/" + .fn[7;2] + "/" + .fn[1;4]
                    endif
                endif    
                exitfunction
            endif
        endif
                
        xcall TRIM, flexdate$       ! [118] remove leading and trailing blanks
        
        ! [124] remove a trailing time string if present
        suffix$ = ucs(flexdate$[-2,-1])
        if suffix$ = "AM" or suffix$ = "PM" then
            i = -4                  ! ignore trailing AM/PM and look for time
        else                        ! in preceding token
            i = -1
        endif
        i = .instrr(i,flexdate$," ")
        if i then 
            suffix$ = flexdate$[i+1,-1]                   ! this could be a time string
            if suffix$[3;1] = ":" then                    ! if it looks like one
                flexdate$ = flexdate$[1,i-1]              ! remove it
                debug.print (99,"fndatetime") suffix$,flexdate$
            endif
        endif
        
        ! [104] if first and last character alpha, check for aliases
        debug.print (99,"fndatetime") "isalpha",flexdate$
        if fn'isalpha(flexdate$[1,1]) then
            ![126] (no longer requires alpha last char) if fn'isalpha(flexdate$[-1,-1]) then
                .fn = Fn'Date'Alias'To'MM'DD'CCYY$(flexdate$,mon'day'order$) ![115]
                if .fn # "" then     ! [126] if success on the alias, we're done
                    debug.print (99,"fndatetime") .fn,flexdate$
                    exitfunction                        ! we're done (no legal formats with first and last char alpha)
                endif

            ![126] else    ! check for other formats that start with an alpha character but don't end with one

                ! from here on, plan is to first convert flexdate$ to idate2$ in mm/dd/yy or dd/mm/yy
                ! format (according to LDF) and then let IDTIM validate and reconvert it
        
                ! [118] check for the RFC822 format,  e.g. Mon, 01 Aug 2017 ...
                if instr(1,flexdate$,"[a-z]{3,3}, \d\d [a-z]{3,3} \d\d\d\d",1) = 1 then   
                    monnum =  Fn'Mon'To'Num(flexdate$[9;3])
                    if monnum then
                        if Ldf.DATE'FORMAT = 1 then     ! day then month
                            idate2$ = flexdate$[6;2] + "-" + (monnum using "#Z") + "-" + flexdate$[13;4]
                        else                            ! month then day
                            idate2$ = (monnum using "#Z") + "-" + flexdate$[6;2] + "-" + flexdate$[13;4]
                        endif
                    endif

                ! [118] Fri Jan 25, 2019 
                elseif instr(1,flexdate$,"[a-z]{3,3} [a-z]{3,3} \d{1,2}, \d\d\d\d",1) = 1 then   
                    monnum =  Fn'Mon'To'Num(flexdate$[5,7])
                    if monnum < 1 then
                        debug.print (99,"fndatetime") "Illegal date format1 : "+flexdate$
                        exitfunction
                    endif
                    if Ldf.DATE'FORMAT = 1 then     ! [115] day then month
                        idate2$ = (val(flexdate$[9;2]) using "#Z") + "-" + (monnum using "#Z") + "-" + ((val(flexdate$[12;5]) mod 100) using "#Z")
                    else                            ! [115] month then day
                        idate2$ = (monnum using "#Z") + "-" + (val(flexdate$[9;2]) using "#Z") + "-" + ((val(flexdate$[12;5]) mod 100) using "#Z")
                    endif

                ! [118] Fri 05-22-17 or Fri, 05-22-2017
                elseif instr(1,flexdate$,"[a-z]{3,3},{0,1} \d\d-\d\d-\d\d",1) then
                    idate2$ = flexdate$[5;11]       
                    xcall TRIM, idate2$
                ![109] Monday, March 07, 2019)
                    
                else
                    debug.print (99,"fndatetime") flexdate$,Ldf'Day'Name'List$
                    i = instr(1,flexdate$,",")                  ! [119]
                    if i then                                   ! [119]
                        flexday$ = flexdate$[1,i]               ! [119]
                        if instr(1,Ldf'Day'Name'List$,flexday$,1) then ! [119][109][113] was "Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday "
                            i = instr(1,flexdate$,", ") + 2   ! -> March
                                
                            j = instr(i,flexdate$," ")        ! space after Month
                            monnum =  Fn'Mon'To'Num(flexdate$[i,j-1])
                            if monnum < 1 then
                                debug.print (99,"fndatetime") "Illegal date format2 : ",flexdate$,i,j
                                exitfunction
                            endif
                            
                            if Ldf.DATE'FORMAT = 1 then     ! [115] day then month
                                idate2$ = (val(flexdate$[j;4]) using "#Z") + "-" + (monnum using "#Z") + "-" + (flexdate$[-2,-1] using "#Z")
                            else                            ! [115] month then day 
                                idate2$ = (monnum using "#Z") + "-" + (val(flexdate$[j;4]) using "#Z") + "-" + (flexdate$[-2,-1] using "#Z")
                            endif
                        endif
                    endif
                endif
                
            ![126] endif
        
        ! now deal with the variations that start with a number
        ! check for ##-aaa-## format (IDTIM doesn't handle it)
        elseif fn'isalpha(flexdate$[4,4]) then
            ! pad to dd-mmm (in case just d-mmm)
            if fn'isdigit(flexdate$[2,2]) = 0 then
                flexdate$ = "0"+ flexdate$
            endif
            monnum = Fn'Mon'To'Num(flexdate$[4;3]) 
            ! [101] monnum = instr(1,"JANFEBMARAPRMAYJUNJULAUGSEPOCTNOVDEC",mon$)
            if monnum < 1 then
                debug.print (99,"fndatetime") "Illegal date format3 : ",flexdate$,Ldf'Day'Name'List$
                exitfunction
            endif
    
            if Ldf.DATE'FORMAT = 1 then     ! [115] day then month
                idate2$ = flexdate$[1,2] + "-" + (monnum using "#Z") + flexdate$[7,-1]
            else
                idate2$ = (monnum using "#Z") + "-" + flexdate$[1,2] + flexdate$[7,-1]
            endif
            
        elseif fn'all'digits(flexdate$) then       ! [115] {cc}yymmdd,mmdd{cc}yy,ddmm{cc}yy
            status = len(flexdate$)
            if status = 8 then                  ! ccyymmdd, mmddccyy or ddmmccyy
                if Ldf.DATE'FORMAT = 1 then     ! ccyymmdd or ddmmccyy
                    if flexdate$[1,2] > "31" or flexdate$[3,4] > "12"  then  ! assume ccyymmdd
                        idate2$ = flexdate$[7,8]  + "/" + flexdate$[5,6] + "/" + flexdate$[1,4]  ! -> dd/mm/ccyy
                    else                                                ! assume ddmmccyy
                        idate2$ = flexdate$[1,2] + "/" + flexdate$[3,4] + "/" + flexdate$[5,8]   ! -> dd/mm/ccyy
                    endif
                else                            ! [115] ccyymmdd or mmddccyy
                    if flexdate$[1,2] > "12" or flexdate$[3,4] > "31" or flexdate$[3,4] = "00" then   ! assume ccyymmdd
                        idate2$ = flexdate$[5,6]  + "/" + flexdate$[7,8] + "/" + flexdate$[1,4]  ! -> mm/dd/ccyy
                    else                                                ! mmddccyy
                        idate2$ = flexdate$[1,2] + "/" + flexdate$[3,4] + "/" + flexdate$[5,8]   ! -> dd/mm/ccyy
                    endif
                endif    
                
            elseif status = 6 then              ! yymmdd, mmddyy, ddmmyy
                if Ldf.DATE'FORMAT = 1 then     ! day first; assume ddmmyy unless dd > 31
                    if flexdate$[1,2] > "31" then  ! can't be dd, assume yymmdd
                        idate2$ = flexdate$[5,6]  + "/" + flexdate$[3,4] + "/" + flexdate$[1,2]    ! now dd/mm/yy
                    else                        ! assume ddmmyy 
                        idate2$ = flexdate$[1,2]  + "/" + flexdate$[3,4] + "/" + flexdate$[5,6]    ! now dd/mm/yy    
                    endif
                else                            ! month first; assume mmddyy unless mm > 12 or dd > 31 or yy > this year+1
                    if flexdate$[1,2] > "12" or flexdate$[3,4] > "31" or flexdate$[3,4] = "00" then  ! can't be mmddyy, assume yymmdd
                        idate2$ = flexdate$[3,4]  + "/" + flexdate$[5,6] + "/" + flexdate$[1,2]    ! yymmdd -> mm/dd/yy  [117]
                    else                        ! assume mmddyy 
                        if val(flexdate$[1,2]) > val(.YYMMDD[1,2])+1 then   ! [152] unless YY in future, i.e. treat 121128 as 11/28/12
                            idate2$ = flexdate$[3,4]  + "/" + flexdate$[5,6] + "/" + flexdate$[1,2]    ! yymmdd -> mm/dd/yy  [152]
                        else
                            idate2$ = flexdate$[1,2]  + "/" + flexdate$[3,4] + "/" + flexdate$[5,6]    ! mmddyy -> mm/dd/yy  [117] 
                        endif
                    endif
                endif
            else        ! error
                debug.print (99,"fndatetime") "Illegal date format: ",flexdate$
            endif
        elseif flexdate$[5;1] = "-" and flexdate$[8;1] = "-" then   ! [125] convert ccyy-mm-dd to mm-dd-ccyy or dd-mm-ccyy
            if Ldf.DATE'FORMAT = 1 then     ! dd-mm-ccyy
                idate2$ = flexdate$[9,10] + "-" + flexdate$[6,7] + "-" + flexdate$[1,4]
            else                            ! mm-dd-ccyy
                idate2$ = flexdate$[6,7] + "-" + flexdate$[9,10] + "-" + flexdate$[1,4]
            endif
        else
            idate2$ = flexdate$        ! assume IDTIM can understand the format
        endif
    
        debug.print (99,"fndatetime") idate2$
        if idate2$ = "00/00/0000" then  ! special case (treat as valid)
            .fn = idate2$
        else
            ![159] add missing /yy if necessary
            if len(idate2$) = 5 then
                if not fn'isdigit(idate2$[3,3]) then
                    idate2$ += idate2$[3,3] + .YYMMDD[1,2]
                endif
            endif
            ! convert from string to internal format
            xcall IDTIM,idate2$,idate,itime,2,status
            debug.print (99,"fndatetime") idate2$, idate, itime, status
            if status # 0 then
                debug.print (99,"fndatetime") "Illegal date format: ",flexdate$,idate2$,status
                exitfunction
            endif

            ! [115] now convert back to mm/dd/ccyy (or dd/mm/ccyy)
            ! [168] if mon'day'order$ = "" or mon'day'order$ = "md" &
            ! [168] or (mon'day'order$ = "ldf" and Ldf.DATE'FORMAT = 0) then
            ! [168] if no mon'day'order specified, default to ldf format
            if mon'day'order$ = "md" &
            or ((mon'day'order$ = "ldf" or mon'day'order$ = "") and Ldf.DATE'FORMAT = 0) then
                flags = &h368       ! mm/dd/ccyy
            else
                flags = &h328       ! dd/mm/ccyy
            endif
            
            xcall ODTIM,.fn,idate,itime,flags,mon'day'order$,Ldf.DATE'FORMAT
            
        endif

        ! check for some weird condition?
        if .fn = "00/01/1900" then
            debug.print (99,"fndatetime") "idate2$: "+idate2$+" -> "+Fn'Dec2Hex$(idate)
        endif
        debug.print (99,"fndatetime") .fn
    endif
EndFunction

![115]
!---------------------------------------------------------------------
!Function:
!   Explicitly DD/MM/CCYY version of Fn'Date'To'MM'DD'CCYY$
!Params:
!   flexdate$ (T_FLEX_DATE) [in] - see Date Format Notes at top of file
!Returns:
!   dd/mm/ccyy if input date valid, else ""
!Globals:
!Notes:
!   see Fn'Date'To'MM'DD'CCYY$
!---------------------------------------------------------------------

Function Fn'Date'To'DD'MM'CCYY$(flexdate$ as T_FLEX_DATE:inputonly) as s10

    map1 locals
        map2 juliany2k, T_JULIANY2K
        map2 filetime,  T_FILETIME48    ! [162]
        
    ! [141] special handling for binary args 
    if (.argtyp(@flexdate$) and ARGTYP_MASK) = ARGTYP_B then
        if .argsiz(@flexdate$) = 2 then                 ! T_JULIANY2K (B,2)
            juliany2k = flexdate$                       ! convert back to T_JULIANY2K
            if juliany2k then                           ! [142] only if not zero
                .fn = Fn'Date'To'MM'DD'CCYY$(juliany2k, "dm")
            endif
        elseif .argsiz(@flexdate$) >= 4 then            ! T_FILETIME (B,4) [162] or T_FILETIME48
            filetime = flexdate$                        ! convert back to T_FILETIME
            if filetime then                            ! [142] only if not zero
                .fn = Fn'Date'To'MM'DD'CCYY$(filetime, "dm")
            endif
        endif
    else
        .fn = Fn'Date'To'MM'DD'CCYY$(flexdate$, "dm")
    endif
EndFunction

![106]   
!---------------------------------------------------------------------
!Function:
!   convert various date formats to MM/DD/YY
!Params:
!   flexdate$ (T_FLEX_DATE) [in] - see Date Format Notes at top of file
!Returns:
!   mm/dd/yy if input date valid, else ""
!Globals:
!Notes:
!   Variation (wrapper) of Fn'Date'To'MM'DD'CCYY$() 
!   For binary formats, if input zero then output ""
!---------------------------------------------------------------------

Function Fn'Date'To'MM'DD'YY$(flexdate$ as T_FLEX_DATE:inputonly) as s10

    map1 locals
        map2 juliany2k, T_JULIANY2K
        map2 filetime,  T_FILETIME48        ! [162]

    ! [141] special handling for binary args 
    if (.argtyp(@flexdate$) and ARGTYP_MASK) = ARGTYP_B then
        if .argsiz(@flexdate$) = 2 then                 ! T_JULIANY2K (B,2)
            juliany2k = flexdate$                       ! convert back to T_JULIANY2K
            if juliany2k then                           ! [142]only if not zero
                .fn = Fn'Date'To'MM'DD'CCYY$(juliany2k)
            endif
        elseif .argsiz(@flexdate$) >= 4 then            ! T_FILETIME (B,4) or T_FILETIME48 (B,6)
            filetime = flexdate$                        ! convert back to T_FILETIME
            if filetime then                           ! only if not zero
                .fn = Fn'Date'To'MM'DD'CCYY$(filetime)
            endif
        endif
    else
        .fn = Fn'Date'To'MM'DD'CCYY$(flexdate$)         ! mm/dd/ccyy
    endif

    .fn = .fn[1,6] + .fn[9,10]
    debug.print (99,"fndatetime") .fn, flexdate$
EndFunction

![133]   
!---------------------------------------------------------------------
!Function:
!   convert various date formats to MM-DD-YY
!Params:
!   flexdate$ (T_FLEX_DATE) [in] - see Date Format Notes at top of file
!Returns:`
!   mm-dd-yy if input date valid, else ""
!Globals:
!Notes:
!   Variation (wrapper) of Fn'Date'To'MM'DD'CCYY$() 
!---------------------------------------------------------------------

Function Fn'Date'To'MM_DD_YY$(flexdate$ as T_FLEX_DATE:inputonly) as s10

    map1 locals
        map2 juliany2k, T_JULIANY2K
        map2 filetime,  T_FILETIME48        ! [162]

    ! [141] special handling for binary args 
    if (.argtyp(@flexdate$) and ARGTYP_MASK) = ARGTYP_B then
        if .argsiz(@flexdate$) = 2 then                 ! T_JULIANY2K (B,2)
            juliany2k = flexdate$                       ! convert back to T_JULIANY2K
            if juliany2k then                           ! [142] only if not zero
                .fn = Fn'Date'To'MM'DD'CCYY$(juliany2k)
            endif
        elseif .argsiz(@flexdate$) >= 4 then            ! T_FILETIME (B,4) [162] or T_FILETIME48 (B,6)
            filetime = flexdate$                        ! convert back to T_FILETIME
            if filetime then                            ! [142] only if not zero
                .fn = Fn'Date'To'MM'DD'CCYY$(filetime)
            endif
        endif
    else
        .fn = Fn'Date'To'MM'DD'CCYY$(flexdate$)
    endif

    .fn = .fn[1,6] + .fn[9,10]
    .fn[3;1] = "-"
    .fn[6;1] = "-"
    debug.print (99,"fndatetime") .fn, flexdate$
EndFunction


![120]   
!---------------------------------------------------------------------
!Function:
!   convert various date formats to MMDDYY
!Params:
!   flexdate$ (T_FLEX_DATE) [in] - see Date Format Notes at top of file
!Returns:
!   mmddyy if input date valid, else ""
!Globals:
!Notes:
!   Variation (wrapper) of Fn'Date'To'MM'DD'CCYY$() 
!---------------------------------------------------------------------

Function Fn'Date'To'MMDDYY$(flexdate$ as T_FLEX_DATE:inputonly) as s10

    map1 locals
        map2 juliany2k, T_JULIANY2K
        map2 filetime,  T_FILETIME48        ! [162] 

    debug.print (99,"fndatetime") "Fn'Date'To'MMDDYY$", flexdate$
    ! [141] special handling for binary args 
    if (.argtyp(@flexdate$) and ARGTYP_MASK) = ARGTYP_B then
        if .argsiz(@flexdate$) = 2 then                 ! T_JULIANY2K (B,2)
            juliany2k = flexdate$                       ! convert back to T_JULIANY2K
            if juliany2k then                           ! [142] only if not zero
                .fn = Fn'Date'To'MM'DD'CCYY$(juliany2k)
            endif
        elseif .argsiz(@flexdate$) >= 4 then            ! T_FILETIME (B,4) [162] or T_FILETIME48 (B,6)
            filetime = flexdate$                        ! convert back to T_FILETIME
            if filetime then                            ! [142] only if not zero
                .fn = Fn'Date'To'MM'DD'CCYY$(filetime)
            endif
        endif
    else
        .fn = Fn'Date'To'MM'DD'CCYY$(flexdate$)
    endif

    .fn = .fn[1,2] + .fn[4,5] + .fn[9,10]
    debug.print (99,"fndatetime") .fn, flexdate$
EndFunction


![115]   
!---------------------------------------------------------------------
!Function:
!   convert various date formats to DD/MM/YY
!Params:
!   flexdate$ (T_FLEX_DATE) [in] - see Date Format Notes at top of file
!Returns:
!   dd/mm/yy if input date valid, else ""
!Globals:
!Notes:
!   Variation (wrapper) of Fn'Date'To'MM'DD'CCYY$() 
!---------------------------------------------------------------------

Function Fn'Date'To'DD'MM'YY$(flexdate$ as T_FLEX_DATE:inputonly) as s10

    map1 locals
        map2 juliany2k, T_JULIANY2K
        map2 filetime,  T_FILETIME48        ! [162]

    ! [141] special handling for binary args 
    if (.argtyp(@flexdate$) and ARGTYP_MASK) = ARGTYP_B then
        if .argsiz(@flexdate$) = 2 then                 ! T_JULIANY2K (B,2)
            juliany2k = flexdate$                       ! convert back to T_JULIANY2K
            if juliany2k then                           ! [142] only if not zero
                .fn = Fn'Date'To'MM'DD'CCYY$(juliany2k, "dm")
            endif
        elseif .argsiz(@flexdate$) >= 4 then            ! T_FILETIME (B,4) [162] or T_FILETIME48 (B,6)
            filetime = flexdate$                        ! convert back to T_FILETIME
            if filetime then                            ! [142] only if not zero
                .fn = Fn'Date'To'MM'DD'CCYY$(filetime, "dm")
            endif
        endif
    else
        .fn = Fn'Date'To'MM'DD'CCYY$(flexdate$, "dm")
    endif

    .fn = .fn[1,6] + .fn[9,10]
    debug.print (99,"fndatetime") .fn, flexdate$
EndFunction



![138]
!---------------------------------------------------------------------
!Function:
!   convert flex date formats to SQL date format (CCYY-MM-DD)
!Params:
!   flexdate$ (T_FLEX_DATE) [in] - see Date Format Notes at top of file
!Returns:
!   ccyy-mm-dd if input date valid, else ""
!Globals:
!Notes:
!   Wraps around Fn'Date'To'MM'DD'CCYY$()
!   if input "", output is also ""
!---------------------------------------------------------------------

Function Fn'Date'To'SQL'Date$(flexdate$ as T_FLEX_DATE:inputonly) as T_SQL_DATE
debug.print (99,"fndatetime") "Fn'Date'To'SQL'Date$()",flexdate$

    map1 locals
        map2 juliany2k, T_JULIANY2K
        map2 filetime,  T_FILETIME48        ! [162]

    ! [141] special handling for binary args 
    if (.argtyp(@flexdate$) and ARGTYP_MASK) = ARGTYP_B then
        if .argsiz(@flexdate$) = 2 then                 ! T_JULIANY2K (B,2)
            juliany2k = flexdate$                       ! convert back to T_JULIANY2K
            if juliany2k then                           ! [142] only if not zero
                .fn = Fn'Date'To'MM'DD'CCYY$(juliany2k) ! [144]
            endif
        elseif .argsiz(@flexdate$) >= 4 then            ! T_FILETIME (B,4) [162] or T_FILETIME48 (B,6)
            filetime = flexdate$                        ! convert back to T_FILETIME
            if filetime then                            ! [142] only if not zero
                .fn = Fn'Date'To'MM'DD'CCYY$(filetime)  ! [144] 
            endif
        endif
        debug.print (99,"fndatetime") juliany2k, .fn
    else
        if val(flexdate$[1,9]) > 100000000 then         ! cvt CCYYMMDDhhmmss to CCYYMMDD (else 
            flexdate$ = flexdate$[1,8]                  ! isn't supported by Fn'Date'To'MM'DD'CCYY$)
        endif
        
        if flexdate$ # "" then      
            .fn = Fn'Date'To'MM'DD'CCYY$(flexdate$)         ! [144]
        endif
    endif

    ![144] .fn = Fn'Date'To'MM'DD'CCYY$(flexdate$)
    if .fn # "" then
        .fn = .fn[7;4] + "-" + .fn[1;2] + "-" + .fn[4;2]
    endif
    
EndFunction

![140]
!---------------------------------------------------------------------
!Function:
!   convert flex date formats to SQL datetime format (CCYY-MM-DD HH:MM:SS)
!Params:
!   flexdate$ (T_FLEX_DATE) [in] - see Date Format Notes at top of file [153] may contain time - see below
!   flextime$ (T_FLEX_TIME) [in] - flexible time ("now", hh:mm:ss or hhmmss)
!Returns:
!   ccyy-mm-dd hh:mm:ss.xxx if input date valid, else ""
!Globals:
!Notes:
!   Wraps around Fn'Date'To'MM'DD'CCYY$()
!   if input "", output is also ""
!   [153] if flextime$ not specified, flexdate$ may contain the time, 
!   e.g. "123122 15:22"
!---------------------------------------------------------------------

Function Fn'Date'To'SQL'DateTime$(flexdate$="today" as T_FLEX_DATE:inputonly, &
                                  flextime$="" as T_FLEX_TIME:inputonly) as T_SQL_DATETIME
                                   
    map1 locals
        map2 tm$,T_SQL_TIME
        map2 i,i,2
        map2 hh,b,2
        map2 idate,f,6
        map2 itime,f,6
        map2 status,f,6
        map2 juliany2k, T_JULIANY2K
        map2 filetime,  T_FILETIME48        ! [162]

    ! [141] special handling for binary args 
    if (.argtyp(@flexdate$) and ARGTYP_MASK) = ARGTYP_B then
        if .argsiz(@flexdate$) = 2 then                 ! T_JULIANY2K (B,2)
            juliany2k = flexdate$                       ! convert back to T_JULIANY2K
            if juliany2k then                           ! [142] only if not zero
                .fn = Fn'Date'To'MM'DD'CCYY$(juliany2k)
            endif
        elseif .argsiz(@flexdate$) >= 4 then            ! T_FILETIME (B,4) [162] or T_FILETIME48 (B,6)
            filetime = flexdate$                        ! convert back to T_FILETIME
            if filetime then                            ! [142] only if not zero
                ![175] .fn = Fn'Date'To'MM'DD'CCYY$(filetime)
                .fn = Fn'FILETIME'To'ODTIM$(filetime, &h16c)                    ! [175] mm/dd/ccyy hh:mm:ss
                .fn = .fn[7;4] + "-" + .fn[1;2] + "-" + .fn[4;2] + .fn[11;8]    ! [175] ccyy-mm-dd hh:mm:ss
                exitfunction                                                    ! [175]
            endif
        endif
    else
        ! [153] check if flexdate includes a time suffix and if so, split it out
        if flextime$ = "" then
            i = instr(1,flexdate$," ")      
            if i then
                if flexdate$[i+1,-1] # "" then
                    flextime$ = trim$(flexdate$[i,-1])
                    flexdate$ = flexdate$[1,i-1]
                endif
            endif
        endif
        .fn = Fn'Date'To'MM'DD'CCYY$(flexdate$)
    endif

    if .fn # "" then
        if (.argtyp(@flextime$) and ARGTYP_MASK) = ARGTYP_B then    ! [173] if binary time, treat as secs
            tm$ = odtim(0,itime,&h1)    ! hh:mm:ss
        else
            if flextime$ = "" then          ! [153]
                flextime$ = "now"
            endif
            if ucs(flextime$) = "NOW" then
                .fn += " " + odtim(0,0,&h1)   ! hh:mm:ss
            else
                xcall TRIM, flextime$
                tm$ = flextime$
                
                ! insert colons if HHMM{SS} format
                if instr(1,tm$,":") = 0 then        ! [158] if no colons
                    if tm$[3;1] # ":" then
                        tm$ = tm$[1,2] + ":" + tm$[3,-1]
                    endif
                    if len(tm$) >= 6 and tm$[6;1] # ":" then
                        tm$ = tm$[1,5] + ":" + tm$[6,-1]
                    endif
                endif
                
                ! convert to secs and back to hh:mm:ss
                xcall IDTIM, tm$, idate, itime, 1, status
                debug.print (99,"fndatetime") tm$, idate, itime, status
                if status = 0 then
                    tm$ = odtim(0,itime,&h1)   ! hh:mm:ss
                else
                    .fn = ""        ! invalid time
                endif
            endif
        endif
        
        if .fn # "" then
            .fn += " " + tm$
            .fn = .fn[7;4] + "-" + .fn[1;2] + "-" + .fn[4;2] + .fn[11,-1]
        endif
    endif
$EXIT:
    debug.print (99,"fndatetime") "Fn'Date'To'SQL'DateTime$()",flexdate$,flextime$,tm$,.fn    
EndFunction




![155]
!---------------------------------------------------------------------
!Function:
!   convert msecs since midnight to hh:mm:ss.xxx 
!Params:
!   msecs (b4) [in] - milliseconds since midnight
!Returns:
!   hh:mm:ss.xxx
!Globals:
!Notes:
!   Warning: output limited to 0-23:59:59.999
!---------------------------------------------------------------------

Function Fn'Msecs'To'SQL'Time$(msecs as b4:inputonly) as T_SQL_TIME

    map1 locals
        map2 n,b,2
        
    n = int(msecs / 3600000)    ! hh
    if n >= 24 then 
        n = n mod 24            ! limit to 24 hours
    endif
    .fn = (n using "#Z") + ":"
    msecs -= (n * 3600000)      
    
    n = int(msecs / 60000)      ! mm
    .fn += (n using "#Z") + ":"
    msecs -= (n * 60000)
    
    n = int(msecs / 1000)      ! ss
    .fn += (n using "#Z") + "."
    msecs -= (n * 1000)
    
    .fn += (msecs using "#ZZ")
    
EndFunction

![138]
!---------------------------------------------------------------------
!Function:
!   convert SQL datetime format to CCYYMMDDhhmmss...
!Params:
!   sqldatetime$ (T_SQL_DATETIME) [in] - SQL format date time
!                                (CCYY-MM-DD{ hh:mm:ss{.ttt}}
!                                (may contain only the time)
!Returns:
!   ccyy-mm-dd{ hh:mm:ss.ttt} (if valid, else "")
!Globals:
!Notes:
!   Caller can just truncate the return value to the degree of precision
!   desired. If sqldatetime$ doesn't contain the full time spec, the
!   trailing positions will be set to 0
!---------------------------------------------------------------------

Function Fn'SQL'DateTime'To'CCYYMMDDhhmmss$(sqldatetime$ as T_SQL_DATETIME:inputonly) as s17

    if sqldatetime$[5;1] = "-" and sqldatetime$[8;1] = "-" then   ! minimum sanity test xxxx-xx-
        xcall XSTRIP, sqldatetime$, " -:.", 1        ! remove spaces, dashes, colons, dots
        .fn = sqldatetime$ + fill("0",sizeof(.fn))   ! pad 0's to full length
    endif
    
EndFunction


![143]
!---------------------------------------------------------------------
!Function:
!   convert SQL date format to CCYYMMDD
!Params:
!   sqldate$ (T_SQL_DATE) [in] - SQL format date (CCYY-MM-DD)
!Returns:
!   CCYYMMDD or ""
!Globals:
!Notes:
!   Caller can just truncate or substring the return value to get CCYY or YYMMDD
!---------------------------------------------------------------------

Function Fn'SQL'Date'To'CCYYMMDD$(sqldate$ as T_SQL_DATE:inputonly) as T_CCYYMMDD

    if sqldate$[5;1] = "-" and sqldate$[8;1] = "-" then   ! minimum sanity test xxxx-xx-
        xcall XSTRIP, sqldate$, " -", 1     ! remove spaces
        .fn = sqldate$
    elseif val(sqldate$) > 10000000 and val(sqldate$) < 29991231 then   ! allow CCYYMMDD
        .fn = sqldate$
    endif
    
EndFunction


![150]
!---------------------------------------------------------------------
!Function:
!   convert SQL date format to MMDDYY
!Params:
!   sqldate$ (T_SQL_DATE) [in] - SQL format date (CCYY-MM-DD)
!           or (T_SQL_DATETIME or CCYYMMDD)
!Returns:
!   MMDDYY or ""
!Globals:
!Notes:
!---------------------------------------------------------------------

Function Fn'SQL'Date'To'MMDDYY$(sqldate$ as T_SQL_DATE:inputonly) as T_MMDDYY

    if (sqldate$[5;1] = "-" and sqldate$[8;1] = "-")  &               ! minimum sanity test xxxx-xx-
    or (val(sqldate$) > 10000000 and val(sqldate$) < 29991231) then   ! allow CCYYMMDD
        xcall XSTRIP, sqldate$, " -", 1     ! remove spaces and dashes
        .fn = sqldate$[5;4] + sqldate$[3;2] ! CCYYMMDD -> MMDDYY
    endif
    
EndFunction


![107]
!---------------------------------------------------------------------
!Function:
!   Return DOW for specified date
!Params:
!   flexdate$ (T_FLEX_DATE) [in] - see Date Format Notes at top of file
!                                   "" treated as today
!Returns:
!   DOW (0=Monday, 6=Sunday)
!   -1 for invalid date
!Globals:
!Notes:
!---------------------------------------------------------------------
Function Fn'DOW'From'Date(flexdate$ as T_FLEX_DATE:inputonly) as i1
    
    map1 locals
        map2 mmddccyy$,s,10
        map2 idatex
            map3 mon,b,1
            map3 day,b,1
            map3 yr,b,1
            map3 dow,b,1
        map2 idate,T_SYSDATE,@idatex
        map2 itime,b,4
        map2 status,f
        map2 juliany2k, T_JULIANY2K
        map2 filetime,  T_FILETIME48        ! [162] 
        
        
    if flexdate$ = "" or flexdate$ = "today" then           ! treat "" as today
        idate = DATE
        debug.print (99,"fndatetime") mon,day,yr,dow
        Fn'DOW'From'Date = dow
    else

        ! [141] special handling for binary args 
        if (.argtyp(@flexdate$) and ARGTYP_MASK) = ARGTYP_B then
            if .argsiz(@flexdate$) = 2 then                 ! T_JULIANY2K (B,2)
                juliany2k = flexdate$                       ! convert back to T_JULIANY2K
                if juliany2k then                           ! [142] only if not zero
                    mmddccyy$ = Fn'Date'To'MM'DD'CCYY$(juliany2k)
                endif
            elseif .argsiz(@flexdate$) >= 4 then            ! T_FILETIME (B,4) [162] or T_FILETIME48 (B,6)
                filetime = flexdate$                        ! convert back to T_FILETIME
                if filetime then                            ! [142] only if not zero
                    mmddccyy$ = Fn'Date'To'MM'DD'CCYY$(filetime)
                endif
            endif
        else
            mmddccyy$ = Fn'Date'To'MM'DD'CCYY$(flexdate$)
        endif

        if mmddccyy$="" then
            Fn'DOW'From'Date = -1
        else
            xcall IDTIM,mmddccyy$,idate,itime,2,status
            if status = 0 then
                Fn'DOW'From'Date = dow
            else
                Fn'DOW'From'Date = -1
            endif
        endif
    endif
    debug.print (99,"fndatetime") Fn'DOW'From'Date,mmddccyy$,flexdate$,idate
EndFunction


![156]
!---------------------------------------------------------------------
!Function:
!   Return weekday name for specified date
!Params:
!   flexdate$  (T_FLEX_DATE) [in] - date
!Returns:
!   Monday, Tuesday, etc. or "" if date invalid
!Globals:
!Notes:
!   Wrapper for ODTIM() 
!---------------------------------------------------------------------
Function Fn'Date'To'Weekday'Name$(flexdate$="today" as T_FLEX_DATE:inputonly) as s12
    map1 sdate, T_SYSDATE
    
    sdate = Fn'Date'To'Sdate(flexdate$)
    .fn = odtim(sdate,0,&h03A00206)
    .fn = .fn[1,instr(1,.fn,",")-1] ! remove trailing comma (bug in ODTIM?)
EndFunction

![156]
!---------------------------------------------------------------------
!Function:
!   Return weekday name for specified numeric DOW
!Params:
!   dow  (num) [in] - 0=sunday, 6=saturday
!Returns:
!   Monday, Tuesday, etc. or "" if dow invalid
!Globals:
!Notes:
!   Oddly there is no ODTIM function for this so we have to do it manually
!---------------------------------------------------------------------
Function Fn'DOW'To'Weekday'Name$(dow as b1:inputonly) as s12

    switch dow
        case 0
            .fn = "Sunday"
            exit
        case 1
            .fn = "Monday"                               ! [19] was MO
            exit
        case 2
            .fn = "Tuesday"                              ! [19] was TU, etc.
        exit
        case 3
            .fn = "Wednesday"
            exit
        case 4
            .fn = "Thursday"
            exit
        case 5
            .fn = "Friday"
            exit
        case 6
            .fn = "Saturday"
            exit
    endswitch
        
EndFunction

!---------------------------------------------------------------------
!Function:
!   Convert month name or abbreviation to number
!Params:
!   month$  (str) [in] - name or abbreviation 
!Returns:
!	0 (invalid) or 1-12
!Globals:
!Notes:
!	1. case insensitive
!	2. uses names from LDF 
!   3. abbreviations are left-anchored subsets of the names (any length,
!      although normally jan,feb,mar etc.)
!---------------------------------------------------------------------
Function Fn'Mon'To'Num(month$ as s20) as b1

![113] ++include'once ashinc:gtlang.map				! local copy of GTLANG'MAP

    static map1 mon$(12),s,20			! month names
	map1 locals
		map2 i,i,2
		map2 n,b,2
		map2 status,f
		
	if mon$(1) = "" then				! load up the array just once from LDF
		![113] xcall GTLANG, status, GTLANG'MAP		
		mon$(1) = lcs(Ldf.JAN)          ! [113] use private module copy of Ldf
		mon$(2) = lcs(Ldf.FEB)
		mon$(3) = lcs(Ldf.MAR)
		mon$(4) = lcs(Ldf.APR)
		mon$(5) = lcs(Ldf.MAY)
		mon$(6) = lcs(Ldf.JUN)
		mon$(7) = lcs(Ldf.JUL)
		mon$(8) = lcs(Ldf.AUG)
		mon$(9) = lcs(Ldf.SEP)
		mon$(10) = lcs(Ldf.OCT)
		mon$(11) = lcs(Ldf.NOV)
		mon$(12) = lcs(Ldf.DEC)
	endif

	month$ = lcs(month$)
	n = len(month$)
	
	for i = 1 to 12
		if month$ = mon$(i)[1,n] then
			Fn'Mon'To'Num = i
			exit
		endif
	next i

EndFunction

!---------------------------------------------------------------------
!Function:
!   check if a string consists of a date and/or time format
!	and if so, parse it into individual fields
!Params:
!   subject  (str) [in] - string containing possible date/time
!	patno (signed num) [in/out] - pattern # for precompiled pattern
!					To precompile, set patno = negative 1 to 20;
!					on success, it comes back as positive value, which
!					can then just be left alone (passed as is for subsequent calls).
!					Otherwise omit or leave it 0 to compile the pattern each time.
! (optional output vars)---
!	day (num) [out] day   (0 if no day, else 1-31)
!	mon (num) [out] month (0 if no mon, else 1-12)
!	yr (num) [out] year   (0 if  no yr, else yy converted to ccyy using CCYY cutoff =60)
!	hh (num) [out] hours  (0-23)
!	mm (num) [out] mins   (0-59)
!	ss (num) [out] secs   (0-59)
!Returns:
!	If patno passed in as -1 to -20, returns 1 to 20 for success, else -errno
!	Otherwise, returns 0=not date/time, 1=date, 2=time, 3=date+time, <0 for errno
!Module Vars:
!   Ldf (from GTLANG) [113]
!Notes:
!	Empty string returns 0.
!	Input string will be trimmed of leading/trailing blanks.
!
!	The regex pattern is complicated, so precompiling may improve 
!	performance if called repetitively. But it is up to caller to 
!	assign a pattern number with no conflicts. 
!
!	Valid date formats:
!		d{d}/m{m}/yy{yy}		! (if LDF LANG'DATE'FORMAT=0)
!		d{d}-m{m}-yy{yy}
!		m{m}/d{d}/yy{yy}		! (if LDF LANG'DATE'FORMAT=1)
!		m{m}-d{d}-yy{yy}
!		d{d}-mon-yy{yy}
!
!	Valid time format:
!		#{#}:##{:##}{ am/pm}
!
!	If both date and time specified, time must follow date (with one space delimiter)
!	yy 00-59 will be converted to 2000-2059; 60-99 to 1960-1999

!---------------------------------------------------------------------
Function Fn'Parse'Date'Time(subject$ as s22:inputonly, patno as i2, &
					day as b1:outputonly, mon as b1:outputonly, yr as b2:outputonly, &
					hh as b1:outputonly, mm as b1:outputonly, ss as b2:outputonly) as i2

![113] ++include ashinc:gtlang.map				! local copy of GTLANG'MAP
define MAX_SUB_MATCHES = 16

    static map1 regex'date$,s,200		! regex {date}{time} pattern
	![113] static map1 date'format,b,1			! 0=month then day, 1=day then month
	
	map1 locals
		map2 status,f
		map2 spos,i,2
		map2 subcnt,i,2
		map2 match$,s,22
		map2 sub$(MAX_SUB_MATCHES),s,22
		
	if regex'date$ = "" then
		regex'date$ = "^((\d{1,2})(-|/)(\d{1,2}|[A-Z]{3,3})(-|/)(\d{2,4})){0,1}" &	! date
			+ "( *(\d{1,2}):(\d\d)(:(\d\d)){0,1}( [AP]M){0,1}){0,1}$"				! time		
			
		![113] xcall GTLANG, status, GTLANG'MAP		
		![113] date'format = Ldf.DATE'FORMAT			! save this static [113] was 
	endif
	
    patno = 0
	
    if (subject$ # "") then			! empty string is not a date

		if patno < 0 and patno >= -20 then 			! precompile the pattern
			patno = abs(patno)
			xcall REGEX,regex'date$,patno
			xputarg 2,patno
		endif
		
		xcall TRIM, subject$
		
		subcnt = MAX_SUB_MATCHES		! limit # of submatches
		if patno > 0 then				! use precompiled pattern
			xcall REGEX, chr(patno), status, subject$, PCRE_CASELESS+PCREX_SUBMATCH_ARRAY, spos, match$,  &
				subcnt, sub$(1)
		else
			xcall REGEX, regex'date$, status, subject$, PCRE_CASELESS+PCREX_SUBMATCH_ARRAY, spos, match$,  &
					subcnt, sub$(1)
		endif
		debug.print (99,"fndatetime") "Fn'Parse'Date'Time("+subject$+") status="+status+", match$="+match$
		if status = 1 and match$ # "" then		! date and/or time; now decide which
			if instr(1,match$,"/") then			! / indicates date
				Fn'Parse'Date'Time += 1
			elseif instr(1,match$,"-") then		! - also indicates date
				Fn'Parse'Date'Time += 1
			endif
			if instr(1,match$,":") then 		! : indicates time
				Fn'Parse'Date'Time += 2
			endif
			debug.print (99,"fndatetime") "Fn'Parse'Date'Time("+subject$+") = "+Fn'Parse'Date'Time
			
			if .argcnt > 2 then		! parse out the individual fields
				if Fn'Parse'Date'Time = 1 or Fn'Parse'Date'Time = 3 then	! starts with date
					if len(sub$(4)) = 3 then				! dd-mon-yr format
						day = sub$(2)
						mon = Fn'Mon'To'Num(sub$(4))			! convert abbreviation to number
					else									    ! dd/mm/yy or mm/dd/yy
						if Ldf.DATE'FORMAT = 1 then				! day first [113]
							day = sub$(2)
							mon = sub$(4)
						else
							mon = sub$(2)
							day = sub$(4)
						endif
					endif
					yr = sub$(6)
					
					! convert yr from yy to ccyy if necessary  ! assume CCYY cutoff is 60
					if yr < 60 then
						yr += 2000					! 00-59 -> 2000-2059
					elseif yr < 100 then
						yr += 1900					! 60-99 -> 1960-1999
					endif
						
				endif

				if Fn'Parse'Date'Time >= 2 then						! contains time
					hh = sub$(8)
					mm = sub$(9)
					ss = sub$(11)
					if ucs(sub$(12)[-2,-1]) = "PM" then
						if hh < 12 then
							hh += 12
						elseif hh = 12 then
							hh = 0
						endif
					endif
				endif
				xputarg 3,day
				xputarg 4,mon
				xputarg 5,yr
				xputarg 6,hh
				xputarg 7,mm
				xputarg 8,ss
				debug.print (99,"fndatetime") "day="+day+", mon="+mon+", yr="+yr+", hh="+hh+", mm="+mm+", ss="+ss
				debug.print (99,"fndatetime") "sub 2)"+sub$(2)+" 4)"+sub$(4)+" 6)"+sub$(6)+" 8)"+sub$(8)+" 9)"+sub$(9)+" 11)"+sub$(11)
			endif	
		endif
	endif
	debug.print (99,"fndatetime") "Fn'Parse'Date'Time("+subject$+","+patno+")= "+Fn'Parse'Date'Time
EndFunction


!---------------------------------------------------------------------
!Function:
!   convert various date aliases to mm/dd/ccyy (or dd/mm/ccyy)
!Params:
!   alias$ (T_FLEX_DATE) [in] - one of the following (not case sensitive) :
!                           First of Last Month
!                           First of Last Year
!                           First of This Month
!                           First of This Year
!                           Today{+/-#}
!                           Yesterday
!   mon'day'order$ (str) [in] - (optional) flag controlling output order of month and day [115]
!                               "" or "md" = mm/dd/ccyy
!                               "dm"       = dd/mm/ccyy
!                               "ldf"      = (according to LDF)     ! [115]
!Returns:
!   mm/dd/ccyy (or dd/mm/ccyy) if recognized; else ""
!Globals:
!Notes:
!---------------------------------------------------------------------

Function Fn'Date'Alias'To'MM'DD'CCYY$(flexdate$ as T_FLEX_DATE:inputonly, &
                                      mon'day'order$ as s3:inputonly) as s10

    map1 locals
        map2 datex
            map3 mon,b,1
            map3 day,b,1
            map3 yr,b,1
            map3 dow,b,1
        map2 bdate,b,4,@datex
        map2 date1,b,3,@datex         ! note: must be b,3 for DATES (not b,4!)
        map2 date2,b,3
        map2 result,f
        map2 days,i,4
        map2 flags,b,4      ! [115] 
        map2 offset,i,2     ! [126]
        
    debug.print (99,"fndatetime") "Fn'Date'Alias'To'MM'DD'CCYY$("+flexdate$+","+mon'day'order$+")"

    ! [115] set output format based on mon'day'order$
    ! [168] if mon'day'order$ = "" or mon'day'order$ = "md" &
    ! [168] or (mon'day'order$ = "ldf" and Ldf.DATE'FORMAT = 0) then
    ! [168] if no mon'day'order specified, default to ldf format
    if mon'day'order$ = "md" &
    or ((mon'day'order$ = "ldf" or mon'day'order$ = "") and Ldf.DATE'FORMAT = 0) then
        flags = &h368       ! mm/dd/ccyy
    else
        flags = &h328       ! dd/mm/ccyy
    endif
        
    ! note: this requires either compiler 845+, or STRSIZ 19+ (else will cause compile error)
    switch ucs(flexdate$)
        case "FIRST OF LAST MONTH"
            bdate = DATE
            if mon = 1 then
                mon = 1
                yr -= 1
            else
                mon -= 1
            endif
            day = 1
            Fn'Date'Alias'To'MM'DD'CCYY$ = odtim(bdate,0,flags)  
            exit
        case "FIRST OF LAST YEAR"
            bdate = DATE
            mon = 1
            day = 1
            yr -= 1
            Fn'Date'Alias'To'MM'DD'CCYY$ = odtim(bdate,0,flags)
            exit
        case "FIRST OF THIS MONTH"
            bdate = DATE
            day = 1
            Fn'Date'Alias'To'MM'DD'CCYY$ = odtim(bdate,0,flags)
            exit
        case "FIRST OF THIS YEAR"
            bdate = DATE
            mon = 1
            day = 1
            Fn'Date'Alias'To'MM'DD'CCYY$ = odtim(bdate,0,flags)
            exit
        case "TODAY"
            Fn'Date'Alias'To'MM'DD'CCYY$ = odtim(DATE,0,flags)
            exit
        case "YESTERDAY"
            bdate = DATE
            xcall DATES, DTOP_DATEAFTER, result, date1, date2, -1    ! calc yesterday
            if result = 0 then
                bdate = date2
                Fn'Date'Alias'To'MM'DD'CCYY$ = odtim(bdate,0,flags)
            endif
            exit
        default
            if ucs(flexdate$[1,5]) = "TODAY" then   ! [126] check for today+# and today-#
                if flexdate$[6;1] = "+" then
                    offset = val(flexdate$[7,-1])
                elseif flexdate$[6;1] = "-" then
                    offset = val(flexdate$[6,-1])
                endif
                if offset then
                    bdate = DATE
                    xcall DATES, DTOP_DATEAFTER, result, date1, date2, offset
                    if result = 0 then
                        bdate = date2
                        Fn'Date'Alias'To'MM'DD'CCYY$ = odtim(bdate,0,flags)
                    endif
                endif
            endif
            exit
    endswitch

debug.print (99,"fndatetime") "Fn'Date'Alias'To'MM'DD'CCYY$() flexdate$=["+flexdate$+"] = " + Fn'Date'Alias'To'MM'DD'CCYY$     
EndFunction

!---------------------------------------------------------------------
!Function:
!   dd/mm/ccyy variation of Fn'Date'Alias'To'MM'DD'CCYY$
!Params:
!   alias$ (str) [in] - one of the following (not case sensitive) :
!                           First of Last Month
!                           First of Last Year
!                           First of This Month
!                           First of This Year
!                           Today
!                           Yesterday
!Returns:
!   dd/mm/ccyy if recognized; else ""
!Globals:
!Notes:
!---------------------------------------------------------------------
Function Fn'Date'Alias'To'DD'MM'CCYY$(flexdate$ as s30:inputonly) as s10
    Fn'Date'Alias'To'DD'MM'CCYY$ = Fn'Date'Alias'To'MM'DD'CCYY$(flexdate$,"dm")
EndFunction

!---------------------------------------------------------------------
!Function:
!   Output date in GMT format used by 
!Params:
!   idate  (num) [in] - separated date (as used by ODTIM)
!	itime  (num) [in] - seconds since midnight (as used by ODTIM)
!	offset (num) [in] - hours offset from current to GMT 
!						(if omitted, should calculate, but not yet implemented)
!Returns:
!	 e.g. "Tue, 17 May 2018 17:45:05 GMT"
!Globals:
!Notes:
! 	RFC7231
!---------------------------------------------------------------------
Function Fn'TimeStamp'GMT$(idate as b4:inputonly, itime as i4:inputonly, offset as i2:inputonly) as s32
	
	map1 sepdatex
		map2 m,b,1
		map2 d,b,1
		map2 y,b,1
	map1 sepdate,b,3,@sepdatex
	map1 result,f
	
	if idate = 0 then
		idate = DATE
	endif
	sepdate = idate
	
	itime += (offset * 3600)			! apply GMT offset
	if itime > 86400 then				! if crossing midnight, bump the day
		itime -= 86400
		xcall DATES, DTOP_DATEAFTER, result, sepdate, sepdate, 1
	elseif itime < 0 then
		itime += 86400
		xcall DATES, DTOP_DATEAFTER, result, sepdate, sepdate, -1
	endif
	
	Fn'TimeStamp'GMT$ = odtim(sepdate,itime,&h20a2)		! "Thu 17 May 2018 11:41:37"
	Fn'TimeStamp'GMT$ = Fn'TimeStamp'GMT$[1,3] + "," + Fn'TimeStamp'GMT$[4,-1] + " GMT"

EndFunction


!---------------------------------------------------------------------
!Function:
!   Returns # of days from today (or as-of date) to specified date
!Params:
!   flexdate$ (T_FLEX_DATE) [in] - see Date Format Notes at top of file
!   asofdate$ (T_FLEX_DATE) [in] - optional as-of date (default is "today")  [134]
!   bweekdays (BOOLEAN) [in] - if .TRUE, don't count weekends [167]
!Returns:
!   # days (0 if flexdate = asofdate$, > 0 flexdate before asofdate$; < 0 if after asoftdate$)
!   -99999999 if flexdate$ invalid
!Globals:
!Notes:
!---------------------------------------------------------------------
Function Fn'Days'Ago(flexdate$ as T_FLEX_DATE:inputonly, &
                     asofdate$="today" as T_FLEX_DATE:inputonly, &
                     bweekdays=.FALSE as BOOLEAN:inputonly) as i4

    map1 locals
        map2 mm'dd'yy$,s,8
        map2 asof'mm'dd'yy$,s,8
        map2 result,f
        map2 status,f
        map2 idate,b,4
        map2 wedays,i,4             ! [167] weekend days
        map2 sepdatex               ! [167]
            map3 s'm,b,1
            map3 s'd,b,1
            map3 s'y,b,1
            map3 d'dow,b,1
        map2 sepdate,b,4,@sepdatex  ! [167]
        map2 dow1,b,1               ! [167]
        
    ! first convert all supported formats to mm/dd/yy    
    mm'dd'yy$ = Fn'Date'To'MM'DD'YY$(flexdate$)
    
    XCALL IDTIM,mm'dd'yy$,idate,0,2,status
    trace.print (99,"ago") mm'dd'yy$, idate, status
    if status # 0 then
        .fn = -99999999
    else    
        asof'mm'dd'yy$ = Fn'Date'To'MM'DD'YY$(ife$(asofdate$,"today"))   ! [134][149] use "today" if ""
        xcall DATES,DTOP_DAYSAFTER,result,asof'mm'dd'yy$,mm'dd'yy$,.fn
        debug.print (99,"fndatetime,ago") flexdate$,asof'mm'dd'yy$,mm'dd'yy$,.fn
        if result then
            .fn = -99999999
        elseif bweekdays then               ! [167] subtract out week ends
            sepdate = idate
            if d'dow > DOW_FRI then         ! [167] if first date a weekend, roll fwd to Monday
                .fn -= (7 - d'dow)
                d'dow = DOW_MON
            endif
            dow1 = d'dow
            mm'dd'yy$ = Fn'Date'To'MM'DD'YY$(asof'mm'dd'yy$)
            XCALL IDTIM,mm'dd'yy$,idate,0,2,status
            sepdate = idate
            if d'dow > DOW_FRI then         ! [167] if second date a weekend, roll back to Friday
                .fn -= (d'dow - DOW_FRI)
                d'dow = DOW_FRI
            endif
            wedays =  (int(.fn / 7) * 2)    ! [167]  remove the weekends
            if dow1 > d'dow then
                wedays += 2
            endif
            trace.print (99,"ago") dow1, d'dow, wedays, .fn
            .fn -= wedays
        endif
    endif
    if .fn <= -99999999 then
        debug.print (99,"fndatetime,ago") "Fn'Days'Ago() invalid input!",flexdate$,asofdate$,mm'dd'yy$,status,asof'mm'dd'yy$,result
    endif
EndFunction


![170]
!---------------------------------------------------------------------
!Function:
!   Returns # second from specified date/time to now or another specified date/time
!Params:
!   flexdatetime$ (T_FLEX_DATE) [in] - date/time (presumably in the past)
!   asofdatetime$ (T_FLEX_DATE) [in] - asof date/time (blank for now)
!Returns:
!   seconds from flexdatetime$ to asofdatetime$ (negative if flexdatetime$
!   is after asofdatetime$)
!Globals:
!Notes:
!   input format is flexible but optional time should follow date with
!   a space separator, e.g. "12/31/22 09:42" or "today 2:00 PM".
!   If only one token provided, it is treated as the date and time="now".
!---------------------------------------------------------------------
Function Fn'Secs'Ago(flexdate$ as T_FLEX_DATE:inputonly, &
                     asofdate$="today" as T_FLEX_DATE:inputonly) as i6
    map1 locals
        map2 ft1, T_FILETIME
        map2 ft2, T_FILETIME
        map2 dt$, T_FLEX_DATE
        map2 tm$, T_FLEX_TIME
        map2 i,i,2
        
    ! convert date/time to FILETIME (seconds)
    ft1 = Fn'Date'Time'To'FILETIME(flexdate$)
    ft2 = Fn'Date'Time'To'FILETIME(asofdate$)
    .fn = ft2 - ft1
EndFunction 


![151] 
!---------------------------------------------------------------------
!Function:
!   Returns MM'DD'YY of date days after specified date
!Params:
!   flexdate$ (T_FLEX_DATE) [in] - see Date Format Notes at top of file
!   days (I2) [in] - # days after (or negative for before)
!Returns:
!   mm/dd/yy or "?" if flexdate$ invalid
!Globals:
!Notes:
!---------------------------------------------------------------------
Function Fn'Date'Plus'Days'To'MM'DD'YY$(flexdate$ as T_FLEX_DATE:inputonly, &
                     days as i4:inputonly) as T_MM'DD'YY

    map1 locals
        map2 mm'dd'yy$,s,8
        map2 result,f
        map2 status,f
        map2 idate,b,4
        map2 j2k, T_JULIANY2K
        map2 ft, T_FILETIME
        
    debug.print (99,"fndatetime") ABC_CURRENT_ROUTINE$,flexdate$,days
    
    ! first convert all supported formats to mm/dd/yy            
    ! [171] need special handling for Julian and FILETIME input since
    ! [171] original arg type would be lost in the redirect to Fn'Date'...()
    if (.argtyp(@flexdate$) and ARGTYP_MASK) = ARGTYP_B then    ! 
        if .argsiz(@flexdate$) = 2 then                         ! T_JULIANY2K (B,2)
            if val(flexdate$) > 0 then                          ! only if not 0
                j2k = flexdate$
                mm'dd'yy$ = Fn'Date'To'MM'DD'YY$(j2k)
            endif
        elseif .argsiz(@flexdate$) >= 4 then                    ! T_FILETIME (B,4 [162] or T_FILETIME48 (B,6)
            if val(flexdate$) > 0 then                          ! only if not 0
                ft = flexdate$
                mm'dd'yy$ = Fn'Date'To'MM'DD'YY$(ft)
            endif
        endif
    endif
    if mm'dd'yy$ = "" then
        mm'dd'yy$ = Fn'Date'To'MM'DD'YY$(flexdate$)             ! [172] was MM'DD'CCYY$
    endif
    
    XCALL IDTIM,mm'dd'yy$,idate,0,2,status  ! check if valid
    if status # 0 then
        .fn = "?"
    else    
        xcall DATES,DTOP_DATEAFTER,result,mm'dd'yy$,.fn,days
        debug.print (99,"fndatetime") flexdate$,mm'dd'yy$,days,.fn
        if result then
            .fn = "?"
        endif
    endif
    debug.print (99,"fndatetime") .fn, status, result
EndFunction

!---------------------------------------------------------------------
!Function:
!   Test if a specified date is within specified range
!Params:
!   testdate (T_FLEX_DATE) [in] - date to test
!   sdate (T_FLEX_DATE_ [in] - start date in range
!   edate (T_FLEX_DATE_ [in] - end date in range
!Returns:
!   .TRUE if within the range (inclusive), else .FALSE
!Globals:
!Notes:
!   Since it is likely to be called repetitively with same range
!   (e.g. applying a date filter to report data), the sdate and edate
!   fields are standardized and saved (to eliminate redundant conversions)   
!---------------------------------------------------------------------
Function Fn'Date'In'Range(testdate as T_FLEX_DATE:inputonly, &
                          sdate as T_FLEX_DATE:inputonly, &
                          edate as T_FLEX_DATE:inputonly) as BOOLEAN

    map1 locals
        map2 testjulian,T_JULIANY2K

    static map1 st'sdate,T_FLEX_DATE
    static map1 st'edate,T_FLEX_DATE
    static map1 st'sjulian, T_JULIANY2K
    static map1 st'ejulian, T_JULIANY2K
        
    ! convert the sdate and edate to julian (using already-converted
    ! values if no change)
    if sdate # st'sdate then
        st'sdate = sdate
        st'sjulian = Fn'Date'To'JulianY2K(sdate)
    endif
    if edate # st'edate then
        st'edate = edate
        st'ejulian = Fn'Date'To'JulianY2K(edate)
    endif

    ! we always convert the testdate...
    testjulian = Fn'Date'To'JulianY2K(testdate)
    
    if testjulian >= st'sjulian then
        if testjulian <= st'ejulian or st'ejulian = 0 then  ! ignore edate if not specified [161] 
            .fn = .TRUE
        endif
    endif
    
    debug.print (99,"date") testdate,testjulian,st'sdate,st'sjulian,st'edate,st'ejulian,.fn
EndFunction
!---------------------------------------------------------------------
!Function:
!   Convert separated date to CCYYMMDD$
!Params:
!   sdate  (T_SYSDATE) [in] - date in separated format:
!                               map1 sdate
!                                   map2 mon,b,1
!                                   map2 day,b,1
!                                   map2 yr,b,1
!                                   map2 dow,b,1
!Returns:
!   CCYYMMDD$
!Globals:
!Notes:
!   Provides a format variation not directly produced by ODTIM

!---------------------------------------------------------------------
Function Fn'Sdate'To'CCYYMMDD$(sdate as T_SYSDATE:inputonly) as s8

    map1 locals
        map2 mm'dd'ccyy$,s,10
        
    mm'dd'ccyy$ = odtim(sdate,0,&h268)     !  mm-dd-ccyy
    Fn'Sdate'To'CCYYMMDD$ = mm'dd'ccyy$[7;4] + mm'dd'ccyy$[1;2] + mm'dd'ccyy$[4;5]

EndFunction

![116]
!---------------------------------------------------------------------
!Function:
!   Convert flexdate to separated date
!Params:
!   flexdate$ (T_FLEX_DATE) [in] - see Date Format Notes at top of file
!   mon'day'order$ (str) [in] - (optional) flag controlling output order of month and day [115]
!                               "" or "md" = mm/dd/ccyy
!                               "dm"       = dd/mm/ccyy
!                               "ldf"      = (according to LDF) 
!Returns
!   sdate  (T_SYSDATE) [in] - date in separated format:
!                               map1 sdate
!                                   map2 mon,b,1
!                                   map2 day,b,1
!                                   map2 yr,b,1
!                                   map2 dow,b,1
!Globals:
!Notes:
!   date only; no time
!---------------------------------------------------------------------
Function Fn'Date'To'Sdate(flexdate$ as T_FLEX_DATE:inputonly, &
                          mon'day'order$ as s3:inputonly) as T_SYSDATE

    map1 locals
        map2 mm'dd'ccyy$,s,10
        map2 sdate,T_SYSDATE
        map2 bdate,b,4,@sdate
        map2 idate,f,6
        map2 juliany2k, T_JULIANY2K
        map2 filetime,  T_FILETIME48    ! [162]
        

    ! [141] special handling for binary args 
    if (.argtyp(@flexdate$) and ARGTYP_MASK) = ARGTYP_B then
        if .argsiz(@flexdate$) = 2 then                 ! T_JULIANY2K (B,2)
            juliany2k = flexdate$                       ! convert back to T_JULIANY2K
            if juliany2k then                           ! [142] only if not zero
                mm'dd'ccyy$ = Fn'Date'To'MM'DD'CCYY$(juliany2k,mon'day'order$)
            endif
        elseif .argsiz(@flexdate$) >= 4 then            ! T_FILETIME (B,4) [162] or T_FILETIME48 (B,6)
            filetime = flexdate$                        ! convert back to T_FILETIME
            if filetime then                            ! [142] only if not zero
                mm'dd'ccyy$ = Fn'Date'To'MM'DD'CCYY$(filetime,mon'day'order$)
            endif
        endif
    else
        mm'dd'ccyy$ = Fn'Date'To'MM'DD'CCYY$(flexdate$,mon'day'order$)
    endif

    xcall IDTIM, mm'dd'ccyy$, idate, 0, 2       ! convert to internal date (no time)
    bdate = idate                               ! convert separated
    .fn = sdate
EndFunction

![116]
!---------------------------------------------------------------------
!Function:
!   Reformat flexible input date using ODTIM flags
!Params:
!   flexdate$ (T_FLEX_DATE) [in] - see Date Format Notes at top of file
!   flags (num) - ODTIM flags
!   mon'day'order$ (str) [in] - (optional) flag controlling output order of month and day [115]
!                               "" or "md" = mm/dd/ccyy
!                               "dm"       = dd/mm/ccyy
!                               "ldf"      = (according to LDF) 
!Returns:
!   ODTIM-formatted output per flags
!Globals:
!Notes:
!   date only; no time
!   for format -1 (e.g. Sunday, June 9, 2019 hh:mm:dd AM) manually
!       remove the time (since the omit time bit is obscured by the -1)
!---------------------------------------------------------------------
Function Fn'Date'ODTIM$(flexdate$ as T_FLEX_DATE:inputonly, &
                        flags as b4:inputonly, &
                        mon'day'order$ as s3:inputonly) as s50
  
    map1 locals
        map2 sdate,T_SYSDATE
        
    sdate = Fn'Date'To'Sdate(flexdate$, mon'day'order$)
    .fn = ODTIM(sdate,0,flags or &h200)     ! (omit time)

    if flags = &hffffffff then
        .fn = .fn[1,-13]        ! remove the hh:mm:ss AM
    endif
EndFunction


![123]
!---------------------------------------------------------------------
!Function:
!   ODTIM$ wrapper for datetime strings in CCYYMMDDhhmmss format
!Params:
!   ccyymmddhhmmss$ (str) [in] - datetime string
!   flags (num) - ODTIM flags
!Returns:
!   ODTIM-formatted output per flags
!Globals:
!Notes:
!---------------------------------------------------------------------
Function Fn'CCYYMMDDHHMMSS'ODTIM$(ccyymmddhhmmss$ as s14:inputonly, &
                        flags as b4:inputonly) as s50
  
    map1 locals
        map2 sdate,T_SYSDATE
        map2 stime,T_SYSTIME
        map2 status,f
        
    sdate = Fn'Date'To'Sdate(ccyymmddhhmmss$[1;8])
    stime = (val(ccyymmddhhmmss$[9;2]) * 3600) + (val(ccyymmddhhmmss$[11;2]) * 60) + val(ccyymmddhhmmss$[13;2])

    .fn = ODTIM(sdate,stime,flags)

EndFunction


!---------------------------------------------------------------------
!Function:
!   Convert flexible date/time to filetime format (i.e. secs since "epoch"
!   aka 1/1/1970, as used by MX_FILESTATS and MX_FTFORMAT
!Params:
!   flexdate$ (T_FLEX_DATE) [in] - flexible date (and optionally hh:mm{:ss} after a space [136]
!   flextime$ (T_FLEX_TIME) [in] - flexible time
!Returns:
!   # secs since epoch, else 0 for error
!Globals:
!Notes:
!   if flextime$ not specified, treat everything after first space in
!   in flexdate$ as flextime$ [136]
!   [153] if flextime$ not specified, flexdate$ may contain the time, 
!   e.g. "123122 15:22"
!   [170] if nothing specified, treat as "today now" 
!---------------------------------------------------------------------
Function Fn'Date'Time'To'FILETIME(flexdate$ as T_FLEX_DATE:inputonly, &
                                  flextime$ as T_FLEX_TIME:inputonly) as T_FILETIME48
                                  
    map1 locals
        map2 mm'dd'ccyy$,s,10
        map2 cdate,b,2
        map2 result,f
        map2 secs,b,4
        map2 epochsecs,b,4
        map2 bytes,i,4
        map2 mtime,b,4
        map2 x,i,2
        map2 juliany2k, T_JULIANY2K
        map2 filetime,  T_FILETIME48        ! [162]
        
    ! [136] separate time from flexdate$
    if .argcnt = 1 then
        xcall TRIM, flexdate$
        x = instr(1,flexdate$,":")      ! [154] look for indicator of hh:mm
        if x then                       ! [154]
            ![154] x = instr(1,flexdate$," ")
            ![154] if x then
            ![154]     flextime$ = flexdate$[x+1,-1]
            ![154]     flexdate$ = flexdate$[1,x-1]
            ![154] endif
            flextime$ = flexdate$[x-2,-1]       ! [154]
            flexdate$ = flexdate$[1,x-4]        ! [154]
        else                                        
            x = instr(1,flexdate$," ")          ! [170]
            if x then                           ! [170]
                flextime$ = flexdate$[x+1,-1]   ! [170]
                flexdate$ = flexdate$[1,x-1]    ! [170]
            else                                ! [170] 
                flextime$ = "now"               ! [170] 
                flexdate$ = ife$(flexdate$,"today")     ! [170]
            endif
        endif
    endif
    
    ! [141] special handling for binary args 
    if (.argtyp(@flexdate$) and ARGTYP_MASK) = ARGTYP_B then
        if .argsiz(@flexdate$) = 2 then                 ! T_JULIANY2K (B,2)
            juliany2k = flexdate$                       ! convert back to T_JULIANY2K
            if juliany2k then                           ! [142] only if not zero
                mm'dd'ccyy$ = Fn'Date'To'MM'DD'CCYY$(juliany2k)
            endif
        elseif .argsiz(@flexdate$) >= 4 then            ! T_FILETIME (B,4) [162] or T_FILETIME48 (B,6)
            filetime = flexdate$                        ! convert back to T_FILETIME
            if filetime then                            ! [142] only if not zero
                mm'dd'ccyy$ = Fn'Date'To'MM'DD'CCYY$(filetime)
            endif
        endif
    else
        mm'dd'ccyy$ = Fn'Date'To'MM'DD'CCYY$(flexdate$)
    endif
    
    xcall DATES,DTOP_CVTFMT,result,mm'dd'ccyy$,cdate
    if result = 0 then
        
        cdate -= EPOCH_1900_OFFSET          ! convert cdate basis from 1/1/1900 to 1/1/1970
        secs = Fn'Time'To'Secs(flextime$)
        .fn = (cdate * 86400) + secs
        .fn += fn'filetime'timezone'offset()    ! [175] was s_offset
        
    endif

    debug.print (99,"fndatetime") Fn'Date'Time'To'FILETIME,flexdate$,flextime$,result,secs
EndFunction


![147]
!---------------------------------------------------------------------
!Function:
!   Convert CCYY-MM-DD hh:mm:ss (SQL format DateTime) to T_FILETIMES
!Params:
!   sqldatetime$ (T_SQL_DATETIME) [in] - CCYY-MM-DD hh:mm{:ss}
!Returns:
!   # secs since epoch, else 0 for error
!Globals:
!Notes:
!   wrapper around Fn'Date'Time'To'FILETIME()
!---------------------------------------------------------------------
Function Fn'SQL'DateTime'To'FILETME(sqldatetime$ as T_SQL_DATETIME:inputonly) as T_FILETIME48   ! [162] 
                                  
    if sqldatetime$[5;1] = "-" and sqldatetime$[8;1] = "-" then   ! minimum sanity test xxxx-xx-
        xcall XSTRIP, sqltimedate$, "-", 1     ! remove dashes  (result is CCYYMMDD hh:mm:ss)
        .fn = Fn'Date'Time'To'FILETIME(sqldatetime$[1;8], sqldatetime$[10;8])
    endif
EndFunction

!---------------------------------------------------------------------
!Function:
!   Format T_FILETIME using ODTIM flags
!Params:
!   filetime (T_FILETIME48) [in] - input time/date  [162] 
!   odtimflags (num [in] - ODTIM flags
!Returns:
!   formatted file and/or time string
!Globals:
!Notes:
!---------------------------------------------------------------------
Function Fn'FILETIME'To'ODTIM$(filetime as T_FILETIME48:inputonly, &
                               odtimflags as B4:inputonly) as s50
                                  
    map1 locals
        map2 strtime$,s,60
        map2 sdate,b,4
        map2 stime,b,4
        
    if filetime then        ! [131] return "" for 0
        xcall MIAMEX,MX_FTFORMAT,filetime,strtime$,sdate,stime
        .fn = odtim(sdate,stime,odtimflags)
    endif
    
EndFunction

!---------------------------------------------------------------------
!Function:
!   Convert T_FILETIME to CCYY-MM-DD HH:MM:SS (Formatted SQL DateTime)
!Params:
!   filetime (T_FILETIME48) [in] - time to format
!Returns:
!   CCYY-MM-DD HH:MM:SS
!Globals:
!Notes:
!   Format used by formatted SQL DateTime.
!   Would be handled directly by Fn'FILETIME'To'ODTIM$ except there
!   doesn't seem to be an ODTIM option for this format.
!   [160] return "" for 0 input
!---------------------------------------------------------------------
Function Fn'FILETIME'To'Formatted'DateTime$(filetime as T_FILETIME48:inputonly) as s20 

    if filetime then        ! [160]
        .fn = Fn'FILETIME'To'ODTIM$(filetime, &h168)  ! mm/dd/ccyy hh:mm:ss
        .fn = .fn[7;4] + "-" + .fn[1;2] + "-" + .fn[4;2] + .fn[11;9]
    endif
EndFunction

![147]
!---------------------------------------------------------------------
!Function:
!   Convert T_FILETIME to CCYYMMDDhhmmss
!Params:
!   filetime (T_FILETIME48) [in] - time to format
!Returns:
!   CCYYMMDDhhmmss
!Globals:
!Notes:
!   Similar to Fn'FILETIME'To'Formatted'DateTime$()
!---------------------------------------------------------------------
Function Fn'FILETIME'To'CCYYMMDDhhmmss$(filetime as T_FILETIME48:inputonly) as s20 

    .fn = Fn'FILETIME'To'ODTIM$(filetime, &h168)  ! mm/dd/ccyy hh:mm:ss
    .fn = .fn[7;4] + .fn[1;2] + .fn[4;2] + .fn[12;2] + .fn[15;2] + .fn[18;2]
    
EndFunction

!---------------------------------------------------------------------
!Function:
!   Convert flexible time to secs since midnight (T_SYSTIME)
!Params:
!   flextime$ (T_FLEX_TIME) [in] - flexible time
!                                   "now"
!                                   hh:mm{:ss} {AM/PM}
!                                   (assumes 24 time unless AM/PM specified)
!Returns:
!   # secs since midnight, else 0 for error
!Globals:
!Notes:
!   Same as Fn'Time'To'Secs()
!---------------------------------------------------------------------
Function Fn'Time'To'SYSTIME(flextime$ as T_FLEX_TIME:inputonly) as b4
    .fn = Fn'Time'To'Secs(flextime$)
EndFunction

!---------------------------------------------------------------------
!Function:
!   Convert flexible time to secs since midnight
!Params:
!   flextime$ (T_FLEX_TIME) [in] - flexible time
!                                   "now"
!                                   hh:mm{:ss} {AM/PM}
!                                   (assumes 24 time unless AM/PM specified)
!Returns:
!   # secs since midnight, else 0 for error
!Globals:
!Notes:
!   [174] supports hh > 23
!---------------------------------------------------------------------
Function Fn'Time'To'Secs(flextime$ as T_FLEX_TIME:inputonly) as b4
                                  
    map1 locals
        map2 hh,b,1
!>!        map2 mm,b,1
!>!        map2 ss,b,1
        map2 idate,f,6
        map2 itime,f,6
        map2 status,f,6
        map2 extrasecs,b,4
        map2 i,i,2
        
    if ucs(flextime$) = "NOW" then
        .fn = TIME
    else
        ! [174] IDTIM doesn't handle times > 24:00 
        if val(flextime$) > 23 then
            i = instr(1,flextime$,":")
            hh = val(flextime$[1,i-1]) 
            extrasecs = (hh - 24) * SECS_PER_DAY
            hh = hh mod 24
            flextime$ = (hh using "#Z") + flextime$[i,-1]
            debug.print (99,"fndatetime") i, hh, extrasecs, flextime$
        endif
            
        xcall IDTIM, flextime$, idate, itime, 1, status
        if status = 0 then
            .fn = itime + extrasecs ! [174]
        endif
    endif
    debug.print (99,"fndatetime") Fn'Time'To'Secs,flextime$
EndFunction

!---------------------------------------------------------------------
!Function:
!   Check if date is valid
!Params:
!   flexdate$  (T_FLEXDATE) [in] - any accepted format
!Returns:
!   .TRUE or .FALSE
!Globals:
!Notes:
!   blank is not valid
!---------------------------------------------------------------------
Function Fn'Date'Is'Valid(flexdate$ as T_FLEX_DATE:inputonly) as BOOLEAN

map1 locals
    map2 idate,f,6
    map2 itime,f,6
    map2 mm'dd'ccyy$,s,10   
    map2 status,f
    
    ! [141] if T_JULIANY2K or T_FILETIME, every value is valid [142] except 0
    if (.argtyp(@flexdate$) and ARGTYP_MASK) = ARGTYP_B then
        if val(flexdate$) # 0 then                      ! [142]
            if .argsiz(@flexdate$) = 2 then                 ! T_JULIANY2K (B,2)
                .fn = .TRUE
            elseif .argsiz(@flexdate$) >= 4 then        ! T_FILETIME (B,4) [162] or T_FILETIME48 (B,6)
                .fn = .TRUE
            endif
        endif
    else
        mm'dd'ccyy$ = Fn'Date'To'MM'DD'CCYY$(flexdate$)
        xcall IDTIM, mm'dd'ccyy$, idate, itime, 2, status
        if status=0 then
            .fn = .TRUE
        endif
    endif
EndFunction


!---------------------------------------------------------------------
!Function:
!   Return the name for the specified month #
!Params:
!   mm  (num) [in] - month #
!Returns:
!   Capitalized name or "" if mm invalid
!Globals:
!Notes:
!---------------------------------------------------------------------
Function Fn'Month'Name$(mm as b1:inputonly) as s20
    switch mm
        case 1
            .fn = Ldf.JAN
            exit
        case 2
            .fn = Ldf.FEB
            exit
        case 3
            .fn = Ldf.MAR
            exit
        case 4
            .fn = Ldf.APR
            exit
        case 5
            .fn = Ldf.MAY
            exit
        case 6
            .fn = Ldf.JUN
            exit
        case 7
            .fn = Ldf.JUL
            exit
        case 8
            .fn = Ldf.AUG
            exit
        case 9
            .fn = Ldf.SEP
            exit
        case 10
            .fn = Ldf.OCT
            exit
        case 11
            .fn = Ldf.NOV
            exit
        case 12
            .fn = Ldf.DEC
            exit
    endswitch

EndFunction

![141]

!---------------------------------------------------------------------
!Function:
!   Convert a T_FLEX_DATE to T_JULIANY2K
!Params:
!   flexdate$ (T_FLEX_DATE) [in] - flexible date format
!Returns:
!   T_JULIANY2K (or 0 if invalid input)
!Globals:
!Notes:
!---------------------------------------------------------------------
Function Fn'Date'To'JulianY2K(flexdate$ as T_FLEX_DATE:inputonly) as T_JULIANY2K
    map1 locals
        map2 ijulian,b,4
        map2 result,f
        map2 juliany2k, T_JULIANY2K
        map2 filetime,  T_FILETIME48        ! [162] 


    ! [141] special handling for binary args 
    if (.argtyp(@flexdate$) and ARGTYP_MASK) = ARGTYP_B then
        if .argsiz(@flexdate$) = 2 then                 ! T_JULIANY2K (B,2)
            juliany2k = flexdate$                       ! convert back to T_JULIANY2K
            if juliany2k then                           ! [142] only if not zero
                flexdate$ = Fn'Date'To'MM'DD'CCYY$(juliany2k)
            endif
        elseif .argsiz(@flexdate$) >= 4 then            ! T_FILETIME (B,4) [162] or T_FILETIME48 (B,6)
            filetime = flexdate$                        ! convert back to T_FILETIME
            if filetime then                            ! [142] only if not zero
                flexdate$ = Fn'Date'To'MM'DD'CCYY$(filetime)
            endif
        endif
    else
        flexdate$ = Fn'Date'To'MM'DD'CCYY$(flexdate$)   ! mm/dd/ccyy
    endif
        
    
    xcall DATES, DTOP_CVTFMT, result, flexdate$, ijulian
    if ijulian >= IJULIAN_Y2K_OFFSET then
        .fn = ijulian - IJULIAN_Y2K_OFFSET
    else
        .fn = 0
    endif
    
    debug.print (99,"fndatetime") "Fn'Date'To'JulianY2K",flexdate$,ijulian
EndFunction


![110]   
!---------------------------------------------------------------------
!Function:
!   convert {CC}YYMMDD to MM/DD/YY
!Params:
!   flexdate$ (str) [in] - date in {CC}YYMMDD
!Returns:
!   mm/dd/yy 
!   (if date not all numeric, leave it as is)
!Globals:
!Notes:
!---------------------------------------------------------------------

Function Fn'YYMMDD'To'MM'DD'YY$(flexdate$ as s8) as s8
    if fn'all'digits(flexdate$) then
        if len(flexdate$) = 6 then
            .fn = flexdate$[3,4] + "/" + flexdate$[5,6] + "/" + flexdate$[1,2]
        elseif len(flexdate$) = 8 then
            .fn = flexdate$[5,6] + "/" + flexdate$[7,8] + "/" + flexdate$[3,4]
        else
            .fn = flexdate$
        endif
    else
        .fn = flexdate$
    endif
    debug.print (99,"fndatetime") Fn'YYMMDD'To'MM'DD'YY$, flexdate$
EndFunction


!---------------------------------------------------------------------
!Function:
!   convert YYMMDD to MM/DD/YY
!Params:
!   yymmdd$  (T_YYMMDD) [in] - input date
!Returns:
!   MM/DD/YY
!Globals:
!Notes:
!   More efficient than the Fn'Date'To'xxx functions and also eliminates
!   potential confusion when all 3 components are <= 12
!---------------------------------------------------------------------
!>!Function Fn'YYMMDD'To'MM'DD'YY$(yymmdd$ as T_YYMMDD$:inputonly) as T_MM'DD'YY
!>!    .fn = yymmdd$[3,4] + "/" + yymmdd$[5,6] + "/" + yymmdd$[1,2] 
!>!EndFunction

!==========================================================================================
! Private Functions (helpers to public functions above)
!==========================================================================================


![141]
!---------------------------------------------------------------------
!Function:
!   Convert Y2K Julian to MM/DD/YY
!Params:
!   cjulian  (T_JULIANY2K) [in] - days since 1/1/2000
!Returns:
!   mm/dd/yy
!Globals:
!Notes:
!   Same as goplib.bsi: Fn'JulianY2K'to'MMDDYY$(); helper function
!   for Fn'Date'To'xxxx() which should be used by pubic callers
!---------------------------------------------------------------------
private function fn'juliany2k'to'mm'dd'yy$(cjulian as T_JULIANY2K:inputonly) as s8
    map1 locals
        map2 result,f
        map2 ijulian,b,4

    ijulian = cjulian + IJULIAN_Y2K_OFFSET ! convert to internal julian
    xcall DATES, DTOP_CVTFMT, result, ijulian, .fn
    debug.print (99,"fndatetime") "fn'juliany2k'to'mm'dd'yy$",cjulian,ijulian,.fn
EndFunction

![141]
!---------------------------------------------------------------------
!Function:
!   Convert Y2K Julian to CCYYMMDD
!Params:
!   cjulian  (b2) [in] - days since 1/1/2000
!Returns:
!   ccyymmdd
!Globals:
!Notes:
!   Same as goplib.bsi: Fn'JulianY2K'to'CCMMDDYY$(); helper function
!   for Fn'Date'To'xxxx() which should be used by pubic callers
!---------------------------------------------------------------------
private function fn'juliany2k'to'ccyymmdd$(cjulian as T_JULIANY2K:inputonly) as T_CCYYMMDD

    .fn = fn'juliany2k'to'mm'dd'yy$(cjulian)    ! mm/dd/yy
    .fn = "20" + .fn[7;4] + .fn[1;2] + .fn[4;2] ! ccyymmdd
    debug.print (99,"fndatetime") fn'juliany2k'to'ccyymmdd$, cjulian,.fn
    
EndFunction


![175]
!---------------------------------------------------------------------
!Function:
!   Compute the local time zone offset to use for FILETIME conversions
!Params:
!Returns:
!   offset in seconds
!Notes:
!   To sync with the timezone; create a file and compare it's
!   time to the one we would calculate directly.
!
!   Callers converting to T_FILETIME should:
!       .fn += fn'filetime'timezone'offset()
!---------------------------------------------------------------------
private function fn'filetime'timezone'offset() as i4
    
    map1 locals
        map2 mm'dd'ccyy$,s,10
        map2 cdate,b,2
        map2 result,f
        map2 secs,b,4
        map2 reffile$,s,15,"fndatetime.tmp"         ! temp file used for GMT reference
        map2 ch,b,2
        map2 epochsecs,b,4
        map2 bytes,i,4
        map2 mtime,b,4
    
    static map1 s_offset,i,4,-1
    
    if s_offset = -1 then       ! if we haven't yet computed the offset
        mm'dd'ccyy$ = Fn'Date'To'MM'DD'CCYY$("today")
        xcall DATES,DTOP_CVTFMT,result,mm'dd'ccyy$,cdate
        debug.print (99,"fndatetime") mm'dd'ccyy$,cdate
        cdate -= EPOCH_1900_OFFSET          ! convert cdate basis from 1/1/1900 to 1/1/1970
        secs = Fn'Time'To'Secs("now")       ! [129] was flextime$)
        epochsecs = (cdate * 86400) + secs
        debug.print (99,"fndatetime") epochsecs,secs,cdate
        
        ch = Fn'NextCh(62000)
        open #ch, reffile$, output
        close #ch
        xcall MIAMEX, MX_FILESTATS, "L", reffile$, bytes, mtime
        
        s_offset = mtime - epochsecs
        debug.print (99,"fndatetime") s_offset,epochsecs,mtime,epochsecs,secs
        kill reffile$
    endif
    .fn = s_offset
    debug.print (99,"fndatetime") fn'filetime'timezone'offset
EndFunction




++endif         ! end of fndatetime.bsi
