View source with formatted comments or as raw
    1/*  Part of SWI-Prolog
    2
    3    Author:        Jan Wielemaker and Willem Robert van Hage
    4    E-mail:        wielemak@science.uva.nl
    5    WWW:           http://www.swi-prolog.org
    6    Copyright (c)  2006-2014, University of Amsterdam
    7    All rights reserved.
    8
    9    Redistribution and use in source and binary forms, with or without
   10    modification, are permitted provided that the following conditions
   11    are met:
   12
   13    1. Redistributions of source code must retain the above copyright
   14       notice, this list of conditions and the following disclaimer.
   15
   16    2. Redistributions in binary form must reproduce the above copyright
   17       notice, this list of conditions and the following disclaimer in
   18       the documentation and/or other materials provided with the
   19       distribution.
   20
   21    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   22    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   23    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
   24    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
   25    COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
   26    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
   27    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   28    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   29    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   30    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
   31    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   32    POSSIBILITY OF SUCH DAMAGE.
   33*/
   34
   35:- module(date,
   36          [ date_time_value/3,          % ?Field, ?DaTime, ?Value
   37            parse_time/2,               % +Date, -Stamp
   38            parse_time/3,               % +Date, ?Format, -Stamp
   39            day_of_the_week/2,          % +Date, -DayOfTheWeek
   40            day_of_the_year/2           % +Date, -DayOfTheYear
   41          ]).   42
   43/** <module> Process dates and times
   44*/
   45
   46%!  date_time_value(?Field:atom, +Struct:datime, -Value) is nondet.
   47%
   48%   Extract values from a date-time structure.  Provided fields are
   49%
   50%           | year | integer | |
   51%           | month | 1..12 | |
   52%           | day | 1..31 | |
   53%           | hour | 0..23 | |
   54%           | minute | 0..59 | |
   55%           | second | 0.0..60.0 | |
   56%           | utc_offset | integer | Offset to UTC in seconds (positive is west) |
   57%           | daylight_saving | bool | Name of timezone; fails if unknown |
   58%           | date | date(Y,M,D) | |
   59%           | time | time(H,M,S) | |
   60
   61date_time_value(year,            date(Y,_,_,_,_,_,_,_,_), Y).
   62date_time_value(month,           date(_,M,_,_,_,_,_,_,_), M).
   63date_time_value(day,             date(_,_,D,_,_,_,_,_,_), D).
   64date_time_value(hour,            date(_,_,_,H,_,_,_,_,_), H).
   65date_time_value(minute,          date(_,_,_,_,M,_,_,_,_), M).
   66date_time_value(second,          date(_,_,_,_,_,S,_,_,_), S).
   67date_time_value(utc_offset,      date(_,_,_,_,_,_,O,_,_), O).
   68date_time_value(time_zone,       date(_,_,_,_,_,_,_,Z,_), Z) :- Z \== (-).
   69date_time_value(daylight_saving, date(_,_,_,_,_,_,_,_,D), D) :- D \== (-).
   70
   71date_time_value(date,            date(Y,M,D,_,_,_,_,_,_), date(Y,M,D)).
   72date_time_value(time,            date(_,_,_,H,M,S,_,_,_), time(H,M,S)).
   73
   74%!  parse_time(+Text, -Stamp) is semidet.
   75%!  parse_time(+Text, ?Format, -Stamp) is semidet.
   76%
   77%   Stamp is a  timestamp  created  from   parsing  Text  using  the
   78%   representation Format. Currently supported formats are:
   79%
   80%       * rfc_1123
   81%       Used for the HTTP protocol to represent time-stamps, e.g.
   82%
   83%           Fri, 08 Dec 2006 15:29:44 GMT
   84%
   85%       * iso_8601
   86%       Commonly used in XML documents. Actually the XML RFC3339
   87%       is a _profile_ of ISO8601.  For example
   88%
   89%           2006-12-08T15:29:44Z
   90%
   91%   @see  xsd_time_string/3  from  library(sgml)    implements   RFC3339
   92%   strictly.
   93
   94parse_time(Text, Stamp) :-
   95    parse_time(Text, _Format, Stamp).
   96
   97parse_time(Text, Format, Stamp) :-
   98    atom_codes(Text, Codes),
   99    phrase(date(Format, Y,Mon,D,H,Min,S,UTCOffset), Codes),
  100    !,
  101    date_time_stamp(date(Y,Mon,D,H,Min,S,UTCOffset,-,-), Stamp).
  102
  103date(iso_8601, Yr, Mon, D, H, Min, S, 0) --> % BC
  104    "-", date(iso_8601, Y, Mon, D, H, Min, S, 0),
  105    { Yr is -1 * Y }.
  106date(iso_8601, Y, Mon, D, H, Min, S, 0) -->
  107    year(Y),
  108    iso_8601_rest(Y, Mon, D, H, Min, S).
  109date(rfc_1123, Y, Mon, D, Hr, Min, Sec, 0) --> % RFC 1123: "Fri, 08 Dec 2006 15:29:44 GMT"
  110    day_name(_), ", ", ws,
  111    day_of_the_month(D), ws,
  112    month_name(Mon), ws,
  113    year(Y), ws,
  114    hour(H), ":", minute(M), ":", second(S), ws,
  115    timezone(DH, DM, DS),
  116    { Hr is H + DH, Min is M + DM, Sec is S + DS }.
  117
  118
  119%!  iso_8601_rest(+Year:int, -Mon, -Day, -H, -M, -S)
  120%
  121%   Process ISO 8601 time-values after parsing the 4-digit year.
  122
  123iso_8601_rest(_, Mon, D, H, Min, S) -->
  124    "-", month(Mon), "-", day(D),
  125    opt_time(H, Min, S).
  126iso_8601_rest(_, Mon, 0, 0, 0, 0) -->
  127    "-", month(Mon).
  128iso_8601_rest(_, Mon, D, H, Min, S) -->
  129    month(Mon), day(D),
  130    opt_time(H, Min, S).
  131iso_8601_rest(_, 1, D, H, Min, S) -->
  132    "-", ordinal(D),
  133    opt_time(H, Min, S).
  134iso_8601_rest(Yr, 1, D, H, Min, S) -->
  135    "-W", week(W), "-", day_of_the_week(DW),
  136    opt_time(H, Min, S),
  137    { week_ordinal(Yr, W, DW, D) }.
  138iso_8601_rest(Yr, 1, D, H, Min, S) -->
  139    "W", week(W), day_of_the_week(DW),
  140    opt_time(H, Min, S),
  141    { week_ordinal(Yr, W, DW, D) }.
  142iso_8601_rest(Yr, 1, D, 0, 0, 0) -->
  143    "W", week(W),
  144    { week_ordinal(Yr, W, 1, D) }.
  145
  146opt_time(Hr, Min, Sec) -->
  147    ("T";" "), !, iso_time(Hr, Min, Sec).
  148opt_time(0, 0, 0) --> "".
  149
  150
  151% TIMEX2 ISO: "2006-12-08T15:29:44 UTC" or "20061208T"
  152iso_time(Hr, Min, Sec) -->
  153    hour(H), ":", minute(M), ":", second(S),
  154    timezone(DH, DM, DS),
  155    { Hr is H + DH, Min is M + DM, Sec is S + DS }.
  156iso_time(Hr, Min, Sec) -->
  157    hour(H), ":", minute(M),
  158    timezone(DH, DM, DS),
  159    { Hr is H + DH, Min is M + DM, Sec is DS }.
  160iso_time(Hr, Min, Sec) -->
  161    hour(H), minute(M), second(S),
  162    timezone(DH, DM, DS),
  163    { Hr is H + DH, Min is M + DM, Sec is S + DS }.
  164iso_time(Hr, Min, Sec) -->
  165    hour(H), minute(M),
  166    timezone(DH, DM, DS),
  167    { Hr is H + DH, Min is M + DM, Sec is DS }.
  168iso_time(Hr, Min, Sec) -->
  169    hour(H),
  170    timezone(DH, DM, DS),
  171    { Hr is H + DH, Min is DM, Sec is DS }.
  172
  173% FIXME: deal with leap seconds
  174timezone(Hr, Min, 0) -->
  175    "+", hour(H), ":", minute(M), { Hr is -1 * H, Min is -1 * M }.
  176timezone(Hr, Min, 0) -->
  177    "+", hour(H), minute(M), { Hr is -1 * H, Min is -1 * M }.
  178timezone(Hr, 0, 0) -->
  179    "+", hour(H), { Hr is -1 * H }.
  180timezone(Hr, Min, 0) -->
  181    "-", hour(H), ":", minute(M), { Hr is H, Min is M }.
  182timezone(Hr, Min, 0) -->
  183    "-", hour(H), minute(M), { Hr is H, Min is M }.
  184timezone(Hr, 0, 0) -->
  185    "-", hour(H), { Hr is H }.
  186timezone(0, 0, 0) -->
  187    "Z".
  188timezone(0, 0, 0) -->
  189    ws, "UTC".
  190timezone(0, 0, 0) -->
  191    ws, "GMT". % remove this?
  192timezone(0, 0, 0) -->
  193    [].
  194
  195day_name(0) --> "Sun".
  196day_name(1) --> "Mon".
  197day_name(2) --> "Tue".
  198day_name(3) --> "Wed".
  199day_name(4) --> "Thu".
  200day_name(5) --> "Fri".
  201day_name(6) --> "Sat".
  202day_name(7) --> "Sun".
  203
  204month_name(1) --> "Jan".
  205month_name(2) --> "Feb".
  206month_name(3) --> "Mar".
  207month_name(4) --> "Apr".
  208month_name(5) --> "May".
  209month_name(6) --> "Jun".
  210month_name(7) --> "Jul".
  211month_name(8) --> "Aug".
  212month_name(9) --> "Sep".
  213month_name(10) --> "Oct".
  214month_name(11) --> "Nov".
  215month_name(12) --> "Dec".
  216
  217day_of_the_month(N) --> int2digit(N), { between(1, 31, N) }.
  218day_of_the_week(N)  --> digit(N),     { between(1,  7, N) }.
  219month(M)            --> int2digit(M), { between(1, 12, M) }.
  220week(W)             --> int2digit(W), { between(1, 53, W) }.
  221day(D)              --> int2digit(D), { between(1, 31, D) }.
  222hour(N)             --> int2digit(N), { between(0, 23, N) }.
  223minute(N)           --> int2digit(N), { between(0, 59, N) }.
  224second(S)           --> int2digit(N), { between(0, 60, N) }, % leap second
  225    opt_fraction(N, S).
  226
  227opt_fraction(I, F) -->
  228    ( "." ; "," ),
  229    !,
  230    digits(D),
  231    { length(D, N),
  232      N > 0,
  233      number_codes(FP, D),
  234      F is I + FP/(10^N)
  235    }.
  236opt_fraction(I, I) -->
  237    [].
  238
  239int2digit(N) -->
  240    digit(D0),
  241    digit(D1),
  242    { N is D0*10+D1 }.
  243
  244year(Y) -->
  245    digit(D0),
  246    digit(D1),
  247    digit(D2),
  248    digit(D3),
  249    { Y is D0*1000+D1*100+D2*10+D3 }.
  250
  251ordinal(N) --> % Nth day of the year, jan 1 = 1, dec 31 = 365 or 366
  252    digit(D0),
  253    digit(D1),
  254    digit(D2),
  255    { N is D0*100+D1*10+D2, between(1, 366, N) }.
  256
  257digit(D) -->
  258    [C],
  259    { code_type(C, digit(D)) }.
  260
  261digits([C|T]) -->
  262    [C],
  263    { code_type(C, digit) },
  264    !,
  265    digits(T).
  266digits([]) --> [].
  267
  268ws -->
  269    " ",
  270    !,
  271    ws.
  272ws -->
  273    [].
  274
  275%!  day_of_the_week(+Date, -DayOfTheWeek) is det.
  276%
  277%   Computes the day of the week for a  given date. Days of the week
  278%   are numbered from one to seven: monday   =  1, tuesday = 2, ...,
  279%   sunday = 7.
  280%
  281%   @param Date is a term of the form date(+Year, +Month, +Day)
  282
  283day_of_the_week(date(Year, Mon, Day), DotW) :-
  284    format_time(atom(A), '%u', date(Year, Mon, Day, 0, 0, 0, 0, -, -)),
  285    atom_number(A, DotW).
  286
  287week_ordinal(Year, Week, Day, Ordinal) :-
  288    format_time(atom(A), '%w', date(Year, 1, 1, 0, 0, 0, 0, -, -)),
  289    atom_number(A, DotW0),
  290    Ordinal is ((Week-1) * 7) - DotW0 + Day + 1.
  291
  292%!  day_of_the_year(+Date, -DayOfTheYear) is det.
  293%
  294%   Computes the day of the year for a  given date. Days of the year
  295%   are numbered from 1 to 365 (366 for a leap year).
  296%
  297%   @param Date is a term of the form date(+Year, +Month, +Day)
  298
  299day_of_the_year(date(Year, Mon, Day), DotY) :-
  300    format_time(atom(A), '%j', date(Year, Mon, Day, 0, 0, 0, 0, -, -)),
  301    atom_number(A, DotY)