File : a-calfor.adb


   1 ------------------------------------------------------------------------------
   2 --                                                                          --
   3 --                         GNAT RUN-TIME COMPONENTS                         --
   4 --                                                                          --
   5 --              A D A . C A L E N D A R . F O R M A T T I N G               --
   6 --                                                                          --
   7 --                                 B o d y                                  --
   8 --                                                                          --
   9 --          Copyright (C) 2006-2012, Free Software Foundation, Inc.         --
  10 --                                                                          --
  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.                                     --
  17 --                                                                          --
  18 --                                                                          --
  19 --                                                                          --
  20 --                                                                          --
  21 --                                                                          --
  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/>.                                          --
  26 --                                                                          --
  27 -- GNAT was originally developed  by the GNAT team at  New York University. --
  28 -- Extensive contributions were provided by Ada Core Technologies Inc.      --
  29 --                                                                          --
  30 ------------------------------------------------------------------------------
  31 
  32 with Ada.Calendar;            use Ada.Calendar;
  33 with Ada.Calendar.Time_Zones; use Ada.Calendar.Time_Zones;
  34 
  35 package body Ada.Calendar.Formatting is
  36 
  37    --------------------------
  38    -- Implementation Notes --
  39    --------------------------
  40 
  41    --  All operations in this package are target and time representation
  42    --  independent, thus only one source file is needed for multiple targets.
  43 
  44    procedure Check_Char (S : String; C : Character; Index : Integer);
  45    --  Subsidiary to the two versions of Value. Determine whether the input
  46    --  string S has character C at position Index. Raise Constraint_Error if
  47    --  there is a mismatch.
  48 
  49    procedure Check_Digit (S : String; Index : Integer);
  50    --  Subsidiary to the two versions of Value. Determine whether the character
  51    --  of string S at position Index is a digit. This catches invalid input
  52    --  such as 1983-*1-j3 u5:n7:k9 which should be 1983-01-03 05:07:09. Raise
  53    --  Constraint_Error if there is a mismatch.
  54 
  55    ----------------
  56    -- Check_Char --
  57    ----------------
  58 
  59    procedure Check_Char (S : String; C : Character; Index : Integer) is
  60    begin
  61       if S (Index) /= C then
  62          raise Constraint_Error;
  63       end if;
  64    end Check_Char;
  65 
  66    -----------------
  67    -- Check_Digit --
  68    -----------------
  69 
  70    procedure Check_Digit (S : String; Index : Integer) is
  71    begin
  72       if S (Index) not in '0' .. '9' then
  73          raise Constraint_Error;
  74       end if;
  75    end Check_Digit;
  76 
  77    ---------
  78    -- Day --
  79    ---------
  80 
  81    function Day
  82      (Date      : Time;
  83       Time_Zone : Time_Zones.Time_Offset := 0) return Day_Number
  84    is
  85       Y  : Year_Number;
  86       Mo : Month_Number;
  87       D  : Day_Number;
  88       H  : Hour_Number;
  89       Mi : Minute_Number;
  90       Se : Second_Number;
  91       Ss : Second_Duration;
  92       Le : Boolean;
  93 
  94       pragma Unreferenced (Y, Mo, H, Mi);
  95 
  96    begin
  97       Split (Date, Y, Mo, D, H, Mi, Se, Ss, Le, Time_Zone);
  98       return D;
  99    end Day;
 100 
 101    -----------------
 102    -- Day_Of_Week --
 103    -----------------
 104 
 105    function Day_Of_Week (Date : Time) return Day_Name is
 106    begin
 107       return Day_Name'Val (Formatting_Operations.Day_Of_Week (Date));
 108    end Day_Of_Week;
 109 
 110    ----------
 111    -- Hour --
 112    ----------
 113 
 114    function Hour
 115      (Date      : Time;
 116       Time_Zone : Time_Zones.Time_Offset := 0) return Hour_Number
 117    is
 118       Y  : Year_Number;
 119       Mo : Month_Number;
 120       D  : Day_Number;
 121       H  : Hour_Number;
 122       Mi : Minute_Number;
 123       Se : Second_Number;
 124       Ss : Second_Duration;
 125       Le : Boolean;
 126 
 127       pragma Unreferenced (Y, Mo, D, Mi);
 128 
 129    begin
 130       Split (Date, Y, Mo, D, H, Mi, Se, Ss, Le, Time_Zone);
 131       return H;
 132    end Hour;
 133 
 134    -----------
 135    -- Image --
 136    -----------
 137 
 138    function Image
 139      (Elapsed_Time          : Duration;
 140       Include_Time_Fraction : Boolean := False) return String
 141    is
 142       To_Char    : constant array (0 .. 9) of Character := "0123456789";
 143       Hour       : Hour_Number;
 144       Minute     : Minute_Number;
 145       Second     : Second_Number;
 146       Sub_Second : Duration;
 147       SS_Nat     : Natural;
 148 
 149       --  Determine the two slice bounds for the result string depending on
 150       --  whether the input is negative and whether fractions are requested.
 151 
 152       First  : constant Integer := (if Elapsed_Time < 0.0 then 1 else 2);
 153       Last   : constant Integer := (if Include_Time_Fraction then 12 else 9);
 154 
 155       Result : String := "-00:00:00.00";
 156 
 157    begin
 158       Split (abs (Elapsed_Time), Hour, Minute, Second, Sub_Second);
 159 
 160       --  Hour processing, positions 2 and 3
 161 
 162       Result (2) := To_Char (Hour / 10);
 163       Result (3) := To_Char (Hour mod 10);
 164 
 165       --  Minute processing, positions 5 and 6
 166 
 167       Result (5) := To_Char (Minute / 10);
 168       Result (6) := To_Char (Minute mod 10);
 169 
 170       --  Second processing, positions 8 and 9
 171 
 172       Result (8) := To_Char (Second / 10);
 173       Result (9) := To_Char (Second mod 10);
 174 
 175       --  Optional sub second processing, positions 11 and 12
 176 
 177       if Include_Time_Fraction and then Sub_Second > 0.0 then
 178 
 179          --  Prevent rounding up when converting to natural, avoiding the zero
 180          --  case to prevent rounding down to a negative number.
 181 
 182          SS_Nat := Natural (Duration'(Sub_Second * 100.0) - 0.5);
 183 
 184          Result (11) := To_Char (SS_Nat / 10);
 185          Result (12) := To_Char (SS_Nat mod 10);
 186       end if;
 187 
 188       return Result (First .. Last);
 189    end Image;
 190 
 191    -----------
 192    -- Image --
 193    -----------
 194 
 195    function Image
 196      (Date                  : Time;
 197       Include_Time_Fraction : Boolean := False;
 198       Time_Zone             : Time_Zones.Time_Offset := 0) return String
 199    is
 200       To_Char : constant array (0 .. 9) of Character := "0123456789";
 201 
 202       Year        : Year_Number;
 203       Month       : Month_Number;
 204       Day         : Day_Number;
 205       Hour        : Hour_Number;
 206       Minute      : Minute_Number;
 207       Second      : Second_Number;
 208       Sub_Second  : Duration;
 209       SS_Nat      : Natural;
 210       Leap_Second : Boolean;
 211 
 212       --  The result length depends on whether fractions are requested.
 213 
 214       Result : String := "0000-00-00 00:00:00.00";
 215       Last   : constant Positive :=
 216         Result'Last - (if Include_Time_Fraction then 0 else 3);
 217 
 218    begin
 219       Split (Date, Year, Month, Day,
 220              Hour, Minute, Second, Sub_Second, Leap_Second, Time_Zone);
 221 
 222       --  Year processing, positions 1, 2, 3 and 4
 223 
 224       Result (1) := To_Char (Year / 1000);
 225       Result (2) := To_Char (Year / 100 mod 10);
 226       Result (3) := To_Char (Year / 10 mod 10);
 227       Result (4) := To_Char (Year mod 10);
 228 
 229       --  Month processing, positions 6 and 7
 230 
 231       Result (6) := To_Char (Month / 10);
 232       Result (7) := To_Char (Month mod 10);
 233 
 234       --  Day processing, positions 9 and 10
 235 
 236       Result (9)  := To_Char (Day / 10);
 237       Result (10) := To_Char (Day mod 10);
 238 
 239       Result (12) := To_Char (Hour / 10);
 240       Result (13) := To_Char (Hour mod 10);
 241 
 242       --  Minute processing, positions 15 and 16
 243 
 244       Result (15) := To_Char (Minute / 10);
 245       Result (16) := To_Char (Minute mod 10);
 246 
 247       --  Second processing, positions 18 and 19
 248 
 249       Result (18) := To_Char (Second / 10);
 250       Result (19) := To_Char (Second mod 10);
 251 
 252       --  Optional sub second processing, positions 21 and 22
 253 
 254       if Include_Time_Fraction and then Sub_Second > 0.0 then
 255 
 256          --  Prevent rounding up when converting to natural, avoiding the zero
 257          --  case to prevent rounding down to a negative number.
 258 
 259          SS_Nat := Natural (Duration'(Sub_Second * 100.0) - 0.5);
 260 
 261          Result (21) := To_Char (SS_Nat / 10);
 262          Result (22) := To_Char (SS_Nat mod 10);
 263       end if;
 264 
 265       return Result (Result'First .. Last);
 266    end Image;
 267 
 268    ------------
 269    -- Minute --
 270    ------------
 271 
 272    function Minute
 273      (Date      : Time;
 274       Time_Zone : Time_Zones.Time_Offset := 0) return Minute_Number
 275    is
 276       Y  : Year_Number;
 277       Mo : Month_Number;
 278       D  : Day_Number;
 279       H  : Hour_Number;
 280       Mi : Minute_Number;
 281       Se : Second_Number;
 282       Ss : Second_Duration;
 283       Le : Boolean;
 284 
 285       pragma Unreferenced (Y, Mo, D, H);
 286 
 287    begin
 288       Split (Date, Y, Mo, D, H, Mi, Se, Ss, Le, Time_Zone);
 289       return Mi;
 290    end Minute;
 291 
 292    -----------
 293    -- Month --
 294    -----------
 295 
 296    function Month
 297      (Date      : Time;
 298       Time_Zone : Time_Zones.Time_Offset := 0) return Month_Number
 299    is
 300       Y  : Year_Number;
 301       Mo : Month_Number;
 302       D  : Day_Number;
 303       H  : Hour_Number;
 304       Mi : Minute_Number;
 305       Se : Second_Number;
 306       Ss : Second_Duration;
 307       Le : Boolean;
 308 
 309       pragma Unreferenced (Y, D, H, Mi);
 310 
 311    begin
 312       Split (Date, Y, Mo, D, H, Mi, Se, Ss, Le, Time_Zone);
 313       return Mo;
 314    end Month;
 315 
 316    ------------
 317    -- Second --
 318    ------------
 319 
 320    function Second (Date : Time) return Second_Number is
 321       Y  : Year_Number;
 322       Mo : Month_Number;
 323       D  : Day_Number;
 324       H  : Hour_Number;
 325       Mi : Minute_Number;
 326       Se : Second_Number;
 327       Ss : Second_Duration;
 328       Le : Boolean;
 329 
 330       pragma Unreferenced (Y, Mo, D, H, Mi);
 331 
 332    begin
 333       Split (Date, Y, Mo, D, H, Mi, Se, Ss, Le);
 334       return Se;
 335    end Second;
 336 
 337    ----------------
 338    -- Seconds_Of --
 339    ----------------
 340 
 341    function Seconds_Of
 342      (Hour       : Hour_Number;
 343       Minute     : Minute_Number;
 344       Second     : Second_Number := 0;
 345       Sub_Second : Second_Duration := 0.0) return Day_Duration is
 346 
 347    begin
 348       --  Validity checks
 349 
 350       if        not Hour'Valid
 351         or else not Minute'Valid
 352         or else not Second'Valid
 353         or else not Sub_Second'Valid
 354       then
 355          raise Constraint_Error;
 356       end if;
 357 
 358       return Day_Duration (Hour   * 3_600) +
 359              Day_Duration (Minute *    60) +
 360              Day_Duration (Second)         +
 361              Sub_Second;
 362    end Seconds_Of;
 363 
 364    -----------
 365    -- Split --
 366    -----------
 367 
 368    procedure Split
 369      (Seconds    : Day_Duration;
 370       Hour       : out Hour_Number;
 371       Minute     : out Minute_Number;
 372       Second     : out Second_Number;
 373       Sub_Second : out Second_Duration)
 374    is
 375       Secs : Natural;
 376 
 377    begin
 378       --  Validity checks
 379 
 380       if not Seconds'Valid then
 381          raise Constraint_Error;
 382       end if;
 383 
 384       Secs := (if Seconds = 0.0 then 0 else Natural (Seconds - 0.5));
 385 
 386       Sub_Second := Second_Duration (Seconds - Day_Duration (Secs));
 387       Hour       := Hour_Number (Secs / 3_600);
 388       Secs       := Secs mod 3_600;
 389       Minute     := Minute_Number (Secs / 60);
 390       Second     := Second_Number (Secs mod 60);
 391 
 392       --  Validity checks
 393 
 394       if not Hour'Valid
 395         or else not Minute'Valid
 396         or else not Second'Valid
 397         or else not Sub_Second'Valid
 398       then
 399          raise Time_Error;
 400       end if;
 401    end Split;
 402 
 403    -----------
 404    -- Split --
 405    -----------
 406 
 407    procedure Split
 408      (Date        : Time;
 409       Year        : out Year_Number;
 410       Month       : out Month_Number;
 411       Day         : out Day_Number;
 412       Seconds     : out Day_Duration;
 413       Leap_Second : out Boolean;
 414       Time_Zone   : Time_Zones.Time_Offset := 0)
 415    is
 416       H  : Integer;
 417       M  : Integer;
 418       Se : Integer;
 419       Su : Duration;
 420       Tz : constant Long_Integer := Long_Integer (Time_Zone);
 421 
 422    begin
 423       Formatting_Operations.Split
 424         (Date        => Date,
 425          Year        => Year,
 426          Month       => Month,
 427          Day         => Day,
 428          Day_Secs    => Seconds,
 429          Hour        => H,
 430          Minute      => M,
 431          Second      => Se,
 432          Sub_Sec     => Su,
 433          Leap_Sec    => Leap_Second,
 434          Use_TZ      => True,
 435          Is_Historic => True,
 436          Time_Zone   => Tz);
 437 
 438       --  Validity checks
 439 
 440       if not Year'Valid
 441         or else not Month'Valid
 442         or else not Day'Valid
 443         or else not Seconds'Valid
 444       then
 445          raise Time_Error;
 446       end if;
 447    end Split;
 448 
 449    -----------
 450    -- Split --
 451    -----------
 452 
 453    procedure Split
 454      (Date       : Time;
 455       Year       : out Year_Number;
 456       Month      : out Month_Number;
 457       Day        : out Day_Number;
 458       Hour       : out Hour_Number;
 459       Minute     : out Minute_Number;
 460       Second     : out Second_Number;
 461       Sub_Second : out Second_Duration;
 462       Time_Zone  : Time_Zones.Time_Offset := 0)
 463    is
 464       Dd : Day_Duration;
 465       Le : Boolean;
 466       Tz : constant Long_Integer := Long_Integer (Time_Zone);
 467 
 468    begin
 469       Formatting_Operations.Split
 470         (Date        => Date,
 471          Year        => Year,
 472          Month       => Month,
 473          Day         => Day,
 474          Day_Secs    => Dd,
 475          Hour        => Hour,
 476          Minute      => Minute,
 477          Second      => Second,
 478          Sub_Sec     => Sub_Second,
 479          Leap_Sec    => Le,
 480          Use_TZ      => True,
 481          Is_Historic => True,
 482          Time_Zone   => Tz);
 483 
 484       --  Validity checks
 485 
 486       if not Year'Valid
 487         or else not Month'Valid
 488         or else not Day'Valid
 489         or else not Hour'Valid
 490         or else not Minute'Valid
 491         or else not Second'Valid
 492         or else not Sub_Second'Valid
 493       then
 494          raise Time_Error;
 495       end if;
 496    end Split;
 497 
 498    -----------
 499    -- Split --
 500    -----------
 501 
 502    procedure Split
 503      (Date        : Time;
 504       Year        : out Year_Number;
 505       Month       : out Month_Number;
 506       Day         : out Day_Number;
 507       Hour        : out Hour_Number;
 508       Minute      : out Minute_Number;
 509       Second      : out Second_Number;
 510       Sub_Second  : out Second_Duration;
 511       Leap_Second : out Boolean;
 512       Time_Zone   : Time_Zones.Time_Offset := 0)
 513    is
 514       Dd : Day_Duration;
 515       Tz : constant Long_Integer := Long_Integer (Time_Zone);
 516 
 517    begin
 518       Formatting_Operations.Split
 519        (Date        => Date,
 520         Year        => Year,
 521         Month       => Month,
 522         Day         => Day,
 523         Day_Secs    => Dd,
 524         Hour        => Hour,
 525         Minute      => Minute,
 526         Second      => Second,
 527         Sub_Sec     => Sub_Second,
 528         Leap_Sec    => Leap_Second,
 529         Use_TZ      => True,
 530         Is_Historic => True,
 531         Time_Zone   => Tz);
 532 
 533       --  Validity checks
 534 
 535       if not Year'Valid
 536         or else not Month'Valid
 537         or else not Day'Valid
 538         or else not Hour'Valid
 539         or else not Minute'Valid
 540         or else not Second'Valid
 541         or else not Sub_Second'Valid
 542       then
 543          raise Time_Error;
 544       end if;
 545    end Split;
 546 
 547    ----------------
 548    -- Sub_Second --
 549    ----------------
 550 
 551    function Sub_Second (Date : Time) return Second_Duration is
 552       Y  : Year_Number;
 553       Mo : Month_Number;
 554       D  : Day_Number;
 555       H  : Hour_Number;
 556       Mi : Minute_Number;
 557       Se : Second_Number;
 558       Ss : Second_Duration;
 559       Le : Boolean;
 560 
 561       pragma Unreferenced (Y, Mo, D, H, Mi);
 562 
 563    begin
 564       Split (Date, Y, Mo, D, H, Mi, Se, Ss, Le);
 565       return Ss;
 566    end Sub_Second;
 567 
 568    -------------
 569    -- Time_Of --
 570    -------------
 571 
 572    function Time_Of
 573      (Year        : Year_Number;
 574       Month       : Month_Number;
 575       Day         : Day_Number;
 576       Seconds     : Day_Duration := 0.0;
 577       Leap_Second : Boolean := False;
 578       Time_Zone   : Time_Zones.Time_Offset := 0) return Time
 579    is
 580       Adj_Year  : Year_Number  := Year;
 581       Adj_Month : Month_Number := Month;
 582       Adj_Day   : Day_Number   := Day;
 583 
 584       H  : constant Integer := 1;
 585       M  : constant Integer := 1;
 586       Se : constant Integer := 1;
 587       Ss : constant Duration := 0.1;
 588       Tz : constant Long_Integer := Long_Integer (Time_Zone);
 589 
 590    begin
 591       --  Validity checks
 592 
 593       if not Year'Valid
 594         or else not Month'Valid
 595         or else not Day'Valid
 596         or else not Seconds'Valid
 597         or else not Time_Zone'Valid
 598       then
 599          raise Constraint_Error;
 600       end if;
 601 
 602       --  A Seconds value of 86_400 denotes a new day. This case requires an
 603       --  adjustment to the input values.
 604 
 605       if Seconds = 86_400.0 then
 606          if Day < Days_In_Month (Month)
 607            or else (Is_Leap (Year)
 608                       and then Month = 2)
 609          then
 610             Adj_Day := Day + 1;
 611          else
 612             Adj_Day := 1;
 613 
 614             if Month < 12 then
 615                Adj_Month := Month + 1;
 616             else
 617                Adj_Month := 1;
 618                Adj_Year  := Year + 1;
 619             end if;
 620          end if;
 621       end if;
 622 
 623       return
 624         Formatting_Operations.Time_Of
 625           (Year         => Adj_Year,
 626            Month        => Adj_Month,
 627            Day          => Adj_Day,
 628            Day_Secs     => Seconds,
 629            Hour         => H,
 630            Minute       => M,
 631            Second       => Se,
 632            Sub_Sec      => Ss,
 633            Leap_Sec     => Leap_Second,
 634            Use_Day_Secs => True,
 635            Use_TZ       => True,
 636            Is_Historic  => True,
 637            Time_Zone    => Tz);
 638    end Time_Of;
 639 
 640    -------------
 641    -- Time_Of --
 642    -------------
 643 
 644    function Time_Of
 645      (Year        : Year_Number;
 646       Month       : Month_Number;
 647       Day         : Day_Number;
 648       Hour        : Hour_Number;
 649       Minute      : Minute_Number;
 650       Second      : Second_Number;
 651       Sub_Second  : Second_Duration := 0.0;
 652       Leap_Second : Boolean := False;
 653       Time_Zone   : Time_Zones.Time_Offset := 0) return Time
 654    is
 655       Dd : constant Day_Duration := Day_Duration'First;
 656       Tz : constant Long_Integer := Long_Integer (Time_Zone);
 657 
 658    begin
 659       --  Validity checks
 660 
 661       if not Year'Valid
 662         or else not Month'Valid
 663         or else not Day'Valid
 664         or else not Hour'Valid
 665         or else not Minute'Valid
 666         or else not Second'Valid
 667         or else not Sub_Second'Valid
 668         or else not Time_Zone'Valid
 669       then
 670          raise Constraint_Error;
 671       end if;
 672 
 673       return
 674         Formatting_Operations.Time_Of
 675           (Year         => Year,
 676            Month        => Month,
 677            Day          => Day,
 678            Day_Secs     => Dd,
 679            Hour         => Hour,
 680            Minute       => Minute,
 681            Second       => Second,
 682            Sub_Sec      => Sub_Second,
 683            Leap_Sec     => Leap_Second,
 684            Use_Day_Secs => False,
 685            Use_TZ       => True,
 686            Is_Historic  => True,
 687            Time_Zone    => Tz);
 688    end Time_Of;
 689 
 690    -----------
 691    -- Value --
 692    -----------
 693 
 694    function Value
 695      (Date      : String;
 696       Time_Zone : Time_Zones.Time_Offset := 0) return Time
 697    is
 698       D          : String (1 .. 22);
 699       Year       : Year_Number;
 700       Month      : Month_Number;
 701       Day        : Day_Number;
 702       Hour       : Hour_Number;
 703       Minute     : Minute_Number;
 704       Second     : Second_Number;
 705       Sub_Second : Second_Duration := 0.0;
 706 
 707    begin
 708       --  Validity checks
 709 
 710       if not Time_Zone'Valid then
 711          raise Constraint_Error;
 712       end if;
 713 
 714       --  Length checks
 715 
 716       if Date'Length /= 19
 717         and then Date'Length /= 22
 718       then
 719          raise Constraint_Error;
 720       end if;
 721 
 722       --  After the correct length has been determined, it is safe to copy the
 723       --  Date in order to avoid Date'First + N indexing.
 724 
 725       D (1 .. Date'Length) := Date;
 726 
 727       --  Format checks
 728 
 729       Check_Char (D, '-', 5);
 730       Check_Char (D, '-', 8);
 731       Check_Char (D, ' ', 11);
 732       Check_Char (D, ':', 14);
 733       Check_Char (D, ':', 17);
 734 
 735       if Date'Length = 22 then
 736          Check_Char (D, '.', 20);
 737       end if;
 738 
 739       --  Leading zero checks
 740 
 741       Check_Digit (D, 6);
 742       Check_Digit (D, 9);
 743       Check_Digit (D, 12);
 744       Check_Digit (D, 15);
 745       Check_Digit (D, 18);
 746 
 747       if Date'Length = 22 then
 748          Check_Digit (D, 21);
 749       end if;
 750 
 751       --  Value extraction
 752 
 753       Year   := Year_Number   (Year_Number'Value   (D (1 .. 4)));
 754       Month  := Month_Number  (Month_Number'Value  (D (6 .. 7)));
 755       Day    := Day_Number    (Day_Number'Value    (D (9 .. 10)));
 756       Hour   := Hour_Number   (Hour_Number'Value   (D (12 .. 13)));
 757       Minute := Minute_Number (Minute_Number'Value (D (15 .. 16)));
 758       Second := Second_Number (Second_Number'Value (D (18 .. 19)));
 759 
 760       --  Optional part
 761 
 762       if Date'Length = 22 then
 763          Sub_Second := Second_Duration (Second_Duration'Value (D (20 .. 22)));
 764       end if;
 765 
 766       --  Sanity checks
 767 
 768       if not Year'Valid
 769         or else not Month'Valid
 770         or else not Day'Valid
 771         or else not Hour'Valid
 772         or else not Minute'Valid
 773         or else not Second'Valid
 774         or else not Sub_Second'Valid
 775       then
 776          raise Constraint_Error;
 777       end if;
 778 
 779       return Time_Of (Year, Month, Day,
 780                       Hour, Minute, Second, Sub_Second, False, Time_Zone);
 781 
 782    exception
 783       when others => raise Constraint_Error;
 784    end Value;
 785 
 786    -----------
 787    -- Value --
 788    -----------
 789 
 790    function Value (Elapsed_Time : String) return Duration is
 791       D          : String (1 .. 11);
 792       Hour       : Hour_Number;
 793       Minute     : Minute_Number;
 794       Second     : Second_Number;
 795       Sub_Second : Second_Duration := 0.0;
 796 
 797    begin
 798       --  Length checks
 799 
 800       if Elapsed_Time'Length /= 8
 801         and then Elapsed_Time'Length /= 11
 802       then
 803          raise Constraint_Error;
 804       end if;
 805 
 806       --  After the correct length has been determined, it is safe to copy the
 807       --  Elapsed_Time in order to avoid Date'First + N indexing.
 808 
 809       D (1 .. Elapsed_Time'Length) := Elapsed_Time;
 810 
 811       --  Format checks
 812 
 813       Check_Char (D, ':', 3);
 814       Check_Char (D, ':', 6);
 815 
 816       if Elapsed_Time'Length = 11 then
 817          Check_Char (D, '.', 9);
 818       end if;
 819 
 820       --  Leading zero checks
 821 
 822       Check_Digit (D, 1);
 823       Check_Digit (D, 4);
 824       Check_Digit (D, 7);
 825 
 826       if Elapsed_Time'Length = 11 then
 827          Check_Digit (D, 10);
 828       end if;
 829 
 830       --  Value extraction
 831 
 832       Hour   := Hour_Number   (Hour_Number'Value   (D (1 .. 2)));
 833       Minute := Minute_Number (Minute_Number'Value (D (4 .. 5)));
 834       Second := Second_Number (Second_Number'Value (D (7 .. 8)));
 835 
 836       --  Optional part
 837 
 838       if Elapsed_Time'Length = 11 then
 839          Sub_Second := Second_Duration (Second_Duration'Value (D (9 .. 11)));
 840       end if;
 841 
 842       --  Sanity checks
 843 
 844       if not Hour'Valid
 845         or else not Minute'Valid
 846         or else not Second'Valid
 847         or else not Sub_Second'Valid
 848       then
 849          raise Constraint_Error;
 850       end if;
 851 
 852       return Seconds_Of (Hour, Minute, Second, Sub_Second);
 853 
 854    exception
 855       when others => raise Constraint_Error;
 856    end Value;
 857 
 858    ----------
 859    -- Year --
 860    ----------
 861 
 862    function Year
 863      (Date      : Time;
 864       Time_Zone : Time_Zones.Time_Offset := 0) return Year_Number
 865    is
 866       Y  : Year_Number;
 867       Mo : Month_Number;
 868       D  : Day_Number;
 869       H  : Hour_Number;
 870       Mi : Minute_Number;
 871       Se : Second_Number;
 872       Ss : Second_Duration;
 873       Le : Boolean;
 874 
 875       pragma Unreferenced (Mo, D, H, Mi);
 876 
 877    begin
 878       Split (Date, Y, Mo, D, H, Mi, Se, Ss, Le, Time_Zone);
 879       return Y;
 880    end Year;
 881 
 882 end Ada.Calendar.Formatting;