home *** CD-ROM | disk | FTP | other *** search
/ Programmer's ROM - The Computer Language Library / programmersrom.iso / ada / misc / tod.ada < prev    next >
Encoding:
Text File  |  1988-05-03  |  76.7 KB  |  1,904 lines

  1.  
  2. -------- SIMTEL20 Ada Software Repository Prologue ------------
  3. --                                                           -*
  4. -- Unit name    : package TOD_Utilities
  5. -- Version      : 1.0 (MOOV115)
  6. -- Author       : Geoffrey O. Mendal
  7. --              : Stanford University
  8. --              : Computer Systems Laboratory
  9. --              : Stanford, CA  94305
  10. --              : (415) 497-1414 or 497-1175
  11. -- DDN Address  : Mendal@SU-SIERRA.ARPA
  12. -- Copyright    : (c) 1985 Geoffrey O. Mendal
  13. -- Date created : Mon 11 Nov 85
  14. -- Release date : Sun 25 Dec 85
  15. -- Last update  : MENDAL Sun 25 Dec 85
  16. -- Machine/System Compiled/Run on : DG MV10000, ROLM ADE
  17. --                                  VAX 11/780, DEC ACS
  18. --                                  RATIONAL R1000
  19. -- Dependent Units : package CALENDAR
  20. --                   generic package Search_Utilities
  21. --                                                           -*
  22. ---------------------------------------------------------------
  23. --                                                           -*
  24. -- Keywords     :  TIME
  25. ----------------:  DAY
  26. ----------------:  DATE
  27. ----------------:  TIME CONVERSION
  28. ----------------:  DATE CONVERSION
  29. --
  30. -- Abstract     :  This package contains time-of-day conversion
  31. ----------------:  routines. One routine takes practically
  32. ----------------:  any time/date STRING and converts it to
  33. ----------------:  CALENDAR.TIME format. The other routine takes
  34. ----------------:  a CALENDAR.TIME value and converts it to a
  35. ----------------:  STRING containing the day name, full date,
  36. ----------------:  and time (resolution to the nearest second).
  37. --                                                           -*
  38. ------------------ Revision history ---------------------------
  39. --                                                           -*
  40. -- DATE         VERSION              AUTHOR     HISTORY
  41. -- 12/29/85     1.0 (MOOV115)     Mendal     Initial Release
  42. --                                                           -*
  43. ------------------ Distribution and Copyright -----------------
  44. --                                                           -*
  45. -- This prologue must be included in all copies of this software.
  46. --
  47. -- This software is copyright by the author.
  48. --
  49. -- This software is released to the Ada community.
  50. -- This software is released to the Public Domain (note:
  51. --   software released to the Public Domain is not subject
  52. --   to copyright protection).
  53. -- Restrictions on use or distribution:  NONE
  54. --                                                           -*
  55. ------------------ Disclaimer ---------------------------------
  56. --                                                           -*
  57. -- This software and its documentation are provided "AS IS" and
  58. -- without any expressed or implied warranties whatsoever.
  59. -- No warranties as to performance, merchantability, or fitness
  60. -- for a particular purpose exist.
  61. --
  62. -- Because of the diversity of conditions and hardware under
  63. -- which this software may be used, no warranty of fitness for
  64. -- a particular purpose is offered.  The user is advised to
  65. -- test the software thoroughly before relying on it.  The user
  66. -- must assume the entire risk and liability of using this
  67. -- software.
  68. --
  69. -- In no event shall any person or organization of people be
  70. -- held responsible for any direct, indirect, consequential
  71. -- or inconsequential damages or lost profits.
  72. --                                                           -*
  73. -------------------END-PROLOGUE--------------------------------
  74.  
  75. -- This package will provide direct conversion from an external
  76. -- time/date string to the internal Ada CALENDAR.TIME representation
  77. -- and visa versa. Most free format external representations are
  78. -- supported. Components of an external format include:
  79. --   Year, Month and Day (as numbers and strings), Hour, Minutes,
  80. --   and Seconds
  81. -- As long as the external representation can be parsed unambiguously,
  82. -- this package should be able to handle the conversion. Examples of
  83. -- valid external formats:
  84. --   7pm Fr March 12, 1982
  85. --   15 Dec. 84 12:36PM
  86. --   YESTERDAY 3PM
  87. --   6/01/83          <-- defaults to 12:00:00AM
  88. --   3:45AM           <-- defaults to the current date
  89. --   18:07:35         <-- defaults to the current date
  90. --   8-26             <-- defaults to 12:00:00AM of the current year
  91. --   friday           <-- defaults to 12:00:00AM of the current or next
  92. --                        future Friday
  93. -- Examples of invalid external representations:
  94. --   2/31/84          <-- February never has a 31st day
  95. --   12:3605/01/84    <-- too tough to parse (nondeterminstic)
  96. --   3/8423:00:00     <-- too tough to parse (nondeterminstic)
  97. --   3:54:29AMTues    <-- too tough to parse (nondeterminstic)
  98. --   Nov 1983         <-- must always include day number in the date
  99. --   Sun 8/3/84       <-- 8/3/84 was a Friday
  100.  
  101. -- Optional periods may be placed after ABBREVIATED day/month names.
  102.  
  103. -- All external formats are converted to upper case, so there are no
  104. -- problems with specifying mixed and/or lower case input. All
  105. -- results are returned in upper case by default (which can be overridden
  106. -- by specifying lower case or mixed case).
  107.  
  108. -- Special external formats: TODAY, TOMORROW, YESTERDAY, NOW
  109. -- TODAY is equivalent to 12AM of the current date. TOMORROW and
  110. -- YESTERDAY are equivalent to the next/previous date. NOW is
  111. -- equivalent to calling the function CALENDAR.CLOCK .
  112.  
  113. -- Defaults:
  114. --   If the year is omitted, it defaults to the current year. If the
  115. --   time is omitted, it defaults to 12:00:00AM. If the day name and no
  116. --   date is specified, the current or next future date is assumed. If
  117. --   only the time is specified, the current date is assumed. If the
  118. --   minutes and/or seconds are not specified in the time, they default
  119. --   to zero. If the year is given in short format (1 or 2 digits) then
  120. --   it defaults to the current century.
  121.  
  122. -- BNF for the external representation:
  123. --   {<special_format> [<time>] |
  124. --    [<time>] <special_format> |
  125. --    <day_string> &|* <date> &|* <time>}
  126.  
  127. --   <special_format> ::= {TODAY | TOMORROW | YESTERDAY | NOW}
  128.  
  129. --   <day_string> ::= SU|NDAY, MO|NDAY, ..., SA|TURDAY
  130.  
  131. --   <date> ::= {<month_number><sep1><day_number>[<sep1><year_number>] |
  132. --               <month_name><sep2><day_number>[<sep2><year_number>] |
  133. --               <day_number><sep2><month_name>[<sep2><year_number>] |
  134. --               <full_year_number><sep2><month_name><sep2><day_number> |
  135. --               <full_year_number><sep2><day_number><sep2><month_name>}
  136.  
  137. --   <time> ::= {<hour>':'<minutes>[':'<seconds>][<AM_PM>] |
  138. --               <AMPM_hour><AM_PM>}
  139.  
  140. --   <month_number> ::= 1 .. 12
  141. --   <month_name> ::= JAN|UARY, FEB|RUARY, ..., DEC|EMBER
  142. --   <day_number> ::= 1 .. 31
  143. --   <year_number> ::= {<short_year_number> | <full_year_number>}
  144. --   <short_year_number> ::= [0]0 .. 99    <-- for century 2000
  145. --                           [0]1 .. 99    <-- for century 2100
  146. --   <full_year_number> ::= 1901 .. 2099
  147. --   <sep1> ::= {'-'|'/'}
  148. --   <sep2> ::= {<sep1> | {' ' | ','} ...}
  149.  
  150. --   <hour> ::= [0]0 .. 24
  151. --   <AMPM_hour> ::= [0]1 .. 12
  152. --   <minutes> ::= 00 .. 59
  153. --   <seconds> ::= 00 .. 59
  154. --   <AM_PM> ::= {"AM" | "PM"}
  155.  
  156. --   Notes on the BNF above:
  157. --     Items in angle brackets must be separated by at least one
  158. --     blank and/or comma when they appear with exactly one space
  159. --     between them.
  160.  
  161. --     However, items in angle brackets which are not separated by
  162. --     exactly one blank have a more rigid syntax, and must be followed
  163. --     precisely as specified in the BNF.
  164.  
  165. --     Some characters/strings are enclosed in quotes to emphasize that
  166. --     they are explicit, and not metasymbols. When specifying an
  167. --     external TOD_STRING, do NOT include the quotes.
  168.  
  169. --     The AM/PM indicator may be left off the time if at least the
  170. --     hours and minutes are specified. If only the hour is specified,
  171. --     it must be in the range 01 .. 12 and must have the AM/PM
  172. --     indicator following it. If the AM/PM indicator is left off a
  173. --     time format, AM is assumed unless the hour is in the range
  174. --     13 .. 23 . If the AM/PM indicator is included, the hour must
  175. --     be in the range 01 .. 12 .
  176.  
  177. --     Notation:
  178. --       {...|...|...}    -- select exactly one alternative
  179. --       [...]            -- optional
  180. --       &|               -- select one or the other or both
  181. --       &|*              -- same as &| with the extension of selecting
  182. --                           the items in any order
  183. --       ' '              -- encloses a character literal
  184. --       " "              -- encloses a string
  185. --       < >              -- encloses a non-terminal symbol
  186. --       ...              -- denotes a repeatable field
  187. --       |                -- separates alternatives and denotes valid
  188. --                        -- abbreviations
  189.  
  190. with CALENDAR;  -- predefined (internal representation) TOD package
  191.  
  192. package TOD_UTILITIES is
  193.   Tod_Utilities_Version : constant STRING := "1.51 MOOV115";
  194.  
  195.   -- the following type should be used to retrieve an external TOD
  196.   -- representation from the CALENDAR.TIME representation.
  197.  
  198.   External_TOD_Representation_Length : constant POSITIVE := 38;
  199.   subtype EXTERNAL_TOD_REPRESENTATION_TYPE is STRING(1 .. External_TOD_Representation_Length);
  200.  
  201.   -- the following type should be used to specify the type set of an
  202.   -- external representation returned by the internal-to-external
  203.   -- function below.
  204.  
  205.   type TYPE_SET is (UPPER_CASE,lower_case,Mixed_Case);
  206.  
  207.   -- the following function will take the CALENDAR.TIME representation
  208.   -- and return an external representation. The external representation
  209.   -- has the following format:
  210.   --   columns  1 ..  9 : Day as a string
  211.   --   columns 11 .. 12 : Day as a number
  212.   --   columns 14 .. 22 : Month as a string
  213.   --   columns 24 .. 27 : year number
  214.   --   columns 29 .. 38 : time in AM/PM format
  215.   --   all unused columns are blank
  216.  
  217.   --  Example string returned:
  218.   --    "THURSDAY  09 AUGUST    1984 05:19:05PM"
  219.  
  220.   function CONVERT_INTERNAL_TOD_TO_EXTERNAL_TOD(
  221.     TOD_VALUE       : in CALENDAR.TIME;
  222.     DEFAULT_SETTING : in TYPE_SET := UPPER_CASE)
  223.     return EXTERNAL_TOD_REPRESENTATION_TYPE;
  224.  
  225.   -- the following function will take an external TOD representation
  226.   -- and return the CALENDAR.TIME representation. The external
  227.   -- representation can be any constrained STRING that conforms to
  228.   -- the BNF given above.
  229.  
  230.   function CONVERT_EXTERNAL_TOD_TO_INTERNAL_TOD(
  231.     TOD_STRING : in STRING) return CALENDAR.TIME;
  232.  
  233.   -- the following exceptions will be raised if the input to the
  234.   -- function CONVERT_EXTERNAL_TOD_TO_INTERNAL_TOD cannot be parsed
  235.   -- unambiguously. Also, that function traps CALENDAR.TIME_ERROR and
  236.   -- instead raises the exception DATE_ERROR below in its place.
  237.  
  238.   DUPLICATION_ERROR,                          -- "5/25/61 May 25 1961"
  239.   DATE_ERROR,                                 -- "2/31/75"
  240.   MONTH_NUMBER_ERROR,                         -- "13/1/1960"
  241.   YEAR_ERROR,                                 -- "1/1/1900"
  242.   DAY_NUMBER_ERROR,                           -- "1/32/1984"
  243.   DAY_DATE_ERROR,                             -- "Sunday 8/3/84"
  244.   MONTH_MISSING_ERROR,                        -- "1961 25"
  245.   DAY_NUMBER_MISSING_ERROR,                   -- "1961 May"
  246.   HOUR_ERROR,                                 -- "25:00:00"
  247.   MINUTE_ERROR,                               -- "23:61:00"
  248.   SECOND_ERROR,                               -- "23:59:60"
  249.   TIME_STRING_ERROR,                          -- "1:05:05:PM"
  250.   ABBREVIATION_ERROR,                         -- "Sept.emb. 5"
  251.   EXTERNAL_REPRESENTATION_ERROR : exception;  -- "blah blah blah"
  252. end TOD_UTILITIES;
  253.  
  254. -- Example use:
  255. --   with TOD_UTILITIES, TEXT_IO, CALENDAR;
  256. --    ...
  257. --   procedure Print_Time_In_Readable_Format(
  258. --     Ada_Time_Format : CALENDAR.TIME := CALENDAR.CLOCK) is
  259. --      ...
  260. --   begin
  261. --      ...
  262. --     TEXT_IO.PUT_LINE(
  263. --       TOD_UTILITIES.CONVERT_INTERNAL_TOD_TO_EXTERNAL_TOD(TOD_VALUE=>Ada_Time_Format));
  264. --      ...
  265. --   end Print_Time_In_Readable_Format;
  266. --    ...
  267. --   procedure Convert_TOD_String_To_Ada_Predefined_Format(
  268. --     TOD_String : STRING := "TODAY") is
  269. --      ...
  270. --     Ada_Time_Format : CALENDAR.TIME;
  271. --      ...
  272. --   begin
  273. --      ...
  274. --     Ada_Time_Format := TOD_UTILITIES.CONVERT_EXTERNAL_TOD_TO_INTERNAL_TOD(
  275. --       TOD_STRING=>TOD_String);
  276. --      ...
  277. --   end Convert_TOD_String_To_Ada_Predefined_Format;
  278. --    ...
  279.  
  280. with Search_Utilities;  -- generic searching package
  281. package body TOD_UTILITIES is
  282.   -- the constants below make for easy conversion of
  283.   -- CALENDAR.DAY_DURATION values.
  284.  
  285.   Noon_Hour                   : constant POSITIVE := 12;
  286.   Number_of_Hours_in_Day      : constant POSITIVE := 24;
  287.   Number_of_Minutes_in_Hour   : constant POSITIVE := 60;
  288.   Number_of_Minutes_in_Day    : constant POSITIVE :=
  289.     Number_of_Minutes_in_Hour * Number_of_Hours_in_Day;
  290.   Number_of_Seconds_in_Minute : constant POSITIVE := 60;
  291.   Number_of_Seconds_in_Hour   : constant POSITIVE :=
  292.     Number_of_Seconds_in_Minute * Number_of_Minutes_in_Hour;
  293.   Number_of_Seconds_in_Day    : constant POSITIVE :=
  294.     Number_of_Seconds_in_Hour * Number_of_Hours_in_Day;
  295.   Number_of_Days_in_a_Week    : constant POSITIVE :=  7;
  296.   Number_of_Months_in_a_Year  : constant POSITIVE := 12;
  297.  
  298.   -- constants needed to access the day name field of an external TOD
  299.   -- representation
  300.  
  301.   Day_Name_Start                : constant POSITIVE  := 1;
  302.   Day_Name_End                  : constant POSITIVE  := 9;
  303.  
  304.   -- constants to make the code more readable
  305.  
  306.   Blank                         : constant CHARACTER := ' ';
  307.   Colon                         : constant CHARACTER := ASCII.COLON;
  308.   Period                        : constant CHARACTER := '.';
  309.   Max_Valid_Letter_Token_Length : constant POSITIVE  := 9;
  310.   UC_LC_Offset                  : constant NATURAL   :=
  311.     CHARACTER'POS(ASCII.LC_A) - CHARACTER'POS('A');
  312.  
  313.   -- types/subtypes and constant array needed by both functions
  314.  
  315.   subtype Set_of_Upper_Case_Letters is CHARACTER range 'A' .. 'Z';
  316.   subtype Search_Value_Type is STRING(1 .. Max_Valid_Letter_Token_Length);
  317.   type Month_Name_Array_Type is array(INTEGER range <>) of Search_Value_Type;
  318.  
  319.   Month_Name_Array : constant Month_Name_Array_Type(CALENDAR.MONTH_NUMBER) :=
  320.     ("JANUARY  ","FEBRUARY ","MARCH    ","APRIL    ","MAY      ","JUNE     ",
  321.      "JULY     ","AUGUST   ","SEPTEMBER","OCTOBER  ","NOVEMBER ","DECEMBER ");
  322.  
  323.   -- the function below uses an algorithm to derive the current day
  324.   -- of the week given a date (in internal format)
  325.  
  326.   function Compute_Day_of_Week(TOD_VALUE : in CALENDAR.TIME) return Search_Value_Type is
  327.  
  328.     -- this function was designed by A. S. Peterson, LMSC according
  329.     -- to the author's specifications. Only extremely minor changes
  330.     -- were made to the algorithm by the author.
  331.  
  332.     -- the following constant hardcodes the algorithm to work at the
  333.     -- reference point of 1984. Other hardcoded constants in the
  334.     -- code nail the exact day, 1/1/84, to Sunday. If the reference
  335.     -- point is changed, then so must the day names returned.
  336.  
  337.     Ref_Year                : constant CALENDAR.YEAR_NUMBER := 1984;
  338.  
  339.     Number_of_Days_in_a_Leap_Year     : constant POSITIVE := 366;
  340.     Number_of_Days_in_a_Normal_Year   : constant POSITIVE := 365;
  341.     Number_of_Days_in_Feb_Leap_Year   : constant POSITIVE :=  29;
  342.     Number_of_Days_in_Feb_Normal_Year : constant POSITIVE :=  28;
  343.     Number_of_Days_in_Long_Months     : constant POSITIVE :=  31;
  344.     Number_of_Days_in_Short_Months    : constant POSITIVE :=  30;
  345.  
  346.     February                          : constant POSITIVE :=   2;
  347.     April                             : constant POSITIVE :=   4;
  348.     June                              : constant POSITIVE :=   6;
  349.     September                         : constant POSITIVE :=   9;
  350.     November                          : constant POSITIVE :=  11;
  351.  
  352.     -- local types/subtypes follow below
  353.  
  354.     subtype Number_of_Days_Type is INTEGER
  355.       range -Number_of_Days_in_a_Week + 1 .. Number_of_Days_in_a_Week - 1;
  356.  
  357.     -- local variables follow below
  358.  
  359.     First_Year,
  360.     Last_Year,
  361.     Input_Year              : CALENDAR.YEAR_NUMBER;
  362.     Month_Count,
  363.     Input_Month             : CALENDAR.MONTH_NUMBER;
  364.     Input_Day               : CALENDAR.DAY_NUMBER;
  365.     Number_of_Days          : INTEGER                       := 0;
  366.     After_Ref_Year          : BOOLEAN;
  367.     Constrained_Num_Days    : Number_of_Days_Type;
  368.  
  369.     -- local function follows below
  370.  
  371.     function Leap_Year(In_Year : in CALENDAR.YEAR_NUMBER) return BOOLEAN is
  372.       Leap_Year_Century : constant POSITIVE := 400;
  373.       Leap_Year_Offset  : constant POSITIVE :=   4;
  374.       Century           : constant POSITIVE := 100;
  375.     begin
  376.       if ((In_Year rem Leap_Year_Century) = 0) or
  377.          (((In_Year rem Leap_Year_Offset) = 0) and ((In_Year rem Century) /= 0)) then
  378.         return TRUE;   -- the year specified is a leap year
  379.       else
  380.         return FALSE;  -- the year specified is not a leap year
  381.       end if;
  382.     end Leap_Year;
  383.   begin  -- Compute_Day_of_Week
  384.     -- decode the CALENDAR.TIME into its subcomponents
  385.  
  386.     Input_Year  := CALENDAR.YEAR(TOD_VALUE);
  387.     Input_Month := CALENDAR.MONTH(TOD_VALUE);
  388.     Input_Day   := CALENDAR.DAY(TOD_VALUE);
  389.  
  390.     -- start of the algorithm follows below
  391.  
  392.     if Input_Year < Ref_Year then
  393.       After_Ref_Year := FALSE;
  394.       First_Year := Input_Year;
  395.       Last_Year := Ref_Year;
  396.     else
  397.       After_Ref_Year := TRUE;
  398.       First_Year := Ref_Year;
  399.       Last_Year  := Input_Year;
  400.     end if;
  401.  
  402.     while First_Year < Last_Year loop
  403.       if Leap_Year(First_Year) then
  404.         if After_Ref_Year then
  405.           Number_of_Days := Number_of_Days + Number_of_Days_in_a_Leap_Year;
  406.         else
  407.           Number_of_Days := Number_of_Days - Number_of_Days_in_a_Leap_Year;
  408.         end if;
  409.       elsif After_Ref_Year then
  410.         Number_of_Days := Number_of_Days + Number_of_Days_in_a_Normal_Year;
  411.       else
  412.         Number_of_Days := Number_of_Days - Number_of_Days_in_a_Normal_Year;
  413.       end if;
  414.  
  415.       First_Year := First_Year + 1;
  416.     end loop;
  417.  
  418.     Month_Count := 1;
  419.  
  420.     while Month_Count < Input_Month loop
  421.       case Month_Count is
  422.        when February =>
  423.         if Leap_Year(Input_Year) then
  424.           Number_of_Days := Number_of_Days + Number_of_Days_in_Feb_Leap_Year;
  425.         else
  426.           Number_of_Days := Number_of_Days + Number_of_Days_in_Feb_Normal_Year;
  427.         end if;
  428.        when April | June | September | November =>
  429.         Number_of_Days := Number_of_Days + Number_of_Days_in_Short_Months;
  430.        when others =>
  431.         Number_of_Days := Number_of_Days + Number_of_Days_in_Long_Months;
  432.       end case;
  433.  
  434.       Month_Count := Month_Count + 1;
  435.     end loop;
  436.  
  437.     Constrained_Num_Days := (Number_of_Days + Input_Day) rem Number_of_Days_in_a_Week;
  438.  
  439.     case Constrained_Num_Days is
  440.      when -6 =>
  441.       return "SUNDAY   ";
  442.      when -5 =>
  443.       return "MONDAY   ";
  444.      when -4 =>
  445.       return "TUESDAY  ";
  446.      when -3 =>
  447.       return "WEDNESDAY";
  448.      when -2 =>
  449.       return "THURSDAY ";
  450.      when -1 =>
  451.       return "FRIDAY   ";
  452.      when  0 =>
  453.       return "SATURDAY ";
  454.      when  1 =>
  455.       return "SUNDAY   ";  -- algorithm hardcoded on this day
  456.      when  2 =>
  457.       return "MONDAY   ";
  458.      when  3 =>
  459.       return "TUESDAY  ";
  460.      when  4 =>
  461.       return "WEDNESDAY";
  462.      when  5 =>
  463.       return "THURSDAY ";
  464.      when  6 =>
  465.       return "FRIDAY   ";
  466.     end case;
  467.   end Compute_Day_of_Week;
  468.  
  469.   -- the function below converts an internal CALENDAR.TIME value to
  470.   -- an external STRING value
  471.  
  472.   function CONVERT_INTERNAL_TOD_TO_EXTERNAL_TOD(
  473.     TOD_VALUE : in CALENDAR.TIME;
  474.     DEFAULT_SETTING : in TYPE_SET := UPPER_CASE)
  475.     return EXTERNAL_TOD_REPRESENTATION_TYPE is
  476.  
  477.     -- constants for array positions of each component of the external
  478.     -- representation type follow below
  479.  
  480.     Day_Number_Start            : constant POSITIVE := 11;
  481.     Day_Number_End              : constant POSITIVE := 12;
  482.     Month_Name_Start            : constant POSITIVE := 14;
  483.     Month_Name_End              : constant POSITIVE := 22;
  484.     Year_Number_Start           : constant POSITIVE := 24;
  485.     Year_Number_End             : constant POSITIVE := 27;
  486.     Time_Start                  : constant POSITIVE := 29;
  487.     Time_End                    : constant POSITIVE := 38;
  488.     Hour_Start                  : constant POSITIVE := 29;
  489.     Hour_End                    : constant POSITIVE := 30;
  490.     Minute_Start                : constant POSITIVE := 32;
  491.     Minute_End                  : constant POSITIVE := 33;
  492.     Second_Start                : constant POSITIVE := 35;
  493.     Second_End                  : constant POSITIVE := 36;
  494.     AMPM_Start                  : constant POSITIVE := 37;
  495.     AMPM_End                    : constant POSITIVE := 38;
  496.  
  497.     -- constants to make the code more readable
  498.  
  499.     Leading_Zero                : constant CHARACTER := '0';
  500.  
  501.     -- local type/subtype declarations follow below
  502.  
  503.     subtype Double_Digits is NATURAL range 10 .. NATURAL'LAST;
  504.     subtype Afternoon_or_Evening is NATURAL range Noon_Hour .. Number_of_Hours_in_Day - 1;
  505.  
  506.     -- local variables follow below
  507.  
  508.     Year : CALENDAR.YEAR_NUMBER;
  509.     Month : CALENDAR.MONTH_NUMBER;
  510.     Day : CALENDAR.DAY_NUMBER;
  511.     Seconds : CALENDAR.DAY_DURATION;
  512.     Curr_Hour : NATURAL range 00 .. Number_of_Hours_in_Day;
  513.     Curr_Minute : NATURAL range 00 .. Number_of_Minutes_in_Hour - 1;
  514.     Curr_Second : NATURAL range 00 .. Number_of_Seconds_in_Minute - 1;
  515.     Seconds_as_Natural : NATURAL range 0 .. Number_of_Seconds_in_Day;
  516.     Temp_Value,
  517.     Return_Value : EXTERNAL_TOD_REPRESENTATION_TYPE := (others => Blank);
  518.  
  519.     -- local procs follow below
  520.  
  521.     procedure Convert_Upper_Case_to_Lower_Case(TOD_Value : in out STRING) is
  522.     begin
  523.       for I in TOD_Value'RANGE loop
  524.         if TOD_Value(I) in Set_of_Upper_Case_Letters then
  525.           TOD_Value(I) := CHARACTER'VAL(CHARACTER'POS(TOD_Value(I)) + UC_LC_Offset);
  526.         end if;
  527.       end loop;
  528.     end Convert_Upper_Case_to_Lower_Case;
  529.  
  530.     procedure Convert_Upper_Case_to_Mixed_Case(TOD_Value : in out STRING) is
  531.     begin
  532.       for I in TOD_Value'FIRST + 1 .. TOD_Value'LAST loop
  533.         if (TOD_Value(I) in Set_of_Upper_Case_Letters) and (TOD_Value(I-1) /= Blank) then
  534.           TOD_Value(I) := CHARACTER'VAL(CHARACTER'POS(TOD_Value(I)) + UC_LC_Offset);
  535.         end if;
  536.       end loop;
  537.  
  538.       -- special case: AM/PM indicator.
  539.  
  540.       TOD_Value(AMPM_Start) := CHARACTER'VAL(CHARACTER'POS(TOD_Value(AMPM_Start)) - UC_LC_Offset);
  541.     end Convert_Upper_Case_to_Mixed_Case;
  542.  
  543.   -- the body of CONVERT_INTERNAL_TOD_TO_EXTERNAL_TOD follows below
  544.  
  545.   begin
  546.     -- store day of the week string
  547.  
  548.     Return_Value(Day_Name_Start .. Day_Name_End) := Compute_Day_of_Week(TOD_VALUE);
  549.  
  550.     -- disect internal format into its components for our own use
  551.  
  552.     CALENDAR.SPLIT(TOD_VALUE,Year,Month,Day,Seconds);
  553.  
  554.     -- store day number value
  555.  
  556.     if Day in Double_Digits then
  557.       Temp_Value(Day_Number_Start - 1 .. Day_Number_End) := CALENDAR.DAY_NUMBER'IMAGE(Day);
  558.       Return_Value(Day_Number_Start .. Day_Number_End) := Temp_Value(Day_Number_Start .. Day_Number_End);
  559.     else
  560.       Temp_Value(Day_Number_End - 1 .. Day_Number_End) := CALENDAR.DAY_NUMBER'IMAGE(Day);
  561.       Return_Value(Day_Number_Start) := '0';
  562.       Return_Value(Day_Number_End) := Temp_Value(Day_Number_End);
  563.     end if;
  564.  
  565.     -- store the month name and year number
  566.  
  567.     Return_Value(Month_Name_Start .. Month_Name_End) := Month_Name_Array(Month);
  568.     Temp_Value(Year_Number_Start - 1 .. Year_Number_End) := CALENDAR.YEAR_NUMBER'IMAGE(Year);
  569.     Return_Value(Year_Number_Start .. Year_Number_End) := Temp_Value(Year_Number_Start .. Year_Number_End);
  570.  
  571.     -- convert CALENDAR.DAY_DURATION value to NATURAL for easier
  572.     -- calculations below
  573.  
  574.     Seconds_as_Natural := NATURAL(Seconds);
  575.  
  576.     -- compute the current hour, minutes, and seconds
  577.  
  578.     Curr_Hour := (Seconds_as_Natural / Number_of_Minutes_in_Hour) /
  579.       Number_of_Seconds_in_Minute;
  580.     Curr_Minute := (Seconds_as_Natural / Number_of_Minutes_in_Hour) mod
  581.       Number_of_Seconds_in_Minute;
  582.     Curr_Second := Seconds_as_Natural -
  583.       (Curr_Hour * Number_of_Seconds_in_Hour) -
  584.       (Curr_Minute * Number_of_Minutes_in_Hour);
  585.  
  586.     -- check for AM/PM in current hour and store AM/PM indication
  587.  
  588.     if    (Curr_Hour = 00) or (Curr_Hour = Number_of_Hours_in_Day) then
  589.       Curr_Hour := Noon_Hour;  -- 00:00:00 === 12:00:00 AM === 24:00:00
  590.       Return_Value(AMPM_Start .. AMPM_End) := "AM";
  591.     elsif (Curr_Hour in Afternoon_or_Evening) and (Curr_Hour /= Noon_Hour) then
  592.       Curr_Hour := Curr_Hour - Noon_Hour;  -- convert to AM/PM format
  593.       Return_Value(AMPM_Start .. AMPM_End) := "PM";
  594.     elsif Curr_Hour = Noon_Hour then
  595.       Return_Value(AMPM_Start .. AMPM_End) := "PM";
  596.     else
  597.       Return_Value(AMPM_Start .. AMPM_End) := "AM";
  598.     end if;
  599.  
  600.     -- store current hour
  601.  
  602.     if Curr_Hour in Double_Digits then
  603.       Temp_Value(Hour_Start - 1 .. Hour_End) := NATURAL'IMAGE(Curr_Hour);
  604.       Return_Value(Hour_Start .. Hour_End) := Temp_Value(Hour_Start .. Hour_End);
  605.     else
  606.       Temp_Value(Hour_End - 1 .. Hour_End) := NATURAL'IMAGE(Curr_Hour);
  607.       Return_Value(Hour_Start) := Leading_Zero;
  608.       Return_Value(Hour_End) := Temp_Value(Hour_End);
  609.     end if;
  610.  
  611.     Return_Value(Hour_End + 1) := Colon;
  612.  
  613.     -- store current minutes
  614.  
  615.     if Curr_Minute in Double_Digits then
  616.       Temp_Value(Minute_Start - 1 .. Minute_End) := NATURAL'IMAGE(Curr_Minute);
  617.       Return_Value(Minute_Start .. Minute_End) := Temp_Value(Minute_Start .. Minute_End);
  618.     else
  619.       Temp_Value(Minute_End - 1 .. Minute_End) := NATURAL'IMAGE(Curr_Minute);
  620.       Return_Value(Minute_Start) := Leading_Zero;
  621.       Return_Value(Minute_End) := Temp_Value(Minute_End);
  622.     end if;
  623.  
  624.     Return_Value(Minute_End + 1) := Colon;
  625.  
  626.     -- store current seconds
  627.  
  628.     if Curr_Second in Double_Digits then
  629.       Temp_Value(Second_Start - 1 .. Second_End) := NATURAL'IMAGE(Curr_Second);
  630.       Return_Value(Second_Start .. Second_End) := Temp_Value(Second_Start .. Second_End);
  631.     else
  632.       Temp_Value(Second_End - 1 .. Second_End) := NATURAL'IMAGE(Curr_Second);
  633.       Return_Value(Second_Start) := Leading_Zero;
  634.       Return_Value(Second_End) := Temp_Value(Second_End);
  635.     end if;
  636.  
  637.     -- set non-default type set for the user?
  638.  
  639.     if DEFAULT_SETTING = lower_case then
  640.       Convert_Upper_Case_to_Lower_Case(Return_Value);
  641.     elsif DEFAULT_SETTING = Mixed_Case then
  642.       Convert_Upper_Case_to_Mixed_Case(Return_Value);
  643.     end if;
  644.  
  645.     -- we are done. Return the external format to the user.
  646.  
  647.     return Return_Value;
  648.   end CONVERT_INTERNAL_TOD_TO_EXTERNAL_TOD;
  649.  
  650.   -- the function below converts an external format TOD to the Ada
  651.   -- internal format, CALENDAR.TIME .
  652.  
  653.   function CONVERT_EXTERNAL_TOD_TO_INTERNAL_TOD(
  654.     TOD_STRING : in STRING) return CALENDAR.TIME is
  655.  
  656.     -- local constants follow below
  657.  
  658.     Comma                     : constant CHARACTER     := ',';
  659.     Minus                     : constant CHARACTER     := '-';
  660.     Slash                     : constant CHARACTER     := '/';
  661.     Current_Time              : constant CALENDAR.TIME := CALENDAR.CLOCK;
  662.     Minimum_TOD_STRING_Length : constant POSITIVE      := 2;
  663.  
  664.     -- local types/subtypes follow below
  665.  
  666.     subtype TOD_Value_Length_Type is NATURAL range 0 .. TOD_STRING'LAST - TOD_STRING'FIRST + 1;
  667.     subtype TOD_Value_Pointer_Type is POSITIVE range TOD_STRING'FIRST .. TOD_STRING'LAST + 1;
  668.  
  669.     type Token_Type is (Day_as_Name,Day_as_Number,Month_Name_or_Number,
  670.       Year_Number,Time_String,Special_Format);
  671.     type Tokens_Specified_Array_Type is array(Token_Type) of BOOLEAN;
  672.  
  673.     -- local variables follow below
  674.  
  675.     TOD_Value                   : STRING(TOD_STRING'FIRST .. TOD_STRING'LAST) := TOD_STRING;
  676.     TOD_Value_Compressed_Length,
  677.     Token_Length                : TOD_Value_Length_Type;
  678.     TOD_Value_Pointer           : TOD_Value_Pointer_Type   := TOD_Value'FIRST;
  679.     Token                       : STRING(TOD_Value'RANGE);
  680.     Year                        : CALENDAR.YEAR_NUMBER     := CALENDAR.YEAR(Current_Time);
  681.     Month                       : CALENDAR.MONTH_NUMBER    := CALENDAR.MONTH(Current_Time);
  682.     Day                         : CALENDAR.DAY_NUMBER      := CALENDAR.DAY(Current_Time);
  683.     Seconds                     : CALENDAR.DAY_DURATION    := CALENDAR.DAY_DURATION'FIRST;
  684.     Day_Name                    : Search_Value_Type;
  685.     No_Token_Found              : BOOLEAN;
  686.     Return_Time_Value           : CALENDAR.TIME            :=
  687.       CALENDAR.TIME_OF(Year,Month,Day,CALENDAR.DAY_DURATION'FIRST);
  688.     Tokens_Specified_Array      : Tokens_Specified_Array_Type := (others => FALSE);
  689.  
  690.     -- local procs/funcs follow below
  691.  
  692.     function "+"(Left : CALENDAR.TIME; Right : DURATION) return CALENDAR.TIME renames CALENDAR."+";
  693.     function "-"(Left : CALENDAR.TIME; Right : DURATION) return CALENDAR.TIME renames CALENDAR."-";
  694.  
  695.     procedure Compress_External_Representation(
  696.       TOD_Value                   : in out STRING;
  697.       TOD_Value_Compressed_Length :    out TOD_Value_Length_Type) is
  698.  
  699.       -- local variables follow below
  700.  
  701.       TOD_Value_Copy : STRING(TOD_Value'RANGE) := (others => Blank);
  702.       TOD_Value_Pointer,
  703.       TOD_Value_Pointer_Copy : TOD_Value_Pointer_Type := TOD_Value'FIRST;
  704.     begin
  705.       -- change all commas to blanks and all minus signs to slash
  706.       -- signs for easier parsing
  707.  
  708.       for I in TOD_Value'RANGE loop
  709.         if TOD_Value(I) = Comma then
  710.           TOD_Value(I) := Blank;
  711.         elsif TOD_Value(I) = Minus then
  712.           TOD_Value(I) := Slash;
  713.         end if;
  714.       end loop;
  715.  
  716.       -- skip over leading blanks
  717.  
  718.       while (TOD_Value_Pointer <= TOD_Value'LAST) and then
  719.             (TOD_Value(TOD_Value_Pointer) = Blank) loop
  720.         TOD_Value_Pointer := TOD_Value_Pointer + 1;
  721.       end loop;
  722.  
  723.       -- skip over excessive number of blanks in the middle of
  724.       -- the string.
  725.  
  726.       while (TOD_Value_Pointer <= TOD_Value'LAST - 2) loop
  727.         if    (TOD_Value(TOD_Value_Pointer)     = Blank) and
  728.               (TOD_Value(TOD_Value_Pointer + 1) = Blank) and
  729.               (TOD_Value(TOD_Value_Pointer + 2) = Blank) then
  730.           TOD_Value_Pointer := TOD_Value_Pointer + 2;
  731.         elsif (TOD_Value(TOD_Value_Pointer)     = Blank) and
  732.               (TOD_Value(TOD_Value_Pointer + 1) = Blank) then
  733.           TOD_Value_Pointer      := TOD_Value_Pointer      + 2;
  734.           TOD_Value_Pointer_Copy := TOD_Value_Pointer_Copy + 1;
  735.         elsif (TOD_Value(TOD_Value_Pointer)     = Blank) then
  736.           TOD_Value_Pointer      := TOD_Value_Pointer      + 1;
  737.           TOD_Value_Pointer_Copy := TOD_Value_Pointer_Copy + 1;
  738.         else
  739.           TOD_Value_Copy(TOD_Value_Pointer_Copy) := TOD_Value(TOD_Value_Pointer);
  740.           TOD_Value_Pointer      := TOD_Value_Pointer      + 1;
  741.           TOD_Value_Pointer_Copy := TOD_Value_Pointer_Copy + 1;
  742.         end if;
  743.       end loop;
  744.  
  745.       -- now handle special cases near the end of the string
  746.  
  747.       if (TOD_Value'FIRST + TOD_Value'LAST - 1 >= 3) and then
  748.          ((TOD_Value(TOD_Value'LAST - 2) /= Blank) and
  749.           (TOD_Value(TOD_Value'LAST - 1)  = Blank) and
  750.           (TOD_Value(TOD_Value'LAST)     /= Blank)
  751.          ) then
  752.         TOD_Value_Pointer_Copy := TOD_Value_Pointer_Copy + 1;
  753.       end if;
  754.  
  755.       if (TOD_Value'FIRST + TOD_Value'LAST - 1 >= 2) and then
  756.          (TOD_Value(TOD_Value'LAST - 1) /= Blank) then
  757.         TOD_Value_Copy(TOD_Value_Pointer_Copy) := TOD_Value(TOD_Value'LAST - 1);
  758.         TOD_Value_Pointer_Copy := TOD_Value_Pointer_Copy + 1;
  759.       end if;
  760.  
  761.       if (TOD_Value'FIRST + TOD_Value'LAST - 1 >= 1) and then
  762.          (TOD_Value(TOD_Value'LAST) /= Blank) then
  763.         TOD_Value_Copy(TOD_Value_Pointer_Copy) := TOD_Value(TOD_Value'LAST);
  764.         TOD_Value_Pointer_Copy := TOD_Value_Pointer_Copy + 1;
  765.       end if;
  766.  
  767.       -- now return the compressed string and corresponding length
  768.  
  769.       TOD_Value := TOD_Value_Copy;
  770.       TOD_Value_Compressed_Length := TOD_Value_Pointer_Copy - TOD_Value'FIRST;
  771.     end Compress_External_Representation;
  772.  
  773.     procedure Convert_External_Representation_to_Upper_Case(TOD_Value : in out STRING) is
  774.       subtype Set_of_Lower_Case_Letters is CHARACTER range ASCII.LC_A .. ASCII.LC_Z;
  775.     begin
  776.       -- loop on all characters in the compressed TOD_Value. Modify
  777.       -- all lower case letters to upper case.
  778.  
  779.       for I in TOD_Value'FIRST .. TOD_Value'FIRST + TOD_Value_Compressed_Length - 1 loop
  780.         if TOD_Value(I) in Set_of_Lower_Case_Letters then
  781.           TOD_Value(I) := CHARACTER'VAL(CHARACTER'POS(TOD_Value(I)) - UC_LC_Offset);
  782.         end if;
  783.       end loop;
  784.     end Convert_External_Representation_to_Upper_Case;
  785.  
  786.     procedure Grab_a_Token(
  787.       TOD_Value         : in     STRING;
  788.       TOD_Value_Pointer : in out TOD_Value_Pointer_Type;
  789.       Token             :    out STRING;
  790.       Token_Length      :    out TOD_Value_Length_Type;
  791.       No_Token_Found    :    out BOOLEAN) is
  792.  
  793.       -- local variables follow below
  794.  
  795.       Local_Token   : STRING(Token'RANGE) := (others => Blank);
  796.       Token_Pointer : TOD_Value_Pointer_Type := Local_Token'FIRST;
  797.     begin
  798.       -- grab the next token
  799.  
  800.       while (TOD_Value_Pointer <= TOD_Value_Compressed_Length + TOD_Value'FIRST - 1) and then
  801.             (TOD_Value(TOD_Value_Pointer) /= Blank) loop
  802.         Local_Token(Token_Pointer) := TOD_Value(TOD_Value_Pointer);
  803.         Token_Pointer := Token_Pointer + 1;
  804.         TOD_Value_Pointer := TOD_Value_Pointer + 1;
  805.       end loop;
  806.  
  807.       -- skip over that blank, but don't skip outside the bounds
  808.  
  809.       if TOD_Value_Pointer < TOD_Value_Pointer_Type'LAST then
  810.         TOD_Value_Pointer := TOD_Value_Pointer + 1;
  811.       end if;
  812.  
  813.       -- did we find a token? Return T/F. Also return the token and length
  814.  
  815.       No_Token_Found := (Local_Token(Local_Token'FIRST) = Blank);
  816.       Token := Local_Token;
  817.       Token_Length := Token_Pointer - Local_Token'FIRST;
  818.     end Grab_a_Token;
  819.  
  820.     procedure Analyze_and_Process_Token(
  821.       Token        : in STRING;
  822.       Token_Length : in TOD_Value_Length_Type;
  823.       Month_Only   : in BOOLEAN) is
  824.  
  825.       Current_Century : constant POSITIVE := (CALENDAR.YEAR(Current_Time) / 100) * 100;
  826.  
  827.       subtype Short_Year_Range is NATURAL range 0 .. 99;
  828.       subtype Set_of_Numerics is CHARACTER range '0' .. '9';
  829.  
  830.       --local procs/funcs follow below
  831.  
  832.       function Token_Contains_Illegal_Characters(
  833.         Token        : in STRING;
  834.         Token_Length : in TOD_Value_Length_Type) return BOOLEAN is
  835.  
  836.         Only_Legals : BOOLEAN := TRUE;  -- assume the best
  837.       begin
  838.         -- short-circuits below used for speed
  839.         
  840.         for I in Token'FIRST .. Token'FIRST + Token_Length - 1 loop
  841.           Only_Legals := Only_Legals and then
  842.             ((Token(I) in Set_of_Upper_Case_Letters) or else
  843.              (Token(I) in Set_of_Numerics)           or else
  844.              (Token(I) = Colon)                      or else
  845.              (Token(I) = Period)                     or else
  846.              (Token(I) = Slash));
  847.         end loop;
  848.  
  849.         return (not Only_Legals);
  850.       end Token_Contains_Illegal_Characters;
  851.  
  852.       function Token_Contains_Only_Letters(
  853.         Token        : in STRING;
  854.         Token_Length : in TOD_Value_Length_Type) return BOOLEAN is
  855.  
  856.         Only_Letters : BOOLEAN := TRUE;   -- assume the best
  857.       begin
  858.         for I in Token'FIRST .. Token'FIRST + Token_Length - 1 loop
  859.           -- check for a period in an abbreviation. The period can only
  860.           -- appear as the last character on the token, otherwise the
  861.           -- token is invalid.
  862.  
  863.           if ((Token(I) = Period) and (I /= Token'FIRST + Token_Length - 1)) and then
  864.              Token(I+1) /= Slash then
  865.             raise ABBREVIATION_ERROR;
  866.           end if;
  867.  
  868.           -- now check to make sure that the current character being
  869.           -- checked is a letter. (short-circuits below used for speed.)
  870.  
  871.           Only_Letters := Only_Letters and then
  872.             ((Token(I) in Set_of_Upper_Case_Letters) or else (Token(I) = Period));
  873.         end loop;
  874.  
  875.         return Only_Letters;
  876.       end Token_Contains_Only_Letters;
  877.  
  878.       function Token_Contains_No_Letters(
  879.         Token        : in STRING;
  880.         Token_Length : in TOD_Value_Length_Type) return BOOLEAN is
  881.  
  882.         No_Letters : BOOLEAN := TRUE;  -- assume the best
  883.       begin
  884.         -- short-circuit below used for speed
  885.          
  886.         for I in Token'FIRST .. Token'FIRST + Token_Length - 1 loop
  887.           No_Letters := No_Letters and then (not (Token(I) in Set_of_Upper_Case_Letters));
  888.         end loop;
  889.  
  890.         return No_Letters;
  891.       end Token_Contains_No_Letters;
  892.  
  893.       function Token_Contains_Only_Numerics(
  894.         Token        : in STRING;
  895.         Token_Length : in TOD_Value_Length_Type) return BOOLEAN is
  896.  
  897.         Only_Numerics : BOOLEAN := TRUE;  -- assume the best
  898.       begin
  899.         -- short-circuit below used for speed
  900.         
  901.         for I in Token'FIRST .. Token'FIRST + Token_Length - 1 loop
  902.           Only_Numerics := Only_Numerics and then (Token(I) in Set_of_Numerics);
  903.         end loop;
  904.  
  905.         return Only_Numerics;
  906.       end Token_Contains_Only_Numerics;
  907.  
  908.       function Token_Contains_Slash(
  909.         Token        : in STRING;
  910.         Token_Length : in TOD_Value_Length_Type) return BOOLEAN is
  911.  
  912.         Slash_Found : BOOLEAN := FALSE;  -- assume the worst
  913.       begin
  914.         -- short-circuit below used for speed
  915.         
  916.         for I in Token'FIRST .. Token'FIRST + Token_Length - 1 loop
  917.           Slash_Found := Slash_Found or else (Token(I) = Slash);
  918.         end loop;
  919.  
  920.         return Slash_Found;
  921.       end Token_Contains_Slash;
  922.  
  923.       function Token_Contains_Colon_and_Numerics_with_Optional_AMPM(
  924.         Token        : in STRING;
  925.         Token_Length : in TOD_Value_Length_Type) return BOOLEAN is
  926.  
  927.         Colon_Found : BOOLEAN := FALSE;  -- assume the worst
  928.         Valid_Token : BOOLEAN := TRUE;   -- assume the best
  929.       begin
  930.         -- short-circuits below used for speed
  931.         
  932.         for I in Token'FIRST .. Token'FIRST + Token_Length - 3 loop
  933.           if Token(I) /= Colon then
  934.             Valid_Token := Valid_Token and then (Token(I) in Set_of_Numerics);
  935.           else
  936.             Colon_Found := TRUE;
  937.           end if;
  938.         end loop;
  939.  
  940.         if Token_Length < 3 then
  941.           Valid_Token := FALSE;
  942.         elsif (Token(Token'FIRST + Token_Length - 2 .. Token'FIRST + Token_Length - 1) /= "AM") and then
  943.               (Token(Token'FIRST + Token_Length - 2 .. Token'FIRST + Token_Length - 1) /= "PM") then
  944.           Valid_Token := Valid_Token and then (Token(Token'FIRST + Token_Length - 2) in Set_of_Numerics);
  945.           Valid_Token := Valid_Token and then (Token(Token'FIRST + Token_Length - 1) in Set_of_Numerics);
  946.         end if;
  947.  
  948.         return Valid_Token and then Colon_Found;
  949.       end Token_Contains_Colon_and_Numerics_with_Optional_AMPM;
  950.  
  951.       function Token_Contains_Numerics_and_AMPM(
  952.         Token        : in STRING;
  953.         Token_Length : in TOD_Value_Length_Type) return BOOLEAN is
  954.  
  955.         Valid_Token  : BOOLEAN := TRUE;  -- assume the best
  956.       begin
  957.         -- short-circuits below used for speed
  958.         
  959.         for I in Token'FIRST .. Token'FIRST + Token_Length - 3 loop
  960.           Valid_Token := Valid_Token and then (Token(I) in Set_of_Numerics);
  961.         end loop;
  962.  
  963.         if (Token(Token'FIRST + Token_Length - 2 .. Token'FIRST + Token_Length - 1) /= "AM") and then
  964.            (Token(Token'FIRST + Token_Length - 2 .. Token'FIRST + Token_Length - 1) /= "PM") then
  965.           Valid_Token := FALSE;
  966.         end if;
  967.  
  968.         return Valid_Token;
  969.       end Token_Contains_Numerics_and_AMPM;
  970.  
  971.       function Convert_Token_to_Proper_Length(Token : in STRING) return Search_Value_Type is
  972.         Token_Copy : Search_Value_Type := (others => blank);
  973.         I : POSITIVE range Token'FIRST .. Token'FIRST + Max_Valid_Letter_Token_Length := Token'FIRST;
  974.       begin
  975.         -- short-circuit below used for speed
  976.         
  977.         while (I <= Token'FIRST + Max_Valid_Letter_Token_Length - 1) and then
  978.               (I <= Token'LAST) loop
  979.           Token_Copy(I) := Token(I);
  980.           I := I + 1;
  981.         end loop;
  982.  
  983.         return Token_Copy;
  984.       end Convert_Token_to_Proper_Length;
  985.  
  986.       procedure Analyze_and_Process_Day_Name_or_Month_Name_or_Special(
  987.         Token        : in STRING;
  988.         Token_Length : in TOD_Value_Length_Type;
  989.         Month_Only   : in BOOLEAN) is
  990.  
  991.         Local_Token            : Search_Value_Type     := Convert_Token_to_Proper_Length(Token);
  992.         Local_Token_Length     : TOD_Value_Length_Type := Token_Length;
  993.         Location_Found         : POSITIVE;
  994.         Component_Found,
  995.         Abbreviation_Specified : BOOLEAN;
  996.  
  997.         -- establish the arrays of possible days, months, and special
  998.         -- components
  999.  
  1000.         Number_of_Day_Match_Components   : constant POSITIVE := 43;
  1001.         Number_of_Month_Match_Components : constant POSITIVE := 50;
  1002.         Number_of_Special_Components     : constant POSITIVE :=  4;
  1003.  
  1004.         type My_Array_Type is array(POSITIVE range <>) of Search_Value_Type;
  1005.         subtype Day_Match_Array_Index_Type is POSITIVE range 1 .. Number_of_Day_Match_Components;
  1006.         subtype Month_Match_Array_Index_Type is POSITIVE range 1 .. Number_of_Month_Match_Components;
  1007.         subtype Specials_Array_Index_Type is POSITIVE range 1 .. Number_of_Special_Components;
  1008.  
  1009.         Day_Match_Array : constant My_Array_Type(Day_Match_Array_Index_Type) :=
  1010.           ("SU       ","SUN      ","SUND     ","SUNDA    ","SUNDAY   ",
  1011.            "MO       ","MON      ","MOND     ","MONDA    ","MONDAY   ",
  1012.            "TU       ","TUE      ","TUES     ","TUESD    ","TUESDA   ","TUESDAY  ",
  1013.            "WE       ","WED      ","WEDN     ","WEDNE    ","WEDNES   ","WEDNESD  ","WEDNESDA ","WEDNESDAY",
  1014.            "TH       ","THU      ","THUR     ","THURS    ","THURSD   ","THURSDA  ","THURSDAY ",
  1015.            "FR       ","FRI      ","FRID     ","FRIDA    ","FRIDAY   ",
  1016.            "SA       ","SAT      ","SATU     ","SATUR    ","SATURD   ","SATURDA  ","SATURDAY ");
  1017.  
  1018.         Month_Match_Array : constant My_Array_Type(Month_Match_Array_Index_Type) :=
  1019.           ("JAN      ","JANU     ","JANUA    ","JANUAR   ","JANUARY  ",
  1020.            "FEB      ","FEBR     ","FEBRU    ","FEBRUA   ","FEBRUAR  ","FEBRUARY ",
  1021.            "MAR      ","MARC     ","MARCH    ",
  1022.            "APR      ","APRI     ","APRIL    ",
  1023.            "MAY      ",
  1024.            "JUN      ","JUNE     ",
  1025.            "JUL      ","JULY     ",
  1026.            "AUG      ","AUGU     ","AUGUS    ","AUGUST   ",
  1027.            "SEP      ","SEPT     ","SEPTE    ","SEPTEM   ","SEPTEMB  ","SEPTEMBE ","SEPTEMBER",
  1028.            "OCT      ","OCTO     ","OCTOB    ","OCTOBE   ","OCTOBER  ",
  1029.            "NOV      ","NOVE     ","NOVEM    ","NOVEMB   ","NOVEMBE  ","NOVEMBER ",
  1030.            "DEC      ","DECE     ","DECEM    ","DECEMB   ","DECEMBE  ","DECEMBER ");
  1031.  
  1032.         Su_First : constant POSITIVE :=  1;  Jan_First : constant POSITIVE :=  1;
  1033.         Su_Last  : constant POSITIVE :=  5;  Jan_Last  : constant POSITIVE :=  5;
  1034.         Mo_First : constant POSITIVE :=  6;  Feb_First : constant POSITIVE :=  6;
  1035.         Mo_Last  : constant POSITIVE := 10;  Feb_Last  : constant POSITIVE := 11;
  1036.         Tu_First : constant POSITIVE := 11;  Mar_First : constant POSITIVE := 12;
  1037.         Tu_Last  : constant POSITIVE := 16;  Mar_Last  : constant POSITIVE := 14;
  1038.         We_First : constant POSITIVE := 17;  Apr_First : constant POSITIVE := 15;
  1039.         We_Last  : constant POSITIVE := 24;  Apr_Last  : constant POSITIVE := 17;
  1040.         Th_First : constant POSITIVE := 25;  May_First : constant POSITIVE := 18;
  1041.         Th_Last  : constant POSITIVE := 31;  May_Last  : constant POSITIVE := 18;
  1042.         Fr_First : constant POSITIVE := 32;  Jun_First : constant POSITIVE := 19;
  1043.         Fr_Last  : constant POSITIVE := 36;  Jun_Last  : constant POSITIVE := 20;
  1044.         Sa_First : constant POSITIVE := 37;  Jul_First : constant POSITIVE := 21;
  1045.         Sa_Last  : constant POSITIVE := 43;  Jul_Last  : constant POSITIVE := 22;
  1046.                                              Aug_First : constant POSITIVE := 23;
  1047.                                              Aug_Last  : constant POSITIVE := 26;
  1048.                                              Sep_First : constant POSITIVE := 27;
  1049.                                              Sep_Last  : constant POSITIVE := 33;
  1050.                                              Oct_First : constant POSITIVE := 34;
  1051.                                              Oct_Last  : constant POSITIVE := 38;
  1052.                                              Nov_First : constant POSITIVE := 39;
  1053.                                              Nov_Last  : constant POSITIVE := 44;
  1054.                                              Dec_First : constant POSITIVE := 45;
  1055.                                              Dec_Last  : constant POSITIVE := 50;
  1056.  
  1057.  
  1058.         Specials_Array : constant My_Array_Type(Specials_Array_Index_Type) :=
  1059.           ("NOW      ","TODAY    ","TOMORROW ","YESTERDAY");
  1060.  
  1061.         -- establish an instantiation of the generic search package
  1062.  
  1063.         package Search_For_Month_or_Day_Name_or_Specials is new Search_Utilities(
  1064.           Component_Type => Search_Value_Type,
  1065.           Index_Type     => POSITIVE,
  1066.           Array_Type     => My_Array_Type);
  1067.  
  1068.         -- local procedures folows below
  1069.  
  1070.         procedure Analyze_and_Process_Day_Name(
  1071.           Token                  : in STRING;
  1072.           Location_Found         : in Day_Match_Array_Index_Type;
  1073.           Abbreviation_Specified : in BOOLEAN) is
  1074.         begin
  1075.           -- check to see if the day name has already been specified
  1076.  
  1077.           if Tokens_Specified_Array(Day_as_Name) then
  1078.             raise DUPLICATION_ERROR;
  1079.           end if;
  1080.  
  1081.           Tokens_Specified_Array(Day_as_Name) := TRUE;
  1082.  
  1083.           -- now check to make sure that a period did not follow a full name
  1084.  
  1085.           if Abbreviation_Specified then
  1086.             declare
  1087.               type Array_Type is array(POSITIVE range <>) of Search_Value_Type;
  1088.  
  1089.               Days_Array : constant Array_Type(1 .. Number_of_Days_in_a_Week) :=
  1090.                 ("SUNDAY   ","MONDAY   ","TUESDAY  ","WEDNESDAY","THURSDAY ","FRIDAY   ","SATURDAY ");
  1091.  
  1092.               package Search_For_Full_Day_Name is new Search_Utilities(
  1093.                 Component_Type => Search_Value_Type,
  1094.                 Index_Type     => POSITIVE,
  1095.                 Array_Type     => Array_Type);
  1096.             begin
  1097.               if Search_For_Full_Day_Name.SEARCH(
  1098.                    Component    => Token,
  1099.                    Search_Array => Days_Array) then
  1100.                 raise ABBREVIATION_ERROR;
  1101.               end if;
  1102.             end;  -- local declare block
  1103.           end if;
  1104.  
  1105.           -- now store the day name for future processing
  1106.  
  1107.           case Location_Found is
  1108.            when Su_First .. Su_Last =>
  1109.             Day_Name := "SUNDAY   ";
  1110.            when Mo_First .. Mo_Last =>
  1111.             Day_Name := "MONDAY   ";
  1112.            when Tu_First .. Tu_Last =>
  1113.             Day_Name := "TUESDAY  ";
  1114.            when We_First .. We_Last =>
  1115.             Day_Name := "WEDNESDAY";
  1116.            when Th_First .. Th_Last =>
  1117.             Day_Name := "THURSDAY ";
  1118.            when Fr_First .. Fr_Last =>
  1119.             Day_Name := "FRIDAY   ";
  1120.            when Sa_First .. Sa_Last =>
  1121.             Day_Name := "SATURDAY ";
  1122.           end case;
  1123.         end Analyze_and_Process_Day_Name;
  1124.  
  1125.         procedure Analyze_and_Process_Month_Name(
  1126.           Token                  : in STRING;
  1127.           Location_Found         : in Month_Match_Array_Index_Type;
  1128.           Abbreviation_Specified : in BOOLEAN) is
  1129.         begin
  1130.           -- check to see if the month name has already been specified
  1131.  
  1132.           if Tokens_Specified_Array(Month_Name_or_Number) then
  1133.             raise DUPLICATION_ERROR;
  1134.           end if;
  1135.  
  1136.           Tokens_Specified_Array(Month_Name_or_Number) := TRUE;
  1137.  
  1138.           -- now check to make sure that a period did not follow a full name
  1139.  
  1140.           if Abbreviation_Specified then
  1141.             declare
  1142.               package Search_For_Full_Month_Name is new Search_Utilities(
  1143.                 Component_Type => Search_Value_Type,
  1144.                 Index_Type  => INTEGER,
  1145.                 Array_Type   => Month_Name_Array_Type);
  1146.             begin
  1147.               if Search_For_Full_Month_Name.SEARCH(
  1148.                    Component    => Token,
  1149.                    Search_Array => Month_Name_Array) then
  1150.                 raise ABBREVIATION_ERROR;
  1151.               end if;
  1152.             end;  -- local declare block
  1153.           end if;
  1154.  
  1155.           -- now store the month number
  1156.  
  1157.           case Location_Found is
  1158.            when Jan_First .. Jan_Last =>
  1159.             Month :=  1;
  1160.            when Feb_First .. Feb_Last =>
  1161.             Month :=  2;
  1162.            when Mar_First .. Mar_Last =>
  1163.             Month :=  3;
  1164.            when Apr_First .. Apr_Last =>
  1165.             Month :=  4;
  1166.            when May_First .. May_Last =>
  1167.             Month :=  5;
  1168.            when Jun_First .. Jun_Last =>
  1169.             Month :=  6;
  1170.            when Jul_First .. Jul_Last =>
  1171.             Month :=  7;
  1172.            when Aug_First .. Aug_Last =>
  1173.             Month :=  8;
  1174.            when Sep_First .. Sep_Last =>
  1175.             Month :=  9;
  1176.            when Oct_First .. Oct_Last =>
  1177.             Month := 10;
  1178.            when Nov_First .. Nov_Last =>
  1179.             Month := 11;
  1180.            when Dec_First .. Dec_Last =>
  1181.             Month := 12;
  1182.           end case;
  1183.         end Analyze_and_Process_Month_Name;
  1184.  
  1185.         procedure Analyze_and_Process_Special(
  1186.           Token                  : in STRING;
  1187.           Abbreviation_Specified : in BOOLEAN) is
  1188.         begin
  1189.           -- check to see if the special element has already been
  1190.           -- specified or if an illegal period was specified
  1191.  
  1192.           if Tokens_Specified_Array(Special_Format) then
  1193.             raise DUPLICATION_ERROR;
  1194.           elsif Abbreviation_Specified then
  1195.             raise ABBREVIATION_ERROR;
  1196.           end if;
  1197.  
  1198.           Tokens_Specified_Array(Special_Format) := TRUE;
  1199.  
  1200.           if    Token(Token'FIRST..Token'FIRST + Max_Valid_Letter_Token_Length - 1) = "NOW      " then
  1201.             Return_Time_Value := CALENDAR.CLOCK;
  1202.           elsif Token(Token'FIRST..Token'FIRST + Max_Valid_Letter_Token_Length - 1) = "YESTERDAY" then
  1203.             Return_Time_Value := CALENDAR.TIME_OF(Year,Month,Day,Seconds) - CALENDAR.DAY_DURATION'LAST;
  1204.           elsif Token(Token'FIRST..Token'FIRST + Max_Valid_Letter_Token_Length - 1) = "TOMORROW " then
  1205.             Return_Time_Value := CALENDAR.TIME_OF(Year,Month,Day,Seconds) + CALENDAR.DAY_DURATION'LAST;
  1206.           end if;
  1207.  
  1208.           -- now store the components of this internal format so that
  1209.           -- they may be used later.
  1210.  
  1211.           Year  := CALENDAR.YEAR(Return_Time_Value);
  1212.           Month := CALENDAR.MONTH(Return_Time_Value);
  1213.           Day   := CALENDAR.DAY(Return_Time_Value);
  1214.         end Analyze_and_Process_Special;
  1215.  
  1216.       -- body of Analyze_and_Process_Day_Name_or_Month_Name_or_Special follows below
  1217.  
  1218.       begin
  1219.         -- check for invalid tokens that are too long
  1220.  
  1221.         if Token_Length > Max_Valid_Letter_Token_Length then
  1222.           raise EXTERNAL_REPRESENTATION_ERROR;
  1223.         end if;
  1224.  
  1225.         -- check to see if an abbreviation has been given
  1226.  
  1227.         if Local_Token(Local_Token'FIRST + Token_Length - 1) /= Period then
  1228.           Abbreviation_Specified := FALSE;
  1229.         else
  1230.           Local_Token(Local_Token'FIRST + Local_Token_Length - 1) := Blank;
  1231.           Local_Token_Length := Local_Token_Length - 1;
  1232.           Abbreviation_Specified := TRUE;
  1233.         end if;
  1234.  
  1235.         -- search the array of day names
  1236.  
  1237.         Search_For_Month_or_Day_Name_or_Specials.SEARCH(
  1238.           Component             => Local_Token,
  1239.           Search_Array          => Day_Match_Array,
  1240.           Location_Found        => Location_Found,
  1241.           Component_Found         => Component_Found);
  1242.  
  1243.         if Component_Found and (not Month_Only) then
  1244.           Analyze_and_Process_Day_Name(Local_Token,Location_Found,Abbreviation_Specified);
  1245.         else
  1246.           -- search the array of month names
  1247.  
  1248.           Search_For_Month_or_Day_Name_or_Specials.SEARCH(
  1249.             Component             => Local_Token,
  1250.             Search_Array          => Month_Match_Array,
  1251.             Location_Found        => Location_Found,
  1252.             Component_Found         => Component_Found);
  1253.  
  1254.           if Component_Found then
  1255.             Analyze_and_Process_Month_Name(Local_Token,Location_Found,Abbreviation_Specified);
  1256.           else
  1257.             -- search the array of special formats
  1258.  
  1259.             Search_For_Month_or_Day_Name_or_Specials.SEARCH(
  1260.               Component             => Local_Token,
  1261.               Search_Array          => Specials_Array,
  1262.               Location_Found        => Location_Found,
  1263.               Component_Found         => Component_Found);
  1264.  
  1265.             if Component_Found and not Month_Only then
  1266.               Analyze_and_Process_Special(Local_Token,Abbreviation_Specified);
  1267.             else
  1268.               raise EXTERNAL_REPRESENTATION_ERROR;
  1269.             end if;
  1270.           end if;
  1271.         end if;
  1272.       end Analyze_and_Process_Day_Name_or_Month_Name_or_Special;
  1273.  
  1274.       procedure Analyze_and_Process_Day_Number_or_Year_Number(
  1275.         Token        : in STRING;
  1276.         Token_Length : in TOD_Value_Length_Type) is
  1277.  
  1278.         Temp_Value : NATURAL := NATURAL'VALUE(Token);
  1279.       begin
  1280.         -- is the number valid? If so, store the year/day.
  1281.  
  1282.         if not (Temp_Value in Short_Year_Range) and
  1283.            not (Temp_Value in CALENDAR.YEAR_NUMBER) then
  1284.           raise EXTERNAL_REPRESENTATION_ERROR;
  1285.         end if;
  1286.  
  1287.         if Temp_Value in CALENDAR.YEAR_NUMBER then
  1288.           if Tokens_Specified_Array(Year_Number) then
  1289.             raise DUPLICATION_ERROR;
  1290.           elsif (Tokens_Specified_Array(Month_Name_or_Number) and
  1291.                  (not Tokens_Specified_Array(Day_as_Number))
  1292.                 ) or
  1293.                 (Tokens_Specified_Array(Day_as_Number) and
  1294.                  (not Tokens_Specified_Array(Month_Name_or_Number))
  1295.                 ) then
  1296.             raise EXTERNAL_REPRESENTATION_ERROR;
  1297.           end if;
  1298.  
  1299.           Tokens_Specified_Array(Year_Number) := TRUE;
  1300.           Year := Temp_Value;
  1301.         elsif (not Tokens_Specified_Array(Day_as_Number)) and
  1302.               (Temp_Value in CALENDAR.DAY_NUMBER) then
  1303.           Tokens_Specified_Array(Day_as_Number) := TRUE;
  1304.           Day := Temp_Value;
  1305.         elsif Tokens_Specified_Array(Year_Number) or
  1306.               (not Tokens_Specified_Array(Month_Name_or_Number)) or
  1307.               (not Tokens_Specified_Array(Day_as_Number)) then
  1308.           raise EXTERNAL_REPRESENTATION_ERROR;
  1309.         else
  1310.           Tokens_Specified_Array(Year_Number) := TRUE;
  1311.  
  1312.           -- special current century check: 00 = 2000 (20th century)
  1313.  
  1314.           if (Temp_Value = 00) and (Current_Century = 1900) then
  1315.             Year := 2000;
  1316.           else
  1317.             Year := Current_Century + Temp_Value;
  1318.           end if;
  1319.         end if;
  1320.       end Analyze_and_Process_Day_Number_or_Year_Number;
  1321.  
  1322.       procedure Analyze_and_Process_Date(
  1323.         Token        : in STRING;
  1324.         Token_Length : in TOD_Value_Length_Type) is
  1325.  
  1326.         -- local procs follow below
  1327.  
  1328.         procedure Analyze_and_Process_Numeric_Date(
  1329.           Token        : in STRING;
  1330.           Token_Length : in TOD_Value_Length_Type) is
  1331.  
  1332.           Curr_Month,
  1333.           Curr_Day,
  1334.           Curr_Year  : NATURAL;
  1335.           Temp_String : STRING(Token'FIRST .. Token'LAST) := (others => Blank);
  1336.           Temp_String_Pointer,
  1337.           Token_Pointer        : TOD_Value_Pointer_Type := Token'FIRST;
  1338.         begin
  1339.           if Tokens_Specified_Array(Month_Name_or_Number) then
  1340.             raise DUPLICATION_ERROR;
  1341.           end if;
  1342.  
  1343.           Tokens_Specified_Array(Month_Name_or_Number) := TRUE;
  1344.  
  1345.           -- grab the month. We should only find 1 or 2 characters.
  1346.  
  1347.           while (Token_Pointer <= Token'FIRST + Token_Length - 1) and then
  1348.                 (Token(Token_Pointer) /= Slash) loop
  1349.             Temp_String(Temp_String_Pointer) := Token(Token_Pointer);
  1350.             Token_Pointer := Token_Pointer + 1;
  1351.             Temp_String_Pointer := Temp_String_Pointer + 1;
  1352.           end loop;
  1353.  
  1354.           if (Temp_String_Pointer > Temp_String'FIRST + 2) or
  1355.              (Temp_String_Pointer = Temp_String'FIRST) then
  1356.             raise MONTH_NUMBER_ERROR;
  1357.           end if;
  1358.  
  1359.           -- store the month and check its range
  1360.  
  1361.           Curr_Month := NATURAL'VALUE(Temp_String);
  1362.  
  1363.           if not (Curr_Month in CALENDAR.MONTH_NUMBER) then
  1364.             raise MONTH_NUMBER_ERROR;
  1365.           else
  1366.             Month := Curr_Month;
  1367.           end if;
  1368.  
  1369.           if Tokens_Specified_Array(Day_as_Number) then
  1370.             raise DUPLICATION_ERROR;
  1371.           end if;
  1372.  
  1373.           Tokens_Specified_Array(Day_as_Number) := TRUE;
  1374.  
  1375.           -- grab the day. Procedure is the same as above.
  1376.  
  1377.           Token_Pointer := Token_Pointer + 1;  -- bumb past slash
  1378.           Temp_String := (others => Blank);
  1379.           Temp_String_Pointer := Temp_String'FIRST;
  1380.  
  1381.           while (Token_Pointer <= Token'FIRST + Token_Length - 1) and then
  1382.                 (Token(Token_Pointer) /= Slash) loop
  1383.             Temp_String(Temp_String_Pointer) := Token(Token_Pointer);
  1384.             Token_Pointer := Token_Pointer + 1;
  1385.             Temp_String_Pointer := Temp_String_Pointer + 1;
  1386.           end loop;
  1387.  
  1388.           if (Temp_String_Pointer > Temp_String'FIRST + 2) or
  1389.              (Temp_String_Pointer = Temp_String'FIRST) then
  1390.             raise DAY_NUMBER_ERROR;
  1391.           end if;
  1392.  
  1393.           -- store the day and check its range
  1394.  
  1395.           Curr_Day := NATURAL'VALUE(Temp_String);
  1396.  
  1397.           if not (Curr_Day in CALENDAR.DAY_NUMBER) then
  1398.             raise DAY_NUMBER_ERROR;
  1399.           else
  1400.             Day := Curr_Day;
  1401.           end if;
  1402.  
  1403.           -- grab the year. Procedure is the same as above.
  1404.           -- year is optional, so check for this first.
  1405.  
  1406.           if (Token_Pointer <= Token'FIRST + Token_Length - 1) and then
  1407.              (Token(Token_Pointer) = Slash) then
  1408.             if Tokens_Specified_Array(Year_Number) then
  1409.               raise DUPLICATION_ERROR;
  1410.             end if;
  1411.  
  1412.             Tokens_Specified_Array(Year_Number) := TRUE;
  1413.  
  1414.             Token_Pointer := Token_Pointer + 1;
  1415.             Temp_String := (others => Blank);
  1416.             Temp_String_Pointer := Temp_String'FIRST;
  1417.  
  1418.             while (Token_Pointer <= Token'FIRST + Token_Length - 1) and then
  1419.                   (Token(Token_Pointer) /= Slash) loop
  1420.               Temp_String(Temp_String_Pointer) := Token(Token_Pointer);
  1421.               Token_Pointer := Token_Pointer + 1;
  1422.               Temp_String_Pointer := Temp_String_Pointer + 1;
  1423.             end loop;
  1424.  
  1425.             if (Temp_String_Pointer > Temp_String'FIRST + 4) or
  1426.                (Temp_String_Pointer = Temp_String'FIRST) then
  1427.               raise YEAR_ERROR;
  1428.             end if;
  1429.  
  1430.             -- store the year and check its range
  1431.  
  1432.             Curr_Year := NATURAL'VALUE(Temp_String);
  1433.  
  1434.             if not (Curr_Year in CALENDAR.YEAR_NUMBER) and
  1435.                not (Curr_Year in Short_Year_Range) then
  1436.               raise YEAR_ERROR;
  1437.             end if;
  1438.  
  1439.             if Curr_Year in Short_Year_Range then
  1440.               -- special current century check: 00 = 2000 (20th century)
  1441.  
  1442.               if (Curr_Year = 00) and (Current_Century = 1900) then
  1443.                 Curr_Year := 2000;
  1444.               else
  1445.                 Curr_Year := Current_Century + Curr_Year;
  1446.               end if;
  1447.  
  1448.               -- special check on the year 1900
  1449.  
  1450.               if Curr_Year = CALENDAR.YEAR_NUMBER'FIRST - 1 then
  1451.                 raise YEAR_ERROR;
  1452.               end if;
  1453.             end if;
  1454.  
  1455.             Year := Curr_Year;
  1456.           end if;
  1457.         end Analyze_and_Process_Numeric_Date;
  1458.  
  1459.         procedure Analyze_and_Process_Combination_Date(
  1460.           TOD_Value        : in STRING;
  1461.           TOD_Value_Length : in TOD_Value_Length_Type) is
  1462.  
  1463.           Local_Token        : STRING(TOD_Value'FIRST .. TOD_Value'LAST);
  1464.           Local_TOD_Value    : STRING(TOD_Value'FIRST .. TOD_Value'LAST) := TOD_Value;
  1465.           Local_Token_Length : TOD_Value_Length_Type;
  1466.           TOD_Value_Pointer  : TOD_Value_Pointer_Type := Local_TOD_Value'FIRST;
  1467.           No_Token_Found     : BOOLEAN;
  1468.         begin
  1469.           -- eliminate the slash sign(s). Replace them with blanks.
  1470.  
  1471.           for I in Local_TOD_Value'FIRST .. Local_TOD_Value'FIRST + TOD_Value_Length - 1 loop
  1472.             if Local_TOD_Value(I) = Slash then
  1473.               Local_TOD_Value(I) := Blank;
  1474.             end if;
  1475.           end loop;
  1476.  
  1477.           -- now process each "token" in turn. Note the recursion.
  1478.  
  1479.           loop
  1480.             Grab_a_Token(Local_TOD_Value,TOD_Value_Pointer,Local_Token,Local_Token_Length,No_Token_Found);
  1481.  
  1482.             exit when No_Token_Found;
  1483.  
  1484.             Analyze_and_Process_Token(Local_Token,Local_Token_Length,TRUE);
  1485.           end loop;
  1486.         end Analyze_and_Process_Combination_Date;
  1487.  
  1488.         -- the body of Analyze_and_Process_Date follows below
  1489.  
  1490.       begin
  1491.         -- check to see if we are dealing with only numerics or not
  1492.  
  1493.         if Token_Contains_No_Letters(Token,Token_Length) then
  1494.           Analyze_and_Process_Numeric_Date(Token,Token_Length);
  1495.         else
  1496.           Analyze_and_Process_Combination_Date(Token,Token_Length);
  1497.         end if;
  1498.       end Analyze_and_Process_Date;
  1499.  
  1500.       procedure Analyze_and_Process_Time(
  1501.         Token        : in STRING;
  1502.         Token_Length : in TOD_Value_Length_Type;
  1503.         Hour_Only    : in BOOLEAN) is
  1504.  
  1505.         Min_HourAMPM_Length : constant POSITIVE :=  3;
  1506.         Max_HourAMPM_Length : constant POSITIVE :=  4;
  1507.         Min_Time_Length     : constant POSITIVE :=  3;
  1508.         Max_Time_Length     : constant POSITIVE := 10;
  1509.  
  1510.         subtype Hour_AMPM_Range is POSITIVE range 01 .. Noon_Hour;
  1511.  
  1512.         Curr_Hour            : NATURAL;
  1513.         Curr_Minute,
  1514.         Curr_Second          : NATURAL := 00;
  1515.         Seconds_as_Natural   : NATURAL range 0 .. Number_of_Seconds_in_Day;
  1516.         Temp_String          : STRING(Token'FIRST .. Token'LAST) := (others => Blank);
  1517.         Temp_String_Pointer,
  1518.         Token_Pointer        : TOD_Value_Pointer_Type := Token'FIRST;
  1519.         Special_Hour_Check   : BOOLEAN;
  1520.       begin
  1521.         if Tokens_Specified_Array(Time_String) then
  1522.           raise DUPLICATION_ERROR;
  1523.         end if;
  1524.  
  1525.         Tokens_Specified_Array(Time_String) := TRUE;
  1526.  
  1527.         -- check to see if only the hour was specified
  1528.  
  1529.         if Hour_Only then
  1530.           -- check length. Should be either 3 or 4 characters.
  1531.  
  1532.           if (Token_Length < Min_HourAMPM_Length) or (Token_Length > MAx_HourAMPM_Length) then
  1533.             raise TIME_STRING_ERROR;
  1534.           end if;
  1535.  
  1536.           -- grab the hour. Store in temporary string.
  1537.  
  1538.           while Token(Token_Pointer) in Set_of_Numerics loop
  1539.             Temp_String(Temp_String_Pointer) := Token(Token_Pointer);
  1540.             Token_Pointer := Token_Pointer + 1;
  1541.             Temp_String_Pointer := Temp_String_Pointer + 1;
  1542.           end loop;
  1543.  
  1544.           -- decode the hour and check the range
  1545.  
  1546.           Curr_Hour := NATURAL'VALUE(Temp_String);
  1547.  
  1548.           if not (Curr_Hour in Hour_AMPM_Range) then
  1549.             raise HOUR_ERROR;
  1550.           end if;
  1551.  
  1552.           -- set hours to AM/PM indicator
  1553.  
  1554.           if Curr_Hour = Noon_Hour then
  1555.             if Token(Token_Pointer .. Token_Pointer + 1) = "AM" then
  1556.               Curr_Hour := 00;
  1557.             else
  1558.               Curr_Hour := Noon_Hour;
  1559.             end if;
  1560.           elsif Token(Token_Pointer .. Token_Pointer + 1) = "PM" then
  1561.             Curr_Hour := Curr_Hour + Noon_Hour;
  1562.           end if;
  1563.         else
  1564.           -- check length. Should be between 3 and 10.
  1565.  
  1566.           if (Token_Length < Min_Time_Length) or (Token_Length > Max_Time_Length) then
  1567.             raise TIME_STRING_ERROR;
  1568.           end if;
  1569.  
  1570.           -- grab the hours. Should only find 1 or 2 characters, both
  1571.           -- numerics.
  1572.  
  1573.           while (Token_Pointer <= Token'FIRST + Token_Length - 1) and then
  1574.                 (Token(Token_Pointer) /= Colon) loop
  1575.             Temp_String(Temp_String_Pointer) := Token(Token_Pointer);
  1576.             Temp_String_Pointer := Temp_String_Pointer + 1;
  1577.             Token_Pointer := Token_Pointer + 1;
  1578.           end loop;
  1579.  
  1580.           if (Temp_String_Pointer > Temp_String'FIRST + 2) or
  1581.              (Temp_String_Pointer = Temp_String'FIRST) then
  1582.             raise HOUR_ERROR;
  1583.           end if;
  1584.  
  1585.           -- store the number of hours and check its range
  1586.  
  1587.           Curr_Hour := NATURAL'VALUE(Temp_String);
  1588.  
  1589.           if not (Curr_Hour in 00 .. Number_of_Hours_in_Day) then
  1590.             raise HOUR_ERROR;
  1591.           end if;
  1592.  
  1593.           if Curr_Hour /= Number_of_Hours_in_Day then
  1594.             Special_Hour_Check := FALSE;
  1595.           else
  1596.             Special_Hour_Check := TRUE;
  1597.             Curr_Hour := 00;
  1598.           end if;
  1599.  
  1600.           -- grab the minutes. Procedure is the same as above.
  1601.  
  1602.           Token_Pointer := Token_Pointer + 1;  -- bump past colon
  1603.           Temp_String := (others => Blank);
  1604.           Temp_String_Pointer := Temp_String'FIRST;
  1605.  
  1606.           while (Token_Pointer <= Token'FIRST + Token_Length - 1) and then
  1607.                 ((Token(Token_Pointer) /= Colon)                   and
  1608.                  (Token(Token_Pointer) /= 'A')                     and
  1609.                  (Token(Token_Pointer) /= 'P')
  1610.                 ) loop
  1611.             Temp_String(Temp_String_Pointer) := Token(Token_Pointer);
  1612.             Temp_String_Pointer := Temp_String_Pointer + 1;
  1613.             Token_Pointer := Token_Pointer + 1;
  1614.           end loop;
  1615.  
  1616.           if Temp_String_Pointer /= Temp_String'FIRST + 2 then
  1617.             raise MINUTE_ERROR;
  1618.           end if;
  1619.  
  1620.           -- store the number of minutes and check its range
  1621.  
  1622.           Curr_Minute := NATURAL'VALUE(Temp_String);
  1623.  
  1624.           if not (Curr_Minute in 00 .. Number_of_Minutes_in_Hour - 1) then
  1625.             raise MINUTE_ERROR;
  1626.           end if;
  1627.  
  1628.           -- grab the seconds. Procedure is the same as above.
  1629.           -- seconds are optional, so check for this first.
  1630.  
  1631.           if (Token_Pointer <= Token'FIRST + Token_Length - 1) and then
  1632.              (Token(Token_Pointer) = Colon) then
  1633.             Token_Pointer := Token_Pointer + 1;  -- bump past colon
  1634.             Temp_String := (others => Blank);
  1635.             Temp_String_Pointer := Temp_String'FIRST;
  1636.  
  1637.             while (Token_Pointer <= Token'FIRST + Token_Length - 1) and then
  1638.                   ((Token(Token_Pointer) /= 'A')                     and
  1639.                    (Token(Token_Pointer) /= 'P')
  1640.                   ) loop
  1641.               if Token(Token_Pointer) = Colon then
  1642.                 raise TIME_STRING_ERROR;
  1643.               end if;
  1644.  
  1645.               Temp_String(Temp_String_Pointer) := Token(Token_Pointer);
  1646.               Temp_String_Pointer := Temp_String_Pointer + 1;
  1647.               Token_Pointer := Token_Pointer + 1;
  1648.             end loop;
  1649.  
  1650.             if Temp_String_Pointer /= Temp_String'FIRST + 2 then
  1651.               raise SECOND_ERROR;
  1652.             end if;
  1653.  
  1654.             -- store the number of seconds and check its range
  1655.  
  1656.             Curr_Second := NATURAL'VALUE(Temp_String);
  1657.  
  1658.             if not (Curr_Second in 00 .. Number_of_Seconds_in_Minute - 1) then
  1659.               raise SECOND_ERROR;
  1660.             end if;
  1661.           end if;
  1662.  
  1663.           -- check for optional AM/PM and check against hours specified
  1664.  
  1665.           if (Token_Pointer /= Token'FIRST + Token_Length - 2) and
  1666.              (Token_Pointer /= Token'FIRST + Token_Length) then
  1667.             raise TIME_STRING_ERROR;
  1668.           end if;
  1669.  
  1670.            if Token_Pointer = Token'FIRST + Token_Length - 2 then
  1671.              if not (Curr_Hour in Hour_AMPM_Range) then
  1672.                raise HOUR_ERROR;
  1673.              end if;
  1674.  
  1675.             if Curr_Hour = Noon_Hour then
  1676.               if Token(Token'FIRST + Token_Length - 2 .. Token'FIRST + Token_Length - 1) = "AM" then
  1677.                 Curr_Hour := 00;
  1678.               else
  1679.                 Curr_Hour := Noon_Hour;
  1680.               end if;
  1681.             elsif Token(Token'FIRST + Token_Length - 2 .. Token'FIRST + Token_Length - 1) = "PM" then
  1682.               Curr_Hour := Curr_Hour + Noon_Hour;
  1683.             end if;
  1684.           end if;
  1685.         end if;
  1686.  
  1687.         -- check for illegal time formats with hours equal to 24.
  1688.  
  1689.         if Special_Hour_Check and
  1690.            ((Curr_Minute /= 00) or (Curr_Second /= 00)) then
  1691.           raise TIME_STRING_ERROR;
  1692.         end if;
  1693.  
  1694.         -- compute the number of seconds given the components.
  1695.  
  1696.         Seconds_as_Natural := (Curr_Hour * Number_of_Seconds_in_Minute * Number_of_Minutes_in_Hour) +
  1697.           (Curr_Minute * Number_of_Seconds_in_Minute) + Curr_Second;
  1698.  
  1699.         Seconds := CALENDAR.DAY_DURATION(Seconds_as_Natural);
  1700.       end Analyze_and_Process_Time;
  1701.  
  1702.     -- body of Analyze_and_Process_Token follows below
  1703.  
  1704.     begin
  1705.       -- determine what type of token we have. See if the token contains
  1706.       -- only numerics, letters, etc. Call the appropriate action
  1707.       -- routine once we have figured out what the token can be. Also,
  1708.       -- if the token is not of any type that we can recognize, then
  1709.       -- raise EXTERNAL_REPRESENTATION_ERROR .
  1710.  
  1711.       if Token_Contains_Illegal_Characters(Token,Token_Length) then
  1712.         raise EXTERNAL_REPRESENTATION_ERROR;
  1713.       elsif Token_Contains_Only_Letters(Token,Token_Length) then
  1714.         Analyze_and_Process_Day_Name_or_Month_Name_or_Special(Token,Token_Length,Month_Only);
  1715.       elsif Token_Contains_Only_Numerics(Token,Token_Length) then
  1716.         Analyze_and_Process_Day_Number_or_Year_Number(Token,Token_Length);
  1717.       elsif Token_Contains_Slash(Token,Token_Length) then
  1718.         Analyze_and_Process_Date(Token,Token_Length);
  1719.       elsif Token_Contains_Colon_and_Numerics_with_Optional_AMPM(Token,Token_Length) then
  1720.         Analyze_and_Process_Time(Token,Token_Length,FALSE);
  1721.       elsif Token_Contains_Numerics_and_AMPM(Token,Token_Length) then
  1722.         Analyze_and_Process_Time(Token,Token_Length,TRUE);
  1723.       else
  1724.         raise EXTERNAL_REPRESENTATION_ERROR;
  1725.       end if;
  1726.     end Analyze_and_Process_Token;
  1727.  
  1728.     procedure Compute_Current_or_Next_Future_Date_For_a_Day_Name is
  1729.       TOD_String           : EXTERNAL_TOD_REPRESENTATION_TYPE :=
  1730.         CONVERT_INTERNAL_TOD_TO_EXTERNAL_TOD(Current_Time);
  1731.       Offset               : NATURAL  range 0 .. Number_of_Days_in_a_Week - 1;
  1732.       Target_Day_Position,
  1733.       Current_Day_Position : POSITIVE range 1 .. Number_of_Days_in_a_Week;
  1734.     begin
  1735.       -- store the current day position
  1736.  
  1737.       if    TOD_String(Day_Name_Start .. Day_Name_End) = "SUNDAY   " then
  1738.         Current_Day_Position := 1;
  1739.       elsif TOD_String(Day_Name_Start .. Day_Name_End) = "MONDAY   " then
  1740.         Current_Day_Position := 2;
  1741.       elsif TOD_String(Day_Name_Start .. Day_Name_End) = "TUESDAY  " then
  1742.         Current_Day_Position := 3;
  1743.       elsif TOD_String(Day_Name_Start .. Day_Name_End) = "WEDNESDAY" then
  1744.         Current_Day_Position := 4;
  1745.       elsif TOD_String(Day_Name_Start .. Day_Name_End) = "THURSDAY " then
  1746.         Current_Day_Position := 5;
  1747.       elsif TOD_String(Day_Name_Start .. Day_Name_End) = "FRIDAY   " then
  1748.         Current_Day_Position := 6;
  1749.       else  -- SATURDAY
  1750.         Current_Day_Position := 7;
  1751.       end if;
  1752.  
  1753.       -- store the target day position
  1754.  
  1755.       if    Day_Name = "SUNDAY   " then
  1756.         Target_Day_Position := 1;
  1757.       elsif Day_Name = "MONDAY   " then
  1758.         Target_Day_Position := 2;
  1759.       elsif Day_Name = "TUESDAY  " then
  1760.         Target_Day_Position := 3;
  1761.       elsif Day_Name = "WEDNESDAY" then
  1762.         Target_Day_Position := 4;
  1763.       elsif Day_Name = "THURSDAY " then
  1764.         Target_Day_Position := 5;
  1765.       elsif Day_Name = "FRIDAY   " then
  1766.         Target_Day_Position := 6;
  1767.       else  -- SATURDAY
  1768.         Target_Day_Position := 7;
  1769.       end if;
  1770.  
  1771.       -- compute the offset
  1772.  
  1773.       if    Current_Day_Position = Target_Day_Position then
  1774.         Offset := 0;
  1775.       elsif Current_Day_Position < Target_Day_Position then
  1776.         Offset := Target_Day_Position - Current_Day_Position;
  1777.       else
  1778.         Offset := (Number_of_Days_in_a_Week - Current_Day_Position) + Target_Day_Position;
  1779.       end if;
  1780.  
  1781.       -- recompute Return_Time_Value if a future date was specified
  1782.  
  1783.       for I in 1 .. Offset loop
  1784.         if Seconds /= CALENDAR.DAY_DURATION'FIRST then
  1785.           Return_Time_Value := CALENDAR.TIME_OF(Year,Month,Day,Seconds) + CALENDAR.DAY_DURATION'LAST;
  1786.         else
  1787.           Return_Time_Value := CALENDAR.TIME_OF(Year,Month,Day,CALENDAR.DAY_DURATION'FIRST) +
  1788.             (CALENDAR.DAY_DURATION'LAST + 1.0);
  1789.         end if;
  1790.  
  1791.         Year  := CALENDAR.YEAR(Return_Time_Value);
  1792.         Month := CALENDAR.MONTH(Return_Time_Value);
  1793.         Day   := CALENDAR.DAY(Return_Time_Value);
  1794.  
  1795.         if Seconds = CALENDAR.DAY_DURATION'FIRST then
  1796.           Return_Time_Value := CALENDAR.TIME_OF(Year,Month,Day,CALENDAR.DAY_DURATION'FIRST);
  1797.         end if;
  1798.       end loop;
  1799.     end Compute_Current_or_Next_Future_Date_For_a_Day_Name;
  1800.  
  1801.     procedure Perform_Error_Checking_and_Wrap_Up_Loose_Ends is
  1802.     begin
  1803.       -- if a day name and date were specified, make sure that the
  1804.       -- day name is correct for that date.
  1805.  
  1806.       if Tokens_Specified_Array(Day_as_Name)   and
  1807.          Tokens_Specified_Array(Day_as_Number) and
  1808.          Compute_Day_of_Week(CALENDAR.TIME_OF(Year,Month,Day,Seconds)) /= Day_Name then
  1809.         raise DAY_DATE_ERROR;
  1810.       end if;
  1811.  
  1812.       -- make sure that if a special format token was specified, that
  1813.       -- the date was not also specified.
  1814.  
  1815.       if (Tokens_Specified_Array(Special_Format))      and
  1816.          (Tokens_Specified_Array(Day_as_Name)           or
  1817.           Tokens_Specified_Array(Day_as_Number)         or
  1818.           Tokens_Specified_Array(Month_Name_or_Number)  or
  1819.           Tokens_Specified_Array(Year_Number)
  1820.          ) then
  1821.         raise EXTERNAL_REPRESENTATION_ERROR;
  1822.       end if;
  1823.  
  1824.       -- make sure that if any part of a date token was specified, that
  1825.       -- at least the day number and month were specified.
  1826.  
  1827.       if Tokens_Specified_Array(Day_as_Number)              and
  1828.          (not Tokens_Specified_Array(Month_Name_or_Number)) then
  1829.         raise MONTH_MISSING_ERROR;
  1830.       elsif Tokens_Specified_Array(Month_Name_or_Number)    and
  1831.             (not Tokens_Specified_Array(Day_as_Number)) then
  1832.         raise DAY_NUMBER_MISSING_ERROR;
  1833.       elsif Tokens_Specified_Array(Year_Number)             and
  1834.             ((not Tokens_Specified_Array(Month_Name_or_Number)) or
  1835.               (not Tokens_Specified_Array(Day_as_Number))
  1836.             ) then
  1837.         raise EXTERNAL_REPRESENTATION_ERROR;
  1838.       end if;
  1839.  
  1840.       -- now set the internal time if a date or time token was found.
  1841.  
  1842.       if Tokens_Specified_Array(Day_as_Number) or
  1843.          Tokens_Specified_Array(Time_String) then
  1844.         Return_Time_Value := CALENDAR.TIME_OF(Year,Month,Day,Seconds);
  1845.       end if;
  1846.  
  1847.       -- if the day name was specified without a date, then compute the
  1848.       -- current or next future internal time format as of that day.
  1849.  
  1850.       if Tokens_Specified_Array(Day_as_Name) and
  1851.          (not Tokens_Specified_Array(Day_as_Number)) then
  1852.         Compute_Current_or_Next_Future_Date_For_a_Day_Name;
  1853.       end if;
  1854.     end Perform_Error_Checking_and_Wrap_Up_Loose_Ends;
  1855.  
  1856.   -- the body of CONVERT_EXTERNAL_TOD_TO_INTERNAL_TOD follows below
  1857.  
  1858.   begin
  1859.     -- check for a null array... let's not deal with it
  1860.  
  1861.     if TOD_Value'FIRST > TOD_Value'LAST then
  1862.       raise EXTERNAL_REPRESENTATION_ERROR;
  1863.     end if;
  1864.  
  1865.     -- compress the external representation, that is, eliminate all
  1866.     -- unnecessary blanks and/or commas. Then convert all lower case
  1867.     -- letters to upper case.
  1868.  
  1869.     Compress_External_Representation(TOD_Value,TOD_Value_Compressed_Length);
  1870.     Convert_External_Representation_to_Upper_Case(TOD_Value);
  1871.  
  1872.     if TOD_Value_Compressed_Length < Minimum_TOD_STRING_Length then
  1873.       raise EXTERNAL_REPRESENTATION_ERROR;
  1874.     end if;
  1875.  
  1876.     -- now loop on all tokens in the external representation. Analyze
  1877.     -- and process each token. Some error checking may be needed
  1878.     -- after all tokens are found.
  1879.  
  1880.     loop
  1881.       Grab_a_Token(TOD_Value,TOD_Value_Pointer,Token,Token_Length,No_Token_Found);
  1882.  
  1883.       exit when No_Token_Found;
  1884.  
  1885.       Analyze_and_Process_Token(Token,Token_Length,FALSE);
  1886.     end loop;
  1887.  
  1888.     -- now perform special error checking and wrap up loose ends
  1889.  
  1890.     Perform_Error_Checking_and_Wrap_Up_Loose_Ends;
  1891.  
  1892.     -- now return the CALENDAR.TIME internal representation. If
  1893.     -- during the processing, CALENDAR.TIME_ERROR was raised, then
  1894.     -- we trap it and send back DATE_ERROR . If any other exception
  1895.     -- was raised, we do nothing and instead let the caller handle it.
  1896.  
  1897.     return Return_Time_Value;
  1898.   exception
  1899.     when CALENDAR.TIME_ERROR =>
  1900.       raise DATE_ERROR;
  1901.   end CONVERT_EXTERNAL_TOD_TO_INTERNAL_TOD;
  1902. end TOD_UTILITIES;
  1903. -------
  1904.