blob: 42b86cce4a15115f047e48d72bf02d09e30fa233 [file] [log] [blame]
------------------------------------------------------------------------------
-- --
-- GNAT RUN-TIME COMPONENTS --
-- --
-- G N A T . C A L E N D A R . T I M E _ I O --
-- --
-- B o d y --
-- --
-- Copyright (C) 1999-2023, AdaCore --
-- --
-- GNAT is free software; you can redistribute it and/or modify it under --
-- terms of the GNU General Public License as published by the Free Soft- --
-- ware Foundation; either version 3, or (at your option) any later ver- --
-- sion. GNAT is distributed in the hope that it will be useful, but WITH- --
-- OUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY --
-- or FITNESS FOR A PARTICULAR PURPOSE. --
-- --
-- As a special exception under Section 7 of GPL version 3, you are granted --
-- additional permissions described in the GCC Runtime Library Exception, --
-- version 3.1, as published by the Free Software Foundation. --
-- --
-- You should have received a copy of the GNU General Public License and --
-- a copy of the GCC Runtime Library Exception along with this program; --
-- see the files COPYING3 and COPYING.RUNTIME respectively. If not, see --
-- <http://www.gnu.org/licenses/>. --
-- --
-- GNAT was originally developed by the GNAT team at New York University. --
-- Extensive contributions were provided by Ada Core Technologies Inc. --
-- --
------------------------------------------------------------------------------
with Ada.Characters.Handling;
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;
with Ada.Text_IO;
with GNAT.Case_Util;
package body GNAT.Calendar.Time_IO is
type Month_Name is
(January,
February,
March,
April,
May,
June,
July,
August,
September,
October,
November,
December);
function Month_Name_To_Number
(Str : String) return Ada.Calendar.Month_Number;
-- Converts a string that contains an abbreviated month name to a month
-- number. Constraint_Error is raised if Str is not a valid month name.
-- Comparison is case insensitive
type Padding_Mode is (None, Zero, Space);
type Sec_Number is mod 2 ** 64;
-- Type used to compute the number of seconds since 01/01/1970. A 32 bit
-- number will cover only a period of 136 years. This means that for date
-- past 2106 the computation is not possible. A 64 bits number should be
-- enough for a very large period of time.
-----------------------
-- Local Subprograms --
-----------------------
function Image_Helper
(Date : Ada.Calendar.Time;
Picture : Picture_String;
Time_Zone : Time_Zones.Time_Offset) return String;
-- This is called by the two exported Image functions. It uses the local
-- time zone for its computations, but uses Time_Zone when interpreting the
-- "%:::z" tag.
function Am_Pm (H : Natural) return String;
-- Return AM or PM depending on the hour H
function Hour_12 (H : Natural) return Positive;
-- Convert a 1-24h format to a 0-12 hour format
function Image (Str : String; Length : Natural := 0) return String;
-- Return Str capitalized and cut to length number of characters. If
-- length is 0, then no cut operation is performed.
function Image
(N : Sec_Number;
Padding : Padding_Mode := Zero;
Length : Natural := 0) return String;
-- Return image of N. This number is eventually padded with zeros or spaces
-- depending of the length required. If length is 0 then no padding occurs.
function Image
(N : Natural;
Padding : Padding_Mode := Zero;
Length : Natural := 0) return String;
-- As above with N provided in Integer format
procedure Parse_ISO_8601
(Date : String;
Time : out Ada.Calendar.Time;
Success : out Boolean);
-- Subsidiary of function Value. It parses the string Date, interpreted as
-- an ISO 8601 time representation, and returns corresponding Time value.
-- Success is set to False when the string is not a supported ISO 8601
-- date.
--
-- Examples:
--
-- 2017-04-14T14:47:06 20170414T14:47:06 20170414T144706
-- 2017-04-14T14:47:06,12 20170414T14:47:06.12
-- 2017-04-14T19:47:06+05 20170414T09:00:06-05:47
-----------
-- Am_Pm --
-----------
function Am_Pm (H : Natural) return String is
begin
if H = 0 or else H > 12 then
return "PM";
else
return "AM";
end if;
end Am_Pm;
-------------
-- Hour_12 --
-------------
function Hour_12 (H : Natural) return Positive is
begin
if H = 0 then
return 12;
elsif H <= 12 then
return H;
else -- H > 12
return H - 12;
end if;
end Hour_12;
-----------
-- Image --
-----------
function Image
(Str : String;
Length : Natural := 0) return String
is
use Ada.Characters.Handling;
Local : constant String :=
To_Upper (Str (Str'First)) &
To_Lower (Str (Str'First + 1 .. Str'Last));
begin
if Length = 0 then
return Local;
else
return Local (1 .. Length);
end if;
end Image;
-----------
-- Image --
-----------
function Image
(N : Natural;
Padding : Padding_Mode := Zero;
Length : Natural := 0) return String
is
begin
return Image (Sec_Number (N), Padding, Length);
end Image;
-----------
-- Image --
-----------
function Image
(N : Sec_Number;
Padding : Padding_Mode := Zero;
Length : Natural := 0) return String
is
function Pad_Char return String;
--------------
-- Pad_Char --
--------------
function Pad_Char return String is
begin
case Padding is
when None => return "";
when Zero => return "00";
when Space => return " ";
end case;
end Pad_Char;
-- Local Declarations
NI : constant String := Sec_Number'Image (N);
NIP : constant String := Pad_Char & NI (2 .. NI'Last);
-- Start of processing for Image
begin
if Length = 0 or else Padding = None then
return NI (2 .. NI'Last);
else
return NIP (NIP'Last - Length + 1 .. NIP'Last);
end if;
end Image;
-----------
-- Image --
-----------
function Image
(Date : Ada.Calendar.Time;
Picture : Picture_String;
Time_Zone : Time_Zones.Time_Offset) return String
is
-- We subtract off the local time zone, and add in the requested
-- Time_Zone, and then pass it on to Image_Helper, which uses the
-- local time zone.
use Time_Zones;
Local_TZ : constant Time_Offset := Local_Time_Offset (Date);
Minute_Offset : constant Integer := Integer (Time_Zone - Local_TZ);
Second_Offset : constant Integer := Minute_Offset * 60;
begin
return Image_Helper
(Date + Duration (Second_Offset), Picture, Time_Zone);
end Image;
-----------
-- Image --
-----------
function Image
(Date : Ada.Calendar.Time;
Picture : Picture_String) return String
is
use Time_Zones;
Local_TZ : constant Time_Offset := Local_Time_Offset (Date);
begin
return Image_Helper (Date, Picture, Local_TZ);
end Image;
------------------
-- Image_Helper --
------------------
function Image_Helper
(Date : Ada.Calendar.Time;
Picture : Picture_String;
Time_Zone : Time_Zones.Time_Offset) return String
is
Padding : Padding_Mode := Zero;
-- Padding is set for one directive
Result : Unbounded_String;
Year : Year_Number;
Month : Month_Number;
Day : Day_Number;
Hour : Hour_Number;
Minute : Minute_Number;
Second : Second_Number;
Sub_Second : Second_Duration;
P : Positive;
begin
-- Get current time in split format
Split (Date, Year, Month, Day, Hour, Minute, Second, Sub_Second);
-- Null picture string is error
if Picture = "" then
raise Picture_Error with "null picture string";
end if;
-- Loop through characters of picture string, building result
Result := Null_Unbounded_String;
P := Picture'First;
while P <= Picture'Last loop
-- A directive has the following format "%[-_]."
if Picture (P) = '%' then
Padding := Zero;
if P = Picture'Last then
raise Picture_Error with "picture string ends with '%";
end if;
-- Check for GNU extension to change the padding
if Picture (P + 1) = '-' then
Padding := None;
P := P + 1;
elsif Picture (P + 1) = '_' then
Padding := Space;
P := P + 1;
end if;
if P = Picture'Last then
raise Picture_Error with "picture string ends with '- or '_";
end if;
case Picture (P + 1) is
-- Literal %
when '%' =>
Result := Result & '%';
-- A newline
when 'n' =>
Result := Result & ASCII.LF;
-- A horizontal tab
when 't' =>
Result := Result & ASCII.HT;
-- Hour (00..23)
when 'H' =>
Result := Result & Image (Hour, Padding, 2);
-- Hour (01..12)
when 'I' =>
Result := Result & Image (Hour_12 (Hour), Padding, 2);
-- Hour ( 0..23)
when 'k' =>
Result := Result & Image (Hour, Space, 2);
-- Hour ( 1..12)
when 'l' =>
Result := Result & Image (Hour_12 (Hour), Space, 2);
-- Minute (00..59)
when 'M' =>
Result := Result & Image (Minute, Padding, 2);
-- AM/PM
when 'p' =>
Result := Result & Am_Pm (Hour);
-- Time, 12-hour (hh:mm:ss [AP]M)
when 'r' =>
Result := Result &
Image (Hour_12 (Hour), Padding, Length => 2) & ':' &
Image (Minute, Padding, Length => 2) & ':' &
Image (Second, Padding, Length => 2) & ' ' &
Am_Pm (Hour);
-- Seconds since 1970-01-01 00:00:00 UTC
-- (a nonstandard extension)
when 's' =>
declare
-- Compute the number of seconds using Ada.Calendar.Time
-- values rather than Julian days to account for Daylight
-- Savings Time.
Neg : Boolean := False;
Sec : Duration := Date - Time_Of (1970, 1, 1, 0.0);
begin
-- Avoid rounding errors and perform special processing
-- for dates earlier than the Unix Epoc.
if Sec > 0.0 then
Sec := Sec - 0.5;
elsif Sec < 0.0 then
Neg := True;
Sec := abs (Sec + 0.5);
end if;
-- Prepend a minus sign to the result since Sec_Number
-- cannot handle negative numbers.
if Neg then
Result :=
Result & "-" & Image (Sec_Number (Sec), None);
else
Result := Result & Image (Sec_Number (Sec), None);
end if;
end;
-- Second (00..59)
when 'S' =>
Result := Result & Image (Second, Padding, Length => 2);
-- Milliseconds (3 digits)
-- Microseconds (6 digits)
-- Nanoseconds (9 digits)
when 'i' | 'e' | 'o' =>
declare
Sub_Sec : constant Long_Integer :=
Long_Integer (Sub_Second * 1_000_000_000);
Img1 : constant String := Sub_Sec'Img;
Img2 : constant String :=
"00000000" & Img1 (Img1'First + 1 .. Img1'Last);
Nanos : constant String :=
Img2 (Img2'Last - 8 .. Img2'Last);
begin
case Picture (P + 1) is
when 'i' =>
Result := Result &
Nanos (Nanos'First .. Nanos'First + 2);
when 'e' =>
Result := Result &
Nanos (Nanos'First .. Nanos'First + 5);
when 'o' =>
Result := Result & Nanos;
when others =>
null;
end case;
end;
-- Time, 24-hour (hh:mm:ss)
when 'T' =>
Result := Result &
Image (Hour, Padding, Length => 2) & ':' &
Image (Minute, Padding, Length => 2) & ':' &
Image (Second, Padding, Length => 2);
-- Time zone. Append "+hh", "-hh", "+hh:mm", or "-hh:mm", as
-- appropriate.
when ':' =>
declare
use type Time_Zones.Time_Offset;
TZ_Form : constant Picture_String := "%:::z";
TZ : constant Natural := Natural (abs Time_Zone);
begin
if P + TZ_Form'Length - 1 <= Picture'Last
and then Picture (P .. P + TZ_Form'Length - 1) = "%:::z"
then
if Time_Zone >= 0 then
Result := Result & "+";
else
Result := Result & "-";
end if;
Result := Result &
Image (Integer (TZ / 60), Padding, Length => 2);
if TZ mod 60 /= 0 then
Result := Result & ":";
Result := Result &
Image (TZ mod 60, Padding, Length => 2);
end if;
P := P + TZ_Form'Length - 2; -- will add 2 below
-- We do not support any of the other standard GNU
-- time-zone formats (%z, %:z, %::z, %Z).
else
raise Picture_Error with "unsupported picture format";
end if;
end;
-- Locale's abbreviated weekday name (Sun..Sat)
when 'a' =>
Result := Result &
Image (Day_Name'Image (Day_Of_Week (Date)), 3);
-- Locale's full weekday name, variable length
-- (Sunday..Saturday)
when 'A' =>
Result := Result &
Image (Day_Name'Image (Day_Of_Week (Date)));
-- Locale's abbreviated month name (Jan..Dec)
when 'b' | 'h' =>
Result := Result &
Image (Month_Name'Image (Month_Name'Val (Month - 1)), 3);
-- Locale's full month name, variable length
-- (January..December).
when 'B' =>
Result := Result &
Image (Month_Name'Image (Month_Name'Val (Month - 1)));
-- Locale's date and time (Sat Nov 04 12:02:33 EST 1989)
when 'c' =>
case Padding is
when Zero =>
Result := Result & Image (Date, "%a %b %d %T %Y");
when Space =>
Result := Result & Image (Date, "%a %b %_d %_T %Y");
when None =>
Result := Result & Image (Date, "%a %b %-d %-T %Y");
end case;
-- Day of month (01..31)
when 'd' =>
Result := Result & Image (Day, Padding, 2);
-- Date (mm/dd/yy)
when 'D' | 'x' =>
Result := Result &
Image (Month, Padding, 2) & '/' &
Image (Day, Padding, 2) & '/' &
Image (Year, Padding, 2);
-- Day of year (001..366)
when 'j' =>
Result := Result & Image (Day_In_Year (Date), Padding, 3);
-- Month (01..12)
when 'm' =>
Result := Result & Image (Month, Padding, 2);
-- Week number of year with Sunday as first day of week
-- (00..53)
when 'U' =>
declare
Offset : constant Natural :=
(Julian_Day (Year, 1, 1) + 1) mod 7;
Week : constant Natural :=
1 + ((Day_In_Year (Date) - 1) + Offset) / 7;
begin
Result := Result & Image (Week, Padding, 2);
end;
-- Day of week (0..6) with 0 corresponding to Sunday
when 'w' =>
declare
DOW : constant Natural range 0 .. 6 :=
(if Day_Of_Week (Date) = Sunday
then 0
else Day_Name'Pos (Day_Of_Week (Date)));
begin
Result := Result & Image (DOW, Length => 1);
end;
-- Week number of year with Monday as first day of week
-- (00..53)
when 'W' =>
Result := Result & Image (Week_In_Year (Date), Padding, 2);
-- Last two digits of year (00..99)
when 'y' =>
declare
Y : constant Natural := Year - (Year / 100) * 100;
begin
Result := Result & Image (Y, Padding, 2);
end;
-- Year (1970...)
when 'Y' =>
Result := Result & Image (Year, None, 4);
when others =>
raise Picture_Error with
"unknown format character in picture string";
end case;
-- Skip past % and format character
P := P + 2;
-- Character other than % is copied into the result
else
Result := Result & Picture (P);
P := P + 1;
end if;
end loop;
return To_String (Result);
end Image_Helper;
--------------------------
-- Month_Name_To_Number --
--------------------------
function Month_Name_To_Number
(Str : String) return Ada.Calendar.Month_Number
is
subtype String3 is String (1 .. 3);
Abbrev_Upper_Month_Names :
constant array (Ada.Calendar.Month_Number) of String3 :=
["JAN", "FEB", "MAR", "APR", "MAY", "JUN",
"JUL", "AUG", "SEP", "OCT", "NOV", "DEC"];
-- Short version of the month names, used when parsing date strings
S : String := Str;
begin
GNAT.Case_Util.To_Upper (S);
for J in Abbrev_Upper_Month_Names'Range loop
if Abbrev_Upper_Month_Names (J) = S then
return J;
end if;
end loop;
return Abbrev_Upper_Month_Names'First;
end Month_Name_To_Number;
--------------------
-- Parse_ISO_8601 --
--------------------
procedure Parse_ISO_8601
(Date : String;
Time : out Ada.Calendar.Time;
Success : out Boolean)
is
pragma Unsuppress (All_Checks);
-- This is necessary because the run-time library is usually compiled
-- with checks suppressed, and we are relying on constraint checks in
-- this code to catch syntax errors in the Date string (e.g. out of
-- bounds slices).
Index : Positive := Date'First;
-- The current character scan index. After a call to Advance, Index
-- points to the next character.
Wrong_Syntax : exception;
-- An exception used to signal that the scan pointer has reached an
-- unexpected character in the source string, or if premature
-- end-of-source was reached.
procedure Advance;
pragma Inline (Advance);
-- Past the current character of Date
procedure Advance_Digits (Num_Digits : Positive);
pragma Inline (Advance_Digits);
-- Past the given number of digit characters
function Scan_Day return Day_Number;
pragma Inline (Scan_Day);
-- Scan the two digits of a day number and return its value
function Scan_Hour return Hour_Number;
pragma Inline (Scan_Hour);
-- Scan the two digits of an hour number and return its value
function Scan_Minute return Minute_Number;
pragma Inline (Scan_Minute);
-- Scan the two digits of a minute number and return its value
function Scan_Month return Month_Number;
pragma Inline (Scan_Month);
-- Scan the two digits of a month number and return its value
function Scan_Second return Second_Number;
pragma Inline (Scan_Second);
-- Scan the two digits of a second number and return its value
function Scan_Separator (Expected_Symbol : Character) return Boolean;
pragma Inline (Scan_Separator);
-- If the current symbol matches the Expected_Symbol then advance the
-- scanner index and return True; otherwise do nothing and return False
procedure Scan_Separator (Required : Boolean; Separator : Character);
pragma Inline (Scan_Separator);
-- If Required then check that the current character matches Separator
-- and advance the scanner index; if not Required then do nothing.
function Scan_Subsecond return Second_Duration;
pragma Inline (Scan_Subsecond);
-- Scan all the digits of a subsecond number and return its value
function Scan_Year return Year_Number;
pragma Inline (Scan_Year);
-- Scan the four digits of a year number and return its value
function Symbol return Character;
pragma Inline (Symbol);
-- Return the current character being scanned
-------------
-- Advance --
-------------
procedure Advance is
begin
-- Signal the end of the source string. This stops a complex scan
-- by bottoming up any recursive calls till control reaches routine
-- Scan, which handles the exception.
if Index > Date'Last then
raise Wrong_Syntax;
-- Advance the scan pointer as long as there are characters to scan,
-- in other words, the scan pointer has not passed the end of the
-- source string.
else
Index := Index + 1;
end if;
end Advance;
--------------------
-- Advance_Digits --
--------------------
procedure Advance_Digits (Num_Digits : Positive) is
begin
for J in 1 .. Num_Digits loop
if Symbol not in '0' .. '9' then
raise Wrong_Syntax;
end if;
Advance; -- past digit
end loop;
end Advance_Digits;
--------------
-- Scan_Day --
--------------
function Scan_Day return Day_Number is
From : constant Positive := Index;
begin
Advance_Digits (Num_Digits => 2);
return Day_Number'Value (Date (From .. Index - 1));
end Scan_Day;
---------------
-- Scan_Hour --
---------------
function Scan_Hour return Hour_Number is
From : constant Positive := Index;
begin
Advance_Digits (Num_Digits => 2);
return Hour_Number'Value (Date (From .. Index - 1));
end Scan_Hour;
-----------------
-- Scan_Minute --
-----------------
function Scan_Minute return Minute_Number is
From : constant Positive := Index;
begin
Advance_Digits (Num_Digits => 2);
return Minute_Number'Value (Date (From .. Index - 1));
end Scan_Minute;
----------------
-- Scan_Month --
----------------
function Scan_Month return Month_Number is
From : constant Positive := Index;
begin
Advance_Digits (Num_Digits => 2);
return Month_Number'Value (Date (From .. Index - 1));
end Scan_Month;
-----------------
-- Scan_Second --
-----------------
function Scan_Second return Second_Number is
From : constant Positive := Index;
begin
Advance_Digits (Num_Digits => 2);
return Second_Number'Value (Date (From .. Index - 1));
end Scan_Second;
--------------------
-- Scan_Separator --
--------------------
function Scan_Separator (Expected_Symbol : Character) return Boolean is
begin
if Symbol = Expected_Symbol then
Advance;
return True;
else
return False;
end if;
end Scan_Separator;
--------------------
-- Scan_Separator --
--------------------
procedure Scan_Separator (Required : Boolean; Separator : Character) is
begin
if Required then
if Symbol /= Separator then
raise Wrong_Syntax;
end if;
Advance; -- Past the separator
end if;
end Scan_Separator;
--------------------
-- Scan_Subsecond --
--------------------
function Scan_Subsecond return Second_Duration is
From : constant Positive := Index;
begin
Advance_Digits (Num_Digits => 1);
while Index <= Date'Length and then Symbol in '0' .. '9' loop
Advance;
end loop;
return Second_Duration'Value ("0." & Date (From .. Index - 1));
end Scan_Subsecond;
---------------
-- Scan_Year --
---------------
function Scan_Year return Year_Number is
From : constant Positive := Index;
begin
Advance_Digits (Num_Digits => 4);
return Year_Number'Value (Date (From .. Index - 1));
end Scan_Year;
------------
-- Symbol --
------------
function Symbol return Character is
begin
-- Signal the end of the source string. This stops a complex scan by
-- bottoming up any recursive calls till control reaches routine Scan
-- which handles the exception. Certain scanning scenarios may handle
-- this exception on their own.
if Index > Date'Last then
raise Wrong_Syntax;
else
return Date (Index);
end if;
end Symbol;
-- Local variables
use Time_Zones;
Date_Separator : constant Character := '-';
Hour_Separator : constant Character := ':';
Day : Day_Number;
Month : Month_Number;
Year : Year_Number;
Hour : Hour_Number := 0;
Minute : Minute_Number := 0;
Second : Second_Number := 0;
Subsec : Second_Duration := 0.0;
Time_Zone_Seen : Boolean := False;
Time_Zone_Offset : Time_Offset; -- Valid only if Time_Zone_Seen
Sep_Required : Boolean := False;
-- True if a separator is seen (and therefore required after it!)
subtype Sign_Type is Character with Predicate => Sign_Type in '+' | '-';
-- Start of processing for Parse_ISO_8601
begin
-- Parse date
Year := Scan_Year;
Sep_Required := Scan_Separator (Date_Separator);
Month := Scan_Month;
Scan_Separator (Sep_Required, Date_Separator);
Day := Scan_Day;
if Index < Date'Last and then Symbol = 'T' then
Advance;
-- Parse time
Hour := Scan_Hour;
Sep_Required := Scan_Separator (Hour_Separator);
Minute := Scan_Minute;
Scan_Separator (Sep_Required, Hour_Separator);
Second := Scan_Second;
-- [ ('.' | ',') s{s} ]
if Index <= Date'Last then
-- A decimal fraction shall have at least one digit, and has as
-- many digits as supported by the underlying implementation.
-- The valid decimal separators are those specified in ISO 31-0,
-- i.e. the comma [,] or full stop [.]. Of these, the comma is
-- the preferred separator of ISO-8601.
if Symbol = ',' or else Symbol = '.' then
Advance; -- past decimal separator
Subsec := Scan_Subsecond;
end if;
end if;
-- [ ('Z' | ('+'|'-')hh':'mm) ]
if Index <= Date'Last then
Time_Zone_Seen := Symbol in 'Z' | Sign_Type;
-- Suffix 'Z' signifies that this is UTC time (time zone 0)
if Symbol = 'Z' then
Time_Zone_Offset := 0;
Advance;
-- Difference between local time and UTC: It shall be expressed
-- as positive (i.e. with the leading plus sign [+]) if the local
-- time is ahead of or equal to UTC of day and as negative (i.e.
-- with the leading minus sign [-]) if it is behind UTC of day.
-- The minutes time element of the difference may only be omitted
-- if the difference between the time scales is exactly an
-- integral number of hours.
elsif Symbol in Sign_Type then
declare
Time_Zone_Sign : constant Sign_Type := Symbol;
Time_Zone_Hour : Hour_Number;
Time_Zone_Minute : Minute_Number;
begin
Advance;
Time_Zone_Hour := Scan_Hour;
-- Past ':'
if Index < Date'Last and then Symbol = Hour_Separator then
Advance;
Time_Zone_Minute := Scan_Minute;
else
Time_Zone_Minute := 0;
end if;
-- Compute Time_Zone_Offset
Time_Zone_Offset :=
Time_Offset (Time_Zone_Hour * 60 + Time_Zone_Minute);
case Time_Zone_Sign is
when '+' => null;
when '-' => Time_Zone_Offset := -Time_Zone_Offset;
end case;
end;
else
raise Wrong_Syntax;
end if;
end if;
end if;
-- Check for trailing characters
if Index /= Date'Length + 1 then
raise Wrong_Syntax;
end if;
-- If a time zone was specified, use Ada.Calendar.Formatting.Time_Of,
-- and specify the time zone. Otherwise, call GNAT.Calendar.Time_Of,
-- which uses local time.
if Time_Zone_Seen then
Time := Ada.Calendar.Formatting.Time_Of
(Year, Month, Day, Hour, Minute, Second, Subsec,
Time_Zone => Time_Zone_Offset);
else
Time := GNAT.Calendar.Time_Of
(Year, Month, Day, Hour, Minute, Second, Subsec);
end if;
-- Notify that the input string was successfully parsed
Success := True;
exception
when Wrong_Syntax | Constraint_Error =>
-- If constraint check fails, we want to behave the same as
-- Wrong_Syntax; we want the caller (Value) to try other
-- allowed syntaxes.
Time :=
Time_Of (Year_Number'First, Month_Number'First, Day_Number'First);
Success := False;
end Parse_ISO_8601;
-----------
-- Value --
-----------
function Value (Date : String) return Ada.Calendar.Time is
pragma Unsuppress (All_Checks); -- see comment in Parse_ISO_8601
D : String (1 .. 21);
D_Length : constant Natural := Date'Length;
Year : Year_Number;
Month : Month_Number;
Day : Day_Number;
Hour : Hour_Number;
Minute : Minute_Number;
Second : Second_Number;
procedure Extract_Date
(Year : out Year_Number;
Month : out Month_Number;
Day : out Day_Number;
Time_Start : out Natural);
-- Try and extract a date value from string D. Time_Start is set to the
-- first character that could be the start of time data.
procedure Extract_Time
(Index : Positive;
Hour : out Hour_Number;
Minute : out Minute_Number;
Second : out Second_Number;
Check_Space : Boolean := False);
-- Try and extract a time value from string D starting from position
-- Index. Set Check_Space to True to check whether the character at
-- Index - 1 is a space. Raise Constraint_Error if the portion of D
-- corresponding to the date is not well formatted.
------------------
-- Extract_Date --
------------------
procedure Extract_Date
(Year : out Year_Number;
Month : out Month_Number;
Day : out Day_Number;
Time_Start : out Natural)
is
begin
if D (3) = '-' or else D (3) = '/' then
if D_Length = 8 or else D_Length = 17 then
-- Formats are "yy*mm*dd" or "yy*mm*dd hh:mm:ss"
if D (6) /= D (3) then
raise Constraint_Error;
end if;
Year := Year_Number'Value ("20" & D (1 .. 2));
Month := Month_Number'Value (D (4 .. 5));
Day := Day_Number'Value (D (7 .. 8));
Time_Start := 10;
elsif D_Length = 10 or else D_Length = 19 then
-- Formats are "mm*dd*yyyy" or "mm*dd*yyyy hh:mm:ss"
if D (6) /= D (3) then
raise Constraint_Error;
end if;
Year := Year_Number'Value (D (7 .. 10));
Month := Month_Number'Value (D (1 .. 2));
Day := Day_Number'Value (D (4 .. 5));
Time_Start := 12;
elsif D_Length = 11 or else D_Length = 20 then
-- Formats are "dd*mmm*yyyy" or "dd*mmm*yyyy hh:mm:ss"
if D (7) /= D (3) then
raise Constraint_Error;
end if;
Year := Year_Number'Value (D (8 .. 11));
Month := Month_Name_To_Number (D (4 .. 6));
Day := Day_Number'Value (D (1 .. 2));
Time_Start := 13;
else
raise Constraint_Error;
end if;
elsif D (3) = ' ' then
if D_Length = 11 or else D_Length = 20 then
-- Possible formats are "dd mmm yyyy", "dd mmm yyyy hh:mm:ss"
if D (7) /= ' ' then
raise Constraint_Error;
end if;
Year := Year_Number'Value (D (8 .. 11));
Month := Month_Name_To_Number (D (4 .. 6));
Day := Day_Number'Value (D (1 .. 2));
Time_Start := 13;
else
raise Constraint_Error;
end if;
else
if D_Length = 8 or else D_Length = 17 then
-- Possible formats are "yyyymmdd" or "yyyymmdd hh:mm:ss"
Year := Year_Number'Value (D (1 .. 4));
Month := Month_Number'Value (D (5 .. 6));
Day := Day_Number'Value (D (7 .. 8));
Time_Start := 10;
elsif D_Length = 10 or else D_Length = 19 then
-- Possible formats are "yyyy*mm*dd" or "yyyy*mm*dd hh:mm:ss"
if (D (5) /= '-' and then D (5) /= '/')
or else D (8) /= D (5)
then
raise Constraint_Error;
end if;
Year := Year_Number'Value (D (1 .. 4));
Month := Month_Number'Value (D (6 .. 7));
Day := Day_Number'Value (D (9 .. 10));
Time_Start := 12;
elsif D_Length = 11 or else D_Length = 20 then
-- Possible formats are "yyyy*mmm*dd"
if (D (5) /= '-' and then D (5) /= '/')
or else D (9) /= D (5)
then
raise Constraint_Error;
end if;
Year := Year_Number'Value (D (1 .. 4));
Month := Month_Name_To_Number (D (6 .. 8));
Day := Day_Number'Value (D (10 .. 11));
Time_Start := 13;
elsif D_Length = 12 or else D_Length = 21 then
-- Formats are "mmm dd, yyyy" or "mmm dd, yyyy hh:mm:ss"
if D (4) /= ' '
or else D (7) /= ','
or else D (8) /= ' '
then
raise Constraint_Error;
end if;
Year := Year_Number'Value (D (9 .. 12));
Month := Month_Name_To_Number (D (1 .. 3));
Day := Day_Number'Value (D (5 .. 6));
Time_Start := 14;
else
raise Constraint_Error;
end if;
end if;
end Extract_Date;
------------------
-- Extract_Time --
------------------
procedure Extract_Time
(Index : Positive;
Hour : out Hour_Number;
Minute : out Minute_Number;
Second : out Second_Number;
Check_Space : Boolean := False)
is
begin
-- If no time was specified in the string (do not allow trailing
-- character either)
if Index = D_Length + 2 then
Hour := 0;
Minute := 0;
Second := 0;
else
-- Not enough characters left ?
if Index /= D_Length - 7 then
raise Constraint_Error;
end if;
if Check_Space and then D (Index - 1) /= ' ' then
raise Constraint_Error;
end if;
if D (Index + 2) /= ':' or else D (Index + 5) /= ':' then
raise Constraint_Error;
end if;
Hour := Hour_Number'Value (D (Index .. Index + 1));
Minute := Minute_Number'Value (D (Index + 3 .. Index + 4));
Second := Second_Number'Value (D (Index + 6 .. Index + 7));
end if;
end Extract_Time;
-- Local Declarations
Success : Boolean;
Time_Start : Natural := 1;
Time : Ada.Calendar.Time;
-- Start of processing for Value
begin
-- Let's try parsing Date as a supported ISO-8601 format. If we do not
-- succeed, then retry using all the other GNAT supported formats.
Parse_ISO_8601 (Date, Time, Success);
if Success then
return Time;
end if;
-- Length checks
if D_Length not in 8 | 10 | 11 | 12 | 17 | 19 | 20 | 21 then
raise Constraint_Error;
end if;
-- After the correct length has been determined, it is safe to create
-- a local string copy in order to avoid String'First N arithmetic.
D (1 .. D_Length) := Date;
if D_Length /= 8 or else D (3) /= ':' then
Extract_Date (Year, Month, Day, Time_Start);
Extract_Time (Time_Start, Hour, Minute, Second, Check_Space => True);
else
declare
Discard : Second_Duration;
begin
Split (Clock, Year, Month, Day, Hour, Minute, Second,
Sub_Second => Discard);
end;
Extract_Time (1, Hour, Minute, Second, Check_Space => False);
end if;
return Time_Of (Year, Month, Day, Hour, Minute, Second);
end Value;
--------------
-- Put_Time --
--------------
procedure Put_Time (Date : Ada.Calendar.Time; Picture : Picture_String) is
begin
Ada.Text_IO.Put (Image (Date, Picture));
end Put_Time;
end GNAT.Calendar.Time_IO;