$include (..\lib\compStch.ext) /* *============================================================================ * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE * * Permission to use for any purpose, modify, copy, and make enhancements * and derivative works of the software is granted if attribution is given to * R.M. Gillmore, dba the ACME Software Deli, as the author * * While the ACME Software Deli does not work for money, there is nonetheless * a significant amount of work involved. The ACME Software Deli maintains the * rights to all code written, though it may be used and distributed as long as * the following conditions are maintained. * * 1. The copyright statement at the top of each code block is maintained in * your distribution. * 2. You do not identify yourself as the ACME Software Deli * 3. Any changes made to the software are sent to the ACME Software Deli *============================================================================ */ dateStringModule: do; $if not noID declare IDString (*) byte data ( '@(#)dateStr.p86 $Author: rmgillmore $ $Date:: 2025-05-04 19:35:39#$:', 0 ); $endif $set ( dateStringSource ) $include (..\lib\comnDefs.ext) $include (..\lib\date.ext) $include (..\lib\string.ext) $include (..\lib\numStrs.ext) declare JanuaryString (*) char data ( 'January', 0 ), FebruaryString (*) char data ( 'February', 0 ), MarchString (*) char data ( 'March', 0 ), AprilString (*) char data ( 'April', 0 ), MayString (*) char data ( 'May', 0 ), JuneString (*) char data ( 'June', 0 ), JulyString (*) char data ( 'July', 0 ), AugustString (*) char data ( 'August', 0 ), SeptemberString (*) char data ( 'September', 0 ), OctoberString (*) char data ( 'October', 0 ), NovemberString (*) char data ( 'November', 0 ), DecemberString (*) char data ( 'December', 0 ); declare monthStrings (*) pointer data ( @JanuaryString, @FebruaryString, @MarchString, @AprilString, @MayString, @JuneString, @JulyString, @AugustString, @SeptemberString, @OctoberString, @NovemberString, @DecemberString ); declare SundayString (*) char data ( 'Sunday', 0 ), MondayString (*) char data ( 'Monday', 0 ), TuesdayString (*) char data ( 'Tuesday', 0 ), WednesdayString (*) char data ( 'Wednesday', 0 ), ThursdayString (*) char data ( 'Thursday', 0 ), FridayString (*) char data ( 'Friday', 0 ), SaturdayString (*) char data ( 'Saturday', 0 ); declare dayStrings (*) pointer data ( @SundayString, @MondayString, @TuesdayString, @WednesdayString, @ThursdayString, @FridayString, @SaturdayString ); charToCase: procedure ( charIn, charArrayPtr ) word; declare charIn char, charArrayPtr pointer; declare charArrayLength word, charPosition word; /* * the charIn may or may not be in the char array. If it is not, it will * receive the value which is associated with the length of the string. that * will be the equivalent of "default" in the C-based languages */ charArrayLength = strlen( charArrayPtr ); charPosition = findb( charArrayPtr, charIn, charArrayLength ); if ( charPosition > charArrayLength ) then charPosition = charArrayLength; return ( charPosition ); end charToCase; /* * The format string is akin to that in printf, and is defined as follows. It * contains any combination of regular characters and special format specifiers. * These format specifiers are replaced by the function to the corresponding values * to represent the time specified in the pointer to the tmStruct. They all begin * with a percentage (%) sign, and are: * * Specifier Replaced by Example * * %a Abbreviated weekday name Thu * %A Full weekday name Thursday * %b Abbreviated month name Aug * %B Full month name August * %c Date and time representation, equivalent to %a %b %e %X %Y Thu Aug 23 14:55:02 2001 * %C Year divided by 100 and truncated to integer (00-99) 20 * %d Day of the month, zero-padded (01-31) 23 * %D Short MM/DD/YY date, equivalent to %m/%d/%y 08/23/01 * %e Day of the month, space-padded ( 1-31) 23 * %F Short YYYY-MM-DD date, equivalent to %Y-%m-%d 2001-08-23 * %H Hour in 24h format (00-23) 14 * %I Hour in 12h format (01-12) 02 * %j Day of the year (001-366) 235 * %m Month as a decimal number (01-12) 08 * %M Minute (00-59) 55 * %p AM or PM designation pm * %r 12-hour clock time, equivalent to %I:%M:%S %p 02:55:02 pm * %R 24-hour HH:MM time, equivalent to %H:%M 14:55 * %S Second (00-61) 02 * %w Weekday as a decimal number with Sunday as 0 (0-6) 4 * %x Date representation, equivalent to %m/%d/%y 08/23/01 * %X Time representation, equivalent to %R:%S 14:55:02 * %y Year, last two digits (00-99) 01 * %Y Year 2001 * ****************************************************************************** * this function really does call itself, so being reentrant is essential ****************************************************************************** */ strftime: procedure ( tmStructPtr, formatStringPtr, returnStringPtr ) reentrant public; declare tmStructPtr pointer, tm based tmStructPtr tmStruct, formatStringPtr pointer, formatString based formatStringPtr (*) char, returnStringPtr pointer, returnString based returnStringPtr (*) char; declare BACKSLASH char data ( '\' ), PERCENT char data ( '%' ); declare formatStringIndex word, formatStringLength word, returnStringIndex word; formatStringIndex, returnStringIndex = 0; returnString( 0 ) = NUL; formatStringLength = strlen( @formatString ); do while ( formatStringIndex < formatStringLength ); if ( PERCENT = formatString( formatStringIndex ) ) then do; /* * PERCENT means that the next part of the string is a format * specifier. However, when the next character is a percent * character, then we do not interpret the format string, we * pass it along into the output string */ declare ABBREVIATED_LENGTH literally '3'; declare switchChars (*) char data ( 'a', 'A', 'b', 'B', 'c', 'C', 'd', 'D', 'e', 'F', 'H', 'I', 'j', 'm', 'M', 'p', 'r', 'R', 'S', 'w', 'x', 'X', 'y', 'Y', 0 ); declare switchCase word; formatStringIndex = formatStringIndex + 1; switchCase = charToCase( formatString( formatStringIndex ), @switchChars ); do case ( switchCase ); do; /* 'a' - Abbreviated weekday name */ call strcpy( @returnString( returnStringIndex ), dayStrings( tm.tm_wday ) ); returnString( returnStringIndex + ABBREVIATED_LENGTH ) = NUL; returnStringIndex = strlen( @returnString ); formatStringIndex = formatStringIndex + 1; end; do; /* 'A' - Full weekday name */ call strcpy( @returnString( returnStringIndex ), dayStrings( tm.tm_wday ) ); returnStringIndex = strlen( @returnString ); formatStringIndex = formatStringIndex + 1; end; do; /* 'b' - Abbreviated month name */ call strcpy( @returnString( returnStringIndex ), monthStrings( tm.tm_mon ) ); returnString( returnStringIndex + ABBREVIATED_LENGTH ) = NUL; returnStringIndex = strlen( @returnString ); formatStringIndex = formatStringIndex + 1; end; do; /* 'B' - Full month name */ call strcpy( @returnString( returnStringIndex ), monthStrings( tm.tm_mon ) ); returnStringIndex = strlen( @returnString ); formatStringIndex = formatStringIndex + 1; end; do; /* 'c' - Date and time representation, equivalent to %a %b %e %X %Y */ call strftime( @tm, @( '%a %b %e %X %Y', 0 ), @returnString( returnStringIndex ) ); returnStringIndex = strlen( @returnString ); formatStringIndex = formatStringIndex + 1; end; do; /* 'C' - Year divided by 100 and truncated to integer */ declare mod100Year word; mod100Year = tm.tm_year mod 100; if ( mod100Year < 10 ) then do; returnString( returnStringIndex ) = '0'; returnStringIndex = returnStringIndex + 1; end; call wordToDecimalString( mod100Year, @returnString( returnStringIndex ) ); returnStringIndex = strlen( @returnString ); formatStringIndex = formatStringIndex + 1; end; do; /* 'd' - Day of the month, zero-padded */ if ( tm.tm_mday < 10 ) then do; returnString( returnStringIndex ) = '0'; returnStringIndex = returnStringIndex + 1; end; call byteToDecimalString( tm.tm_mday, @returnString( returnStringIndex ) ); returnStringIndex = strlen( @returnString ); formatStringIndex = formatStringIndex + 1; end; do; /* 'D' - Short MM/DD/YY date, equivalent to %m/%d/%y */ call strftime( @tm, @( '%m/%d/%y', 0 ), @returnString( returnStringIndex ) ); returnStringIndex = strlen( @returnString ); formatStringIndex = formatStringIndex + 1; end; do; /* 'e' - Day of the month, space-padded */ if ( tm.tm_mday < 10 ) then do; returnString( returnStringIndex ) = ' '; returnStringIndex = returnStringIndex + 1; end; call byteToDecimalString( tm.tm_mday, @returnString( returnStringIndex ) ); returnStringIndex = strlen( @returnString ); formatStringIndex = formatStringIndex + 1; end; do; /* 'F' - Short YYYY-MM-DD date, equivalent to %Y-%m-%d */ call strftime( @tm, @( '%Y-%m-%d', 0 ), @returnString( returnStringIndex ) ); returnStringIndex = strlen( @returnString ); formatStringIndex = formatStringIndex + 1; end; do; /* 'H' - Hour in 24h format (00-23) */ if ( tm.tm_hour < 10 ) then do; returnString( returnStringIndex ) = ' '; returnStringIndex = returnStringIndex + 1; end; call byteToDecimalString( tm.tm_hour, @returnString( returnStringIndex ) ); returnStringIndex = strlen( @returnString ); formatStringIndex = formatStringIndex + 1; end; do; /* 'I' - Hour in 12h format (01-12) */ declare adjustedHour byte; if ( tm.tm_hour < 1 ) then adjustedHour = 12; else if ( tm.tm_hour > 12 ) then adjustedHour = tm.tm_hour - 12; else adjustedHour = tm.tm_hour; if ( adjustedHour < 10 ) then do; returnString( returnStringIndex ) = ' '; returnStringIndex = returnStringIndex + 1; end; call byteToDecimalString( adjustedHour, @returnString( returnStringIndex ) ); returnStringIndex = strlen( @returnString ); formatStringIndex = formatStringIndex + 1; end; do; /* 'j' - Day of the year (001-366) */ if ( tm.tm_yday < 10 ) then do; returnString( returnStringIndex ) = '0'; returnStringIndex = returnStringIndex + 1; end; if ( tm.tm_yday < 100 ) then do; returnString( returnStringIndex ) = '0'; returnStringIndex = returnStringIndex + 1; end; call byteToDecimalString( tm.tm_yday, @returnString( returnStringIndex ) ); returnStringIndex = strlen( @returnString ); formatStringIndex = formatStringIndex + 1; end; do; /* 'm' - Month as a decimal number (01-12) */ declare localMonth byte; localMonth = tm.tm_mon + 1; if ( localMonth < 10 ) then do; returnString( returnStringIndex ) = ' '; returnStringIndex = returnStringIndex + 1; end; call byteToDecimalString( localMonth, @returnString( returnStringIndex ) ); returnStringIndex = strlen( @returnString ); formatStringIndex = formatStringIndex + 1; end; do; /* 'M' Minute (00-59) */ if ( tm.tm_min < 10 ) then do; returnString( returnStringIndex ) = '0'; returnStringIndex = returnStringIndex + 1; end; call byteToDecimalString( tm.tm_min, @returnString( returnStringIndex ) ); returnStringIndex = strlen( @returnString ); formatStringIndex = formatStringIndex + 1; end; do; /* 'p' - AM or PM designation */ if ( tm.tm_hour < 12 ) then call strcpy( @returnString( returnStringIndex ), @( 'am', 0 ) ); else call strcpy( @returnString( returnStringIndex ), @( 'pm', 0 ) ); returnStringIndex = strlen( @returnString ); formatStringIndex = formatStringIndex + 1; end; do; /* 'r' - 12-hour clock time, equivalent to %I:%M:%S %p */ call strftime( @tm, @( '%I:%M:%S %p', 0 ), @returnString( returnStringIndex ) ); returnStringIndex = strlen( @returnString ); formatStringIndex = formatStringIndex + 1; end; do; /* 'R' - 24-hour HH:MM time, equivalent to %H:%M */ call strftime( @tm, @( '%H:%M', 0 ), @returnString( returnStringIndex ) ); returnStringIndex = strlen( @returnString ); formatStringIndex = formatStringIndex + 1; end; do; /* 'S' - Second (00-61) */ if ( tm.tm_sec < 10 ) then do; returnString( returnStringIndex ) = '0'; returnStringIndex = returnStringIndex + 1; end; call byteToDecimalString( tm.tm_sec, @returnString( returnStringIndex ) ); returnStringIndex = strlen( @returnString ); formatStringIndex = formatStringIndex + 1; end; do; /* 'w' - Weekday as a decimal number with Sunday as 0 (0-6) */ call byteToDecimalString( tm.tm_wday, @returnString( returnStringIndex ) ); returnStringIndex = strlen( @returnString ); formatStringIndex = formatStringIndex + 1; end; do; /* 'x' - Date representation, equivalent to %m/%d/%y */ call strftime( @tm, @( '%m/%d/%y', 0 ), @returnString( returnStringIndex ) ); returnStringIndex = strlen( @returnString ); formatStringIndex = formatStringIndex + 1; end; do; /* 'X' - Time representation, equivalent to %R:%S */ call strftime( @tm, @( '%R:%S', 0 ), @returnString( returnStringIndex ) ); returnStringIndex = strlen( @returnString ); formatStringIndex = formatStringIndex + 1; end; do; /* 'y' - Year, last two digits (00-99), equivalent to %C */ call strftime( @tm, @( '%C', 0 ), @returnString( returnStringIndex ) ); returnStringIndex = strlen( @returnString ); formatStringIndex = formatStringIndex + 1; end; do; /* 'Y' - Year */ declare adjustedYear word; adjustedYear = tm.tm_year; call wordToDecimalString( adjustedYear, @returnString( returnStringIndex ) ); returnStringIndex = strlen( @returnString ); formatStringIndex = formatStringIndex + 1; end; do; /* - default */ /* * if we get here, the only thing that is still valid is a % * character. if not that, do nothing, just pass it along into * the output string */ /* *** WORK TO BE DONE HERE *** */ returnString( returnStringIndex ) = formatString( formatStringIndex - 1 ); returnString( returnStringIndex + 1 ) = formatString( formatStringIndex ); returnStringIndex = returnStringIndex + 2; formatStringIndex = formatStringIndex + 1; end; /* - default */ end; /* switch on the format specifier */ end; /* % specifier used */ else do; /* * just append the character to the output string */ returnString( returnStringIndex ) = formatString( formatStringIndex ); returnStringIndex = returnStringIndex + 1; formatStringIndex = formatStringIndex + 1; end; end; /* while not at the end of the format string */ end strftime; /* * a special case of strftime, using the %c format, resulting in a string * whose contents resemble these: * * Mon Jul 27 22:43:16 2009 * Fri Jun 7 10:22:22 2019 * */ asctime: procedure ( tmStructPtr, stringPtr ) reentrant public; declare tmStructPtr pointer, stringPtr pointer; call strftime( tmStructPtr, @( '%c', 0 ), stringPtr ); end asctime; end dateStringModule;