DateUtility

From Pickwiki
Revision as of 00:30, 18 February 2007 by Stuboy (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigationJump to search

HomePage>>SourceCode>>BasicSource>>DateUtility

This is a toolbox of date/time zone conversion utilities, not supplied by ICONV/OCONV.

All in one convenient place!

Methods

  • GetDateFormat - return: true if International format (dd/mm/yy) is set.
  • GetElapsedPeriod - return: elapsed years @am months @am days
  • GetElapsedTime - return: difference in seconds
  • GetElapsedTime.toString - return: oconv'd time (hh:mm:ss)
  • GetEpochTime - return: epoch time - seconds from 1970-01-01 UTC
  • GetISODateTime - return: ISO Date Time string yyyy-mm-ddThh:mm:ssZ
  • GetISOWeekOfYear - return: ISO8601 week of year
  • GetRFCDateTime - return: RFC1123 Date Time string: Sun, 06 Nov 1994 08:49:37 GMT
  • GetNumberSuffix - return: numeric ordinal suffix ('st,nd,rd,th')
  • GetTimeZoneString - return: users current TZ string
  • ParseEpochTime - return: date:@am:time [local at the time zone]
  • ParseEpochTime.toString - return: iso date time
  • ParseISODateTime - return: epoch time - seconds from 1970-01-01 UTC
  • ParseISODateTime.local - return: (user local) date:@am:time
  • ParseRFCDateTime - return: Epoch time - seconds from 1970-01-01 UTC
  • ParseRFCDateTime.local - return: (user local) date:@am:time
  • SetDateFormat - Set the date format ON to International (dd/mm/yy)

Background

The main goal of this project was to provide a tool to convert to and from ISO8601. This is the date/time format used as standard in such things as XML documents. The nature of ISO8601, means that time zone must be taken into consideration. So, this turned into a bunch of methods which will also assist in Time Zone handling.
Overkill? Well possibly for most applications. However, regard placing orders on foreign countries and setting local delivery dates & times or easily finding the east coast time of an XML document stamped with west coast time (amongst others uses). We found we had several places we needed these types of time & date conversion, so it grew.
If you don't see something useful, please feel free to comment and add to it.
The program is designed as a function requiring a method and input arguments and returns a result value, and sets status() if conversion fails. It means it can be easily called from basic or an I-type subr() statement. See the comments in the code for examples and details of methods and the arguments required for each method.
Remember to tweak the E.DEFAULT.TZ equate constant, or read it in from a parameter file.

Glossary

  • Epoch - meaning the Unix epoch - (milli)seconds from 1970-01-01T00:00Z.
  • GMT - Greenwich Mean Time, unadjusted time at Greenwich, London UK.
  • ISO - International Standards Organisation - they set, er, international standards.
  • ISO8601 - Set of standards for representing local dates and time relative to UTC.
  • RFC - Request For Comment. The set of documents which make up the standards which the internet is built around.
  • RFC1123 - preferred as an Internet standard date format (HTTP).
  • TZ - Unix environment variable used to carry time zone info. On a Windows system this must be derived from the registry.
  • UTC - Universal Coordinated Time - International standard for time measurement (something close to GMT).
  • Z - Zulu, shorthand for the time at zero meridian or UTC.

The Code


function DATE.UTILITY(method,arguments)
* DTV: Various date & time methods not handled by OCONV, eg.ISO8601, RFC1123
*// Stuart Boydell 2003-02-24
*//
*//    Change E.DEFAULT.TZ for target server time zone locale.
*//
*// see http://www.timeanddate.com for more information about times and time zones
*//
*// Catalog this globally.
*// Call as either function ! i-type subr() with method and arguments.
*// Methods and arguments are enumerated in the Mainline subroutine.
*// No method, default datetime() string is returned
*// No arguments, results for "now" are returned.
*// Use of Epoch time - seconds since 1970-01-01T00:00Z
*// Use of iso8601 date time formats (used extensively with xml, html & sql)
*// general format is: YYYY-MM-DDThh:mmTZD (eg 1997-07-16T19:20+10:00)
*//
*// Usage 1: program
*//      PROGRAM SOME.PROGRAM
*//      DEFFUN DATE.UTIL(method,arguments) CALLING '*DATE.UTILITY'
*//      ...
*//      MY.METHOD     = 'GetISODateTime'
*//      MY.ARGUMENTS  = ORD.DATE:@AM:ORD.TIME
*//      ISO.DATE.TIME = DATE.UTIL(MY.METHOD,MY.ARGUMENTS)
*//
*// Usage 2: I-type from a file with MY.DATE/TIME.FIELD attributes.
*//      0001 I
*//      0002 subr('*DATE.UTILITY','GetISODateTime',MY.DATE.FIELD:@AM:MY.TIME.FIELD)
*//      ...
*//
*// see Methods below for further information.

*--------------------------------------------
DefineEquates:
*--------------------------------------------

   equ E.THIS.PROG      to 'DATE.UTILITY',
       E.VERSION        to '1.0'

   *// set this to your local server time as a default/fallback position
   equ E.DEFAULT.TZ     to 'EET-10EETDT-11,M10.5.0,M3.5.0'

   *// various time constants
   equ E.SECS.PER.MIN   to 60,
       E.SECS.PER.HOUR  to 3600,
       E.SECS.PER.DAY   to 86400,
       E.SECS.PER.YEAR  to 31556926,  ;*// tropical year seconds (approx 365.25 days)
       E.EPOCH.SECS     to system(99),;*// seconds from 1970-01-01T00:00Z
       E.EPOCH.OFFSET   to 732        ;*// iconv('01-Jan-1970','d')

   *// numeric ordinal suffix ; eg 1st 2nd 3rd 4th ...
   equ E.NUM.SUFFIX     to 'st':@am:'nd':@am:'rd':@am:'th'

   *// codes, characters, conversions and such
   equ E.ISODATE.CONV   to 'D-YMD[4,2,2]',
       E.ISOTIME.CONV   to 'MT',
       E.RFCDATE.CONV   to 'D WBDMBYL[", "]',
       E.RFCTIME.CONV   to 'MTS',
       E.TZ.ENVSTR      to 'TZ',     ;*// time zone environment string lead-in
       E.ISOTIME.SEP    to 'T',
       E.ISOZONE.SEP    to 'Z',
       E.PLUS           to '+',
       E.MINUS          to '-',
       E.POINT          to '.',
       E.TMP.ID         to @tty:'tz.vbs'

   *// timeZoneArray
   *// 1=tz desc, 2=tz offset, 3=dls desc, 4=dls offset
   *// 5=start month,  6=week,  7=day,  8=time
   *// 9=stop  month, 10=week, 11=day, 12=time
   equ E.GTZO.DESC        to timeZoneArray<1>,
       E.GTZO.OFFSET      to timeZoneArray<2>,
       E.GTZO.DLS.DESC    to timeZoneArray<3>,
       E.GTZO.DLS.OFFSET  to timeZoneArray<4>,
       E.GTZO.START.MONTH to timeZoneArray<5>,
       E.GTZO.START.WEEK  to timeZoneArray<6>,
       E.GTZO.START.DAY   to timeZoneArray<7>,
       E.GTZO.START.TIME  to timeZoneArray<8>,
       E.GTZO.STOP.MONTH  to timeZoneArray<9>,
       E.GTZO.STOP.WEEK   to timeZoneArray<10>,
       E.GTZO.STOP.DAY    to timeZoneArray<11>,
       E.GTZO.STOP.TIME   to timeZoneArray<12>

*--------------------------------------------
Mainline:
*--------------------------------------------

   returnValue = oconv('','c')               ;*// set RV to '' and status() function to false

   *// handle passed method parameter
   begin case

   case method = 'GetDateFormat'
      *// args:   null
      *// return: true/false
      gosub GetDateFormat:

   case method = 'GetElapsedPeriod'
      *// args:   start date @am stop date
      *// return: elapsed years am months am days
      gosub GetElapsedPeriod:

   case method = 'GetElapsedTime'
      *// args:   [start date @vm] start time @am [stop date @vm] stop time
      *// return: difference in seconds
      gosub GetElapsedTime:

   case method = 'GetElapsedTime.toString'
      *// args: [start date @vm] start time @am [stop date @vm] stop time
      *// return: oconv'd time (hh:mm:ss)
      gosub GetElapsedTime:
      returnValue = oconv(returnValue,E.ISOTIME.CONV)
      if status() then returnValue = ''

   case method = 'GetEpochTime'
      *// args: [date][@am time][@am timeZoneString]
      *// return: epoch time - seconds from epoch
      gosub GetEpochTime:

   case method = 'GetISODateTime' ! method = 'getISODateTime.toString'
      *// args: [date],[time],[timezone]
      *// return ISO Date Time string yyyy-mm-ddThh:mm:ssZ
      gosub GetISODateTime:

   case method = 'GetISOWeekOfYear'
      *// args: [date]
      *// return ISO8601 week of year
      gosub GetISOWeekOfYear:

   case method = 'GetRFCDateTime'
      *// args: [date],[time]
      *// return rfc1123 Date Time string: Sun, 06 Nov 1994 08:49:37 GMT
      gosub GetRFCDateTime:

   case method = 'GetNumberSuffix'
      *// args: number
      *// return: numeric ordinal suffix ('st,nd,rd,th')
      gosub GetNumberSuffix:

   case method = 'GetTimeZoneString'
      *// args: null
      *// return users current TZ string
      gosub GetTimeZoneString:

   case method = 'GetTimeZoneStringFromArray'
      *// args: TimeZoneArray (eg timeZoneArray)
      *// return users current TZ string
      gosub GetTimeZoneStringFromArray:

   case method = 'ParseEpochTime'
      *// args: [UTCSeconds][@am timeZoneString]
      *// return: date:@am:time [local at the time zone]
      gosub ParseEpochTime:

   case method = 'ParseEpochTime.toString'
      *// args: a value being seconds from epoch
      *// return: iso date time
      gosub ParseEpochTime:
      arguments = returnValue
      gosub GetISODateTime:

   case method = 'ParseISODateTime'
      *// args: ISO formatted date string eg 1997-07-16T19:20+10:00
      *// return epoch time - seconds from 1/1/70 UTC
      gosub ParseISODateTime:

   case method = 'ParseISODateTime.local'
      *// args: ISO formatted date string eg 1997-07-16T19:20+10:00
      *// return (user local) date:@am:time
      gosub ParseISODateTime:
      if validTime then
         arguments = returnValue
         gosub ParseEpochTime:
      end

   case method = 'ParseRFCDateTime'
      *// args: rfc1123 Date Time string: Sun, 06 Nov 1994 08:49:37 GMT
      *// return Epoch time - seconds from epoch
      gosub ParseRFCDateTime:

   case method = 'ParseRFCDateTime.local'
      *// args: rfc1123 Date Time string: Sun, 06 Nov 1994 08:49:37 GMT
      *// return (user local) date:@am:time
      gosub ParseRFCDateTime:
      arguments = returnValue
      gosub ParseEpochTime:

   case method = 'ParseTimeZoneString'
      *// args: [time zone string]
      *// return Time Zone Object
      gosub ParseTimeZoneString:

   case method = 'SetDateEuropean'
   *// deprecated - used in legacy calls - use setDateFormat
      *// args: null
      *// return: null
      arguments = 'ON'
      gosub SetDateFormat:

   case method = 'SetDateFormat'
      *// args: set [ON/OFF][D new date format] 
      *// return: null
      gosub SetDateFormat:

   case @true
      *// default case, return time date string
      *// return: timedate eg "15:39:12 22 APR 2003"
      returnValue = timedate()

   end case

return(returnValue)
stop

*--------------------------------------------
GetDateFormat:    *// International = true, US = false
*--------------------------------------------

   getDateFormatCmd = 'DATE.FORMAT INFORM'
   execute getDateFormatCmd capturing cap returning returnValue

return(@null)

*--------------------------------------------
GetElapsedPeriod:
*--------------------------------------------

   periodStart = arguments<1>
   periodEnd   = arguments<2>
   if (periodEnd >= periodStart) then 
      totalYears  = oconv(periodEnd,'dy') - oconv(periodStart,'dy') 
      totalMonths = oconv(periodEnd,'dm') - oconv(periodStart,'dm') 
      totalDays   = oconv(periodEnd,'dd') - oconv(periodStart,'dd') 
      if (totalDays < 0) then 
         totalDays   += oconv(iconv('1 ':oconv(periodEnd,'d mby'),'d') -1,'dd') 
         totalMonths -= 1 
      end 
      if (totalMonths < 0) then 
         totalMonths += 12 
         totalYears  -= 1 
      end 
      returnValue = totalYears : @am : totalMonths : @am : totalDays 
   end 

return(@null)

*--------------------------------------------
GetElapsedTime:   *// find elapsed time from 2 second counts
*--------------------------------------------

   startTime = arguments<1>
   stopTime  = arguments<2>

   begin case
   case dcount(startTime,@vm) = 2 & dcount(stopTime,@vm) = 2 ;*// dates & times provided
      arguments = raise(startTime<1>)
      gosub GetEpochTime:        ;*// get start as secs from epoch
      startTime = returnValue
      arguments = raise(stopTime<1>)
      gosub GetEpochTime:        ;*// get stop as secs from epoch
      returnValue -= startTime

   case not(num(startTime) & num(stopTime)) ;*// bad data

   case (startTime - stopTime) > E.SECS.PER.DAY
   case stopTime < startTime
      *// calculate up to same time over a midnight boundry
      *// ie start=23:00, end=03:00 : total => (27:00 - 23:00) => 05:00
      returnValue = stopTime + E.SECS.PER.DAY - startTime

   case @true
      returnValue = (stopTime - startTime)
   end case

return(@null)

*--------------------------------------------
GetEpochTime:    *// return seconds from 1/1/70 UTC
*--------------------------------------------

   internalDate   = arguments<1>
   internalTime   = arguments<2>
   timeZoneString = arguments<3>

   if internalDate   = '' then internalDate = date()
   if internalTime   = '' then internalTime = time()
   if timeZoneString = '' then
      *// get default
      gosub GetTimeZoneString:
      timeZoneString = returnValue
   end
   arguments = timeZoneString
   gosub ParseTimeZoneString:

   arguments = internalDate
   gosub GetTimeZoneOffset:

   returnValue += (internalDate - E.EPOCH.OFFSET) * E.SECS.PER.DAY
   returnValue += internalTime

return(@null)

*--------------------------------------------
GetISODateTime:  *// return date in ISO format yyyy-mm-ddThh:mm:ssZhh:mm
*--------------------------------------------

   internalDate   = arguments<1>
   internalTime   = arguments<2>
   if unassigned(timeZoneString) then timeZoneString = arguments<3>

   if internalDate = '' then internalDate = date()
   if internalTime = '' then internalTime = time()
   if timeZoneString = '' then
      *// get default
      gosub GetTimeZoneString:
      timeZoneString = returnValue
   end

   isoDate = oconv(internalDate,E.ISODATE.CONV)
   if not(status()) then
      isoTime = oconv(internalTime,E.RFCTIME.CONV)
      if not(status()) then
         if len(timeZoneString) then
            arguments = timeZoneString
            gosub ParseTimeZoneString:
         end
         arguments = internalDate
         gosub GetTimeZoneOffset:
         arguments = returnValue
         gosub GetISOZone:
         if not(status()) then
            returnValue = isoDate : E.ISOTIME.SEP : isoTime : returnValue
         end
      end
   end

return(@null)

*--------------------------------------------
GetISOWeekOfYear:
*--------------------------------------------
   
   internalDate = arguments<1>
   if internalDate = '' then internalDate = date()
   dayOfWeek = oconv(internalDate, 'dw')
   if status() then                 ;*// test for bad conversion
      weekNumber = '-1'
   end else
      thisThursday = internalDate - dayOfWeek + 4 ;* calculate date for that Thursday
      dayOfYear    = oconv(thisThursday,'dj')
      weekNumber   = int((dayOfYear + 6) / 7) ;*// ta WOL
   end
   returnValue = weekNumber

return(@null)

*--------------------------------------------
GetISOZone:   *// return ISO time zone offset
*--------------------------------------------

   localOffsetSecs = arguments
   isoZone = oconv(abs(localOffsetSecs),E.ISOTIME.CONV)

   begin case
   case status()                                ;*// invalid oconv         15-Apr-2003 return default offset for melbourne
      returnValue = '+10:00'
   case abs(localOffsetSecs) >= E.SECS.PER.DAY  ;*// invalid offset value  15-Apr-2003 return default offset for melbourne
      returnValue = '+10:00'
   case localOffsetSecs < 0
      returnValue = E.PLUS:isoZone
   case localOffsetSecs > 0
      returnValue = E.MINUS:isoZone
   case localOffsetSecs = 0
      returnValue = E.ISOZONE.SEP:isoZone
   case @true
      returnValue = '+10:00'
   end case

return(@null)

*--------------------------------------------
GetRFCDateTime:  *// return Date Time string: Sun, 06 Nov 1994 08:49:37 GMT
*--------------------------------------------
      
   gosub GetEpochTime:

   *// get time and date at GMT
   internalDate = int(returnValue/E.SECS.PER.DAY) + E.EPOCH.OFFSET - (returnValue < 0)
   internalTime = mod(returnValue,E.SECS.PER.DAY) + (E.SECS.PER.DAY * (returnValue < 0))

   rfcDate = oconv(internalDate,E.RFCDATE.CONV)
   if not(status()) then
      rfcTime = oconv(internalTime,E.RFCTIME.CONV)
      if not(status()) then
         returnValue = rfcDate : ' ': rfcTime : ' GMT'
      end
   end

return(@null)

*--------------------------------------------
GetNumberSuffix: *// get numerical ordinal suffix
*--------------------------------------------

   number = arguments<1>
   if len(number) then
      lastChar = number[1] ;*// right(number,1)
      lastTeen = number[2] ;*// right(number,2)

      begin case
      case lastTeen >= 11 & lastTeen <= 20
         returnValue = number:(E.NUM.SUFFIX)<4>
      case lastChar > 0 & lastChar <= 3
         returnValue = number:(E.NUM.SUFFIX)<lastChar>
      case @true
         returnValue = number:(E.NUM.SUFFIX)<4>
      end case

   end

return(@null)

*--------------------------------------------
GetTimeZoneOffset: *// get time zone offset 
*--------------------------------------------

   *// Args in: internal date
   *// timeZoneArray required for accurate offset.

   if unassigned(timeZoneArray) then timeZoneArray = ''
   checkDLSFlag = num(arguments<1>) ;*// date passed in
   checkDLSFlag = checkDLSFlag * (E.GTZO.OFFSET # E.GTZO.DLS.OFFSET)
   checkDLSFlag = checkDLSFlag * len(E.GTZO.START.MONTH)
   checkDLSFlag = checkDLSFlag * len(E.GTZO.START.WEEK)
   checkDLSFlag = checkDLSFlag * len(E.GTZO.START.DAY)
   checkDLSFlag = checkDLSFlag * len(E.GTZO.STOP.MONTH)
   checkDLSFlag = checkDLSFlag * len(E.GTZO.STOP.WEEK)
   checkDLSFlag = checkDLSFlag * len(E.GTZO.STOP.DAY)

   if checkDLSFlag then

      *// find the dls start date for this year
      tzInternalDate = arguments<1>
      tzYear       = oconv(tzInternalDate,'d4y')
      tzStartMonth = E.GTZO.START.MONTH
      tzStartWeek  = E.GTZO.START.WEEK
      tzStartDay   = E.GTZO.START.DAY

      tzStartDate  = iconv(tzStartMonth:'/':tzYear,'dmy') ;*// finds the first
      tzStartOffset = tzStartDay - mod(tzStartDate,7)
      if tzStartOffset < 1 then tzStartOffset += 7
      tzStartDate += tzStartOffset
      tzStartDate += tzStartWeek * 7
      loop until oconv(tzStartDate,'dm') = tzStartMonth do tzStartDate -= 7 repeat

      *// find the dls stop date for this year
      tzStopMonth = E.GTZO.STOP.MONTH
      tzStopWeek  = E.GTZO.STOP.WEEK
      tzStopDay   = E.GTZO.STOP.DAY

      tzStopDate  = iconv(tzStopMonth:'/':tzYear,'dmy')   ;*// finds the first
      tzStopOffset = tzStopDay - mod(tzStopDate,7)
      if tzStopOffset < 1 then tzStopOffset += 7
      tzStopDate += tzStopOffset
      tzStopDate += tzStopWeek * 7
      loop until oconv(tzStopDate,'dm') = tzStopMonth do tzStopDate -= 7 repeat

      *// return the appropriate time offset for the date.
      begin case
      case tzStartDate < tzStopDate & tzInternalDate > tzStartDate & tzInternalDate < tzStopDate ;*// northern hemisphere dls
         timeZoneOffset = E.GTZO.DLS.OFFSET
      case tzStartDate > tzStopDate & (tzInternalDate < tzStopDate ! tzInternalDate > tzStartDate) ;*// southern hemisphere dls
         timeZoneOffset = E.GTZO.DLS.OFFSET
      case @true
         timeZoneOffset = E.GTZO.OFFSET
      end case
      returnValue = timeZoneOffset
   end else
      if abs(E.GTZO.OFFSET) > 0 then
         timeZoneOffset = E.GTZO.OFFSET
         returnValue = timeZoneOffset
      end else
         returnValue = 0 ;*// return as UTC
      end
   end

return(@null)

*--------------------------------------------
GetTimeZoneString:  *// return current unix TZ setting - unix
*--------------------------------------------

   if system(91) then ;*// WIN NT needs registry reader
   *// http://msdn.microsoft.com/library/en-us/wmisdk/wmi/win32_timezone.asp
   *// return unix tz string like: EET-10EETDT-11,M10.5.0/2,M3.5.0/2
      open '&SAVEDLISTS&' to f.temp then
         script     = 'Set wbemObjectSet = GetObject("winmgmts:").InstancesOf("Win32_TimeZone")'
         script<-1> = 'For Each wbemObject In wbemObjectSet : With wbemObject'
         script<-1> = 'StdOffset = 0 - .Bias + .StandardBias'
         script<-1> = 'SS = mid("+-",1-(StdOffset <0),1)'
         script<-1> = 'DltOffset = 0 - .Bias + .DaylightBias'
         script<-1> = 'DS = mid("+-",1-(DltOffset <0),1)'
         script<-1> = 'WScript.stdOut.writeline _'
         script<-1> = '"STD" & SS & formatdatetime(timeserial(0,StdOffset,0),4) & _'
         script<-1> = '"DLT" & DS & formatdatetime(timeserial(0,DltOffset,0),4) & _'
         script<-1> = '",M" & .DaylightMonth & "." & .DaylightDay & _'
         script<-1> = '"." & .DaylightDayOfWeek & "/" & .DaylightHour & _'
         script<-1> = '",M" & .StandardMonth & "." & .StandardDay & _'
         script<-1> = '"." & .StandardDayOfWeek & "/" & .StandardHour'
         script<-1> = 'End With : Next : Set wbemObjectSet = Nothing'
         write script on f.temp,E.TMP.ID then
            execute 'dos /c cscript //B //T:1 ^&SAVEDLISTS^&/':E.TMP.ID capturing cap
            returnValue = cap<1>
         end else
            returnValue = E.DEFAULT.TZ
         end
         delete f.temp,E.TMP.ID
      end else
         returnValue = E.DEFAULT.TZ
      end
   end else
      execute 'ENVIRONMENT' capturing envStrings
      envVars = fields(envStrings,'=',1)
      locate E.TZ.ENVSTR in envVars setting tzPos then
         returnValue = field(envStrings<tzPos>,'=',2)
      end else
         returnValue = E.DEFAULT.TZ
      end
   end

return(@null)

*--------------------------------------------
GetTimeZoneStringFromArray:  *// return current unix TZ setting - unix
*--------------------------------------------

   timeZoneArray = arguments

   if E.GTZO.DESC then
      returnValue = E.GTZO.DESC
      if E.GTZO.OFFSET then
         z = int(E.GTZO.OFFSET/E.SECS.PER.HOUR)
         if z > 0 then returnValue := E.PLUS
         returnValue := z
         if mod(E.GTZO.OFFSET,E.SECS.PER.HOUR) then returnValue := (':':mod(abs(E.GTZO.OFFSET),E.SECS.PER.HOUR)/E.SECS.PER.MIN)
      end else
         returnValue := '0'
      end

      if E.GTZO.DLS.DESC then
         returnValue := E.GTZO.DLS.DESC
         if E.GTZO.DLS.OFFSET then
            z = int(E.GTZO.DLS.OFFSET/E.SECS.PER.HOUR)
            if z > 0 then returnValue := E.PLUS
            returnValue:= z
            if mod(E.GTZO.DLS.OFFSET,E.SECS.PER.HOUR) then returnValue := ':':mod(abs(E.GTZO.DLS.OFFSET),E.SECS.PER.HOUR)/E.SECS.PER.MIN
         end else
            returnValue := '0'
         end

         returnValue := ',M':E.GTZO.START.MONTH
         returnValue := '.':E.GTZO.START.WEEK
         returnValue := '.':E.GTZO.START.DAY:'/2'
         returnValue := ',M':E.GTZO.STOP.MONTH
         returnValue := '.':E.GTZO.STOP.WEEK
         returnValue := '.':E.GTZO.STOP.DAY:'/2'
      end

   end

return(@null)

*--------------------------------------------
ParseEpochTime:    *// return local UV seconds from 1970-01-01T00:00Z
*--------------------------------------------

   if num(arguments<1>) then
      timeToParse    = arguments<1>
      timeZoneString = arguments<2>

      *// convert to local time
      if timeToParse    = '' then timeToParse = E.EPOCH.SECS
      if timeZoneString = '' then
         *// get default
         gosub GetTimeZoneString:
         timeZoneString = returnValue
      end
      arguments = timeZoneString
      gosub ParseTimeZoneString:

      arguments = int(timeToParse/E.SECS.PER.DAY) + E.EPOCH.OFFSET - (timeToParse < 0) ;*// internal date - is DLS applicable?
      gosub GetTimeZoneOffset:
      timeToParse -= returnValue

      *// get time and date at UTC
      internalDate = int(timeToParse/E.SECS.PER.DAY) + E.EPOCH.OFFSET - (timeToParse < 0)
      internalTime = mod(timeToParse,E.SECS.PER.DAY) + (E.SECS.PER.DAY * (timeToParse < 0))

      returnValue  = internalDate:@am:internalTime

   end

return(@null)

*--------------------------------------------
ParseISODateTime:    *// return Epoch
*--------------------------------------------

* sample of possible formats for ISO date/time
* ISO 8601:2000, The full iso8601 date/time spec as at 2000 may be downloaded here:
* http://www.iso.ch/iso/en/CatalogueDetailPage.CatalogueDetail?CSNUMBER=26780
*   Year:
*      YYYY (eg 1997)
*   Year and month:
*      YYYY-MM (eg 1997-07)
*   Complete date:
*      YYYY-MM-DD (eg 1997-07-16)
*   Complete date plus hours and minutes:
*      YYYY-MM-DDThh:mmTZD (eg 1997-07-16T19:20+01:00)
*   Complete date plus hours, minutes and seconds:
*      YYYY-MM-DDThh:mm:ssTZD (eg 1997-07-16T19:20:30+01:00)
*   Complete date plus hours, minutes, seconds and a decimal fraction of a second
*      YYYY-MM-DDThh:mm:ss.sTZD (eg 1997-07-16T19:20:30.45+01:00)

   isoString   = upcase(arguments<1>)

   if len(isoString) then

      isoDateString     = field(isoString,E.ISOTIME.SEP,1)
      isoTimeZoneString = field(isoString,E.ISOTIME.SEP,2)
      internalTime      = 0
      timeOffset        = 0
      validTime         = @true

      internalDate      = iconv(isoDateString,E.ISODATE.CONV)
      begin case
      case status()         ;*// date not parsed
         validTime    = @false
      case index(isoTimeZoneString,E.PLUS,1)
         internalTime = iconv(field(isoTimeZoneString,E.PLUS,1),E.ISOTIME.CONV)
         if status() then validTime = @false
         timeOffset   = -1 * iconv(field(isoTimeZoneString,E.PLUS,2),E.ISOTIME.CONV)
         if status() then validTime = @false
      case index(isoTimeZoneString,E.MINUS,1)
         internalTime = iconv(field(isoTimeZoneString,E.MINUS,1),E.ISOTIME.CONV)
         if status() then validTime = @false
         timeOffset   = iconv(field(isoTimeZoneString,E.MINUS,2),E.ISOTIME.CONV)
         if status() then validTime = @false
      case index(isoTimeZoneString,E.ISOZONE.SEP,1)
         internalTime = iconv(field(isoTimeZoneString,E.ISOZONE.SEP,1),E.ISOTIME.CONV)
         if status() then validTime = @false
      case len(isoTimeZoneString)
         internalTime = iconv(field(isoTimeZoneString,E.ISOZONE.SEP,1),E.ISOTIME.CONV)
         if status() then validTime = @false
      end case

      if validTime then
         internalDate  = (internalDate - E.EPOCH.OFFSET) * E.SECS.PER.DAY
         internalTime  = internalTime + timeOffset
         returnValue   = internalDate + internalTime
      end
   end else
      validTime = @false
   end

return(@null)

*--------------------------------------------
ParseRFCDateTime:    *// return Epoch
*--------------------------------------------
* RFC date time string: Sun, 06 Nov 1994 08:49:37 GMT

   timeToParse = arguments<1>
   if timeToParse then
      datePart = iconv(field(timeToParse,' ',2,3),'D')
      if not(status()) then
         timePart = iconv(field(timeToParse,' ',5),'MT')
         if not(status()) then
            returnValue = datePart * E.SECS.PER.DAY + timePart
         end
      end
   end

return(@null)

*--------------------------------------------
ParseTimeZoneString:  *// parse unix TZ env string
*--------------------------------------------

   tzString         = arguments<1>
   returnValue      = ''
   timeZoneArray  = ''
   *// 1=tz desc, 2=tz offset, 3=dls desc, 4=dls offset
   *// 5=start month,  6=week,  7=day,  8=time
   *// 9=stop  month, 10=week, 11=day, 12=time

   if len(tzString) then
      *// get standard and dls offsets (and descriptors)
      tzPart = field(tzString,',',1)
      maxCharacter = len(tzPart)
      for charPos = 1 to maxCharacter
         character  = tzPart[charPos,1]
         lastDOChar = timeZoneArray[1]

         begin case
         case character = ':'
            timeZoneArray    := character
         case not(alpha(character) ! num(character))
            timeZoneArray<-1> = character
         case alpha(lastDOChar) & num(character)
            timeZoneArray<-1> = character
         case num(lastDOChar) & alpha(character)
            timeZoneArray<-1> = character
         case @true
            timeZoneArray    := character
         end case

      next charPos

      if len(E.GTZO.OFFSET) then
         *// convert offset to seconds
         utcOffset = ''
         utcOffsetTime = E.GTZO.OFFSET
         if utcOffsetTime[1,1] = E.PLUS ! utcOffsetTime[1,1] = E.MINUS then
         *// first char is +/-
            utcOffset = utcOffsetTime[1,1]
            utcOffsetTime = utcOffsetTime[2,99]
         end
         utcOffsetSecs  = field(utcOffsetTime,':',1) * E.SECS.PER.HOUR
         utcOffsetSecs += field(utcOffsetTime,':',2) * E.SECS.PER.MIN
         utcOffsetSecs += field(utcOffsetTime,':',3)
         E.GTZO.OFFSET = utcOffset:utcOffsetSecs
      end

      if len(E.GTZO.DLS.OFFSET) then
         *// convert offset to seconds
         utcOffset = ''
         utcOffsetTime = E.GTZO.DLS.OFFSET
         if utcOffsetTime[1,1] = E.PLUS ! utcOffsetTime[1,1] = E.MINUS then
         *// first char is +/-
            utcOffset = utcOffsetTime[1,1]
            utcOffsetTime = utcOffsetTime[2,99]
         end
         utcOffsetSecs  = field(utcOffsetTime,':',1) * E.SECS.PER.HOUR
         utcOffsetSecs += field(utcOffsetTime,':',2) * E.SECS.PER.MIN
         utcOffsetSecs += field(utcOffsetTime,':',3)
         E.GTZO.DLS.OFFSET = utcOffset:utcOffsetSecs
      end else
         if utcOffsetSecs then ;*// use standard time as default - hour
*!!        E.GTZO.DLS.OFFSET = utcOffset:(utcOffsetSecs - E.SECS.PER.HOUR)
         end
      end

      if dcount(timeZoneArray,@am) > 4 then
         ndo = ''
         for i=1 to 4; ndo<i> = timeZoneArray<i>; next i
         timeZoneArray = ndo
      end

      *// get summer time start params
      tzPart       = field(tzString,',',2)
      if len(tzPart) then
         E.GTZO.START.MONTH = ''
         maxCharacter = len(field(tzPart,'/',1)) ;*// assume 2am start/stop
         for charPos  = 1 to maxCharacter
            character = tzPart[charPos,1]

            begin case
            case character = 'M'
            case character = 'J' ;*!!! julian date - shouldn't happen
            case character = '.'
               timeZoneArray := @am
            case @true
               timeZoneArray := character
            end case

         next charPos
         E.GTZO.START.TIME = 7200 ;*// assume 2am

         if dcount(timeZoneArray,@am) > 8 then
            ndo = ''
            for i=1 to 8; ndo<i> = timeZoneArray<i>; next i
            timeZoneArray = ndo
         end
      end else
         *// standard default - northern hemisphere
*!!         E.GTZO.START.MONTH = 4
*!!         E.GTZO.START.WEEK  = 1
*!!         E.GTZO.START.DAY   = 0
*!!         E.GTZO.START.TIME  = '02:00'
      end

      *//  get summer time stop params
      tzPart = field(tzString,',',3)
      if len(tzPart) then
         E.GTZO.STOP.MONTH = ''
         maxCharacter = len(field(tzPart,'/',1)) ;*// assume 2am start/stop
         for charPos  = 1 to maxCharacter
            character = tzPart[charPos,1]

            begin case
            case character = 'M'
            case character = 'J' ;*!!! julian date - shouldn't happen
            case character = '.'
               timeZoneArray := @am
            case @true
               timeZoneArray := character
            end case

         next charPos
         E.GTZO.STOP.TIME = 7200 ;*// assume 2am

         if dcount(timeZoneArray,@am) > 12 then
            ndo = ''
            for i=1 to 12; ndo<i> = timeZoneArray<i>; next i
            timeZoneArray = ndo
         end
      end else
         *// standard default - northern hemisphere
*!!         E.GTZO.STOP.MONTH = 10
*!!         E.GTZO.STOP.WEEK  = 5
*!!         E.GTZO.STOP.DAY   = 0
*!!         E.GTZO.STOP.TIME  = '02:00'
      end

      returnValue = timeZoneArray

   end

return(@null)

*--------------------------------------------
SetDateFormat:       *// 'ON' = International, 'OFF' = US
*--------------------------------------------

   switch = upcase(trim(arguments<1,1>))

   begin case
   case switch = 'OFF'
      setDateFormatCmd = 'DATE.FORMAT OFF'

   case switch[1,1] = 'D'  ;*// change default date output format
      setDateFormatCmd = 'DATE.FORMAT ':arguments<1,1>

   case @true
      setDateFormatCmd = 'DATE.FORMAT ON'

   end case

   execute setDateFormatCmd capturing cap
   gosub GetDateFormat: ;*// return current date format

return(@null)

*--------------------------------------------
ThatsAllFolks: end
*--------------------------------------------


HomePage>>SourceCode>>BasicSource>>DateUtility