1 ------------------------------------------------------------------------------
3 -- GNAT RUN-TIME COMPONENTS --
5 -- G N A T . C A L E N D A R . T I M E _ I O --
9 -- Copyright (C) 1999-2016, AdaCore --
11 -- GNAT is free software; you can redistribute it and/or modify it under --
12 -- terms of the GNU General Public License as published by the Free Soft- --
13 -- ware Foundation; either version 3, or (at your option) any later ver- --
14 -- sion. GNAT is distributed in the hope that it will be useful, but WITH- --
15 -- OUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY --
16 -- or FITNESS FOR A PARTICULAR PURPOSE. --
18 -- As a special exception under Section 7 of GPL version 3, you are granted --
19 -- additional permissions described in the GCC Runtime Library Exception, --
20 -- version 3.1, as published by the Free Software Foundation. --
22 -- You should have received a copy of the GNU General Public License and --
23 -- a copy of the GCC Runtime Library Exception along with this program; --
24 -- see the files COPYING3 and COPYING.RUNTIME respectively. If not, see --
25 -- <http://www.gnu.org/licenses/>. --
27 -- GNAT was originally developed by the GNAT team at New York University. --
28 -- Extensive contributions were provided by Ada Core Technologies Inc. --
30 ------------------------------------------------------------------------------
32 with Ada
.Calendar
; use Ada
.Calendar
;
33 with Ada
.Characters
.Handling
;
34 with Ada
.Strings
.Unbounded
; use Ada
.Strings
.Unbounded
;
39 package body GNAT
.Calendar
.Time_IO
is
55 function Month_Name_To_Number
56 (Str
: String) return Ada
.Calendar
.Month_Number
;
57 -- Converts a string that contains an abbreviated month name to a month
58 -- number. Constraint_Error is raised if Str is not a valid month name.
59 -- Comparison is case insensitive
61 type Padding_Mode
is (None
, Zero
, Space
);
63 type Sec_Number
is mod 2 ** 64;
64 -- Type used to compute the number of seconds since 01/01/1970. A 32 bit
65 -- number will cover only a period of 136 years. This means that for date
66 -- past 2106 the computation is not possible. A 64 bits number should be
67 -- enough for a very large period of time.
69 -----------------------
70 -- Local Subprograms --
71 -----------------------
73 function Am_Pm
(H
: Natural) return String;
74 -- Return AM or PM depending on the hour H
76 function Hour_12
(H
: Natural) return Positive;
77 -- Convert a 1-24h format to a 0-12 hour format
79 function Image
(Str
: String; Length
: Natural := 0) return String;
80 -- Return Str capitalized and cut to length number of characters. If
81 -- length is 0, then no cut operation is performed.
85 Padding
: Padding_Mode
:= Zero
;
86 Length
: Natural := 0) return String;
87 -- Return image of N. This number is eventually padded with zeros or spaces
88 -- depending of the length required. If length is 0 then no padding occurs.
92 Padding
: Padding_Mode
:= Zero
;
93 Length
: Natural := 0) return String;
94 -- As above with N provided in Integer format
100 function Am_Pm
(H
: Natural) return String is
102 if H
= 0 or else H
> 12 then
113 function Hour_12
(H
: Natural) return Positive is
130 Length
: Natural := 0) return String
132 use Ada
.Characters
.Handling
;
133 Local
: constant String :=
134 To_Upper
(Str
(Str
'First)) &
135 To_Lower
(Str
(Str
'First + 1 .. Str
'Last));
140 return Local
(1 .. Length
);
150 Padding
: Padding_Mode
:= Zero
;
151 Length
: Natural := 0) return String
154 return Image
(Sec_Number
(N
), Padding
, Length
);
159 Padding
: Padding_Mode
:= Zero
;
160 Length
: Natural := 0) return String
162 function Pad_Char
return String;
168 function Pad_Char
return String is
171 when None
=> return "";
172 when Zero
=> return "00";
173 when Space
=> return " ";
177 -- Local Declarations
179 NI
: constant String := Sec_Number
'Image (N
);
180 NIP
: constant String := Pad_Char
& NI
(2 .. NI
'Last);
182 -- Start of processing for Image
185 if Length
= 0 or else Padding
= None
then
186 return NI
(2 .. NI
'Last);
188 return NIP
(NIP
'Last - Length
+ 1 .. NIP
'Last);
197 (Date
: Ada
.Calendar
.Time
;
198 Picture
: Picture_String
) return String
200 Padding
: Padding_Mode
:= Zero
;
201 -- Padding is set for one directive
203 Result
: Unbounded_String
;
206 Month
: Month_Number
;
209 Minute
: Minute_Number
;
210 Second
: Second_Number
;
211 Sub_Second
: Second_Duration
;
216 -- Get current time in split format
218 Split
(Date
, Year
, Month
, Day
, Hour
, Minute
, Second
, Sub_Second
);
220 -- Null picture string is error
223 raise Picture_Error
with "null picture string";
226 -- Loop through characters of picture string, building result
228 Result
:= Null_Unbounded_String
;
230 while P
<= Picture
'Last loop
232 -- A directive has the following format "%[-_]."
234 if Picture
(P
) = '%' then
237 if P
= Picture
'Last then
238 raise Picture_Error
with "picture string ends with '%";
241 -- Check for GNU extension to change the padding
243 if Picture
(P
+ 1) = '-' then
247 elsif Picture
(P
+ 1) = '_' then
252 if P
= Picture
'Last then
253 raise Picture_Error
with "picture string ends with '- or '_";
256 case Picture
(P
+ 1) is
261 Result
:= Result
& '%';
266 Result
:= Result
& ASCII
.LF
;
271 Result
:= Result
& ASCII
.HT
;
276 Result
:= Result
& Image
(Hour
, Padding
, 2);
281 Result
:= Result
& Image
(Hour_12
(Hour
), Padding
, 2);
286 Result
:= Result
& Image
(Hour
, Space
, 2);
291 Result
:= Result
& Image
(Hour_12
(Hour
), Space
, 2);
296 Result
:= Result
& Image
(Minute
, Padding
, 2);
301 Result
:= Result
& Am_Pm
(Hour
);
303 -- Time, 12-hour (hh:mm:ss [AP]M)
307 Image
(Hour_12
(Hour
), Padding
, Length
=> 2) & ':' &
308 Image
(Minute
, Padding
, Length
=> 2) & ':' &
309 Image
(Second
, Padding
, Length
=> 2) & ' ' &
312 -- Seconds since 1970-01-01 00:00:00 UTC
313 -- (a nonstandard extension)
317 -- Compute the number of seconds using Ada.Calendar.Time
318 -- values rather than Julian days to account for Daylight
321 Neg
: Boolean := False;
322 Sec
: Duration := Date
- Time_Of
(1970, 1, 1, 0.0);
325 -- Avoid rounding errors and perform special processing
326 -- for dates earlier than the Unix Epoc.
332 Sec
:= abs (Sec
+ 0.5);
335 -- Prepend a minus sign to the result since Sec_Number
336 -- cannot handle negative numbers.
340 Result
& "-" & Image
(Sec_Number
(Sec
), None
);
342 Result
:= Result
& Image
(Sec_Number
(Sec
), None
);
349 Result
:= Result
& Image
(Second
, Padding
, Length
=> 2);
351 -- Milliseconds (3 digits)
352 -- Microseconds (6 digits)
353 -- Nanoseconds (9 digits)
355 when 'i' |
'e' |
'o' =>
357 Sub_Sec
: constant Long_Integer :=
358 Long_Integer (Sub_Second
* 1_000_000_000
);
360 Img1
: constant String := Sub_Sec
'Img;
361 Img2
: constant String :=
362 "00000000" & Img1
(Img1
'First + 1 .. Img1
'Last);
363 Nanos
: constant String :=
364 Img2
(Img2
'Last - 8 .. Img2
'Last);
367 case Picture
(P
+ 1) is
370 Nanos
(Nanos
'First .. Nanos
'First + 2);
374 Nanos
(Nanos
'First .. Nanos
'First + 5);
377 Result
:= Result
& Nanos
;
384 -- Time, 24-hour (hh:mm:ss)
388 Image
(Hour
, Padding
, Length
=> 2) & ':' &
389 Image
(Minute
, Padding
, Length
=> 2) & ':' &
390 Image
(Second
, Padding
, Length
=> 2);
392 -- Locale's abbreviated weekday name (Sun..Sat)
396 Image
(Day_Name
'Image (Day_Of_Week
(Date
)), 3);
398 -- Locale's full weekday name, variable length
399 -- (Sunday..Saturday)
403 Image
(Day_Name
'Image (Day_Of_Week
(Date
)));
405 -- Locale's abbreviated month name (Jan..Dec)
409 Image
(Month_Name
'Image (Month_Name
'Val (Month
- 1)), 3);
411 -- Locale's full month name, variable length
412 -- (January..December).
416 Image
(Month_Name
'Image (Month_Name
'Val (Month
- 1)));
418 -- Locale's date and time (Sat Nov 04 12:02:33 EST 1989)
423 Result
:= Result
& Image
(Date
, "%a %b %d %T %Y");
425 Result
:= Result
& Image
(Date
, "%a %b %_d %_T %Y");
427 Result
:= Result
& Image
(Date
, "%a %b %-d %-T %Y");
430 -- Day of month (01..31)
433 Result
:= Result
& Image
(Day
, Padding
, 2);
439 Image
(Month
, Padding
, 2) & '/' &
440 Image
(Day
, Padding
, 2) & '/' &
441 Image
(Year
, Padding
, 2);
443 -- Day of year (001..366)
446 Result
:= Result
& Image
(Day_In_Year
(Date
), Padding
, 3);
451 Result
:= Result
& Image
(Month
, Padding
, 2);
453 -- Week number of year with Sunday as first day of week
458 Offset
: constant Natural :=
459 (Julian_Day
(Year
, 1, 1) + 1) mod 7;
461 Week
: constant Natural :=
462 1 + ((Day_In_Year
(Date
) - 1) + Offset
) / 7;
465 Result
:= Result
& Image
(Week
, Padding
, 2);
468 -- Day of week (0..6) with 0 corresponding to Sunday
472 DOW
: constant Natural range 0 .. 6 :=
473 (if Day_Of_Week
(Date
) = Sunday
475 else Day_Name
'Pos (Day_Of_Week
(Date
)));
477 Result
:= Result
& Image
(DOW
, Length
=> 1);
480 -- Week number of year with Monday as first day of week
484 Result
:= Result
& Image
(Week_In_Year
(Date
), Padding
, 2);
486 -- Last two digits of year (00..99)
490 Y
: constant Natural := Year
- (Year
/ 100) * 100;
492 Result
:= Result
& Image
(Y
, Padding
, 2);
498 Result
:= Result
& Image
(Year
, None
, 4);
501 raise Picture_Error
with
502 "unknown format character in picture string";
505 -- Skip past % and format character
509 -- Character other than % is copied into the result
512 Result
:= Result
& Picture
(P
);
517 return To_String
(Result
);
520 --------------------------
521 -- Month_Name_To_Number --
522 --------------------------
524 function Month_Name_To_Number
525 (Str
: String) return Ada
.Calendar
.Month_Number
527 subtype String3
is String (1 .. 3);
528 Abbrev_Upper_Month_Names
:
529 constant array (Ada
.Calendar
.Month_Number
) of String3
:=
530 ("JAN", "FEB", "MAR", "APR", "MAY", "JUN",
531 "JUL", "AUG", "SEP", "OCT", "NOV", "DEC");
532 -- Short version of the month names, used when parsing date strings
537 GNAT
.Case_Util
.To_Upper
(S
);
539 for J
in Abbrev_Upper_Month_Names
'Range loop
540 if Abbrev_Upper_Month_Names
(J
) = S
then
545 return Abbrev_Upper_Month_Names
'First;
546 end Month_Name_To_Number
;
552 function Value
(Date
: String) return Ada
.Calendar
.Time
is
553 D
: String (1 .. 21);
554 D_Length
: constant Natural := Date
'Length;
557 Month
: Month_Number
;
560 Minute
: Minute_Number
;
561 Second
: Second_Number
;
563 procedure Extract_Date
564 (Year
: out Year_Number
;
565 Month
: out Month_Number
;
566 Day
: out Day_Number
;
567 Time_Start
: out Natural);
568 -- Try and extract a date value from string D. Time_Start is set to the
569 -- first character that could be the start of time data.
571 procedure Extract_Time
573 Hour
: out Hour_Number
;
574 Minute
: out Minute_Number
;
575 Second
: out Second_Number
;
576 Check_Space
: Boolean := False);
577 -- Try and extract a time value from string D starting from position
578 -- Index. Set Check_Space to True to check whether the character at
579 -- Index - 1 is a space. Raise Constraint_Error if the portion of D
580 -- corresponding to the date is not well formatted.
586 procedure Extract_Date
587 (Year
: out Year_Number
;
588 Month
: out Month_Number
;
589 Day
: out Day_Number
;
590 Time_Start
: out Natural)
593 if D
(3) = '-' or else D
(3) = '/' then
594 if D_Length
= 8 or else D_Length
= 17 then
596 -- Formats are "yy*mm*dd" or "yy*mm*dd hh:mm:ss"
598 if D
(6) /= D
(3) then
599 raise Constraint_Error
;
602 Year
:= Year_Number
'Value ("20" & D
(1 .. 2));
603 Month
:= Month_Number
'Value (D
(4 .. 5));
604 Day
:= Day_Number
'Value (D
(7 .. 8));
607 elsif D_Length
= 10 or else D_Length
= 19 then
609 -- Formats are "mm*dd*yyyy" or "mm*dd*yyyy hh:mm:ss"
611 if D
(6) /= D
(3) then
612 raise Constraint_Error
;
615 Year
:= Year_Number
'Value (D
(7 .. 10));
616 Month
:= Month_Number
'Value (D
(1 .. 2));
617 Day
:= Day_Number
'Value (D
(4 .. 5));
620 elsif D_Length
= 11 or else D_Length
= 20 then
622 -- Formats are "dd*mmm*yyyy" or "dd*mmm*yyyy hh:mm:ss"
624 if D
(7) /= D
(3) then
625 raise Constraint_Error
;
628 Year
:= Year_Number
'Value (D
(8 .. 11));
629 Month
:= Month_Name_To_Number
(D
(4 .. 6));
630 Day
:= Day_Number
'Value (D
(1 .. 2));
634 raise Constraint_Error
;
637 elsif D
(3) = ' ' then
638 if D_Length
= 11 or else D_Length
= 20 then
640 -- Possible formats are "dd mmm yyyy", "dd mmm yyyy hh:mm:ss"
643 raise Constraint_Error
;
646 Year
:= Year_Number
'Value (D
(8 .. 11));
647 Month
:= Month_Name_To_Number
(D
(4 .. 6));
648 Day
:= Day_Number
'Value (D
(1 .. 2));
652 raise Constraint_Error
;
656 if D_Length
= 8 or else D_Length
= 17 then
658 -- Possible formats are "yyyymmdd" or "yyyymmdd hh:mm:ss"
660 Year
:= Year_Number
'Value (D
(1 .. 4));
661 Month
:= Month_Number
'Value (D
(5 .. 6));
662 Day
:= Day_Number
'Value (D
(7 .. 8));
665 elsif D_Length
= 10 or else D_Length
= 19 then
667 -- Possible formats are "yyyy*mm*dd" or "yyyy*mm*dd hh:mm:ss"
669 if (D
(5) /= '-' and then D
(5) /= '/')
670 or else D
(8) /= D
(5)
672 raise Constraint_Error
;
675 Year
:= Year_Number
'Value (D
(1 .. 4));
676 Month
:= Month_Number
'Value (D
(6 .. 7));
677 Day
:= Day_Number
'Value (D
(9 .. 10));
680 elsif D_Length
= 11 or else D_Length
= 20 then
682 -- Possible formats are "yyyy*mmm*dd"
684 if (D
(5) /= '-' and then D
(5) /= '/')
685 or else D
(9) /= D
(5)
687 raise Constraint_Error
;
690 Year
:= Year_Number
'Value (D
(1 .. 4));
691 Month
:= Month_Name_To_Number
(D
(6 .. 8));
692 Day
:= Day_Number
'Value (D
(10 .. 11));
695 elsif D_Length
= 12 or else D_Length
= 21 then
697 -- Formats are "mmm dd, yyyy" or "mmm dd, yyyy hh:mm:ss"
703 raise Constraint_Error
;
706 Year
:= Year_Number
'Value (D
(9 .. 12));
707 Month
:= Month_Name_To_Number
(D
(1 .. 3));
708 Day
:= Day_Number
'Value (D
(5 .. 6));
712 raise Constraint_Error
;
721 procedure Extract_Time
723 Hour
: out Hour_Number
;
724 Minute
: out Minute_Number
;
725 Second
: out Second_Number
;
726 Check_Space
: Boolean := False)
729 -- If no time was specified in the string (do not allow trailing
732 if Index
= D_Length
+ 2 then
738 -- Not enough characters left ?
740 if Index
/= D_Length
- 7 then
741 raise Constraint_Error
;
744 if Check_Space
and then D
(Index
- 1) /= ' ' then
745 raise Constraint_Error
;
748 if D
(Index
+ 2) /= ':' or else D
(Index
+ 5) /= ':' then
749 raise Constraint_Error
;
752 Hour
:= Hour_Number
'Value (D
(Index
.. Index
+ 1));
753 Minute
:= Minute_Number
'Value (D
(Index
+ 3 .. Index
+ 4));
754 Second
:= Second_Number
'Value (D
(Index
+ 6 .. Index
+ 7));
758 -- Local Declarations
760 Time_Start
: Natural := 1;
762 -- Start of processing for Value
768 and then D_Length
/= 10
769 and then D_Length
/= 11
770 and then D_Length
/= 12
771 and then D_Length
/= 17
772 and then D_Length
/= 19
773 and then D_Length
/= 20
774 and then D_Length
/= 21
776 raise Constraint_Error
;
779 -- After the correct length has been determined, it is safe to create
780 -- a local string copy in order to avoid String'First N arithmetic.
782 D
(1 .. D_Length
) := Date
;
784 if D_Length
/= 8 or else D
(3) /= ':' then
785 Extract_Date
(Year
, Month
, Day
, Time_Start
);
786 Extract_Time
(Time_Start
, Hour
, Minute
, Second
, Check_Space
=> True);
790 Discard
: Second_Duration
;
792 Split
(Clock
, Year
, Month
, Day
, Hour
, Minute
, Second
,
793 Sub_Second
=> Discard
);
796 Extract_Time
(1, Hour
, Minute
, Second
, Check_Space
=> False);
802 or else not Month
'Valid
803 or else not Day
'Valid
804 or else not Hour
'Valid
805 or else not Minute
'Valid
806 or else not Second
'Valid
808 raise Constraint_Error
;
811 return Time_Of
(Year
, Month
, Day
, Hour
, Minute
, Second
);
818 procedure Put_Time
(Date
: Ada
.Calendar
.Time
; Picture
: Picture_String
) is
820 Ada
.Text_IO
.Put
(Image
(Date
, Picture
));
823 end GNAT
.Calendar
.Time_IO
;