1/* $Id$ 2 3 Part of SWI-Prolog 4 5 Author: Jan Wielemaker and Willem Robert van Hage 6 E-mail: wielemak@science.uva.nl 7 WWW: http://www.swi-prolog.org 8 Copyright (C): 1985-2005, University of Amsterdam 9 10 This program is free software; you can redistribute it and/or 11 modify it under the terms of the GNU General Public License 12 as published by the Free Software Foundation; either version 2 13 of the License, or (at your option) any later version. 14 15 This program is distributed in the hope that it will be useful, 16 but WITHOUT ANY WARRANTY; without even the implied warranty of 17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 GNU General Public License for more details. 19 20 You should have received a copy of the GNU General Public 21 License along with this library; if not, write to the Free Software 22 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 23 24 As a special exception, if you link this library with other files, 25 compiled with a Free Software compiler, to produce an executable, this 26 library does not by itself cause the resulting executable to be covered 27 by the GNU General Public License. This exception does not however 28 invalidate any other reasons why the executable file might be covered by 29 the GNU General Public License. 30*/ 31 32:- module(date, 33 [ date_time_value/3, % ?Field, ?DaTime, ?Value 34 parse_time/2, % +Date, -Stamp 35 parse_time/3, % +Date, ?Format, -Stamp 36 day_of_the_week/2 % +Date, -DayOfTheWeek 37 ]). 38 39%% date_time_value(?Field:atom, +Struct:datime, -Value) is nondet. 40% 41% Extract values from a date-time structure. Provided fields are 42% 43% | year | integer | | 44% | month | 1..12 | | 45% | day | 1..31 | | 46% | hour | 0..23 | | 47% | minute | 0..59 | | 48% | second | 0.0..60.0 | | 49% | utc_offset | integer | Offset to UTC in seconds (positive is west) | 50% | daylight_saving | bool | Name of timezone; fails if unknown | 51% | date | date(Y,M,D) | | 52% | time | time(H,M,S) | | 53 54date_time_value(year, date(Y,_,_,_,_,_,_,_,_), Y). 55date_time_value(month, date(_,M,_,_,_,_,_,_,_), M). 56date_time_value(day, date(_,_,D,_,_,_,_,_,_), D). 57date_time_value(hour, date(_,_,_,H,_,_,_,_,_), H). 58date_time_value(minute, date(_,_,_,_,M,_,_,_,_), M). 59date_time_value(second, date(_,_,_,_,_,S,_,_,_), S). 60date_time_value(utc_offset, date(_,_,_,_,_,_,O,_,_), O). 61date_time_value(time_zone, date(_,_,_,_,_,_,_,Z,_), Z) :- Z \== (-). 62date_time_value(daylight_saving, date(_,_,_,_,_,_,_,_,D), D) :- D \== (-). 63 64date_time_value(date, date(Y,M,D,_,_,_,_,_,_), date(Y,M,D)). 65date_time_value(time, date(_,_,_,H,M,S,_,_,_), time(H,M,S)). 66 67%% parse_time(+Text, -Stamp) is semidet. 68%% parse_time(+Text, ?Format, -Stamp) is semidet. 69% 70% Stamp is a timestamp created from parsing Text using the 71% representation Format. Currently supported formats are: 72% 73% * rfc_1123 74% Used for the HTTP protocol to represent time-stamps 75% * iso_8601 76% Commonly used in XML documents. 77 78parse_time(Text, Stamp) :- 79 parse_time(Text, _Format, Stamp). 80 81parse_time(Text, Format, Stamp) :- 82 atom_codes(Text, Codes), 83 phrase(date(Format, Y,Mon,D,H,Min,S,UTCOffset), Codes), !, 84 date_time_stamp(date(Y,Mon,D,H,Min,S,UTCOffset,-,-), Stamp). 85 86date(iso_8601, Yr, Mon, D, H, Min, S, 0) --> % BC 87 "-", date(iso_8601, Y, Mon, D, H, Min, S, 0), 88 { Yr is -1 * Y }. 89date(iso_8601, Y, Mon, D, H, Min, S, 0) --> 90 year(Y), 91 iso_8601_rest(Y, Mon, D, H, Min, S). 92date(rfc_1123, Y, Mon, D, H, Min, S, 0) --> % RFC 1123: "Fri, 08 Dec 2006 15:29:44 GMT" 93 day_name(_), ", ", ws, 94 day_of_the_month(D), ws, 95 month_name(Mon), ws, 96 year(Y), ws, 97 hour(H), ":", minute(Min), ":", second(S), ws, 98 ( "GMT" 99 -> [] 100 ; [] 101 ). 102 103%% iso_8601_rest(+Year:int, -Mon, -Day, -H, -M, -S) 104% 105% Process ISO 8601 time-values after parsing the 4-digit year. 106 107iso_8601_rest(_, Mon, D, H, Min, S) --> 108 "-", month(Mon), "-", day(D), 109 opt_time(H, Min, S). 110iso_8601_rest(_, Mon, 0, 0, 0, 0) --> 111 "-", month(Mon). 112iso_8601_rest(_, Mon, D, H, Min, S) --> 113 month(Mon), day(D), 114 opt_time(H, Min, S). 115iso_8601_rest(_, 1, D, H, Min, S) --> 116 "-", ordinal(D), 117 opt_time(H, Min, S). 118iso_8601_rest(Yr, 1, D, H, Min, S) --> 119 "-W", week(W), "-", day_of_the_week(DW), 120 opt_time(H, Min, S), 121 { week_ordinal(Yr, W, DW, D) }. 122iso_8601_rest(Yr, 1, D, H, Min, S) --> 123 "W", week(W), day_of_the_week(DW), 124 opt_time(H, Min, S), 125 { week_ordinal(Yr, W, DW, D) }. 126iso_8601_rest(Yr, 1, D, 0, 0, 0) --> 127 "W", week(W), 128 { week_ordinal(Yr, W, 1, D) }. 129 130opt_time(Hr, Min, Sec) --> 131 "T", !, iso_time(Hr, Min, Sec). 132opt_time(0, 0, 0) --> "". 133 134 135% TIMEX2 ISO: "2006-12-08T15:29:44 UTC" or "20061208T" 136iso_time(Hr, Min, Sec) --> 137 hour(H), ":", minute(M), ":", second(S), 138 timezone(DH, DM, DS), 139 { Hr is H + DH, Min is M + DM, Sec is S + DS }. 140iso_time(Hr, Min, Sec) --> 141 hour(H), ":", minute(M), 142 timezone(DH, DM, DS), 143 { Hr is H + DH, Min is M + DM, Sec is DS }. 144iso_time(Hr, Min, Sec) --> 145 hour(H), minute(M), second(S), 146 timezone(DH, DM, DS), 147 { Hr is H + DH, Min is M + DM, Sec is S + DS }. 148iso_time(Hr, Min, Sec) --> 149 hour(H), minute(M), 150 timezone(DH, DM, DS), 151 { Hr is H + DH, Min is M + DM, Sec is DS }. 152iso_time(Hr, Min, Sec) --> 153 hour(H), 154 timezone(DH, DM, DS), 155 { Hr is H + DH, Min is DM, Sec is DS }. 156 157% FIXME: deal with leap seconds 158timezone(Hr, Min, 0) --> 159 "+", hour(H), ":", minute(M), { Hr is -1 * H, Min is -1 * M }. 160timezone(Hr, Min, 0) --> 161 "+", hour(H), minute(M), { Hr is -1 * H, Min is -1 * M }. 162timezone(Hr, 0, 0) --> 163 "+", hour(H), { Hr is -1 * H }. 164timezone(Hr, Min, 0) --> 165 "-", hour(H), ":", minute(M), { Hr is H, Min is M }. 166timezone(Hr, Min, 0) --> 167 "-", hour(H), minute(M), { Hr is H, Min is M }. 168timezone(Hr, 0, 0) --> 169 "-", hour(H), { Hr is H }. 170timezone(0, 0, 0) --> 171 "Z". 172timezone(0, 0, 0) --> 173 ws, "UTC". 174timezone(0, 0, 0) --> 175 ws, "GMT". % remove this? 176timezone(0, 0, 0) --> 177 []. 178 179day_name(0) --> "Sun". 180day_name(1) --> "Mon". 181day_name(2) --> "Tue". 182day_name(3) --> "Wed". 183day_name(4) --> "Thu". 184day_name(5) --> "Fri". 185day_name(6) --> "Sat". 186day_name(7) --> "Sun". 187 188month_name(1) --> "Jan". 189month_name(2) --> "Feb". 190month_name(3) --> "Mar". 191month_name(4) --> "Apr". 192month_name(5) --> "May". 193month_name(6) --> "Jun". 194month_name(7) --> "Jul". 195month_name(8) --> "Aug". 196month_name(9) --> "Sep". 197month_name(10) --> "Oct". 198month_name(11) --> "Nov". 199month_name(12) --> "Dec". 200 201day_of_the_month(N) --> int2digit(N), { between(1, 31, N) }. 202day_of_the_week(N) --> digit(N), { between(1, 7, N) }. 203month(M) --> int2digit(M), { between(1, 12, M) }. 204week(W) --> int2digit(W), { between(1, 53, W) }. 205day(D) --> int2digit(D), { between(1, 31, D) }. 206hour(N) --> int2digit(N), { between(0, 23, N) }. 207minute(N) --> int2digit(N), { between(0, 59, N) }. 208second(N) --> int2digit(N), { between(0, 60, N) }. % leap second 209 210int2digit(N) --> 211 digit(D0), 212 digit(D1), 213 { N is D0*10+D1 }. 214 215year(Y) --> 216 digit(D0), 217 digit(D1), 218 digit(D2), 219 digit(D3), 220 { Y is D0*1000+D1*100+D2*10+D3 }. 221 222ordinal(N) --> % Nth day of the year, jan 1 = 1, dec 31 = 365 or 366 223 digit(D0), 224 digit(D1), 225 digit(D2), 226 { N is D0*100+D1*10+D2, between(1, 366, N) }. 227 228digit(D) --> 229 [C], 230 { code_type(C, digit(D)) }. 231 232ws --> 233 " ", !, 234 ws. 235ws --> 236 []. 237 238%% day_of_the_week(+Date, -DayOfTheWeek) is det. 239% 240% Computes the day of the week for a given date. 241% Days of the week are numbered from one to seven: 242% monday = 1, tuesday = 2, ..., sunday = 7. 243% 244% @param Date is a term of the form date(+Year, +Month, +Day) 245 246day_of_the_week(date(Year, Mon, Day), DotW) :- 247 format_time(atom(A), '%u', date(Year, Mon, Day, 0, 0, 0, 0, -, -)), 248 atom_number(A, DotW). 249 250week_ordinal(Year, Week, Day, Ordinal) :- 251 format_time(atom(A), '%w', date(Year, 1, 1, 0, 0, 0, 0, -, -)), 252 atom_number(A, DotW0), 253 Ordinal is ((Week-1) * 7) - DotW0 + Day + 1. 254 255