home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 18 REXX / 18-REXX.zip / inetlg75.zip / INETLOG.CMD < prev    next >
OS/2 REXX Batch file  |  2001-09-15  |  71KB  |  1,927 lines

  1. /* INetLog
  2. Copyright 1996 - 2001 by Chuck McKinnis,  Sandia Park, NM (USA) 04 May 2001
  3. mckinnis@attglobal.net
  4. Copyright 1995 by Jerry Levy,  Marblehead, MA (USA) 03 Nov 95
  5.  
  6. REXX Program to extract and totalize daily and monthly time-ons by
  7. analyzing the IBM WARP Internet Dialer log or the InJoy dialer log */
  8.  
  9. Trace 'N'
  10. Parse Upper Arg otherparms '|' pmrexx
  11. our_parms = otherparms
  12.  
  13. /* Where are we ? */
  14. Parse Source . . our_prog .
  15. install_path = Filespec('D',our_prog) || Filespec('P',our_prog)
  16. inetcfg_ini = install_path || 'inetcfg.ini'
  17. save_path = Directory()
  18. our_path = Strip(install_path, 'T', '\')
  19. our_path = Directory(our_path)
  20.  
  21. If Rxfuncquery('SysLoadFuncs') Then
  22.    Call Rxfuncadd 'SysLoadFuncs', 'RexxUtil', 'SysLoadFuncs'
  23. Call SysLoadFuncs
  24.  
  25. pmrexx = (pmrexx <> '')
  26.  
  27. obj_id = '<INETLOG_RUN>'
  28. If \pmrexx Then
  29.    Do
  30.       Call PMRexxGo 'START', obj_id, our_prog, 'Internet Log Analyzer', our_parms
  31.       Return
  32.    End
  33.  
  34. say_out_pipe = 'STDOUT'
  35. /*
  36. If Rxfuncquery('RxExtra') Then
  37.    Call Rxfuncadd 'RxExtra', 'RxExtras', 'RxExtra'
  38. Call RxExtra 'LOAD'
  39. */
  40. Signal On Failure Name errhandler
  41. Signal On Halt Name errhandler
  42. Signal On Syntax Name errhandler
  43.  
  44. /*========MAIN PROGRAM=========*/
  45.  
  46. Call Initialize_inetlog
  47.  
  48. If \ibm_dialer & \injoy_dialer Then
  49.    Do
  50.       Say 'You have not selected any logs to analyze'
  51.       Call Cleanup
  52.    End
  53.  
  54. If ibm_dialer Then
  55.    Do
  56.       /* gets, calculates, totalizes connect times.
  57.          Mostly a bunch of conditionals with a Call to a
  58.          data-formatting routine. All is stored in
  59.          variables for output all at once */
  60.       Call Analyze_inetlog
  61.       save_quiet = quiet
  62.       If combineonly Then
  63.          quiet = 1
  64.       Call Output_inetlog/* Outputs everything to console and to disk */
  65.       quiet = save_quiet
  66.    End
  67.  
  68. If injoy_dialer Then
  69.    Do
  70.       injoy_parms = ''
  71.       save_quiet = quiet
  72.       If combineonly Then
  73.          quiet = 1
  74.       injoy_rc = IJoyLog()
  75.       If injoy_rc Then        /* pick up any changes made by injoylog */
  76.          Call Read_config
  77.       quiet = save_quiet
  78.    End
  79.  
  80. If (combine | combineonly) & injoy_rc Then
  81.    Do
  82.       save_quiet = quiet
  83.       quiet = 0
  84.       Call Combined_output
  85.       quiet = save_quiet
  86.    End
  87.  
  88. Call Cleanup                                                 /* Exits */
  89.  
  90. Return
  91. /*====END OF MAIN PROGRAM=======*/
  92.  
  93. Initialize_inetlog:
  94. trace_save = Trace('N')
  95.  
  96. quiet = (Wordpos('QUIET', otherparms) <> 0)
  97. combine = (Wordpos('COMBINE', otherparms) <> 0)
  98. combineonly = (Wordpos('COMBINEONLY', otherparms) <> 0)
  99.  
  100. If Rx_fileexists(inetcfg_ini) Then      /* save any existing ini file */
  101.    Do
  102.       ini_save = Left(inetcfg_ini, Lastpos('.', inetcfg_ini)) || 'sav'
  103.       Address cmd '@copy' inetcfg_ini ini_save '> nul'
  104.    End
  105.  
  106. Call Read_config
  107.  
  108. If ibm_dialer Then
  109.    Do
  110.       /* Get path for connection log file and install directory */
  111.       x = Setlocal()
  112.       tcpip_etc_path = Value('ETC', ,'OS2ENVIRONMENT')
  113.       tcpip_etc_path = tcpip_etc_path || '\'
  114.       x = Endlocal()
  115.       dialer_ini = tcpip_etc_path || ibm_dialer_ini_file
  116.       Parse Value Log_file_parms(ibm_dialer_ini_file, dialer_ini) ,
  117.          With dialer_log_file dialer_log_size
  118.       log_file = dialer_log_file
  119.       /* Get full paths for output file and summary file */
  120.       output_file = data_path || ibm_output_file
  121.       summary_file = data_path || ibm_summary_file
  122.       Call Set_monthly_summary
  123.       ibm_summary = summary_file
  124.       /* initialize variables */
  125.       crlf = D2c(13) || D2c(10)         /* carriage return + linefeed */
  126.       esc = D2c(27)                               /* Escape character */
  127.       time_stamp = ''            /* Time stamp of each connect record */
  128.  
  129.       Do i = 1 To 12                             /* initialize months */
  130.          x = Right(i,2,'0')
  131.          month.x = Word('Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec',i)
  132.       End
  133.  
  134.       /* calculated variables
  135.          signons_each_day    Accumulate number of connects daily
  136.          signons_each_month  Accumulate number of connects monthly
  137.          time_on             Time, each connect  (minutes)
  138.          daily_time_on       Accumulated minutes, daily
  139.          monthly_time_on     Accumulated minutes, monthly */
  140.  
  141.       /* More variables: these we initialize as follows: */
  142.       old_m = 0           /* Storage of a 2-digit month (eg 05 = May) */
  143.       old_d = 0                              /* ... and a 2-digit day */
  144.       dcounter = 0        /* Counter increments each sign-on in a day */
  145.       mcounter = 0                   /* Same for each sign-on in a month */
  146.       monthline. = ''     /* Initialize both of these as null strings */
  147.       dayline. = '' /* These are for monthly and daily output strings */
  148.  
  149.       /* A typical line generated in the connect.log upon disconnect looks like
  150.          either this for a dialer version preceding v1.45:
  151.             11/15 19:08:38 Disconnected after 00:06:20  0 errors  0 discards'
  152.          or this style for v. 1.45 and later:
  153.             1995/11/15 19:08:38 Disconnected after 00:06:20  0 errors  0 discards
  154.          We search for a key_phrase using the RexxUtil function SysFileSearch */
  155.  
  156.       /* Word or phrase we'll search for */
  157.       key_phrase = 'Disconnected after'
  158.       user_phrase = 'dialed' /* word to search for account and userid */
  159.  
  160.       /* Here is where we Check if the output file exists.  If it does, we
  161.          overwrite it, and if not we create it.  BUT....  we don't want to
  162.          do something stupid like try to erase a vital file or the connect
  163.          logfile... */
  164.  
  165.       If \Rx_fileexists(log_file) Then
  166.          Do
  167.             Say 'Aborting.' log_file 'does not exist.'
  168.             Call Beep 1000,100
  169.             Call Cleanup
  170.          End
  171.  
  172.       file1 = Translate(Filespec('N', log_file))
  173.       file2 = Translate(Filespec('N', output_file))
  174.       If file2 == 'CONNECT.LOG',
  175.          | file2 == 'IPDIALER.LOG',
  176.          | file2 == 'CONFIG.SYS',
  177.          | file2 == 'AUTOEXEC.BAT',
  178.          | file2 == file1 Then
  179.          Do
  180.             Say log_file 'was entered as the dialer log file name and'
  181.             Say output_file 'was entered as the INETLOG output file name.'
  182.             Say 'These files cannot have the same name, and the names'
  183.             Say '"CONNECT.LOG, IPDIALER.LOG, CONFIG.SYS, and AUTOEXEC.BAT"'
  184.             Say 'are not permitted as INETLOG output file names.'
  185.             Say crlf
  186.             Call Beep 1000,100
  187.             Call Cleanup                            /* Error, so Exit */
  188.          End
  189.  
  190.       /* make sure that we have control of the log file */
  191.       xrc = File_cmd(log_file, 'W')
  192.       If \xrc Then
  193.          Do Until xrc
  194.             Say 'Log file' log_file 'returned' result
  195.             xrc = File_cmd(log_file, 'C')
  196.             Say 'Unable to open' log_file
  197.             Say 'Press Esc to abort run'
  198.             answer = Say_message('Press any other key to wait 5 seconds')
  199.             If answer = esc Then
  200.                Call Cleanup
  201.             Call SysSleep 5
  202.             Call SysCls
  203.             xrc = File_cmd(log_file, 'W')
  204.          End
  205.       xrc = File_cmd(log_file, 'C')
  206.  
  207.       /* Backup any output file of the same name if it exists, then erase orig. */
  208.       If Rx_fileexists(output_file) Then
  209.          Do
  210.             Parse Var output_file fname '.' ext
  211.             Address cmd '@COPY' output_file fname||'.bak > NUL'
  212.             Call SysFileDelete output_file
  213.          End
  214.    End
  215.  
  216. Trace(trace_save)
  217. Return                                          /* initialize_inetlog */
  218.  
  219. /* read the config file */
  220. Read_config:
  221. trace_save = Trace('N')
  222. If Rx_fileexists(inetcfg_ini) Then        /* do we have a config file */
  223.    input_file = inetcfg_ini
  224. Else
  225.    Do
  226.       ini_save = Left(inetcfg_ini, Lastpos('.', inetcfg_ini)) || 'sav'
  227.       If Rx_fileexists(ini_save) Then
  228.          Do
  229.             Say 'Restoring' inetcfg_ini 'from' ini_save
  230.             Address cmd '@copy' ini_save inetcfg_ini '> nul'
  231.             input_file = inetcfg_ini
  232.          End
  233.       Else
  234.          Do
  235.             Say inetcfg_ini 'not found'
  236.             Say 'Please run Install'
  237.             Call Cleanup
  238.          End
  239.    End
  240.  
  241. Say 'Using configuration file -' input_file
  242. cfg_common = SysIni(input_file, 'cfg_common', 'cfg_common')
  243. If cfg_common <> 'ERROR:' Then
  244.    Do i = 1 To Words(cfg_common)
  245.       keyid = Word(cfg_common, i)
  246.       Interpret keyid '= sysini("' || input_file || '", "cfg_common", "' || keyid || '")'
  247.    End
  248. Else
  249.    Do
  250.       Say 'cfg_common not found in' input_file
  251.       Say 'Please run Install'
  252.       Call Cleanup
  253.    End
  254. cfg_inetlog = SysIni(input_file, 'cfg_inetlog', 'cfg_inetlog')
  255. If cfg_inetlog <> 'ERROR:' Then
  256.    Do i = 1 To Words(cfg_inetlog)
  257.       keyid = Word(cfg_inetlog, i)
  258.       Interpret keyid '= sysini("' || input_file || '", "cfg_inetlog", "' || keyid || '")'
  259.    End
  260. Else
  261.    Do
  262.       Say 'cfg_inetlog not found in' input_file
  263.       Say 'Please run Install'
  264.       Call Cleanup
  265.    End
  266. cfg_ijoylog = SysIni(input_file, 'cfg_ijoylog', 'cfg_ijoylog')
  267. If cfg_ijoylog <> 'ERROR:' Then
  268.    Do i = 1 To Words(cfg_ijoylog)
  269.       keyid = Word(cfg_ijoylog, i)
  270.       Interpret keyid '= sysini("' || input_file || '", "cfg_ijoylog", "' || keyid || '")'
  271.    End
  272. Else
  273.    Do
  274.       Say 'cfg_ijoylog not found in' input_file
  275.       Say 'Please run Install'
  276.       Call Cleanup
  277.    End
  278. Trace(trace_save)
  279. Return 0
  280.  
  281. Log_file_parms: Procedure
  282. trace_save = Trace('N')
  283. Parse Arg dialer_ini_name, dialer_ini
  284. Select
  285.    When Translate(dialer_ini_name) = 'DIALER.INI' Then
  286.       Do
  287.          dlog_eparm = 'AdvLog'
  288.          dlog_nparm = 'Cfn'
  289.          dlog_sparm = 'Cfs'
  290.       End
  291.    When Translate(dialer_ini_name) = 'TCPDIAL.INI' Then
  292.       Do
  293.          dlog_eparm = 'Common'
  294.          dlog_nparm = 'LoggingCfn'
  295.          dlog_sparm = 'LoggingCfs'
  296.       End
  297.    Otherwise Do
  298.       Say 'Unable to recognize dialer ini file'
  299.       Call Cleanup
  300.       Exit
  301.    End
  302. End
  303. dialer_log_file = Strip(SysIni(dialer_ini, dlog_eparm, dlog_nparm),,'00'x)
  304. dialer_log_size = C2d(Left(SysIni(dialer_ini, dlog_eparm, dlog_sparm),3))
  305. Trace(trace_save)
  306. Return dialer_log_file dialer_log_size
  307.  
  308. /* Now find all lines in connect.log that contain the key_phrase
  309.    string.  A typical line generated in the connect.log after
  310.    disconnect is either this for a dialer version preceding v. 1.45:
  311.     11/15 19:08:38 Disconnected after 00:06:20  0 errors  0 discards
  312.    or this style for v. 1.45 and later:
  313.     1995/11/15 19:08:38 Disconnected after 00:06:20  0 errors  0 discards
  314.    which we would parse as follows:
  315.     date  word2       word3    word4 connect_time */
  316.  
  317. Analyze_inetlog:
  318. trace_save = Trace('N')
  319.  
  320. Call SysFileSearch key_phrase, log_file, 'line.', 'N'
  321.  
  322. Call SysFileSearch user_phrase, log_file, 'user.', 'N'
  323.  
  324. /* this section will read backwards through the connection log file
  325.    data and attempt to assign account information and userid to each
  326.    connection log record */
  327. k = user.0
  328. Do i = line.0 To 1 By -1 While k > 0
  329.    Parse Var line.i disc_line_no .
  330.    acct_data = 'acctid=unknown userid=unknown'
  331.    j = i - 1
  332.    If j > 0 Then
  333.  
  334.    Parse Var line.j prev_disc_line_no .
  335.    Else
  336.       prev_disc_line_no = 0
  337.    If k > 0 Then
  338.       Do
  339.          Parse Var user.k user_line_no . . acctid userid .
  340.          If disc_line_no > user_line_no & user_line_no > prev_disc_line_no Then
  341.             Do
  342.                acct_data = 'acctid=' || acctid 'userid=' || userid
  343.                k = k - 1
  344.             End
  345.       End
  346.    line.i = line.i acct_data
  347. End
  348.  
  349. xrc = 0
  350.  
  351. Do i = 1 To line.0
  352.    Parse Var line.i . date word2 word3 word4 connect_time remainder
  353.    If remainder = '' Then
  354.       Iterate i
  355.    Parse Var remainder . 'acctid=' acctid 'userid=' userid .
  356.    acctid = Strip(acctid)
  357.    userid = Strip(userid)
  358.    /* Date, excluding year if year is or is not present, is 5 chars: mm/dd */
  359.    date2 = Right(date,5)
  360.    /* Make all dates uniform.  If more than mm/dd (5 chars) then year
  361.       is there.  The length(date) - 6 = length of however many
  362.       characters are used for the year (4 now (e.g., 1995) but some
  363.       joker might change it to 2 (e.g., 95) We then add 1 char for the
  364.       / separator + 1 */
  365.    If Length(date) > 5 Then
  366.       year = Substr((date), 1, Length(date) -6 ) || ' '
  367.    Else
  368.       year = '     '    /* If pre v1.45 Dialer, no year, pad 5 spaces */
  369.    /* Extract the month of a connection as a 2-dig number */
  370.    mm = Substr(date2, 1, 2)
  371.    dd = Substr(date2, 4, 2)                         /* ...and the day */
  372.    /* Extract for time stamp and save */
  373.    Parse Var word2 t_hr ':' t_min ':' t_sec
  374.    If year <> '     ' Then
  375.       t_yr = Strip(year)
  376.    Else
  377.       t_yr = '    '
  378.    time_stamp = 'T' || t_yr || mm || dd || t_hr || t_min || t_sec
  379.    /* Extract the number of hours on-line */
  380.    hrs = Substr(connect_time, 1, 2)
  381.    mins = Substr(connect_time, 4, 2)                   /*... and mins */
  382.    secs = Substr(connect_time, 7, 2)                 /*...and seconds */
  383.    If hrs < 0,                                  /* If hrs is negative */
  384.       | mins < 0,                              /* or mins is negative */
  385.       | secs < 0 Then
  386.       Call Errdst   /* or secs is negative.  Time change error? Abort */
  387.  
  388.    /* Calculate time_on for that connection */
  389.    time_on = (60*hrs + mins + (1/60)*secs)
  390.    If time_stamp > last_time_stamp Then
  391.       Call Monthly_update
  392.  
  393.    If old_d = 0 Then
  394.       Do                            /* for very first connection line */
  395.          old_m = mm
  396.          old_d = dd
  397.          old_y = year
  398.          signons_each_day = 1                           /* reset to 1 */
  399.          signons_each_month = 1
  400.          /* This and next one are timeons in minutes */
  401.          daily_time_on = time_on
  402.          monthly_time_on = time_on
  403.       End
  404.  
  405.    Else         /* continue to accumulate times if same month and day */
  406.       If old_m = mm & old_d = dd & month <> 0 Then
  407.       Do
  408.          old_y = year
  409.          signons_each_day = signons_each_day + 1
  410.          signons_each_month = signons_each_month + 1
  411.          daily_time_on = daily_time_on + time_on
  412.          monthly_time_on = monthly_time_on + time_on
  413.       End
  414.  
  415.    Else                                      /* new day of same month */
  416.       If old_m = mm & old_d <> dd Then
  417.       Do
  418.          Call Prepare_data
  419.          dcounter = dcounter + 1
  420.          year.dcounter = Space(year)
  421.          year.mcounter = Space(year)
  422.          dayline.dcounter = year.dcounter month.old_m old_d d_signons d_mins d_hhmmss d_hrs
  423.  
  424.          old_d = dd
  425.          old_y = year
  426.          signons_each_day = 1            /* Start counting over again */
  427.          signons_each_month = signons_each_month + 1
  428.          daily_time_on = time_on
  429.          monthly_time_on = monthly_time_on + time_on
  430.       End
  431.  
  432.    Else   /* for any new month, which by definition is also a new day */
  433.       If old_m <> mm & old_m <> 0 Then
  434.       Do
  435.          Call Prepare_data
  436.          dcounter = dcounter + 1
  437.          year.dcounter = Space(old_y)
  438.          dayline.dcounter = year.dcounter month.old_m old_d d_signons d_mins d_hhmmss d_hrs
  439.  
  440.          mcounter = mcounter + 1
  441.          year.mcounter = Space(old_y)
  442.          monthline.mcounter = year.mcounter month.old_m m_signons m_mins m_hhmmss m_hrs
  443.  
  444.          old_m = mm
  445.          old_d = dd
  446.          old_y = year
  447.          signons_each_day = 1
  448.          signons_each_month = 1
  449.          daily_time_on = time_on
  450.          monthly_time_on = time_on
  451.       End
  452.  
  453. End             /* end of all these If's and Else If's of searching for 
  454.                    key_phrase in all possible lines in the connect.log */
  455.  
  456. /* Now, since last day and last month is done: */
  457. Call Prepare_data
  458. dcounter = dcounter + 1
  459. year.dcounter = Space(year)
  460. dayline.dcounter = year.dcounter month.old_m old_d d_signons d_mins d_hhmmss d_hrs
  461.  
  462. mcounter = mcounter + 1
  463. year.mcounter = Space(year)
  464. monthline.mcounter = year.mcounter month.old_m m_signons m_mins m_hhmmss m_hrs
  465.  
  466. /* save the last */
  467. ibm_last_time_stamp = time_stamp
  468. Call SysIni inetcfg_ini, 'cfg_inetlog', 'ibm_last_time_stamp', time_stamp
  469. Trace(trace_save)
  470. return                                          /* from analyze_inetlog     */
  471.  
  472. Monthly_update:
  473. s_yr = Strip(t_yr)
  474. s_mo = Strip(mm)
  475. If summary.acctid.userid.s_yr.s_mo <> '' Then
  476.    Do
  477.       Parse Var summary.acctid.userid.s_yr.s_mo s_yr s_mo s_stamp s_sess s_min .
  478.       If time_stamp > s_stamp Then
  479.          Do
  480.             s_sess = s_sess + 1
  481.             s_min = s_min + time_on
  482.             summary.acctid.userid.s_yr.s_mo = s_yr s_mo time_stamp s_sess s_min
  483.          End
  484.    End
  485. Else
  486.    Do
  487.       x = monthly.0 + 1
  488.       monthly.x = acctid userid s_yr s_mo
  489.       monthly.0 = x
  490.       summary.acctid.userid.s_yr.s_mo = s_yr s_mo time_stamp '1' time_on
  491.    End
  492. Return
  493.  
  494. Output_inetlog:
  495. /* Now output everything to console and to file */
  496.  
  497. /* get the screen size; rows is what we are interested in */
  498. Parse Value SysTextScreenSize() With rows cols
  499. Call Stream stdin, 'C', 'OPEN READ'
  500. /* Tell us all */
  501. intro = what_r_we
  502. Call Say_out intro
  503. Call Lineout output_file, intro
  504. Call Lineout output_file, crlf || 'Running with parms =' otherparms
  505. intro = 'Analysis of' log_file '(' || Date() '@' Time() || ')'
  506. Call Say_out ' ', intro
  507. Call Lineout output_file, crlf || intro
  508. If daily_report Then
  509.    Call Say_out ' ', 'Daily Totals', ' '
  510. Call Lineout output_file, crlf || 'Daily Totals' || crlf
  511. Do j = 1 To dcounter
  512.    If daily_report Then
  513.       Call Say_out dayline.j
  514.    Call Lineout output_file, dayline.j
  515. End
  516.  
  517. /* back up the summary file and delete it */
  518. If Rx_fileexists(summary_file) Then
  519.    Do
  520.       Parse Var summary_file fname '.' ext
  521.       Address cmd '@COPY' summary_file fname || '.bak > NUL'
  522.       Call SysFileDelete summary_file
  523.    End
  524. xrc = File_cmd(summary_file,'W')
  525. acct_user_save = ''
  526.  
  527. Call SysStemSort 'monthly.', 'A'             /* sort the summary data */
  528.  
  529. Call Summary_sample                     /* get the header information */
  530. /* insert the header info into the summary data */
  531. Do k = summary_sample.0 To 1 By -1
  532.    Call SysStemInsert 'monthly.', 1, summary_sample.k
  533. End
  534.  
  535. Do k = 1 To monthly.0
  536.    If Abbrev(monthly.k,'*') Then
  537.       Do                                   /* write comments back out */
  538.          Call Lineout summary_file, monthly.k
  539.          Iterate k
  540.       End
  541.    Parse Var monthly.k acctid userid s_yr s_mo
  542.    acct_user = acctid userid
  543.    Call Lineout summary_file, acctid userid '*' summary.acctid.userid.s_yr.s_mo
  544.    Parse Var summary.acctid.userid.s_yr.s_mo . . . s_sess s_mins
  545.    m_sess = Format(s_sess, sig_x) || 'X'
  546.    m_mins = Format(s_mins, sig_min, 2) 'mins'
  547.    mo_hh = Format(Trunc(s_mins / 60),sig_hr)
  548.    mo_mm = Right(Trunc(s_mins // 60),2,'0')
  549.    mo_ss = Right(Trunc(60 * ((s_mins // 60) - Trunc(s_mins // 60))),2,'0')
  550.    m_hhmmss = '  '||mo_hh||':'||mo_mm||':'||mo_ss
  551.    m_hrs = Format((s_mins / 60), sig_hr, 2) 'hrs'
  552.    a_sess = s_mins / s_sess
  553.    If a_sess < 60 Then
  554.       a_sess = Format(a_sess,2,0) 'mins'
  555.    Else
  556.       a_sess = Format((a_sess / 60),2,2) 'hrs'
  557.    a_sess = '- Ave =' a_sess
  558.    monthline = s_yr month.s_mo m_sess m_mins m_hhmmss m_hrs a_sess
  559.    If acct_user <> acct_user_save Then
  560.       Do
  561.          intro = 'Monthly Totals for Account(' || acctid || ') Userid(' || userid || ')'
  562.          Call Say_out ' ', intro, ' '
  563.          Call Lineout output_file, crlf || intro || crlf
  564.          acct_user_save = acct_user
  565.       End
  566.    Call Lineout output_file, monthline
  567.    monthline = s_yr month.s_mo m_sess m_hrs a_sess
  568.    Call Say_out monthline
  569. End
  570. xrc = File_cmd(summary_file,'C')
  571. finished = 'End of analysis of' log_file
  572. Call Say_out ' ', finished
  573. Call Lineout output_file, crlf || finished
  574.  
  575. xrc = SysFileTree(dialer_log_file, 'info.', 'F')
  576. Parse Var info.1 . . log_file_size .
  577. If dialer_log_size > 0 Then
  578.    Do
  579.       log_file_pct = Format((log_file_size / dialer_log_size) * 100,,0)
  580.       Call Say_out ' ', 'The connection log file is at' log_file_pct || '% of the maximum'
  581.       Call Say_out 'size,' Format(dialer_log_size,,0) 'bytes, specified in the Dialer settings.'
  582.       If log_file_pct > warn_pct Then
  583.          Do
  584.             Call Say_out ' ', 'You may want to consider running editing the'
  585.             Call Say_out dialer_log_file 'with the IBM tedit.exe', ' '
  586.          End
  587.    End
  588.  
  589. Return                                         /* from output_inetlog */
  590.  
  591. Combined_output:
  592. /* Combine output from IGN and InJoy and display */
  593. save_trace = Trace('N')
  594. If \injoy_dialer Then
  595.    Return
  596. If quiet Then
  597.    Return
  598.  
  599. ign. = ''
  600. ign.0 = 0
  601. If ibm_dialer Then
  602.    Do
  603.       /* read the IBM dialer summary log */
  604.       If Rx_fileexists(ibm_summary) Then
  605.          Do
  606.             Call Rx_readlines ibm_summary
  607.             Do x = 1 To file_lines.0
  608.                If \Abbrev(file_lines.x,'*') & file_lines.x <> '' Then
  609.                   Do
  610.                      Parse Var file_lines.x acctid userid '*' s_yr s_mo s_time s_data
  611.                      i = ign.0 + 1
  612.                      ign.i = Space(acctid userid s_yr s_mo s_data)
  613.                      ign.0 = i
  614.                   End
  615.             End
  616.          End
  617.    End
  618. i = ign.0 + 1                           /* make an end of file record */
  619. ign.i = Copies('FF'x, 8) Copies('FF'x, 8) Copies('FF'x, 4) 'FFFF'x
  620. ign.0 = i
  621.  
  622. joy. = ''
  623. joy.0 = 0
  624. /* read the InJoy dialer summary file */
  625. If Rx_fileexists(injoy_summary) Then
  626.    Do
  627.       Call Rx_readlines injoy_summary
  628.       Do x = 1 To file_lines.0
  629.          If \Abbrev(file_lines.x,'*') & file_lines.x <> '' Then
  630.             Do
  631.                Parse Var file_lines.x acctid userid '*' s_yr s_mo s_time s_data
  632.                acctid = Strip(acctid)
  633.                userid = Strip(userid)
  634.                s_yr = Strip(s_yr)
  635.                s_mo = Strip(s_mo)
  636.                i = joy.0 + 1
  637.                joy.i = Space(acctid userid s_yr s_mo s_data)
  638.                joy.0 = i
  639.             End
  640.       End
  641.    End
  642. i = joy.0 + 1                           /* make an end of file record */
  643. joy.i = Copies('FF'x, 8) Copies('FF'x, 8) Copies('FF'x, 4) 'FFFF'x
  644. joy.0 = i
  645.  
  646. combined. = ''
  647. combined.0 = 0
  648. i = 1
  649. j = 1
  650. Do While (i < ign.0) | (j < joy.0)
  651.    Parse Var ign.i i_acctid i_userid i_s_yr i_s_mo i_sess i_min
  652.    i_value = i_acctid || i_userid || i_s_yr || i_s_mo
  653.    Parse Var joy.j j_acctid j_userid j_s_yr j_s_mo j_sess j_min
  654.    j_value = j_acctid || j_userid || j_s_yr || j_s_mo
  655.    Select
  656.       When i_value = j_value Then
  657.          Do
  658.             t_sess = i_sess + j_sess
  659.             t_min = i_min + j_min
  660.             k = combined.0 + 1
  661.             combined.k = i_acctid i_userid i_s_yr i_s_mo t_sess t_min
  662.             combined.0 = k
  663.             i = i + 1
  664.             j = j + 1
  665.          End
  666.       When i_value < j_value Then
  667.          Do
  668.             k = combined.0 + 1
  669.             combined.k = i_acctid i_userid i_s_yr i_s_mo i_sess i_min
  670.             combined.0 = k
  671.             i = i + 1
  672.          End
  673.       When i_value > j_value Then
  674.          Do
  675.             k = combined.0 + 1
  676.             combined.k = j_acctid j_userid j_s_yr j_s_mo j_sess j_min
  677.             combined.0 = k
  678.             j = j + 1
  679.          End
  680.       Otherwise Nop
  681.    End
  682. End
  683. Trace 'n'
  684. acct_user_save = ''
  685. intro = what_r_we
  686. If combine & \combineonly Then
  687.    Call Say_out ' '
  688. Call Say_out intro
  689. intro = 'Analysis of combined IGN and InJoy log files (' || Date() '@' Time() || ')'
  690. Call Say_out ' ', intro
  691. Do k = 1 To combined.0
  692.    If Abbrev(combined.k,'*') Then
  693.       Iterate
  694.    Parse Var combined.k acctid userid s_yr s_mo s_sess s_mins
  695.    acct_user = acctid userid
  696.    m_sess = Format(s_sess, sig_x) || 'X'
  697.    m_mins = Format(s_mins, sig_min, 2) 'mins'
  698.    mo_hh = Format(Trunc(s_mins / 60),sig_hr)
  699.    mo_mm = Right(Trunc(s_mins // 60),2,'0')
  700.    mo_ss = Right(Trunc(60 * ((s_mins // 60) - Trunc(s_mins // 60))),2,'0')
  701.    m_hhmmss = '  ' || mo_hh || ':' || mo_mm || ':' || mo_ss
  702.    m_hrs = Format((s_mins / 60), sig_hr, 2) 'hrs'
  703.    a_sess = s_mins / s_sess
  704.    If a_sess < 60 Then
  705.       a_sess = Format(a_sess,2,0) 'mins'
  706.    Else
  707.       a_sess = Format((a_sess / 60),2,2) 'hrs'
  708.    a_sess = '- Ave =' a_sess
  709.    monthline = s_yr month.s_mo m_sess m_hrs a_sess
  710.    If acct_user <> acct_user_save Then
  711.       Do
  712.          intro = 'MONTHLY TOTALS for Account(' || acctid || ') Userid(' || userid || '}'
  713.          Call Say_out ' ', intro
  714.          acct_user_save = acct_user
  715.       End
  716.    Call Say_out monthline
  717. End
  718. xrc = File_cmd(summary_file,'C')
  719. finished = 'End of analysis of combined log files'
  720. Call Say_out ' ', finished
  721. Trace(save_trace)
  722. Return                                         /* from Combine_output */
  723.  
  724. Prepare_data:
  725. /* Calculates and formats what is to be put into an output line */
  726.  
  727. /* Signons per day or month, as, e.g.: '4X' */
  728. d_signons = Format(signons_each_day, sig_x)||'X'
  729. m_signons = Format(signons_each_month, sig_x)||'X'
  730.  
  731. /* Minutes/day, minutes/month, hrs/day, hrs/month as, e.g.: '105.50 mins or hrs' */
  732. d_mins = Format(daily_time_on, sig_min, 2) 'mins'
  733. d_hrs = Format((daily_time_on/60), sig_hr, 2) 'hrs'
  734. m_mins = Format(monthly_time_on, sig_min, 2) 'mins'
  735. m_hrs = Format((monthly_time_on/60), sig_hr, 2) 'hrs'
  736.  
  737. /* minutes, seconds per day or month, as 2-digit numbers; hrs can exceed 2 digs */
  738. dy_hh = Format(Trunc(daily_time_on/60),sig_hr)            
  739. dy_mm = Right(Trunc(daily_time_on//60),2,'0')
  740. dy_ss = Right(Trunc(60*((daily_time_on//60) - Trunc(daily_time_on//60))),2,'0')
  741. mo_hh = Format(Trunc(monthly_time_on/60),sig_hr)
  742. mo_mm = Right(Trunc(monthly_time_on//60),2,'0')
  743. mo_ss = Right(Trunc(60*((monthly_time_on//60) - Trunc(monthly_time_on//60))),2,'0')
  744.  
  745. /* hours, minutes, seconds per day or month, as, e.g.: '1:45:30'*/
  746. d_hhmmss = '  '||dy_hh||':'||dy_mm||':'||dy_ss
  747. m_hhmmss = '  '||mo_hh||':'||mo_mm||':'||mo_ss
  748.  
  749. Return                                                /* Prepare_data */
  750.  
  751. Errdst:                                /* If error due to time change */
  752. Say 'INETLOG has found a negative number for time-on-line'
  753. Say 'in an entry in your' log_file 'file.'
  754. Say ''
  755. Say 'You may have reset your clock backwards while on line, e.g.,'
  756. Say 'while changing from Summer time (Daylight Saving Time) to'
  757. Say 'Winter time (Standard time).'
  758. Say ''
  759. Say 'To fix:'
  760. Say '   1.  Open' log_file 'in your Tiny Editor (tedit.exe)'
  761. Say '   2.  Edit any connection time(s) that have a minus sign.'
  762. Say '       Example (for a typical one-hour summer-to-winter time correction):'
  763. Say ''
  764. Say '       change'
  765. Say '          1995/10/29 06:26:43 Disconnected after -00:-58:-4  0 errors  0 discards'
  766. Say '             to'
  767. Say '          1995/10/29 06:26:43 Disconnected after 00:01:56  0 errors  0 discards'
  768. Say ''
  769. Say '       If the error was not from a summer-time change, make whatever'
  770. Say '       correction seems reasonable to eliminate the offending minus'
  771. Say '       signs (like just removing them).'
  772. Say ''
  773. Say '   3.  Save' log_file
  774. Say '   4.  Run EOF2CRLF to remove the EOF characters'
  775. Say '       added by the editor.'
  776. Say 'Aborting . . .'
  777. Call Cleanup
  778. Return
  779.  
  780. Errhandler:
  781. Call Beep 300, 500
  782. Say 'Rexx error' rc 'in line' sigl||':' Errortext(rc)
  783. Say Sourceline(sigl)
  784. Call Cleanup
  785. Return
  786.  
  787. Cleanup:                                                      /* Exit */
  788. save_path = Directory(save_path)
  789. Call PMRexxGo 'EXIT', obj_id
  790. Return                                                 /* for Cleanup */
  791.  
  792. /* performs common Stream commands and returns 1 or a date if successful */
  793. File_cmd: Procedure Expose result
  794. trace_save = Trace('N')
  795. Parse Arg file_name, command
  796. command = Translate(command)
  797. Select
  798.    When command = 'X' Then
  799.       Do
  800.          result = Stream(file_name, 'C', 'QUERY EXISTS')
  801.          answer = (result <> '')
  802.       End
  803.    When command = 'C' Then
  804.       Do
  805.          result = Stream(file_name, 'C', 'CLOSE')
  806.          answer = Abbrev(result,'READY') | (result = '')
  807.       End
  808.    When command = 'W' Then
  809.       Do
  810.          result = Stream(file_name, 'C', 'OPEN WRITE')
  811.          answer = Abbrev(result,'READY')
  812.       End
  813.    When command = 'R' Then
  814.       Do
  815.          result = Stream(file_name, 'C', 'OPEN READ')
  816.          answer = Abbrev(result,'READY')
  817.       End
  818.    When command = 'D' Then
  819.       Do
  820.          result = Stream(file_name, 'C', 'QUERY DATETIME')
  821.          If result <> '' Then
  822.             Do
  823.                Parse Var result date time
  824.                date = Dateconv(Translate(date, '/', '-'), 'U', 'S')
  825.                Parse Var time hr ':' min ':' sec
  826.                answer = Strip(date) || Strip(hr) || Strip(min) || Strip(sec)
  827.             End
  828.          Else
  829.             answer = '00000000000000'
  830.       End
  831.    Otherwise answer = 0
  832. End
  833. Trace(trace_save)
  834. Return answer
  835.  
  836. Say_out:                            /* performs output to the console */
  837. Procedure Expose quiet say_out_pipe
  838. trace_save = Trace('N')
  839. If quiet Then
  840.    Return
  841. Parse Arg line1, line2, line3
  842. If Length(line1) <> 0 Then
  843.    Call Lineout say_out_pipe, line1
  844. If Length(line2) <> 0 Then
  845.    Call Lineout say_out_pipe, line2
  846. If Length(line3) <> 0 Then
  847.    Call Lineout say_out_pipe, line3
  848. Trace(trace_save)
  849. Return
  850.  
  851. Say_message:       /* performs message output and returns key entered */
  852. Procedure Expose quiet
  853. trace_save = Trace('N')
  854. Parse Arg msg
  855. Say msg
  856. answer = SysGetKey('NOECHO')
  857. Trace(trace_save)
  858. Return answer
  859.  
  860. Summary_sample: Procedure Expose summary_sample.
  861. trace_save = Trace('N')
  862. summary_sample. = ''
  863. x = 0
  864. x = x + 1; summary_sample.x = '* The purpose of this file is to maintain monthly summaries across'
  865. x = x + 1; summary_sample.x = '* pruning of the connection log'
  866. x = x + 1; summary_sample.x = '*'
  867. x = x + 1; summary_sample.x = '* The format of the file is:'
  868. x = x + 1; summary_sample.x = '*'
  869. x = x + 1; summary_sample.x = '*     account userid * year month time-stamp sessions minutes'
  870. x = x + 1; summary_sample.x = '*     for example: usinet chmckin * 1995 01 T19950131235959 1 1.00'
  871. x = x + 1; summary_sample.x = '*        (the time-stamp is in the form'
  872. x = x + 1; summary_sample.x = '*         year || month || day || hour || minute || second'
  873. x = x + 1; summary_sample.x = '*         prefixed by a "T" to'
  874. x = x + 1; summary_sample.x = '*         force character compares)'
  875. x = x + 1; summary_sample.x = '*'
  876. x = x + 1; summary_sample.x = '* An "*" in column 1 indicates a comment. Comments and blank lines will'
  877. x = x + 1; summary_sample.x = '* not be processed'
  878. x = x + 1; summary_sample.x = '*'
  879. x = x + 1; summary_sample.x = '* If your first run of INETLOG does not provide the monthly summaries that'
  880. x = x + 1; summary_sample.x = '* you want, you may want to re-format monthly entries from the old'
  881. x = x + 1; summary_sample.x = '* inetlog file, normally called inetlog.$$$, into this file.  If the'
  882. x = x + 1; summary_sample.x = '* month has ended set the time stamp to the highest possible timestamp'
  883. x = x + 1; summary_sample.x = '* value for that month as shown below.'
  884. x = x + 1; summary_sample.x = '*'
  885. x = x + 1; summary_sample.x = '* 1995 Dec   15X     131.63 mins        2:11:37      2.19 hrs - Ave =  9 mins'
  886. x = x + 1; summary_sample.x = '* unknown unknown * 1995 12 T19951231235959 15 131.63'
  887. x = x + 1; summary_sample.x = '* 1996 Jan  178X    2341.83 mins       39:01:49     39.03 hrs - Ave = 13 mins'
  888. x = x + 1; summary_sample.x = '* unknown unknown * 1996 01 T19960131235959 178 2342.83'
  889. summary_sample.0 = x
  890. Trace(trace_save)
  891. Return
  892.  
  893. /* IJoyLog - InJoy log analyzer
  894. ╕ 1998 - 2000 by Charles H McKinnis,  Sandia Park, NM (USA) 04 Feb 2000
  895. mckinnis@attglobal.net
  896.  
  897. REXX Program to extract and totalize monthly time-ons by
  898. analyzing the InJoy dialer log
  899. */
  900. Ijoylog:
  901. save_trace = Trace('N')
  902.  
  903. injoy_rc = Initialize_ijoylog()
  904.  
  905. If injoy_rc Then
  906.    injoy_rc = Analyze_ijoylog()/* gets, calculates, totalizes connect times. 
  907.                                         Mostly a bunch of conditionals with a Call to a
  908.                                         data-formatting routine. All is stored in
  909.                                         variables for output all at once */
  910.  
  911. If injoy_rc Then
  912.    injoy_rc = Output_ijoylog()/* Outputs everything to console and to disk */
  913.  
  914. Return injoy_rc
  915.  
  916. Analyze_ijoylog:
  917. trace_save = Trace('N')
  918. /* Now find all lines in connect.log that contain the key_phrase
  919.    string.  A typical line generated in the connect.log after
  920.    disconnect is:
  921.      DATE 21.02.1998, START 10:54:25, END 11:33:30, DURATION 39  min, 2345  sec
  922.    which we would parse as follows:
  923.     'DATE' date ','  . 'END' word2 ',' . 'min,' connect_time 'sec' */
  924.  
  925. /* this section will read through the connection log files
  926.    data and assign account information and userid to each
  927.    connection log record */
  928. line. = ''
  929. line.0 = 0
  930. Do m = 1 To Words(injoy_logs)
  931.    log_file = Word(injoy_logs, m)
  932.    injoy_acct = Word(injoy_accts, m)
  933.    injoy_user = Word(injoy_users, m)
  934.    last_time.injoy_acct.injoy_user = Word(injoy_times, m)
  935.  
  936.    lrc = SysFileSearch(key_phrase, log_file, 'temp.')
  937.  
  938.    If lrc <> 0 | temp.0 < 1 Then
  939.       Iterate                        /* the log may have been cleared */
  940.  
  941.    Do i = 1 To temp.0
  942.       j = line.0 + 1
  943.       line.j = Space(Injoyformat(temp.i) injoy_acct injoy_user)
  944.       line.0 = j
  945.    End
  946. End
  947.  
  948. /* this is a great way to sort the entries */
  949. /*
  950. Call RxSort 'line.', 'A'
  951. */
  952. Call SysStemSort 'line.', 'A'
  953.  
  954. Do i = 1 To line.0
  955.    Parse Var line.i time_stamp connect_time acctid userid .
  956.    acctid = Strip(acctid)
  957.    userid = Strip(userid)
  958.    connect_time = Strip(connect_time)
  959.    year = Substr(time_stamp, 2, 4)
  960.    t_yr = year
  961.    mm = Substr(time_stamp, 6, 2)
  962.    dd = Substr(time_stamp, 8, 2)
  963.    mins = connect_time % 60                            /*... and mins */
  964.    secs = connect_time - (mins * 60)                 /*...and seconds */
  965.    /* Calculate time_on for that connection */
  966.    time_on = (mins + (1/60)*secs)
  967.    If time_stamp > last_time.acctid.userid Then
  968.       Call Monthly_update
  969.  
  970.    If old_d = 0 Then
  971.       Do                            /* for very first connection line */
  972.          old_m = mm
  973.          old_d = dd
  974.          old_y = year
  975.          signons_each_day = 1                           /* reset to 1 */
  976.          signons_each_month = 1
  977.          /* This and next one are timeons in minutes */
  978.          daily_time_on = time_on
  979.          monthly_time_on = time_on
  980.       End
  981.  
  982.    Else         /* continue to accumulate times if same month and day */
  983.       If old_m = mm & old_d = dd & month <> 0 Then
  984.       Do
  985.          old_y = year
  986.          signons_each_day = signons_each_day + 1
  987.          signons_each_month = signons_each_month + 1
  988.          daily_time_on = daily_time_on + time_on
  989.          monthly_time_on = monthly_time_on + time_on
  990.       End
  991.  
  992.    Else                                      /* new day of same month */
  993.       If old_m = mm & old_d <> dd Then
  994.       Do
  995.          Call Prepare_data
  996.          dcounter = dcounter + 1
  997.          year.dcounter = year
  998.          year.mcounter = year
  999.          dayline.dcounter = year.dcounter month.old_m old_d d_signons d_mins d_hhmmss d_hrs
  1000.  
  1001.          old_d = dd
  1002.          old_y = year
  1003.          signons_each_day = 1            /* Start counting over again */
  1004.          signons_each_month = signons_each_month + 1
  1005.          daily_time_on = time_on
  1006.          monthly_time_on = monthly_time_on + time_on
  1007.       End
  1008.  
  1009.    Else   /* for any new month, which by definition is also a new day */
  1010.       If old_m <> mm & old_m <> 0 Then
  1011.       Do
  1012.          Call Prepare_data
  1013.          dcounter = dcounter + 1
  1014.          year.dcounter = old_y
  1015.          dayline.dcounter = year.dcounter month.old_m old_d d_signons d_mins d_hhmmss d_hrs
  1016.  
  1017.          mcounter = mcounter + 1
  1018.          year.mcounter = old_y
  1019.          monthline.mcounter = year.mcounter month.old_m m_signons m_mins m_hhmmss m_hrs
  1020.  
  1021.          old_m = mm
  1022.          old_d = dd
  1023.          old_y = year
  1024.          signons_each_day = 1
  1025.          signons_each_month = 1
  1026.          daily_time_on = time_on
  1027.          monthly_time_on = time_on
  1028.       End
  1029.  
  1030. End             /* end of all these If's and Else If's of searching for 
  1031.                    key_phrase in all possible lines in the connect.log */
  1032.  
  1033. /* Now, since last day and last month is done: */
  1034. Call Prepare_data
  1035. dcounter = dcounter + 1
  1036. year.dcounter = year
  1037. dayline.dcounter = year.dcounter month.old_m old_d d_signons d_mins d_hhmmss d_hrs
  1038.  
  1039. mcounter = mcounter + 1
  1040. year.mcounter = year
  1041. monthline.mcounter = year.mcounter month.old_m m_signons m_mins m_hhmmss m_hrs
  1042. time_stamp = ''
  1043. Do m = 1 To Words(injoy_logs)
  1044.    injoy_acct = Word(injoy_accts, m)
  1045.    injoy_user = Word(injoy_users, m)
  1046.    time_stamp = time_stamp last_time.injoy_acct.injoy_user
  1047. End
  1048. /* save the last */
  1049. injoy_times = Space(time_stamp)
  1050. Call SysIni inetcfg_ini, 'cfg_ijoylog', 'injoy_times' injoy_times
  1051. Trace (trace_save)
  1052. Return 1                                         /* from analyze_ijoylog */
  1053.  
  1054. Injoyformat: Procedure
  1055. Parse Arg line
  1056. Parse Var line 'DATE' date ',' . 'END' word2 ',' . 'min,' connect_time 'sec' .
  1057. date = Strip(date)
  1058. Parse Var date dd '.' mm '.' year
  1059. word2 = Strip(word2)
  1060. /* Extract for time stamp and save */
  1061. Parse Var word2 t_hr ':' t_min ':' t_sec
  1062. t_yr = year
  1063. time_stamp = 'T' || t_yr || mm || dd || t_hr || t_min || t_sec
  1064. connect_time = Strip(connect_time)
  1065. line = time_stamp connect_time
  1066. Return line
  1067.  
  1068. Output_ijoylog:
  1069. /* Now output everything to console and to file */
  1070.  
  1071. /* get the screen size; rows is what we are interested in */
  1072. Parse Value SysTextScreenSize() With rows cols
  1073. Call Stream stdin, 'C', 'OPEN READ'
  1074. /* Tell us all */
  1075. intro = what_r_we
  1076. Call Say_out ' ', intro
  1077. Call Lineout output_file, intro
  1078. Call Lineout output_file, crlf || 'Running with parms =' otherparms
  1079. intro = 'Analysis of InJoy log file(s)' injoy_logs '(' || Date() '@' Time() || ')'
  1080. Call Say_out ' ', intro
  1081. Call Lineout output_file, crlf || intro
  1082. If daily_report Then
  1083.    Call Say_out ' ', 'Daily Totals', ' '
  1084. Call Lineout output_file, crlf || 'Daily Totals' || crlf
  1085. Do j = 1 To dcounter
  1086.    If daily_report Then
  1087.       Call Say_out dayline.j
  1088.    Call Lineout output_file, dayline.j
  1089. End
  1090.  
  1091. j = 0
  1092.  
  1093. /* back up the summary file and delete it */
  1094. If Rx_fileexists(summary_file) Then
  1095.    Do
  1096.       Parse Var summary_file fname '.' ext
  1097.       Address cmd '@COPY' summary_file fname || '.bak > NUL'
  1098.       Call SysFileDelete summary_file
  1099.    End
  1100. rc = File_cmd(summary_file,'W')
  1101. acct_user_save = ''
  1102. /*
  1103. Call RxSort 'monthly.', 'A'                  /* sort the summary data */
  1104. */
  1105. Call SysStemSort 'monthly.', 'A'
  1106.  
  1107. Call Summary_sample                     /* get the header information */
  1108. /* insert the header info into the summary data */
  1109. Do k = summary_sample.0 To 1 By -1
  1110.    /*
  1111.       Call RxStemInsert 'monthly.', 1, summary_sample.k
  1112.    */
  1113.    Call SysStemInsert 'monthly.', 1, summary_sample.k
  1114. End
  1115.  
  1116. Do k = 1 To monthly.0
  1117.    If Abbrev(monthly.k,'*') Then
  1118.       Do                                   /* write comments back out */
  1119.          Call Lineout summary_file, monthly.k
  1120.          Iterate k
  1121.       End
  1122.    Parse Var monthly.k acctid userid s_yr s_mo
  1123.    acct_user = acctid userid
  1124.    Call Lineout summary_file, acctid userid '*' summary.acctid.userid.s_yr.s_mo
  1125.    Parse Var summary.acctid.userid.s_yr.s_mo . . . s_sess s_mins
  1126.    m_sess = Format(s_sess, sig_x) || 'X'
  1127.    m_mins = Format(s_mins, sig_min, 2) 'mins'
  1128.    mo_hh = Format(Trunc(s_mins / 60),sig_hr)
  1129.    mo_mm = Right(Trunc(s_mins // 60),2,'0')
  1130.    mo_ss = Right(Trunc(60 * ((s_mins // 60) - Trunc(s_mins // 60))),2,'0')
  1131.    m_hhmmss = '  '||mo_hh||':'||mo_mm||':'||mo_ss
  1132.    m_hrs = Format((s_mins / 60), sig_hr, 2) 'hrs'
  1133.    a_sess = s_mins / s_sess
  1134.    If a_sess < 60 Then
  1135.       a_sess = Format(a_sess,2,0) 'mins'
  1136.    Else
  1137.       a_sess = Format((a_sess / 60),2,2) 'hrs'
  1138.    a_sess = '- Ave =' a_sess
  1139.    monthline = s_yr month.s_mo m_sess m_mins m_hhmmss m_hrs a_sess
  1140.    If acct_user <> acct_user_save Then
  1141.       Do
  1142.          intro = 'Monthly Totals for Account(' || acctid || ') Userid(' || userid || '}'
  1143.          Call Say_out ' ', intro, ' '
  1144.          Call Lineout output_file, crlf || intro || crlf
  1145.          acct_user_save = acct_user
  1146.       End
  1147.    Call Lineout output_file, monthline
  1148.    monthline = s_yr month.s_mo m_sess m_hrs a_sess
  1149.    Call Say_out monthline
  1150. End
  1151. rc = File_cmd(summary_file,'C')
  1152. finished = 'End of analysis of InJoy log file(s)'
  1153. Call Say_out ' ', finished
  1154. Call Lineout output_file, crlf || finished
  1155.  
  1156. Return 1                                       /* from output_ijoylog */
  1157.  
  1158. Initialize_ijoylog:
  1159. trace_save = Trace('N')
  1160.  
  1161. If Read_config() Then
  1162.    Return 0
  1163.  
  1164. /* Get full paths for output file and summary file */
  1165. output_file = data_path || injoy_output_file
  1166. summary_file = data_path || injoy_summary_file
  1167. Call Set_monthly_summary
  1168. injoy_summary = summary_file
  1169.  
  1170. /* initialize variables */
  1171. crlf = D2c(13) || D2c(10)               /* carriage return + linefeed */
  1172. esc = D2c(27)                                     /* Escape character */
  1173. time_stamp = ''                  /* Time stamp of each connect record */
  1174.  
  1175. Do i = 1 To 12                                   /* initialize months */
  1176.    x = Right(i,2,'0')
  1177.    month.x = Word('Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec',i)
  1178. End
  1179.  
  1180. /* calculated variables
  1181.    signons_each_day    Accumulate number of connects daily
  1182.    signons_each_month  Accumulate number of connects monthly
  1183.    time_on             Time, each connect  (minutes)
  1184.    daily_time_on       Accumulated minutes, daily
  1185.    monthly_time_on     Accumulated minutes, monthly */
  1186.  
  1187. /* More variables: these we initialize as follows: */
  1188. old_m = 0                 /* Storage of a 2-digit month (eg 05 = May) */
  1189. old_d = 0                                    /* ... and a 2-digit day */
  1190. dcounter = 0              /* Counter increments each sign-on in a day */
  1191. mcounter = 0                         /* Same for each sign-on in a month */
  1192. monthline. = ''           /* Initialize both of these as null strings */
  1193. dayline. = ''       /* These are for monthly and daily output strings */
  1194.  
  1195. /* A typical line generated in the default.log upon disconnect looks like
  1196.       DATE 20.02.1998, START 16:22:32, END 16:22:33, DURATION 0   min, 1     sec
  1197.    We search for a key_phrase using the RexxUtil function SysFileSearch */
  1198.  
  1199. key_phrase = 'DATE'                /* Word or phrase we'll search for */
  1200.  
  1201. Do i = 1 To Words(injoy_logs)
  1202.    If \Rx_fileexists(Word(injoy_logs, i)) Then
  1203.       Do
  1204.          Say 'Log file' Word(injoy_logs, i) 'does not exist.'
  1205.          Say 'It may have been cleared and will be bypassed.'
  1206.       End
  1207.    /* make sure that we have control of the log file */
  1208.    Else
  1209.       Do
  1210.          rc = File_cmd(Word(injoy_logs, i), 'W')
  1211.          If \ rc Then
  1212.             Do Until rc
  1213.                Say 'Log file' Word(injoy_logs, i) 'returned' result
  1214.                rc = File_cmd(Word(injoy_logs, i), 'C')
  1215.                Say 'Unable to open' Word(injoy_logs, i)
  1216.                Say 'Press Esc to abort run'
  1217.                answer = Say_message('Press any other key to wait 5 seconds')
  1218.                If answer = esc Then
  1219.                   Return 0
  1220.                Call SysSleep 5
  1221.                Call SysCls
  1222.                rc = File_cmd(Word(injoy_logs, i), 'W')
  1223.             End
  1224.       End
  1225.    rc = File_cmd(Word(injoy_logs, i), 'C')
  1226. End
  1227.  
  1228. /* Here is where we Check if the output file exists.  If it does, we
  1229.    overwrite it, and if not we create it.  BUT....  we don't want to
  1230.    do something stupid like try to erase a vital file or the connect
  1231.    logfile... */
  1232.  
  1233. /* Backup any output file of the same name if it exists, then erase orig. */
  1234. If Rx_fileexists(output_file) Then
  1235.    Do
  1236.       Parse Var output_file fname '.' ext
  1237.       Address cmd '@COPY' output_file fname||'.bak > NUL'
  1238.       Call SysFileDelete output_file
  1239.    End
  1240.  
  1241. Trace (trace_save)
  1242. Return 1                                        /* initialize_ijoylog */
  1243.  
  1244. Set_monthly_summary:                           /* set monthly summary */
  1245. monthly. = ''
  1246. monthly.0 = 0
  1247. summary. = ''
  1248. s_time = 'T00000000000000'
  1249. If Rx_fileexists(summary_file) Then
  1250.    Do
  1251.       Call Rx_readlines summary_file
  1252.       Do i = 1 To file_lines.0
  1253.          If \ Abbrev(file_lines.i,'*') & file_lines.i <> '' Then
  1254.             Do
  1255.                Parse Var file_lines.i acctid userid '*' s_yr s_mo s_time s_data
  1256.                If s_yr = '' Then
  1257.                   Do
  1258.                      acctid = 'unknown'
  1259.                      userid = 'unknown'
  1260.                      Parse Var file_lines.i s_yr s_mo s_data
  1261.                   End
  1262.                acctid = Strip(acctid)
  1263.                userid = Strip(userid)
  1264.                s_yr = Strip(s_yr)
  1265.                s_mo = Strip(s_mo)
  1266.                j = monthly.0 + 1
  1267.                monthly.j = acctid userid s_yr s_mo
  1268.                monthly.0 = j
  1269.                summary.acctid.userid.s_yr.s_mo = s_yr s_mo s_time s_data
  1270.             End
  1271.       End
  1272.       /*
  1273.             Call RxSort 'monthly.0', 'A'
  1274.             */
  1275.       Call SysStemSort 'monthly.', 'A'
  1276.  
  1277.    End
  1278. Else
  1279.    s_time = 'T00000000000000'       /* reset timestamp to get updates */
  1280. Return                                         /* Set_monthly_summary */
  1281.  
  1282. /*:VRX         Rx_readlines
  1283. Usage:  rx_readlines(input_file, <flag>)
  1284. Where:     input_file = a fully qualified file to be read into an
  1285.            exposed stem, "file_lines.", with 1 additional
  1286.            stem entry if the file ended with an end-of-file
  1287.            character ('1a'x) and the "-eof" flag is set
  1288.            flag = "-e<of>" - read and preserve the eof flag ('1a'x)
  1289. Result:    1 - the file exists
  1290.            0 - the file did not exist
  1291. */
  1292. Rx_readlines: Procedure Expose file_lines. verbose log_out log_file
  1293. save_trace = Trace('N')
  1294. Parse Arg input_file, flag
  1295. If \Datatype(verbose, 'b') Then
  1296.    verbose = 1
  1297. If \Datatype(log_out, 'b') Then
  1298.    Do
  1299.       xrc = SysFileTree(log_file, 'test.', 'fo')
  1300.       If xrc = 0 & test.0 <> 0 Then
  1301.          log_out = 1
  1302.       Else
  1303.          log_out = 0
  1304.    End
  1305. flag = Translate(flag)
  1306. set_eof = (Pos('E', flag) <> 0)
  1307. If Stream(input_file, 'c', 'query exists') <> '' Then
  1308.    Do
  1309.       Call Stream input_file, 'c', 'open read'
  1310.       If set_eof Then
  1311.          Do
  1312.             /* check for eof character */
  1313.             file_size = Stream(input_file, 'c', 'query size')
  1314.             If Charin(input_file, file_size, 1) = '1a'x Then
  1315.                eof = 1
  1316.             Else
  1317.                eof = 0
  1318.             Call Stream input_file, 'c', 'close'
  1319.          End
  1320.       Else
  1321.          eof = 0
  1322.       Call Stream input_file, 'c', 'open read'
  1323.       file_lines. = ''
  1324.       file_lines.0 = 0
  1325.       Do i = 1 Until Lines(input_file) = 0
  1326.          file_lines.i = Linein(input_file)
  1327.       End
  1328.       file_lines.0 = i
  1329.       If eof Then
  1330.          Do
  1331.             i = file_lines.0 + 1
  1332.             file_lines.i = '1a'x
  1333.             file_lines.0 = i
  1334.          End
  1335.       Call Stream input_file, 'c', 'close'
  1336.       xrc = 1
  1337.    End
  1338. Else
  1339.    xrc = 0
  1340. Trace(save_trace)
  1341. Return xrc
  1342.  
  1343. /*:VRX         rx_fileexists
  1344. Usage:  rx_fileexists(file_name)
  1345. Where:     file_name = name of file or path to be tested
  1346. Result:    1 if file or path exists, 0 otherwise
  1347. */
  1348. Rx_fileexists: Procedure Expose verbose log_out log_file
  1349. save_trace = Trace('N')
  1350. Parse Arg file_name
  1351. If \Datatype(verbose, 'b') Then
  1352.    verbose = 1
  1353. If \Datatype(log_out, 'b') Then
  1354.    Do
  1355.       xrc = SysFileTree(log_file, 'test.', 'fo')
  1356.       If xrc = 0 & test.0 <> 0 Then
  1357.          log_out = 1
  1358.       Else
  1359.          log_out = 0
  1360.    End
  1361. xrc = SysFileTree(file_name, 'test.', 'fo')
  1362. If xrc = 0 & test.0 <> 0 Then
  1363.    exists = 1
  1364. Else
  1365.    Do
  1366.       /* may be a directory */
  1367.       xrc = SysFileTree(file_name, 'test.', 'do')
  1368.       If xrc = 0 & test.0 <> 0 Then
  1369.          exists = 1
  1370.       Else
  1371.          exists = 0
  1372.    End
  1373. Trace(save_trace)
  1374. Return exists
  1375.  
  1376. /*:VRX         Dateconv
  1377. */
  1378. /*----------------------------------------------------------------------------+
  1379. | DATECONV FUNCTION                                                           |
  1380. +-----------------------------------------------------------------------------+
  1381. | This code is a REXX internal function; add it to your REXX programs.        |
  1382. | See DATECONV PACKAGE and DATECONV HELPCMS for more information.             |
  1383. +-----------------------------------------------------------------------------+
  1384. | Labels used within DATECONV Procedure are:                                  |
  1385. |   Dateconv:             <--- entry point                                    |
  1386. |   Dateconv_yy2cc:       <--- 2 digit to 4 digit year conversion
  1387. |   Dateconv_b2s_s2b:     <--- Basedate/Sorted format conversion              |
  1388. +-----------------------------------------------------------------------------+
  1389. | YYMMDD Change history:
  1390. | 900803 rlb v1.0 new code.   Russel L. Brooks    BROOKS/SJFEVMX
  1391. | 900808 rlb v2.0 new, better, faster.  doesn't use old BASEDATE code.
  1392. | 900821 rlb v3.0 add Arg(4) "Yx" to control assumed leading year digits.
  1393. |                 add format_out "L" for leap year.
  1394. | 910220 rlb v4.0 add Arg(5) Offset output date +/- days.
  1395. |                 Turn Trace Off at both labels. Set ERROR in Month Select.
  1396. | 910226 rlb v4.1 move TRACE past PROCEDURE for compiler.
  1397. | 910418 rlb v4.2 add ISO date format yyyy-mm-dd.
  1398. |                 allow input date to default to TODAY.
  1399. |                 convert all uses of EBCDIC Not sign "¬" to "<>".
  1400. |                 change input date parsing to allow leading blanks.
  1401. |                 if offset amount is 0 turn offset off.
  1402. | 910916 rlb v5.0 generate all formats but select what is requested.
  1403. |                 reduce overchecking, drop numerics 15 in b2s2b routine.
  1404. |                 allow muliple format request.
  1405. | 930414 rlb v5.1 bugfix: don't allow yyyymm00 as a valid Date(S) date.
  1406. | 940831 rlb v6.0 bugfix: better detection of invalid Date(J|U) dates.
  1407. |                 combine Date(E|O|U) code.  remove unneeded code.
  1408. |                 Signal on NoValue (but _we_ don't have 'novalue' label).
  1409. |                 only develop DOW, Month, Leapyear if asked for.
  1410. |                 test numbers w/ verify(integer) instead of datatype(W).
  1411. | 950113 rlb v6.1 parse out days/month for very small speed increase.
  1412. | 980629 rlb v7.0 changed internal variable 'yx' to 'cc' (century).
  1413. |                 if Fi=U/E/O/J & cc = '' then 100 year sliding window.
  1414. |
  1415. +----------------------------------------------------------------------------*/
  1416. Dateconv:
  1417. Procedure
  1418. Trace o
  1419. Signal On Novalue                            /* force error detection */
  1420.  
  1421. Parse Upper Arg date date_xtra, fi xtra1, fo xtra2, cc xtra3, offset
  1422.  
  1423. Select
  1424.    When xtra1 <> '' Then
  1425.       out = 'ERROR'
  1426.    When xtra2 <> '' Then
  1427.       out = 'ERROR'
  1428.    When xtra3 <> '' Then
  1429.       out = 'ERROR'
  1430.    When Arg() > 5 Then
  1431.       out = 'ERROR'
  1432.    Otherwise                                            /* initialize */
  1433.       Parse Value fi With 1 fi 2 . sdate bdate out .  /* 1 ltr + nuls */
  1434.    today = Date('S') Date('B')
  1435. End
  1436.  
  1437. /*----------------------------------------------------------------------+
  1438. | Input date formats U/E/O/J only use 2 digit years.  If CC is null then
  1439. | we'll calculate an appropriate century using a 100 year sliding window
  1440. | similar to what Rexx's Date() uses.
  1441. |
  1442. | Date format "C" is different.  Event though it doesn't specify a
  1443. | century we won't try to calculate one based on a sliding window.
  1444. | The user can specify an alternate century via Arg(4) "CC".
  1445. +----------------------------------------------------------------------*/
  1446. If cc <> '' Then                                  /* check user value */
  1447.    Select
  1448.       When Verify(cc,'0123456789') > 0 Then
  1449.          out = 'ERROR'                                       /* <>Num */
  1450.       When Length(cc) <> 2 Then
  1451.          out = 'ERROR'
  1452.       When cc < 0 Then
  1453.          out = 'ERROR'
  1454.       Otherwise Nop                             /* user's CC looks ok */
  1455.    End
  1456.  
  1457. /*----------------------------------------------------------------------+
  1458. | If no leading +/- sign then treat as +.  User could use + but if not
  1459. | included in quotes then REXX strips off the + sign.
  1460. +----------------------------------------------------------------------*/
  1461. Parse Value Space(offset,0) With 1 offset_sign 2 offset_amnt . 1 offset .
  1462. If offset = '' Then
  1463.    offset = 0
  1464. Else
  1465.    Do
  1466.       If offset_sign = '+' | offset_sign = '-' Then
  1467.          Nop
  1468.       Else
  1469.          Do
  1470.             offset_sign = '+'            /* missing so default to '+' */
  1471.             offset_amnt = offset   /* use entire user field as amount */
  1472.          End
  1473.       If Verify(offset_amnt,'0123456789') >0 Then
  1474.          out = 'ERROR'                                       /* <>Num */
  1475.       If offset_amnt = 0 Then
  1476.          offset = 0                              /* no offset request */
  1477.       Else
  1478.          offset = 1                /* yes, return date needs shifting */
  1479.    End
  1480.  
  1481. /*----------------------------------------------------------------------+
  1482. | Examine date according to "fi" (format in) caller passed.  If ok then
  1483. | convert date to either "B"asedate, "S"orted, or both formats.
  1484. |
  1485. | Dates are converted because it is easy to create "fo" (format out)
  1486. | dates from one or the other of these input formats.  This also limits
  1487. | having to directly support every possible "fi" to "fo" combination.
  1488. +----------------------------------------------------------------------*/
  1489. Select
  1490.    When out <> '' Then
  1491.       Nop                                   /* Error already detected */
  1492.    When fi = '' Then                                         /* today */
  1493.       Do
  1494.          /*----------------------------------------------------------------+
  1495.          | special case.  allow input date and input format to default to
  1496.          | TODAY.  This bypasses input date validation because we can rely
  1497.          | on REXX to supply valid dates.
  1498.          +----------------------------------------------------------------*/
  1499.          If date = '' Then
  1500.             Parse Value today With sdate bdate .
  1501.          Else
  1502.             out = 'ERROR'                /* missing FormatIN for date */
  1503.       End
  1504.    When fi = 'N' Then                           /* Normal dd Mmm yyyy */
  1505.       Do/* Test for N early because its only one that uses 'date_xtra' */
  1506.          Parse Value date date_xtra With dd mm yy date_xtra
  1507.          If date_xtra <> '' Then
  1508.             out = 'ERROR'                           /* too many parms */
  1509.          Else
  1510.             Do
  1511.                mm = Wordpos(mm,'JAN FEB MAR APR MAY JUN JUL AUG SEP OCT NOV DEC')
  1512.                If mm = 0 Then
  1513.                   out = 'ERROR'             /* invalid 3 letter month */
  1514.                Else
  1515.                   sdate = yy || Right(mm,2,0)Right(dd,2,0)
  1516.             End
  1517.       End
  1518.    When date_xtra <> '' Then
  1519.       out = 'ERROR'                                 /* too many parms */
  1520.    When fi = 'B' Then
  1521.       bdate = date                                 /* Basedate dddddd */
  1522.    When fi = 'S' Then
  1523.       sdate = date                                 /* Sorted yyyymmdd */
  1524.    When fi = 'D' Then                                     /* Days ddd */
  1525.       Select
  1526.          When Verify(date,'0123456789') > 0 Then
  1527.             out = 'ERROR'                                    /* <>Num */
  1528.          Otherwise
  1529.             yyyy = Left(today,4)
  1530.          dd = Dateconv_b2s_s2b(yyyy'0101','S')             /* Jan 1st */
  1531.          temp = Dateconv_b2s_s2b(yyyy+1'0101','S')/* Jan 1st next year */
  1532.          If date < 1 | date > temp-dd Then
  1533.             out = 'ERROR'                              /* max 365|366 */
  1534.          Else
  1535.             bdate = dd + date - 1
  1536.       End
  1537.    When fi = 'C' Then                                /* Century ddddd */
  1538.       Select
  1539.          When Verify(date,'0123456789') > 0 Then
  1540.             out = 'ERROR'                                    /* <>Num */
  1541.          Otherwise
  1542.             If cc = '' Then
  1543.             cc = Left(today,2)
  1544.          dd = Dateconv_b2s_s2b(cc'000101','S')        /* this century */
  1545.          temp = Dateconv_b2s_s2b(cc+1'000101','S')    /* next century */
  1546.          If date<1 | date>temp-dd Then
  1547.             out = 'ERROR'                          /* max 36524|36525 */
  1548.          Else
  1549.             bdate = dd + date - 1
  1550.       End
  1551.    When fi = 'J' Then                                 /* Julian yyddd */
  1552.       Select
  1553.          When Length(date) <> 5 Then
  1554.             out = 'ERROR'
  1555.          When Verify(date,'0123456789') > 0 Then
  1556.             out = 'ERROR'                                    /* <>Num */
  1557.          Otherwise
  1558.             Parse Value date With 1 yy 3 ddd .
  1559.          If cc = '' Then
  1560.             cc = Dateconv_yy2cc(yy)
  1561.          yyyy = cc || yy
  1562.          dd = Dateconv_b2s_s2b(yyyy'0101','S')             /* Jan 1st */
  1563.          temp = Dateconv_b2s_s2b(yyyy+1'0101','S') /* Jan 1st next yy */
  1564.          If ddd < 1 | ddd > temp-dd Then
  1565.             out = 'ERROR'                              /* max 365|366 */
  1566.          Else
  1567.             bdate = dd + ddd - 1
  1568.       End
  1569.    Otherwise                /* USA|European|Ordered|ISO ...or invalid */
  1570.       Select
  1571.          When fi = 'U' Then
  1572.             Parse Value date With mm'/'dd'/'yy .
  1573.          When fi = 'E' Then
  1574.             Parse Value date With dd'/'mm'/'yy .
  1575.          When fi = 'O' Then
  1576.             Parse Value date With yy'/'mm'/'dd .
  1577.          When fi = 'I' Then
  1578.             Parse Value date With 1 cc 3 yy'-'mm'-'dd .
  1579.          Otherwise out = 'ERROR'                 /* invalid Format_In */
  1580.       End
  1581.    Select
  1582.       When out <> '' Then
  1583.          Nop
  1584.       When Verify(Space(cc yy mm dd,0),'0123456789') > 0 Then
  1585.          out = 'ERROR'
  1586.       When Length(yy) <> 2 Then
  1587.          out = 'ERROR'
  1588.       When Length(mm) > 2 Then
  1589.          out = 'ERROR'
  1590.       When Length(dd) > 2 Then
  1591.          out = 'ERROR'
  1592.       Otherwise
  1593.          If cc = '' Then
  1594.          cc = Dateconv_yy2cc(yy)
  1595.       sdate = cc || Right(yy,2,0)Right(mm,2,0)Right(dd,2,0)
  1596.    End
  1597. End
  1598.  
  1599. /*----------------------------------------------------------------------+
  1600. | If the output date is being shifted by an offset then...
  1601. |   1- get the basedate if it doesn't already exist
  1602. |   2- offset the basedate by the amount requested
  1603. |   3- scratch sorted date because it doesn't match offset basedate
  1604. +----------------------------------------------------------------------*/
  1605. If offset & out = '' Then
  1606.    Do
  1607.       If bdate = '' Then
  1608.          Do
  1609.             bdate = Dateconv_b2s_s2b(sdate,'S')
  1610.             If bdate = '' Then
  1611.                out = 'ERROR'
  1612.          End
  1613.       If out = '' Then                                    /* no Error */
  1614.          Do
  1615.             If offset_sign = '+' Then
  1616.                bdate = bdate + offset_amnt
  1617.             Else
  1618.                bdate = bdate - offset_amnt
  1619.          End
  1620.       sdate = ''  /* date shifted, if sdate existed it is now invalid */
  1621.    End
  1622.  
  1623. /*----------------------------------------------------------------------+
  1624. | we have Basedate or Sorted, generate the other if we don't have both.
  1625. +----------------------------------------------------------------------*/
  1626. Select
  1627.    When out <> '' Then
  1628.       Nop                                                    /* error */
  1629.    When bdate = '' Then
  1630.       Do
  1631.          bdate = Dateconv_b2s_s2b(sdate,'S')
  1632.          If bdate = '' Then
  1633.             out = 'ERROR'
  1634.       End
  1635.    When sdate = '' Then
  1636.       Do
  1637.          sdate = Dateconv_b2s_s2b(bdate,'B')
  1638.          If sdate = '' Then
  1639.             out = 'ERROR'
  1640.       End
  1641.    Otherwise Nop/* both Bdate and Sdate already exist (and no errors) */
  1642. End
  1643.  
  1644. Parse Value sdate With 1 yyyy 5 . 1 cc 3 yy 5 mm 7 dd .
  1645. Parse Value '' With ddd ddddd month .       /* (re)initialize to null */
  1646.  
  1647. /*----------------------------------------------------------------------+
  1648. | "fo" Format_Out defaults to "Normal" out.
  1649. | "*" means return multiple formats, ALL if just "*" or the set of dates
  1650. | specified by the letters following "*".
  1651. +----------------------------------------------------------------------*/
  1652. Parse Value fo With 1 fo_string 2 temp
  1653. Select
  1654.    When fo_string = '' Then
  1655.       fo_string = 'N'                     /* default: "Normal" format */
  1656.    When fo_string <> '*' Then
  1657.       Nop                         /* use single letter in 'fo_string' */
  1658.    Otherwise
  1659.       If temp = '' Then
  1660.       fo_string = 'NBSMWDJCOEULI'                      /* all formats */
  1661.    Else
  1662.       fo_string = temp         /* multiple formats selected by caller */
  1663. End
  1664.  
  1665. If out = '' Then                                   /* if no Error yet */
  1666.    Do While fo_string <> ''
  1667.       Parse Value fo_string With 1 fo 2 fo_string
  1668.       Select
  1669.          When fo = 'B' Then
  1670.             out = out bdate                               /* Basedate */
  1671.          When fo = 'S' Then
  1672.             out = out sdate                                 /* Sorted */
  1673.          When fo = 'M' | fo = 'N' Then
  1674.             Do
  1675.                If month = '' Then
  1676.                   Do
  1677.                      temp = 'January February March April May June July'
  1678.                      temp = temp 'August September October November December'
  1679.                      month = Word(temp,mm)
  1680.                      If month = '' Then
  1681.                         Do
  1682.                            out = 'ERROR'
  1683.                            Leave
  1684.                         End
  1685.                   End
  1686.                If fo = 'M' Then
  1687.                   out = out month                            /* Month */
  1688.                Else
  1689.                   out = out dd+0 Left(month,3) yyyy         /* Normal */
  1690.             End
  1691.          When fo = 'W' Then                                /* Weekday */
  1692.             Do
  1693.                temp = 'Monday Tuesday Wednesday Thursday Friday Saturday Sunday'
  1694.                out = out Word(temp,(bdate//7)+1)
  1695.             End
  1696.          When fo = 'D' | fo = 'J' Then
  1697.             Do
  1698.                If ddd = '' Then
  1699.                   Do
  1700.                      ddd = Dateconv_b2s_s2b(yyyy'0101','S')
  1701.                      If ddd = '' Then
  1702.                         Do
  1703.                            out = 'ERROR'
  1704.                            Leave
  1705.                         End
  1706.                      Else
  1707.                         ddd = bdate - ddd + 1
  1708.                   End
  1709.                If fo = 'D' Then
  1710.                   out = out ddd                               /* Days */
  1711.                Else
  1712.                   out = out yy || Right(ddd,3,0)            /* Julian */
  1713.             End
  1714.          When fo = 'C' Then                                /* Century */
  1715.             Do
  1716.                ddddd = Dateconv_b2s_s2b(cc'000101','S')
  1717.                If ddddd = '' Then
  1718.                   Do
  1719.                      out = 'ERROR'
  1720.                      Leave
  1721.                   End
  1722.                Else
  1723.                   ddddd = bdate - ddddd + 1
  1724.                out = out ddddd
  1725.             End
  1726.          When fo = 'L' Then                               /* Leapyear */
  1727.             Do
  1728.                Select
  1729.                   When yyyy // 4 > 0 Then
  1730.                      leap_year = 0
  1731.                   When yyyy // 100 > 0 Then
  1732.                      leap_year = 1
  1733.                   When yyyy // 400 = 0 Then
  1734.                      leap_year = 1
  1735.                   Otherwise leap_year = 0
  1736.                End
  1737.                out = out leap_year
  1738.             End
  1739.          When fo = 'E' Then
  1740.             out = out dd'/'mm'/'yy                        /* European */
  1741.          When fo = 'O' Then
  1742.             out = out yy'/'mm'/'dd                         /* Ordered */
  1743.          When fo = 'U' Then
  1744.             out = out mm'/'dd'/'yy                             /* USA */
  1745.          When fo = 'I' Then
  1746.             out = out yyyy'-'mm'-'dd                           /* ISO */
  1747.          Otherwise
  1748.             out = 'ERROR'                /* Format_Out not recognized */
  1749.          Leave
  1750.       End
  1751.    End
  1752.  
  1753. If out = 'ERROR' Then
  1754.    out = ''                   /* null return indicates function error */
  1755. Return Strip(out,'L')          /*  <---  Dateconv Function exits here */
  1756.  
  1757.  
  1758. /*----------------------------------------------------------------------+
  1759. | Calculate a suitable Century for a 2 digit year using a sliding window
  1760. | similar to Rexx's Date() function.
  1761. |
  1762. |   (current_year - 50) = low end of window
  1763. |   (current_year + 49) = high end of window
  1764. +----------------------------------------------------------------------*/
  1765. Dateconv_yy2cc:
  1766. temp = Left(today,4) + 49
  1767. If (Left(temp,2)||Arg(1)) <= temp Then
  1768.    Return Left(temp,2)
  1769. Else
  1770.    Return Left(temp,2) - 1
  1771.  
  1772.  
  1773. /*----------------------------------------------------------------------+
  1774. | Convert Date(B) <--> Date(S)
  1775. |
  1776. | Arg(1) :  Date(B) or Date(S) date to be converted to other format.
  1777. |
  1778. | Arg(2) :  "B" or "S" to identify Arg(1)
  1779. |
  1780. | Return :  the converted date or "" (null) if an error detected.
  1781. +----------------------------------------------------------------------*/
  1782. Dateconv_b2s_s2b:
  1783. Procedure
  1784. Trace o
  1785. Signal On Novalue                            /* force error detection */
  1786.  
  1787. Arg dd .         /* Total days or sorted date, don't know which (yet) */
  1788. If Verify(dd,'0123456789') > 0 Then
  1789.    Return ''                                                 /* <>Num */
  1790.  
  1791. /* Initialize Days per month stem */
  1792. temp = 0 31 28 31 30 31 30 31 31 30 31 30 31
  1793. Parse Value temp With d. d.1 d.2 d.3 d.4 d.5 d.6 d.7 d.8 d.9 d.10 d.11 d.12 .
  1794.  
  1795. Select
  1796.    When Arg(2) = 'B' Then               /* Convert Date(B) to Date(S) */
  1797.       Do
  1798.          dd = dd + 1                           /* Date(S) = Date(B)+1 */
  1799.          yyyy = dd % 146097 * 400                  /* 400 year groups */
  1800.          dd = dd // 146097         /* all 400 year groups are similar */
  1801.  
  1802.          temp = dd % 36524                         /* 100 year groups */
  1803.          dd = dd // 36524
  1804.          If temp = 4 Then
  1805.             Do
  1806.                temp = 3/* back up 1, 4th 100 year group not same as 1st 3 */
  1807.                dd = dd + 36524
  1808.             End
  1809.          yyyy = temp * 100 + yyyy
  1810.  
  1811.          temp = dd % 1461                            /* 4 year groups */
  1812.          dd = dd // 1461
  1813.          If temp = 25 Then
  1814.             Do
  1815.                temp = 24/* back up 1, 25th 4 year group not same as 1st 24 */
  1816.                dd = dd + 1461
  1817.             End
  1818.          yyyy = temp * 4 + yyyy
  1819.  
  1820.          yyyy = dd % 365.25 + yyyy                   /* 1 year groups */
  1821.          dd = dd - ((dd % 365.25) * 365.25) % 1
  1822.  
  1823.          If dd = 0 Then
  1824.             Parse Value 12 31 With mm dd .                /* Dec 31st */
  1825.          Else
  1826.             Do
  1827.                yyyy = yyyy + 1                /* partial year = mm/dd */
  1828.                Select
  1829.                   When yyyy // 4 > 0 Then
  1830.                      Nop
  1831.                   When yyyy // 100 > 0 Then
  1832.                      d.2 = 29                            /* Leap Year */
  1833.                   When yyyy // 400 = 0 Then
  1834.                      d.2 = 29                            /* Leap Year */
  1835.                   Otherwise Nop
  1836.                End
  1837.                Do mm = 1 While dd > d.mm              /* count months */
  1838.                   dd = dd - d.mm            /* while subtracting days */
  1839.                End
  1840.             End
  1841.          Return Right(yyyy,4,0)Right(mm,2,0)Right(dd,2,0)/* Date(Sorted) */
  1842.       End
  1843.    When Arg(2) = 'S' Then               /* Convert Date(S) to Date(B) */
  1844.       Do
  1845.          If Length(dd) <> 8 Then
  1846.             Return ''
  1847.          Parse Value dd With 1 yyyy 5 mm 7 dd .
  1848.          Select
  1849.             When yyyy // 4 > 0 Then
  1850.                Nop
  1851.             When yyyy // 100 > 0 Then
  1852.                d.2 = 29                                  /* Leap Year */
  1853.             When yyyy // 400 = 0 Then
  1854.                d.2 = 29                                  /* Leap Year */
  1855.             Otherwise Nop
  1856.          End
  1857.          mm = mm + 0                              /* strip leading 0s */
  1858.          If d.mm = 0 Then
  1859.             Return ''                                    /* bad month */
  1860.          If dd = 0 | dd > d.mm Then
  1861.             Return ''                                     /* bad days */
  1862.  
  1863.          /* What was the Basedate December 31st of the "PREVIOUS" year?   */
  1864.          yyyy = yyyy - 1                             /* previous year */
  1865.          If yyyy = 0 Then
  1866.             days = 0                    /* there was no previous year */
  1867.          Else
  1868.             days = yyyy * 365 + (yyyy % 4) - (yyyy % 100) + (yyyy % 400)
  1869.  
  1870.          /* What 'nth' day of this year is mm/dd? */
  1871.          Do i = 1 To (mm-1)
  1872.             days = days + d.i         /* add days of completed months */
  1873.          End
  1874.          Return days + dd - 1           /* Date(Basedate) = Date(S)-1 */
  1875.       End
  1876.    Otherwise Return ''                /* Error: Arg(2) not "B" or "S" */
  1877. End
  1878. /*----------------------------------------------------------------------------+
  1879. | End of DATECONV FUNCTION code.                                              |
  1880. +----------------------------------------------------------------------------*/
  1881.  
  1882. /*:VRX         PMRexxGo
  1883. */
  1884. /* setup to run under PMREXX */
  1885. Pmrexxgo: Procedure Expose quiet say_out_pipe
  1886. save_trace = Trace('N')
  1887. Parse Arg action, obj_id, our_prog, title, our_parms
  1888. obj_id = '<' || obj_id || '>'
  1889.  
  1890. If action = 'START' Then
  1891.    Do
  1892.       our_dir = Left(our_prog, Lastpos('\', our_prog) - 1)
  1893.       pmrexx_name = SysSearchPath('PATH', 'PMREXX.EXE')
  1894.       If pmrexx_name <> '' Then
  1895.          Do
  1896.             class = 'WPProgram'
  1897.             location = '<WP_DESKTOP>'
  1898.             obj = 'OBJECTID=' || obj_id || ';'
  1899.             exec = 'EXENAME=' || pmrexx_name || ';'
  1900.             parm = 'PARAMETERS=' || our_prog our_parms '| X' || ';'
  1901.             startdir = 'STARTUPDIR=' || our_dir || ';'
  1902.             window = 'MAXIMIZED=NO;MINIMIZED=NO;NOAUTOCLOSE=NO;'
  1903.             setup = obj || exec || parm || startdir || window
  1904.             rc = SysCreateObject(class, title, location, setup, 'R')
  1905.             If rc Then
  1906.                Do
  1907.                   rc = SysSetObjectData(obj_id, 'OPEN=DEFAULT;CCVIEW=NO;')
  1908.                   If rc Then
  1909.                      Call Say_out 'Started' title 'under PMRexx'
  1910.                   Else
  1911.                      Call Say_out 'Unable to start' title 'under PMRexx'
  1912.                   Exit
  1913.                End
  1914.             Else
  1915.                Call Say_out 'Unable to create object' title
  1916.          End
  1917.    End
  1918. Else
  1919.    Do
  1920.       Call Say_out 'Press enter to terminate this program'
  1921.       Parse Upper Pull .
  1922.       Call SysDestroyObject obj_id
  1923.       Call SysSleep 2
  1924.    End
  1925. Trace(save_trace)
  1926. Return
  1927.