home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 18 REXX / 18-REXX.zip / CRONRF.ZIP / DATERGF.CMD < prev   
OS/2 REXX Batch file  |  1992-06-18  |  40KB  |  1,066 lines

  1. /*
  2. program: datergf.cmd
  3. type:    REXXSAA-OS/2
  4. purpose: allow transformations and calculations on sorted dates
  5.          (reverse Julian dates into sorted dates)
  6. version: 1.3
  7. date:    1991-05-20
  8. changed: 1992-06-07, RGF, error-message is only displayed on STDERR, if DATERGF was not
  9.                           invoked as a function
  10. changed: 1992-06-18, removed bundling to ATRGF.CMD etc., RGF
  11.  
  12. author:  Rony G. Flatscher,
  13.          Wirtschaftsuniversität/Vienna
  14.          RONY@AWIWUW11.BITNET
  15.          flatscher@wu-wien.ac.at
  16.  
  17. usage:   DATERGF(argument)
  18.          DATERGF(argument, flag)
  19.          DATERGF(argument1, flag, argument2)
  20.          see enclosed Tutorial "RGFSHOW.CMD" and syntax below
  21.  
  22. remark:  This program reflects the change in 1582, where the calendar
  23.          was corrected by subtracting 10 days (1582/10/05 - 1582/10/14 were
  24.          skipped) by the Roman Catholic pope Gregor XIII:
  25.          0000/01/01 ---> 1581/12/31 ... Julian Calendar (every 4 years leap year)
  26.          1582/01/01 ---> 9999/12/31 ... Gregorian Calendar, with 10 days less in 1582,
  27.                                         every 4 years leap year, EXCEPT whole centuries
  28.                                         which are not dividable MOD 400 = 0
  29.  
  30.  
  31.  
  32. All rights reserved, copyrighted 1991, 1992, no guarantee that it works without
  33. errors, etc. etc.
  34.  
  35. donated to the public domain granted that you are not charging anything
  36. (money etc.) for it and derivates based upon it, as you did not write it,
  37. etc. if that holds you may bundle it with commercial programs too
  38.  
  39. Please, if you find an error, post me a message describing it, I will
  40. try to fix and rerelease it to the net.
  41.  
  42. procedures:
  43.       CHECK_DATE:  check correct date and parse it
  44.       CHECK_TIME:  check correct time and parse it
  45.       DATE2DAYS:   calculate days based on 0000/01/01 (== 1)
  46.       DAYS2DATE:   generate sorted day from given days
  47.       TIME2FRACT:  calculate decimal fraction for time
  48.       FRACT2TIME:  calculate time from given decimal fraction
  49.       DATE2JULIAN: generate Julian date (YYYYDDD)
  50.       JULIAN2DATE: generate sorted date (YYYYMMDD)
  51.       WEEKDAY:     generate dayname and/or dayindex
  52.  
  53.  
  54. syntax:
  55.     SDATE ...... sorted date (YYYYMMDD)
  56.     JDATE ...... Julian date (YYYYDDD)
  57.     DAYS ....... DATERGF-days, if time supplied, time is a decimal fraction
  58.     TIME ....... on input 24hour- (military) or 12hour-format allowed,
  59.                  output will be allways in 24hour-format (military, computer)
  60.     FRACT ...... decimal fraction of time
  61.     SECONDS .... second-value for DAYS.FRACT
  62.  
  63.    Argument1:      Flag: Argument2:        Result:
  64.  
  65.    "SDATE[ TIME]"                          DAYS[.FRACT] since 00000101 inclusive
  66.    "DAYS[.FRACT]", "S"                     SDATE[ TIME24]
  67.    "SDATE[ TIME]", "J"                     JDATE
  68.    "JDATE",        "JR"                    SDATE ("Julian Reversed")
  69.  
  70.    "TIME",         "F"                     FRACT
  71.    "FRACT",        "FR"                    TIME24 ("Fraction Reversed")
  72.  
  73.    "DAYS[.FRACT]", "SEC"                   SECONDS
  74.    "SECONDS",      "SECR"                  DAYS[.FRACT] ("SEConds Reversed")
  75.  
  76.    "SDATE[ TIME]", "-S", "SDATE[ TIME]"    DAYS[.FRACT]
  77.    "SDATE[ TIME]", "-",  "DAYS[.FRACT]"    SDATE[ TIME24]
  78.    "SDATE[ TIME]", "+",  "DAYS[.FRACT]"    SDATE[ TIME24]
  79.  
  80.    "SDATE[ TIME]", "C"                     SDATE if correct, nothing ('') if false
  81.    "SDATE[ TIME]", "L"                     if leap-year, return 1, else 0
  82.  
  83.    "SDATE[ TIME]", "Y"                     check date & return year
  84.    "SDATE[ TIME]", "YB"[, DAYS[.FRACT]]    year begin [+/- DAYS[.FRACT]]
  85.    "SDATE[ TIME]", "YE"[, DAYS[.FRACT]]    year end [+/- DAYS[.FRACT]]
  86.  
  87.    "SDATE[ TIME]", "HY"                    semester index (1-2)
  88.    "SDATE[ TIME]", "HYB"[, DAYS[.FRACT]]   semester begin [+/- DAYS[.FRACT]]
  89.    "SDATE[ TIME]", "HYE"[, DAYS[.FRACT]]   semester end [+/- DAYS[.FRACT]]
  90.  
  91.    "SDATE[ TIME]", "M"                     check date & return month
  92.    "SDATE[ TIME]", "MN"                    monthname
  93.    "SDATE[ TIME]", "MB"[, DAYS[.FRACT]]    month begin [+/- DAYS[.FRACT]]
  94.    "SDATE[ TIME]", "ME"[, DAYS[.FRACT]]    month end [+/- DAYS[.FRACT]]
  95.  
  96.    "SDATE[ TIME]", "D"                     check date & return day
  97.    "SDATE[ TIME]", "DN"                    dayname
  98.    "SDATE[ TIME]", "DI"                    dayindex (1=monday, ..., 7=sunday)
  99.  
  100.    "SDATE[ TIME]", "W"                     week-number
  101.    "SDATE[ TIME]", "WB"[, DAYS[.FRACT]]    week begin (Monday) [+/- DAYS.[FRACT]]
  102.    "SDATE[ TIME]", "WE"[, DAYS[.FRACT]]    week end (Sunday) [+/- DAYS.[FRACT]]
  103.  
  104.    "SDATE[ TIME]", "Q"                     quarter index (1-4)
  105.    "SDATE[ TIME]", "QB"[, DAYS[.FRACT]]    quarter begin [+/- DAYS[.FRACT]]
  106.    "SDATE[ TIME]", "QE"[, DAYS[.FRACT]]    quarter end [+/- DAYS[.FRACT]]
  107.  
  108. */
  109.  
  110.  
  111. IF ARG(1) = ''|ARG(1) = '?' THEN SIGNAL usage
  112.  
  113.  
  114. /* invoked as a COMMAND (from command-line) or as a FUNCTION ? */
  115. PARSE SOURCE . invocation .
  116. invocation_as_function = (invocation = "FUNCTION")
  117.  
  118. IF \invocation_as_function THEN     /* called as COMMAND or SUBROUTINE and not as a FUNCTION! */
  119. DO                                  /* only one argument-string, which has to be parsed ! */
  120.    IF VERIFY(ARG(1), "(", "M") > 0 THEN
  121.       PARSE ARG "(" tmp ")"
  122.    ELSE
  123.       tmp = ARG(1)
  124.  
  125.    PARSE VAR tmp argument1 "," argument2 "," argument3
  126.  
  127.    argument1 = STRIP(argument1)     /* strip blanks */
  128.    argument2 = STRIP(argument2)
  129.    argument3 = STRIP(argument3)
  130.    argument1 = STRIP(argument1,,"'")/* strip ' */
  131.    argument2 = STRIP(argument2,,"'")
  132.    argument3 = STRIP(argument3,,"'")
  133.    argument1 = STRIP(argument1,,'"')/* strip " */
  134.    argument2 = STRIP(argument2,,'"')
  135.    argument3 = STRIP(argument3,,'"')
  136.  
  137.    SELECT                  /* number of arguments */
  138.       WHEN argument3 <> '' THEN argcount = 3
  139.       WHEN argument2 <> '' THEN argcount = 2
  140.       OTHERWISE                 argcount = 1
  141.  
  142.    END
  143.  
  144. END
  145. ELSE     /* invoked as a function */
  146. DO
  147.     argument1 = ARG(1)
  148.     argument2 = ARG(2)
  149.     argument3 = ARG(3)
  150.     argcount  = ARG()      /* number of arguments */
  151. END
  152.  
  153.  
  154. NUMERIC DIGITS 14          /* set precision to 14 digits after comma */
  155.  
  156. PARSE UPPER VAR argument1 date1 time1
  157. flag = TRANSLATE(argument2)
  158. PARSE UPPER VAR argument3 date2 time2
  159.  
  160. /* build monthdays array */
  161. monthdays.1 = 31
  162. monthdays.2 = 28
  163. monthdays.3 = 31
  164. monthdays.4 = 30
  165. monthdays.5 = 31
  166. monthdays.6 = 30
  167. monthdays.7 = 31
  168. monthdays.8 = 31
  169. monthdays.9 = 30
  170. monthdays.10 = 31
  171. monthdays.11 = 30
  172. monthdays.12 = 31
  173.  
  174.  
  175. /* check sorted dates & numbers */
  176.  
  177. IF argument3 <> '' & flag <> '-S' THEN     /* check whether third argument is a valid number */
  178.    IF \DATATYPE(argument3, 'N') THEN
  179.    DO
  180.       errmsg = argument3': not numeric'
  181.       SIGNAL error
  182.    END
  183.  
  184. SELECT
  185.    /* check sorted date and adjust monthdays. array, if necessary */
  186.    WHEN WORDPOS(flag, "S SEC SECR JR F FR") = 0 | flag = '' THEN
  187.         DO
  188.            date1 = check_date(date1)
  189.            IF time1 <> '' THEN time1 = check_time(time1)
  190.         END
  191.  
  192.    WHEN flag = 'F' THEN time1 = check_time(date1)       /* time in hand ? */
  193.  
  194.    OTHERWISE    /* argument1 a positive number ? */
  195.         DO
  196.            IF \DATATYPE(argument1,'N') THEN
  197.            DO
  198.               errmsg = argument1': not numeric'
  199.               SIGNAL error
  200.            END
  201.            ELSE IF argument1 < 0 THEN
  202.            DO
  203.               errmsg = argument1': must be a positive number'
  204.               SIGNAL error
  205.            END
  206.         END
  207. END
  208.  
  209.  
  210. /* act according to flag */
  211. SELECT
  212.    WHEN flag = '' THEN                  /* SDATE: calculate days of date = DAYS */
  213.         DO
  214.            days1 = date2days(date1)
  215.            if time1 <> '' THEN fraction1 = time2fract(time1)
  216.                           ELSE fraction1 = ''
  217.            result_datergf = days1||SUBSTR(fraction1,2)
  218.         END
  219.  
  220.    WHEN flag = 'S' THEN                 /* DAY: calculate date/time = SDATE */
  221.         DO
  222.           IF date1 < 1 THEN
  223.           DO
  224.                 errmsg = argument1 argument2": yields invalid date (< 0000/01/01) !"
  225.                 SIGNAL error
  226.           END
  227.  
  228.           days_int = date1 % 1
  229.           IF days_int > 3652427 THEN    /* > 9999/12/31 ? */
  230.           DO
  231.                 errmsg = argument1 argument2": yields invalid date (> 9999/12/31) !"
  232.                 SIGNAL error
  233.           END
  234.  
  235.           time_only = date1 - days_int
  236.           date = days2date(days_int)      /* get rid of fraction */
  237.  
  238.           IF time_only > 0 THEN date = date fract2time(time_only)
  239.  
  240.           result_datergf = date
  241.         END
  242.  
  243.    WHEN flag = '-S' THEN                /* SDATE - SDATE = DAYS */
  244.         DO
  245.            days1 = date2days(date1)
  246.            IF time1 <> '' THEN fraction1 = time2fract(time1)
  247.                           ELSE fraction1 = ''
  248.            result_datergf1 = days1||SUBSTR(fraction1,2)
  249.  
  250.            /* check date2 and prepare monthdays. */
  251.            date2 = check_date(date2)
  252.            days2 = date2days(date2)
  253.  
  254.            IF time2 <> '' THEN fraction2 = time2fract(check_time(time2))
  255.            ELSE fraction2 = ''
  256.  
  257.            result_datergf2 = days2||SUBSTR(fraction2,2)
  258.            result_datergf = result_datergf1 - result_datergf2
  259.         END
  260.  
  261.    WHEN (flag = '-') | (flag = '+') THEN        /* SDATE-DAYS = SDATE */
  262.         DO
  263.           days1 = date2days(date1)
  264.  
  265.           IF time1 <> '' THEN fraction1 = time2fract(time1)
  266.                          ELSE fraction1 = ''
  267.  
  268.           temp = days1||SUBSTR(fraction1,2)
  269.  
  270.           IF flag = '-' THEN
  271.              temp = temp - date2           /* subtract */
  272.           ELSE
  273.              temp = temp + date2           /* add */
  274.  
  275.           IF temp < 1 | ABS(temp) > 3652427 THEN        /* < 0000/01/01 or > 9999/12/31 ? */
  276.           DO
  277.                errmsg = argument1 argument2 argument3": does not yield a valid date!"
  278.                SIGNAL error
  279.           END
  280.  
  281.           days1 = temp % 1                  /* days since 0000/01/01 inclusive == 1 */
  282.           time1 = temp // 1                 /* time */
  283.           result_datergf = days2date(days1)
  284.  
  285.           IF time1 > 0 THEN
  286.              result_datergf = result_datergf fract2time(time1)
  287.         END
  288.  
  289.    WHEN flag = 'J' THEN                 /* SDATE = JDATE */
  290.         result_datergf = date2julian(date1)
  291.  
  292.    WHEN flag = 'JR' THEN                /* JDATE = SDATE (reverse Julian date) */
  293.         result_datergf = julian2date(date1)
  294.  
  295.    WHEN flag = 'F' THEN                 /* generate FRACT from TIME */
  296.         result_datergf = time2fract(time1)
  297.  
  298.    WHEN flag = 'FR' THEN                /* generate TIME24 from decimal fraction */
  299.         result_datergf = fract2time(date1 // 1)
  300.  
  301.    WHEN flag = 'SEC' THEN               /* generate SECONDS from DAYS[.FRACTION] */
  302.         result_datergf = (date1 * 86400 + 0.5) % 1  /* round to seconds */
  303.  
  304.    WHEN flag = 'SECR' THEN              /* generate DAYS[.FRACTION] from SECONDS */
  305.         result_datergf = date1 / 86400
  306.  
  307.    WHEN flag = 'C' THEN                 /* check date[ time] */
  308.         result_datergf = argument1
  309.  
  310.    WHEN flag = 'L' THEN                 /* is date in a leap year? */
  311.         DO
  312.            year = WORD(date1, 1)
  313.            IF year > 1582 THEN             /* Gregorian calender */
  314.               result_datergf = (((year // 4) = 0) & \((year // 100) = 0)) | ((year // 400)=0)
  315.            ELSE result_datergf = ((year // 4) = 0) /* Julian calender    */
  316.         END
  317.  
  318.    WHEN flag = 'Y' THEN                 /* return year */
  319.         result_datergf = WORD(date1, 1)
  320.  
  321.    WHEN flag = 'YB' | flag = 'YE' THEN     /* return year-begin or -end  */
  322.         DO
  323.            year = WORD(date1, 1)           /* optionally add (negative) days */
  324.  
  325.            IF argcount < 3 THEN               /* no third argument */
  326.            DO
  327.               IF flag = 'YB' THEN result_datergf = year||'0101'
  328.               ELSE result_datergf = year||'1231'
  329.  
  330.               IF time1 <> '' THEN result_datergf = result_datergf TRANSLATE(time1,':',' ')
  331.            END
  332.            ELSE
  333.            DO
  334.               IF flag = 'YB' THEN result_datergf = year '1 1'
  335.               ELSE result_datergf = year '12 31'
  336.  
  337.               result_datergf = date2days(result_datergf)
  338.               IF time1 <> '' THEN result_datergf = result_datergf + time2fract(time1)
  339.               result_datergf = result_datergf + date2
  340.  
  341.               days = result_datergf % 1
  342.               IF days < 1 | ABS(days) > 3652427 THEN    /* < 0000/01/01 or > 9999/12/31 ? */
  343.               DO
  344.                    errmsg = argument1 argument2 argument3": does not yield a valid date!"
  345.                    SIGNAL error
  346.               END
  347.  
  348.               fraction = result_datergf // 1
  349.               result_datergf = days2date(days)
  350.               IF fraction > 0 THEN result_datergf = result_datergf fract2time(fraction)
  351.            END
  352.         END
  353.  
  354.    WHEN flag = 'M' THEN                 /* return month */
  355.         result_datergf = WORD(date1, 2)
  356.  
  357.    WHEN flag = 'MN' THEN                /* return monthname */
  358.         result_datergf = WORD("January February March April May June July",
  359.                  "August September October November December", WORD(date1, 2))
  360.  
  361.    WHEN flag = 'MB' | flag = 'ME' THEN     /* return month-begin or -end  */
  362.         DO
  363.            PARSE VAR date1 year month .    /* optionally add (negative) days */
  364.  
  365.            IF argcount < 3 THEN               /* no third argument */
  366.            DO
  367.               IF flag = 'MB' THEN result_datergf = year||RIGHT(month,2,'0')||'01'
  368.               ELSE IF year = 1582 & month = 10 THEN result_datergf = year||RIGHT(month,2,'0')||'31'
  369.               ELSE result_datergf = year||RIGHT(month,2,'0')||monthdays.month
  370.  
  371.               IF time1 <> '' THEN result_datergf = result_datergf TRANSLATE(time1,':',' ')
  372.            END
  373.            ELSE
  374.            DO
  375.               IF flag = 'MB' THEN result_datergf = year month '1'
  376.               ELSE IF year = 1582 & month = 10 THEN result_datergf = year month '31'
  377.               ELSE result_datergf = year month monthdays.month
  378.  
  379.               result_datergf = date2days(result_datergf)
  380.               IF time1 <> '' THEN result_datergf = result_datergf + time2fract(time1)
  381.               result_datergf = result_datergf + date2
  382.  
  383.               days = result_datergf % 1
  384.               IF days < 1 | ABS(days) > 3652427 THEN    /* < 0000/01/01 or > 9999/12/31 ? */
  385.               DO
  386.                    errmsg = argument1 argument2 argument3": does not yield a valid date!"
  387.                    SIGNAL error
  388.               END
  389.  
  390.               fraction = result_datergf // 1
  391.               result_datergf = days2date(days)
  392.               IF fraction > 0 THEN result_datergf = result_datergf fract2time(fraction)
  393.            END
  394.         END
  395.  
  396.  
  397.    WHEN flag = 'W' THEN                 /* calculate week of year */
  398.         DO
  399.           PARSE VAR date1 year month day
  400.  
  401.           PARSE VALUE weekday(year "1 1", "ALL") WITH days_a d_ia
  402.  
  403.           /* 1. week or old year's week ? */
  404.           IF d_ia > 4 THEN diff = d_ia - 9
  405.           ELSE diff = d_ia - 2
  406.  
  407.           c1 = SUBSTR(date2julian(date1),5,3) + diff
  408.           result_datergf = c1 % 7 + 1           /* number of weeks */
  409.  
  410.           IF result_datergf > 51 THEN           /* last week in year ?, probably 1st week? */
  411.           DO
  412.  
  413.              PARSE VALUE weekday(year "12 31", "ALL") WITH days_0 di0
  414.  
  415.              IF di0 < 4 THEN
  416.                 IF day > (31-di0) THEN result_datergf = 1 /* first week, as 31st smaller than thursday  */
  417.           END
  418.           ELSE IF result_datergf = 1 THEN       /* beginning of January, is it last year's last week ? */
  419.           DO
  420.              IF (day + diff) < 0 THEN
  421.              DO
  422.                 PARSE VALUE weekday(RIGHT(year-1,4,'0') "1 1", "ALL") WITH days_0 di0
  423.  
  424.                 IF di0 > 4 THEN diff2 = di0 - 9 /* second week is first */
  425.                 ELSE diff2 = di0 - 2            /* first week */
  426.  
  427.                 c1 = SUBSTR(date2julian((RIGHT(year-1,4,'0') "12 31")),5,3) + diff2
  428.                 result_datergf  = c1 % 7 + 1            /* number of weeks */
  429.              END
  430.           END
  431.         END
  432.  
  433.    WHEN flag = 'D' THEN                 /* return day */
  434.         result_datergf = WORD(date1, 3)
  435.  
  436.    WHEN flag = 'DN' THEN                /* return dayname */
  437.         result_datergf = weekday(date1)
  438.  
  439.    WHEN flag = 'DI' THEN               /* return dayindex */
  440.         result_datergf = weekday(date1, "I")
  441.  
  442.    WHEN flag = 'WB' | flag = 'WE' THEN     /* return week-begin (MON) or -end (SUN)  */
  443.         DO
  444.            PARSE VALUE weekday(date1, "ALL") WITH tmp di
  445.  
  446.            IF flag = 'WB' THEN diff = 1 - di
  447.            ELSE diff = 7 - di
  448.  
  449.            new_days = tmp + diff
  450.  
  451.            IF argcount < 3 THEN               /* no third argument */
  452.            DO
  453.               IF new_days < 1 THEN         /* 0000/01/01 = THU, no monday available */
  454.               DO
  455.                    errmsg = argument1 argument2": does not yield a valid date!"
  456.                    SIGNAL error
  457.               END
  458.               result_datergf = days2date(new_days)
  459.               IF time1 <> '' THEN result_datergf = result_datergf TRANSLATE(time1,':',' ')
  460.            END
  461.            ELSE
  462.            DO
  463.               IF time1 <> '' THEN result_datergf = new_days + time2fract(time1)
  464.               ELSE result_datergf = new_days
  465.  
  466.               result_datergf = result_datergf + date2
  467.  
  468.               days = result_datergf % 1
  469.               IF days < 1 | ABS(days) > 3652427 THEN    /* < 0000/01/01 or > 9999/12/31 ? */
  470.               DO
  471.                    errmsg = argument1 argument2 argument3": does not yield a valid date!"
  472.                    SIGNAL error
  473.               END
  474.  
  475.               fraction = result_datergf // 1
  476.               result_datergf = days2date(days)
  477.               IF fraction > 0 THEN result_datergf = result_datergf fract2time(fraction)
  478.            END
  479.         END
  480.  
  481.  
  482.    WHEN flag = 'Q' THEN                 /* return quarter */
  483.         DO
  484.            year = WORD(date1, 1)
  485.            tmp = WORD(argument1, 1)           /* sorted date */
  486.            SELECT
  487.               WHEN tmp < year||'0401' THEN result_datergf = 1
  488.               WHEN tmp < year||'0701' THEN result_datergf = 2
  489.               WHEN tmp < year||'1001' THEN result_datergf = 3
  490.               OTHERWISE result_datergf = 4
  491.            END
  492.         END
  493.  
  494.    WHEN flag = 'QB' | flag = 'QE' THEN  /* return quarter-begin or -end  */
  495.         DO                              /* optionally add (negative) days */
  496.            year = WORD(date1, 1)
  497.            tmp = WORD(argument1, 1)        /* sorted date */
  498.  
  499.            IF argcount < 3 THEN            /* no third argument */
  500.            DO
  501.               IF flag = 'QB' THEN       /* quarter begin */
  502.               DO
  503.                  SELECT
  504.                     WHEN tmp < year||'0401' THEN result_datergf = year||'0101'
  505.                     WHEN tmp < year||'0701' THEN result_datergf = year||'0401'
  506.                     WHEN tmp < year||'1001' THEN result_datergf = year||'0701'
  507.                     OTHERWISE result_datergf = year||'1001'
  508.                  END
  509.               END
  510.               ELSE                      /* quarter end */
  511.               DO
  512.                  SELECT
  513.                     WHEN tmp < year||'0401' THEN result_datergf = year||'0331'
  514.                     WHEN tmp < year||'0701' THEN result_datergf = year||'0630'
  515.                     WHEN tmp < year||'1001' THEN result_datergf = year||'0930'
  516.                     OTHERWISE result_datergf = year||'1231'
  517.                  END
  518.               END
  519.  
  520.               IF time1 <> '' THEN result_datergf = result_datergf TRANSLATE(time1,':',' ')
  521.            END
  522.            ELSE
  523.            DO
  524.               IF flag = 'QB' THEN
  525.               DO
  526.                  SELECT
  527.                     WHEN tmp < year||'0401' THEN result_datergf = year  '1 1'
  528.                     WHEN tmp < year||'0701' THEN result_datergf = year  '4 1'
  529.                     WHEN tmp < year||'1001' THEN result_datergf = year  '7 1'
  530.                     OTHERWISE result_datergf = year  '10 1'
  531.                  END
  532.               END
  533.               ELSE
  534.               DO
  535.                  SELECT
  536.                     WHEN tmp < year||'0401' THEN result_datergf = year  '3 31'
  537.                     WHEN tmp < year||'0701' THEN result_datergf = year  '6 30'
  538.                     WHEN tmp < year||'1001' THEN result_datergf = year  '9 30'
  539.                     OTHERWISE result_datergf = year  '12 31'
  540.                  END
  541.               END
  542.  
  543.               result_datergf = date2days(result_datergf)
  544.               IF time1 <> '' THEN result_datergf = result_datergf + time2fract(time1)
  545.               result_datergf = result_datergf + date2
  546.  
  547.               days = result_datergf % 1
  548.               IF days < 1 | ABS(days) > 3652427 THEN    /* < 0000/01/01 or > 9999/12/31 ? */
  549.               DO
  550.                    errmsg = argument1 argument2 argument3": does not yield a valid date!"
  551.                    SIGNAL error
  552.               END
  553.  
  554.               fraction = result_datergf // 1
  555.               result_datergf = days2date(days)
  556.               IF fraction > 0 THEN result_datergf = result_datergf fract2time(fraction)
  557.            END
  558.         END
  559.  
  560.    WHEN flag = 'HY' THEN                /* return semester (1 = 1.half, 2 = 2.half */
  561.            IF WORD(date1, 2) < 7 THEN result_datergf = 1
  562.            ELSE result_datergf = 2
  563.  
  564.    WHEN flag = 'HYB' | flag = 'HYE' THEN   /* return quarter-begin or -end  */
  565.         DO
  566.            PARSE VAR date1 year month .    /* optionally add (negative) days */
  567.  
  568.            IF argcount < 3 THEN               /* no third argument */
  569.            DO
  570.               IF flag = 'HYB' THEN
  571.               DO
  572.                  IF month < 7 THEN result_datergf = year||'0101'
  573.                  ELSE result_datergf = year||'0701'
  574.               END
  575.               ELSE
  576.               DO
  577.                  IF month < 7 THEN result_datergf = year||'0630'
  578.                  ELSE result_datergf = year||'1231'
  579.               END
  580.  
  581.               IF time1 <> '' THEN result_datergf = result_datergf TRANSLATE(time1,':',' ')
  582.            END
  583.            ELSE
  584.            DO
  585.               IF flag = 'HYB' THEN
  586.               DO
  587.                  IF month < 7 THEN result_datergf = year '1 1'
  588.                  ELSE result_datergf = year '7 1'
  589.               END
  590.               ELSE
  591.               DO
  592.                  IF month < 7 THEN result_datergf = year  '6 30'
  593.                  ELSE result_datergf = year '12 31'
  594.               END
  595.  
  596.               result_datergf = date2days(result_datergf)
  597.               IF time1 <> '' THEN result_datergf = result_datergf + time2fract(time1)
  598.               result_datergf = result_datergf + date2
  599.  
  600.               days = result_datergf % 1
  601.               IF days < 1 | ABS(days) > 3652427 THEN    /* < 0000/01/01 or > 9999/12/31 ? */
  602.               DO
  603.                    errmsg = argument1 argument2 argument3": does not yield a valid date!"
  604.                    SIGNAL error
  605.               END
  606.  
  607.               fraction = result_datergf // 1
  608.               result_datergf = days2date(days)
  609.               IF fraction > 0 THEN result_datergf = result_datergf fract2time(fraction)
  610.            END
  611.         END
  612.  
  613.    OTHERWISE
  614.         DO
  615.           errmsg = flag': unknown flag'
  616.           SIGNAL error
  617.         END
  618. END
  619.  
  620. IF invocation_as_function THEN   /* invoked as function, therefore return the value */
  621.    RETURN result_datergf         /* return value */
  622.  
  623. /* invoked from the COMMAND-line or as a SUBROUTINE, both invocations must not return a value */
  624. SAY "DATERGF - result:" result_datergf       /* show result on standard output */
  625. RETURN
  626. /* end of main routine */
  627.  
  628.  
  629.  
  630.  
  631.  
  632.  
  633. /* parse & check arguments */
  634. CHECK_DATE: PROCEDURE EXPOSE monthdays.
  635.  
  636.     PARSE ARG 1 year 5 month 7 day 9
  637.  
  638.     IF \DATATYPE(year,'N') THEN
  639.     DO
  640.        errmsg = ARG(1)": year is not numeric"
  641.        SIGNAL error
  642.     END
  643.  
  644.     IF year < 0 THEN
  645.     DO
  646.        errmsg = ARG(1)": year must be 0000 or greater"
  647.        SIGNAL error
  648.     END
  649.  
  650.     /* is year a leap year ? */
  651.     IF year > 1582 THEN                 /* Gregorian calender */
  652.        leap_year = (((year // 4) = 0) & \((year // 100) = 0)) | ((year // 400)=0)
  653.     ELSE leap_year = ((year // 4) = 0)  /* Julian calender    */
  654.  
  655.     monthdays.2 = 28 + leap_year
  656.     IF year = 1582 THEN monthdays.10 = 21       /* 1582: October had 10 days less */
  657.  
  658.     SELECT
  659.        WHEN \DATATYPE(month,'N') THEN
  660.             DO
  661.                errmsg = ARG(1)||": month is not numeric"
  662.                SIGNAL error
  663.             END
  664.        WHEN (month < 1) | (month > 12) THEN
  665.             DO
  666.                errmsg = ARG(1)||": month out of range"
  667.                SIGNAL error
  668.             END
  669.        OTHERWISE
  670.             month = month % 1   /* get rid of leading nulls */
  671.     END
  672.  
  673.     SELECT
  674.        WHEN \DATATYPE(day,'N') THEN
  675.             DO
  676.                errmsg = ARG(1)": day is not numeric"
  677.                SIGNAL error
  678.             END
  679.        WHEN (day < 1) THEN
  680.             DO
  681.                errmsg = ARG(1)": day out of range"
  682.                SIGNAL error
  683.             END
  684.        WHEN year = 1582 & month = 10 THEN    /* Gregorian: 1582, October 1-4, 15-31 */
  685.             DO
  686.                IF (day > 4 & day < 15) | day > 31 THEN
  687.                DO
  688.                   IF day > 31 THEN
  689.                      errmsg = ARG(1)": too many days for given month"
  690.                   ELSE
  691.                      errmsg = ARG(1)": day out of range (1582/10/05-1582/10/14 do not exist)"
  692.                   SIGNAL error
  693.                END
  694.             END
  695.        WHEN day > monthdays.month THEN
  696.             DO
  697.                errmsg = ARG(1)": too many days for given month"
  698.                SIGNAL error
  699.             END
  700.        OTHERWISE
  701.             day = day % 1 /* get rid of leading nulls */
  702.     END
  703.  
  704.     RETURN year month day
  705. /* end of CHECK_DATE */
  706.  
  707.  
  708. /* parse & check time, return 24hour-Time (military time) */
  709. CHECK_TIME: PROCEDURE EXPOSE monthdays.
  710.     PARSE UPPER ARG tmp
  711.     time24 = 1                  /* starting with 24 hour time in mind */
  712.     time12 = POS('M', tmp)      /* AM or PM ? */
  713.     IF time12 > 0 THEN
  714.     DO
  715.       time24 = 0                /* 12 hour time in hand */
  716.       letter = SUBSTR(tmp, time12 - 1, 1)
  717.       IF \((letter = 'A') | letter = 'P') THEN
  718.       DO
  719.          errmsg = ARG(1)': not a valid AM/PM-time'
  720.          SIGNAL error
  721.       END
  722.       tmp = SUBSTR(tmp, 1, time12 - 2)  /* remove ?M */
  723.     END
  724.  
  725.     PARSE VAR tmp hours ':' minutes ':' seconds
  726.  
  727.     SELECT
  728.       WHEN hours = '' THEN hours = 0
  729.       WHEN \datatype(hours,'N') THEN     /* no numeric type */
  730.            DO
  731.               errmsg = ARG(1)": hours are not numeric"
  732.               SIGNAL error
  733.            END
  734.       WHEN (hours < 0) | (hours > 23) THEN      /* out of range    */
  735.            DO
  736.               errmsg = ARG(1)": hours out of range"
  737.               SIGNAL error
  738.            END
  739.       OTHERWISE NOP
  740.     END
  741.  
  742.     SELECT
  743.       WHEN minutes = '' THEN minutes = 0
  744.       WHEN \datatype(minutes,'N') THEN     /* no numeric type */
  745.            DO
  746.               errmsg = ARG(1)": minutes are not numeric"
  747.               SIGNAL error
  748.            END
  749.       WHEN (minutes < 0) | (minutes > 59) THEN /* out of range    */
  750.            DO
  751.               errmsg = ARG(1)": minutes out of range"
  752.               SIGNAL error
  753.            END
  754.       OTHERWISE NOP
  755.     END
  756.  
  757.     SELECT
  758.       WHEN seconds = '' THEN seconds = 0
  759.       WHEN \datatype(seconds,'N') THEN     /* no numeric type */
  760.            DO
  761.               errmsg = ARG(1)": seconds are not numeric"
  762.               SIGNAL error
  763.            END
  764.       WHEN (seconds < 0) | (seconds >= 60) THEN /* out of range    */
  765.            DO
  766.               errmsg = ARG(1)": seconds out of range"
  767.               SIGNAL error
  768.            END
  769.       OTHERWISE NOP
  770.     END
  771.  
  772.     IF \time24 THEN             /* received a 12hour time, adjust it to 24hour time */
  773.     DO
  774.        IF (letter = 'A') & (hours = 12) THEN hours = 0
  775.        ELSE IF ((letter = 'P') & (hours < 12)) THEN hours = hours + 12
  776.     END
  777.  
  778.     RETURN hours  minutes seconds
  779. /* end of CHECK_TIME */
  780.  
  781.  
  782.  
  783. /* calculate days based on 0000/01/01 (= 1. day == 1) */
  784. DATE2DAYS: PROCEDURE  EXPOSE monthdays.
  785.     PARSE ARG year month day
  786.  
  787.     days_1    = year * 365
  788.     leap_days = year % 4
  789.  
  790.     IF year > 0 THEN
  791.     DO
  792.        leap_days = leap_days + 1        /* account for leap year in 0000 */
  793.  
  794.        IF year > 1582 THEN days_1 = days_1 - 10 /* account for 1582, which had 10 days less */
  795.  
  796.        IF year > 1600 THEN         /* account for Gregorian calender */
  797.        DO
  798.            diff = year - 1600
  799.            leap_days = leap_days - (diff % 100 - diff % 400)
  800.            leap_year = (((diff // 4) = 0) & \((diff // 100) = 0)) | ((diff // 400)=0) /* leap year in hand ? */
  801.        END
  802.        ELSE leap_year = ((year // 4) = 0)       /* leap year in hand ? */
  803.  
  804.        leap_days = leap_days - leap_year
  805.     END
  806.  
  807.     days_2 = SUBSTR(date2julian(ARG(1)), 5, 3)
  808.  
  809.     RETURN (days_1 + leap_days + days_2)
  810. /* end of DATE2DAYS */
  811.  
  812.  
  813. DAYS2DATE: PROCEDURE  EXPOSE monthdays. /* calculate sorted day from given days */
  814.     days = ARG(1)
  815.  
  816.     avg_days = 365.25        /* average days a year */
  817.  
  818.     /* estimate years */
  819.     IF days > 578181 THEN               /* year greater than 1582/12/31 ? */
  820.        year1 = (days + 10 ) % avg_days  /* account for 10 missing days in 1582 */
  821.     ELSE
  822.        year1 = days % avg_days
  823.  
  824.  
  825.     DO FOREVER
  826.  
  827.        /* is year1 a leap year ? */
  828.        IF year1 > 1582 THEN                     /* Gregorian calender */
  829.           year_days = (((year1 // 4) = 0) & \((year1 // 100) = 0)) | ((year1 // 400)=0)
  830.        ELSE year_days = ((year1 // 4) = 0)      /* Julian calender    */
  831.  
  832.        IF year1 <> 1582 THEN year_days = year_days + 365
  833.        ELSE year_days = 355             /* 1582 had 10 days less */
  834.  
  835.        days_year1 = date2days(year1 "1 1")   /* pad year with 0 */
  836.        diff1 = days - days_year1
  837.  
  838.        IF diff1 < 0 THEN year1 = year1 - 1
  839.        ELSE IF diff1 > (year_days - 1) THEN year1 = year1 + 1
  840.        ELSE LEAVE
  841.     END
  842.     diff1 = diff1 + 1           /* one day off, due to subtraction */
  843.  
  844.     tmp = RIGHT(year1,4,'0')|| RIGHT(diff1,3,'0')       /* build Julian date */
  845.  
  846.     RETURN julian2date(tmp)     /* build sorted day */
  847. /* end of DAYS2DATE */
  848.  
  849.  
  850.  
  851.  
  852. /* calculate decimal fraction from time */
  853. TIME2FRACT: PROCEDURE  EXPOSE monthdays.     /* calculate decimal value for time */
  854.     PARSE ARG hours minutes seconds
  855.  
  856.     /* hour_div = 24      =   24           */
  857.     /* min_div  = 1440    =   24 * 60      */
  858.     /* sec_div  = 86400   =   24 * 60 * 60 */
  859.  
  860.     RETURN ((hours/24) + (minutes/1440) + (seconds/86400))
  861. /* end of TIME2FRACT */
  862.  
  863.  
  864. /* calculate time from fraction */
  865. FRACT2TIME: PROCEDURE  EXPOSE monthdays.     /* calculate time from given value */
  866.     /* hours    = 24      =   24           */
  867.     /* minutes  = 1440    =   24 * 60      */
  868.     /* seconds  = 86400   =   24 * 60 * 60 */
  869.  
  870.     tmp = arg(1) + 0.0000001            /* account for possible precision error */
  871.  
  872.     hours   = (tmp * 24) % 1
  873.     minutes = (tmp * 1440 - hours * 60) % 1
  874.     seconds = (tmp * 86400 - hours * 3600 - minutes * 60) % 1
  875.  
  876.     RETURN RIGHT(hours,2,'0')':'RIGHT(minutes,2,'0')':'RIGHT(seconds,2,'0')
  877.  
  878. /* end of FRACT2TIME */
  879.  
  880.  
  881. /* build Julian date from sorted date, result: yyyyddd */
  882. DATE2JULIAN: PROCEDURE EXPOSE monthdays.
  883.     PARSE ARG year month day
  884.  
  885.     /* is year a leap year ? */
  886.     IF year > 1582 THEN                 /* Gregorian calender */
  887.        leap_year = (((year // 4) = 0) & \((year // 100) = 0)) | ((year // 400)=0)
  888.     ELSE leap_year = (year // 4) = 0    /* Julian calender    */
  889.  
  890.     monthdays.2 = 28 + leap_year
  891.     IF year = 1582 THEN monthdays.10 = 21       /* 1582: October just had 21 days */
  892.  
  893.     result_function = 0
  894.     DO i = 1 TO month - 1
  895.        result_function = result_function + monthdays.i
  896.     END
  897.  
  898.     IF year = 1582 & month = 10 & day > 4 THEN day = day - 10       /* Gregorian: 10 days too many */
  899.     result_function = result_function + day
  900.  
  901.     RETURN year||RIGHT(result_function,3,'0')
  902. /* end of DATE2JULIAN */
  903.  
  904.  
  905.  
  906.  
  907. /* build sorted date from Julian date, result: yyyymmdd */
  908. JULIAN2DATE: PROCEDURE EXPOSE monthdays.
  909.     year = SUBSTR(ARG(1),1,4)
  910.  
  911.     /* is year a leap year ? */
  912.     IF year > 1582 THEN                 /* Gregorian calender */
  913.        leap_year = (((year // 4) = 0) & \((year // 100) = 0)) | ((year // 400)=0)
  914.     ELSE leap_year = (year // 4) = 0    /* Julian calender    */
  915.  
  916.     monthdays.2 = 28 + leap_year
  917.     IF year = 1582 THEN monthdays.10 = 21       /* 1582: October just had 21 days */
  918.  
  919.     jul_days = SUBSTR(ARG(1),5)       /* Julian days */
  920.     SELECT
  921.        WHEN jul_days > (365 + leap_year) THEN
  922.           DO
  923.              errmsg = ARG(1)": too many days for the given year"
  924.              SIGNAL error
  925.           END
  926.        WHEN year = 1582 & jul_days > 355 THEN       /* 1582: 10 days less than other years */
  927.           DO
  928.              errmsg = ARG(1)": too many days for 1582 (had 355 days only)"
  929.              SIGNAL error
  930.           END
  931.        OTHERWISE NOP
  932.     END
  933.  
  934.     /* calculate days */
  935.     tmp = 0
  936.     DO month = 1 TO 12
  937.        tmp = tmp + monthdays.month
  938.        IF tmp = jul_days THEN        /* exactly given days ?       */
  939.        DO
  940.           day = monthdays.month
  941.           LEAVE
  942.        END
  943.  
  944.        IF tmp > jul_days THEN      /* got over month              */
  945.        DO
  946.           day = monthdays.month - (tmp - jul_days)
  947.           LEAVE
  948.        END
  949.     END
  950.  
  951.     /* 1582: October 1-4, 15-31,  adjust for 10 missing days if necessary */
  952.     IF year = 1582 & month = 10 & day > 4 THEN day = day + 10
  953.  
  954.     RETURN year||RIGHT(month,2,'0')||RIGHT(day,2,'0')
  955. /* end of JULIAN2DATE */
  956.  
  957.  
  958.  
  959.  
  960. /* return day of sorted date as name or as index */
  961. WEEKDAY: PROCEDURE EXPOSE monthdays.
  962.     total_days = date2days(ARG(1))
  963.     dayindex = (total_days + 2) // 7 + 1  /* normalize on Mondays = 1, ..., Sunday = 7 */
  964.  
  965.     IF ARG(2) = 'I' THEN result_function = dayindex
  966.     ELSE IF ARG(2) = 'ALL' THEN result_function = total_days dayindex
  967.     ELSE result_function = WORD("Monday Tuesday Wednesday Thursday Friday Saturday Sunday", dayindex)
  968.  
  969.     RETURN result_function
  970.     /* remark:
  971.         According to an advice of ISO a week starts with MONDAY, hence:
  972.         Monday = 1, Tuesday = 2, Wednesday = 3, Thursday = 4, Friday = 5,
  973.         Saturday = 6, Sunday = 7.
  974.         The German DIN-organization already normalized on the ISO advice.
  975.     */
  976. /* end of WEEKDAY */
  977.  
  978. USAGE:
  979. /* define some colors to demonstrate them */
  980. esc    = '1B'x          /* define ESCape character */
  981. red    = esc||"[31m"    /* ANSI.SYS-control for red foreground */
  982. yellow = esc||"[33m"    /* ANSI.SYS-control for yellow foreground */
  983. cyan   = esc||"[36m"    /* ANSI.SYS-control for cyan foreground */
  984. normal = esc||"[0m"     /* ANSI.SYS-control for resetting attributes to normal */
  985.  
  986. SAY
  987. SAY cyan'DATERGF:'normal' manipulate sorted dates, time, days and time-fractions'
  988. SAY
  989. SAY red'usage as a function in REXX-programs:'
  990. SAY yellow'         DATERGF(argument)'
  991. SAY '         DATERGF(argument, flag)'
  992. SAY '         DATERGF(argument1, flag, argument2)'
  993. SAY
  994. SAY red'usage from command-line:'
  995. SAY yellow'         DATERGF argument '
  996. SAY '         DATERGF argument, flag '
  997. SAY '         DATERGF argument1, flag, argument2 '
  998. SAY
  999. SAY normal'         see enclosed Tutorial "RGFSHOW.CMD" and syntax below'
  1000. SAY
  1001. SAY red'syntax:'
  1002. SAY
  1003. SAY cyan'SDATE'normal' ...... sorted date (YYYYMMDD)'
  1004. SAY cyan'JDATE'normal' ...... Julian date (YYYYDDD)'
  1005. SAY cyan'DAYS'normal' ....... DATERGF-days, if time supplied, time is a decimal fraction'
  1006. SAY cyan'TIME'normal' ....... on input 24hour- (military) or 12hour-format allowed,'
  1007. SAY '             output will be allways in 24hour-format (military, computer)'
  1008. SAY cyan'FRACT'normal' ...... decimal fraction of time'
  1009. SAY cyan'SECONDS'normal' .... second-value for DAYS.FRACT'
  1010. SAY
  1011. SAY 'Argument1:      Flag: Argument2:        Result:'
  1012. SAY
  1013. SAY cyan'"SDATE[ TIME]"                          DAYS[.FRACT]'normal' since 00000101 inclusive'
  1014. SAY cyan'"DAYS[.FRACT]", "S"                     SDATE[ TIME24]'
  1015. SAY '"SDATE[ TIME]", "J"                     JDATE'
  1016. SAY '"JDATE",        "JR"                    SDATE'normal' ("Julian Reversed")'
  1017. SAY
  1018. SAY cyan'"TIME",         "F"                     FRACT'
  1019. SAY '"FRACT",        "FR"                    TIME24'normal' ("Fraction Reversed")'
  1020. SAY
  1021. SAY cyan'"DAYS[.FRACT]", "SEC"                   SECONDS'
  1022. SAY '"SECONDS",      "SECR"                  DAYS[.FRACT]'normal' ("SEConds Reversed")'
  1023. SAY
  1024. SAY cyan'"SDATE[ TIME]", "-S", "SDATE[ TIME]"    DAYS[.FRACT]'
  1025. SAY '"SDATE[ TIME]", "-",  "DAYS[.FRACT]"    SDATE[ TIME24]'
  1026. SAY '"SDATE[ TIME]", "+",  "DAYS[.FRACT]"    SDATE[ TIME24]'
  1027. SAY
  1028. SAY '"SDATE[ TIME]", "C"                     SDATE'normal' if correct, nothing ('cyan''''''normal') if false'
  1029. SAY cyan'"SDATE[ TIME]", "L"'normal'                     if leap-year, return 1, else 0'
  1030. SAY cyan'"SDATE[ TIME]", "Y"'normal'                     check date & return year'
  1031. SAY cyan'"SDATE[ TIME]", "YB"[, DAYS[.FRACT]]'normal'    year begin 'cyan'[+/- DAYS[.FRACT]]'
  1032. SAY '"SDATE[ TIME]", "YE"[, DAYS[.FRACT]]'normal'    year end 'cyan'[+/- DAYS[.FRACT]]'
  1033. SAY '"SDATE[ TIME]", "HY"'normal'                    semester index (1-2)'
  1034. SAY cyan'"SDATE[ TIME]", "HYB"[, DAYS[.FRACT]]'normal'   semester begin 'cyan'[+/- DAYS[.FRACT]]'
  1035. SAY '"SDATE[ TIME]", "HYE"[, DAYS[.FRACT]]'normal'   semester end 'cyan'[+/- DAYS[.FRACT]]'
  1036. SAY
  1037. SAY '"SDATE[ TIME]", "M"'normal'                     check date & return month'
  1038. SAY cyan'"SDATE[ TIME]", "MN"'normal'                    monthname'
  1039. SAY cyan'"SDATE[ TIME]", "MB"[, DAYS[.FRACT]]'normal'    month begin 'cyan'[+/- DAYS[.FRACT]]'
  1040. SAY '"SDATE[ TIME]", "ME"[, DAYS[.FRACT]]'normal'    month end 'cyan'[+/- DAYS[.FRACT]]'
  1041. SAY ''
  1042. SAY '"SDATE[ TIME]", "D"'normal'                     check date & return day'
  1043. SAY cyan'"SDATE[ TIME]", "DN"'normal'                    dayname'
  1044. SAY cyan'"SDATE[ TIME]", "DI"'normal'                    dayindex (1=monday, ..., 7=sunday)'
  1045. SAY
  1046. SAY cyan'"SDATE[ TIME]", "W"'normal'                     week-number'
  1047. SAY cyan'"SDATE[ TIME]", "WB"[, DAYS[.FRACT]]'normal'    week begin (Monday) 'cyan'[+/- DAYS.[FRACT]]'
  1048. SAY '"SDATE[ TIME]", "WE"[, DAYS[.FRACT]]'normal'    week end (Sunday) 'cyan'[+/- DAYS.[FRACT]]'
  1049. SAY
  1050. SAY '"SDATE[ TIME]", "Q"'normal'                     quarter index (1-4)'
  1051. SAY cyan'"SDATE[ TIME]", "QB"[, DAYS[.FRACT]]'normal'    quarter begin 'cyan'[+/- DAYS[.FRACT]]'
  1052. SAY '"SDATE[ TIME]", "QE"[, DAYS[.FRACT]]'normal'    quarter end 'cyan'[+/- DAYS[.FRACT]]'normal
  1053. EXIT
  1054.  
  1055.  
  1056. ERROR:
  1057.    /* invoked as a COMMAND (from command-line) or as a FUNCTION ? */
  1058.    PARSE SOURCE . invocation .
  1059.    invocation_as_function = (invocation = "FUNCTION")
  1060.  
  1061.    /* error message on device "STDERR", only if not called as function */
  1062.    IF \invocation_as_function THEN
  1063.       '@ECHO DATERGF:' errmsg '>&2'
  1064.  
  1065.    EXIT ''                                      /* error, therefore return empty string */
  1066.