1% ---------------------------------------------------------------------- 2% BEGIN LICENSE BLOCK 3% Version: CMPL 1.1 4% 5% The contents of this file are subject to the Cisco-style Mozilla Public 6% License Version 1.1 (the "License"); you may not use this file except 7% in compliance with the License. You may obtain a copy of the License 8% at www.eclipse-clp.org/license. 9% 10% Software distributed under the License is distributed on an "AS IS" 11% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See 12% the License for the specific language governing rights and limitations 13% under the License. 14% 15% The Original Code is The ECLiPSe Constraint Logic Programming System. 16% The Initial Developer of the Original Code is Cisco Systems, Inc. 17% Portions created by the Initial Developer are 18% Copyright (C) 1995-2006 Cisco Systems, Inc. All Rights Reserved. 19% 20% Contributor(s): ECRC GmbH 21% Contributor(s): IC-Parc, Imperal College London 22% 23% END LICENSE BLOCK 24% 25% System: ECLiPSe Constraint Logic Programming System 26% Version: $Id: calendar.pl,v 1.4 2014/01/07 00:53:18 jschimpf Exp $ 27% ---------------------------------------------------------------------- 28 29:- module(calendar). 30 31:- comment(categories, ["Algorithms"]). 32:- comment(summary, 33 "Routines for calendar computations, based on modified julian dates (MJD)."). 34 35:- comment(date, "$Date: 2014/01/07 00:53:18 $"). 36:- comment(copyright, "Cisco Systems, Inc"). 37:- comment(author, "Joachim Schimpf, IC-Parc"). 38:- comment(index, ["date and time","julian date","ISO 8601"]). 39:- comment(desc, html("\ 40 Julian Dates (JD) and Modified Julian Dates (MJD) are a 41 consecutive day numbering scheme widely used in astronomy, 42 space travel etc. It is defined for a long period from 43 12 noon, 1 Jan 4713 BC to 12 noon, 1 Jan 3268 AD. 44 <P> 45 Here we use MJDs as the central representation (JDs are a bit 46 awkward because they change at noon and are very large numbers). 47 <P> 48 Note that you can use fractional MJDs to denote the time of day. 49 The time is then defined to be Universal Time (UT, formerly GMT). 50 That means that every day has a unique integer number, 51 or every time point has a unique float representation! 52 (Using double floats, the resolution is better than 10 53 microseconds until the year 2576, and better than 100 54 microseconds until the year 24826). 55 <P> 56 Differences between times are obviously trivial to compute, 57 and so are weekdays (by simple mod(7) operation). 58 <P> 59 The code is valid for dates starting from 60 1 Mar 0004 = MJD -677422 = JD 1722578.5 61 <P> 62 The relationship between JD and MJD is simply 63 MJD = JD-2400000.5, ie MJD 0 = 17 Nov 1858.")). 64 65% Implementation note: 66% To simplify the leap year computations, we work internally with years 67% that start on the 1st of Mar with day 0 and end on 28 or 29 Feb of 68% the next year with day 364 or 365. Also, we internally use a day 69% numbering starting from (nonexistent) 1.3.0000. 70% 71% The 15 Oct 1582 (MJD -100840, JD 2299160.5) marks the starting 72% date of the gregorian calendar reform (modified leap year rule). 73 74:- export 75 date_to_mjd/2, % date_to_mjd(+D/M/Y, -MJD) 76 mjd_to_date/2, % mjd_to_date(+MJD, -D/M/Y) 77 mjd_to_weekday/2, % mjd_to_weekday(+MJD, -DayName) 78 mjd_to_dow/3, % mjd_to_dow(+MJD, +FirstWeekday, -DoW) 79 mjd_to_dow/2, % mjd_to_dow(+MJD, -DoW) 80 mjd_to_time/2, % mjd_to_time(+MJD, -H:M:S) GMT/UT 81 time_to_mjd/2, % time_to_mjd(+H:M:S, -MJD) MJD < 1.0 82 mjd_to_dy/2, % mjd_to_dy(+MJD, -DoY/Y) 83 dy_to_mjd/2, % dy_to_mjd(+DoY/Y, -MJD) 84 mjd_to_dwy/2, % mjd_to_dwy(+MJD, -DoW/WoY/Y) 85 mjd_to_dwy/3, % mjd_to_dwy(+MJD, +FirstWeekday, -DoW/WoY/Y) 86 dwy_to_mjd/2, % dwy_to_mjd(+DoW/WoY/Y, -MJD) 87 dwy_to_mjd/3, % dwy_to_mjd(+DoW/WoY/Y, +FirstWeekday, -MJD) 88 ymd_to_mjd/2, % ymd_to_mjd(+YMD, -MJD) 89 mjd_to_ymd/2, % mjd_to_ymd(+MJD, -YMD) 90 mjd_to_ywd/2, % mjd_to_ywd(+MJD, -YWD) 91 ywd_to_mjd/2, % ywd_to_mjd(+YWD, -MJD) 92 93 unix_to_mjd/2, % unix_to_mjd(+UnixSec, -MJD) 94 mjd_to_unix/2, % mjd_to_unix(+MJD, -UnixSec) 95 mjd_now/1, % mjd_now(-MJD) 96 easter_mjd/2, % easter_mjd(+Y, -MJD) 97 98 jd_to_mjd/2, % jd_to_mjd(+JD, -MJD) 99 mjd_to_jd/2. % mjd_to_jd(+MJD, -JD) 100 101:- comment(date_to_mjd/2, [ 102 summary:"Convert a date in day/month/year form to an MJD day number", 103 args:["DMY":"A structure of the form D/M/Y where D,M and Y are integers", 104 "MJD":"Variable or integer"], 105 amode:date_to_mjd(++,?), 106 see_also:[mjd_to_date/2] 107 ]). 108:- comment(mjd_to_date/2, [ 109 summary:"Converts an MJD day number into the corresponding Day/Month/Year", 110 args:["MJD":"Integer or float","DMY":"Variable or structure"], 111 amode:mjd_to_date(++,?), 112 see_also:[date_to_mjd/2] 113 ]). 114:- comment(ymd_to_mjd/2, [ 115 summary:"Convert a date in ISO8601 Year-Month-Day form to an MJD day number", 116 args:["YMD":"A structure of the form Y-M-D where D,M and Y are integers", 117 "MJD":"Variable or integer"], 118 amode:ymd_to_mjd(++,?), 119 see_also:[mjd_to_ymd/2] 120 ]). 121:- comment(mjd_to_ymd/2, [ 122 summary:"Converts an MJD day number into the corresponding ISO8601 Year-Month-Day form", 123 args:["MJD":"Integer or float","YMD":"Variable or structure"], 124 amode:mjd_to_ymd(++,?), 125 see_also:[ymd_to_mjd/2] 126 ]). 127:- comment(time_to_mjd/2, [ 128 summary:"Convert the time in H:M:S form to a float MJD <1.0", 129 desc:html("Returns a float MJD <1.0 encoding the time of day (UTC/GMT). This can be added to an integral day number to obtain a full MJD."), 130 args:["HMS":"A structure of the form H:M:S or H:M, where H and M are integers and S is a float", 131 "MJD":"Variable or float"], 132 amode:time_to_mjd(++,?) 133 ]). 134:- comment(mjd_to_time/2, [ 135 summary:"Extracts the time in H:M:S form from a float MJD", 136 desc:html("returns the time of day (UTC/GMT) corresponding to the given MJD as Hour:Minute:Seconds structure, where Hour and Minute are integers and Seconds is a float"), 137 args:["MJD":"Integer or float", "HMS":"Variable or structure"], 138 amode:mjd_to_time(++,?) 139 ]). 140:- comment(mjd_to_weekday/2, [ 141 summary:"returns the weekday of the specified MJD as atom monday, tuesday etc", 142 args:["MJD":"Integer or float", "DayName":"Variable or atom"], 143 amode:mjd_to_weekday(++,?) 144 ]). 145:- comment(mjd_to_dy/2, [ 146 summary:"Convert an MJD to a DayOfYear/Year representation", 147 desc:html("Convert MJD to a DayOfYear/Year representation, where DayOfYear is the relative day number starting with 1 on every January 1st"), 148 args:["MJD":"Integer or float", "DY":"Variable or structure of the form DayOfYear/Year"], 149 amode:mjd_to_dy(++,?) 150 ]). 151:- comment(dy_to_mjd/2, [ 152 summary:"Convert a DayOfYear/Year representation to MJD", 153 desc:html("Convert a DayOfYear/Year representation to MJD, where DayOfYear is the relative day number starting with 1 on every January 1st"), 154 args:["DY":"structure of the form DayOfYear/Year", 155 "MJD":"Variable or integer"], 156 amode:dy_to_mjd(++,?) 157 ]). 158:- comment(mjd_to_dwy/2, [ 159 summary:"Convert an MJD to a DayOfWeek/WeekOfYear/Year representation", 160 desc:html("Convert MJDs to a DayOfWeek/WeekOfYear/Year representation, 161 where DayOfWeek is the day number within the week (1 for monday up to 162 7 for sunday), and WeekOfYear is the week number within the year 163 (starting with 1 for the week that contains January 1st)"), 164 args:["MJD":"Integer or float", "DWY":"Variable or structure of the form Day/Week/Year"], 165 amode:mjd_to_dwy(++,?) 166 ]). 167:- comment(dwy_to_mjd/2, [ 168 summary:"Convert a DayOfWeek/WeekOfYear/Year representation to MJD", 169 desc:html("Convert a DayOfWeek/WeekOfYear/Year representation to MJD, 170 where DayOfWeek is the day number within the week (1 for monday up to 171 7 for sunday), and WeekOfYear is the week number within the year 172 (starting with 1 for the week that contains January 1st)"), 173 args:["DWY":"structure of the form Day/Week/Year", 174 "MJD":"Variable or integer"], 175 amode:dwy_to_mjd(++,?) 176 ]). 177:- comment(mjd_to_dwy/3, [ 178 summary:"Convert an MJD to a DayOfWeek/WeekOfYear/Year representation", 179 desc:html("as mjd_to_dwy/2, but allows to choose a different starting day for weeks, specified as atom monday, tuesday etc"), 180 args:["MJD":"Integer or float", 181 "FirstWeekday":"Atom (monday,tuesday,etc)", 182 "DWY":"Structure of the form Day/Week/Year" ], 183 amode:mjd_to_dwy(++,++,?) 184 ]). 185:- comment(dwy_to_mjd/3, [ 186 summary:"Convert a DayOfWeek/WeekOfYear/Year representation to MJD", 187 desc:html("as dwy_to_mjd/2, but allows to choose a different starting day for weeks, specified as atom monday, tuesday etc"), 188 args:["DWY":"Structure of the form Day/Week/Year", 189 "FirstWeekday":"Atom (monday,tuesday,etc)", 190 "MJD":"Variable or integer"], 191 amode:dwy_to_mjd(++,++,?) 192 ]). 193:- comment(mjd_to_ywd/2, [ 194 summary:"Convert an MJD to ISO8601 Year-Week-Day representation", 195 desc:html("Convert MJDs to a Year-WeekOfYear-DayOfWeek representation 196 according to ISO 8601, where DayOfWeek is the day number within the 197 week (1 for monday up to 7 for sunday), and WeekOfYear is the ISO8601 198 week number (where week 1 is the week containing January 4th). 199 Note that January 1 to 3 may belong to the previous year."), 200 args:["MJD":"Integer or float", "YWD":"Variable or structure of the form Year-Week-Day"], 201 amode:mjd_to_ywd(++,?) 202 ]). 203:- comment(ywd_to_mjd/2, [ 204 summary:"Convert an ISO8601 Year-Week-Day representation to MJD", 205 desc:html("Convert a Year-WeekOfYear-DayOfWeek representation to MJD, 206 where DayOfWeek is the day number within the week (1 for monday 207 up to 7 for sunday), and WeekOfYear is the ISO8601 week number 208 (where week 1 is the week containing January 4th). Note that 209 January 1 to 3 may belong to the previous year."), 210 args:["DWY":"structure of the form Year-Week-Day", 211 "MJD":"Variable or integer"], 212 amode:ywd_to_mjd(++,?) 213 ]). 214:- comment(unix_to_mjd/2, [ 215 summary:"Convert the UNIX time representation into a (float) MJD", 216 args:["UnixTime":"Integer or float (seconds since 1 Jan 1970)", 217 "MJD":"Variable or float"], 218 amode:unix_to_mjd(++,?) 219 ]). 220:- comment(mjd_to_unix/2, [ 221 summary:"Convert an MJD to the UNIX time representation", 222 args:["MJD":"Integer or float", 223 "UnixTime":"Variable or integer"], 224 amode:mjd_to_unix(++,?) 225 ]). 226:- comment(mjd_now/1, [ 227 summary:"Returns the current date/time as (float) MJD", 228 args:["MJD":"Variable or float"], 229 amode:mjd_now(?) 230 ]). 231:- comment(jd_to_mjd/2, [ 232 summary:"Convert Julian Dates (JD) to Modified Julian Dates (MJD)", 233 desc:html("Convert Julian Dates (JD) to Modified Julian Dates (MJD). The relationship is simply MJD = JD-2400000.5"), 234 args:["JD":"Integer or float", 235 "MJD":"Variable or float"], 236 amode:jd_to_mjd(++,?) 237 ]). 238:- comment(mjd_to_jd/2, [ 239 summary:"Convert Modified Julian Dates (MJD) to Julian Dates (JD)", 240 desc:html("Convert Modified Julian Dates (MJD) to Julian Dates (JD). The relationship is simply JD = MJD+2400000.5"), 241 args:["MJD":"Integer or float", 242 "JD":"Variable or float"], 243 amode:mjd_to_jd(++,?) 244 ]). 245:- comment(easter_mjd/2, [ 246 summary:"Calculate Easter Sunday date for a given year", 247 args:["Year":"Integer", 248 "MJD":"Variable or integer"], 249 amode:easter_mjd(+,?), 250 eg:" 251 ?- easter_mjd(2020, MJD), mjd_to_ymd(MJD, YMD). 252 MJD = 58951 253 YMD = 2020 - 4 - 12 254 Yes (0.00s cpu) 255 "]). 256 257 258 259%--------------------------------------------------------------------- 260:- pragma(expand). 261:- pragma(nodebug). 262%--------------------------------------------------------------------- 263 264date_to_mjd(Din/Min/Yin, MJD) :- 265 date_to_internal(Din, Min, Yin, DayNr, Y), 266 dy_leap_days(DayNr, Y, Leapdays), 267 MJD is DayNr + 365*Y + Leapdays - 678883. 268 269 270mjd_to_date(MJD, Dout/Mout/Yout) :- 271 mjd_to_internal(MJD, Days, Leapdays), 272 MJD1 is MJD + 1, 273 mjd_to_internal(MJD1, _Days1, Leapdays1), 274 Y is (Days - Leapdays1)//365, 275 DayNr is Days - Y*365 - Leapdays, 276 date_from_internal(Dout, Mout, Yout, DayNr, Y). 277 278 279:- mode mjd_to_internal(+,-,-). 280mjd_to_internal(MJD, DD, Leapdays) :- 281 MJD >= -100840, !, % after 15 Oct 1582: gregorian 282 DD is fix(MJD + 678881), % 1 Mar 0000 if always gregorian 283 Mod400 is DD mod 146097, % mod days in 400 gregorian years 284 Mod100 is Mod400 mod 36524, % mod days in 100 gregorian years 285 Leapdays is (DD//146097)*97 + (Mod400//36524)*24 + (Mod100//1461). 286mjd_to_internal(MJD, DD, Leapdays) :- 287 DD is fix(MJD + 678883), % 1 Mar 0000 for real 288 Leapdays is DD//1461. % julian rule 289 290 291:- mode dy_leap_days(+,+,-). 292dy_leap_days(DayNr, Y, Leapdays) :- 293 Y*365 + DayNr >= 577658, !, % after 15 Oct 1582? 294 Leapdays is Y//4 - Y//100 + Y//400 + 2. % gregorian rule 295dy_leap_days(_, Y, Leapdays) :- 296 Leapdays is Y//4. % julian rule 297 298 299date_to_internal(D, M, Y, DayNr, Ynorm) :- 300 month_offset(M, Moffs, Yoffs), 301 DayNr is Moffs + D - 1, 302 Ynorm is Y + Yoffs. 303 304date_from_internal(D, M, Y, DayNr, Ynorm) :- 305 month_offset(Month, Moffs, Yoffs), 306 DayNr >= Moffs, 307 !, 308 M = Month, 309 D is DayNr - Moffs + 1, 310 Y is Ynorm - Yoffs. 311 312 313% month_offset(Month, MonthOffs, YearOffs) 314% month_offset(1..12, 0..365, -1..0) 315:- mode month_offset(?, -, -). % clause order important! 316month_offset( 2, 337, -1). % Feb 317month_offset( 1, 306, -1). % Jan 318month_offset(12, 275, 0). % Dec 319month_offset(11, 245, 0). % Nov 320month_offset(10, 214, 0). % Oct 321month_offset( 9, 184, 0). % Sep 322month_offset( 8, 153, 0). % Aug 323month_offset( 7, 122, 0). % Jul 324month_offset( 6, 92, 0). % Jun 325month_offset( 5, 61, 0). % May 326month_offset( 4, 31, 0). % Apr 327month_offset( 3, 0, 0). % Mar 328 329 330%--------------------------------------------------------------------- 331% These have a problem with leap seconds: on these days, the day is 332% 86401 seconds long, but we ignore this. 333% In the mjd_to_time direction, we could stretch the day. 334% In the time_to_mjd direction, we'd need an additional parameter. 335 336mjd_to_time(MJD, H:M:S) :- 337 Frac is MJD-floor(MJD), 338 H is fix(Frac*24), 339 M is fix(Frac*1440) mod 60, 340 S is Frac*86400 - H*3600 - M*60. 341 342time_to_mjd(H:M:S, MJD) :- !, 343 MJD is ((H*60 + M)*60 + S)/86400. 344time_to_mjd(H:M, MJD) :- 345 MJD is (H*60 + M)/1440. 346 347%--------------------------------------------------------------------- 348 349unix_to_mjd(Seconds_since_1970, MJD) :- 350 MJD is 40587 + Seconds_since_1970/86400. 351 352mjd_to_unix(MJD, Seconds_since_1970) :- 353 Seconds_since_1970 is fix(round(86400*(MJD - 40587))). 354 355mjd_now(MJD) :- 356 get_flag(unix_time, T), 357 unix_to_mjd(T, MJD). 358 359%--------------------------------------------------------------------- 360 361jd_to_mjd(JD, MJD) :- 362 MJD is JD-2400000.5. 363 364mjd_to_jd(MJD, JD) :- 365 JD is MJD+2400000.5. 366 367%--------------------------------------------------------------------- 368% CAUTION: Make sure argument of fix() is always positive!!! 369 370mjd_to_dy(MJD, DoY/Y) :- 371 mjd_to_date(MJD, _/_/Y), 372 DoY is fix(MJD - date_to_mjd(1/1/Y)) + 1. 373 374dy_to_mjd(DoY/Y, MJD) :- 375 MJD is date_to_mjd(1/1/Y) + DoY - 1. 376 377 378ymd_to_mjd(Yin-Min-Din, MJD) :- % ISO 8601 379 date_to_mjd(Din/Min/Yin, MJD). 380 381mjd_to_ymd(MJD, Yin-Min-Din) :- % ISO 8601 382 mjd_to_date(MJD, Din/Min/Yin). 383 384 385mjd_to_dow(MJD, DoW) :- 386 mjd_to_dow(MJD, monday, DoW). 387 388mjd_to_dow(MJD, FirstWeekday, DoW) :- 389 DoW is fix(MJD + 678881 + wd_nr(FirstWeekday)) mod 7 + 1. 390 391 wd_nr(monday, 2). 392 wd_nr(tuesday, 1). 393 wd_nr(wednesday, 0). 394 wd_nr(thursday, 6). 395 wd_nr(friday, 5). 396 wd_nr(saturday, 4). 397 wd_nr(sunday, 3). 398 399 400mjd_to_dwy(MJD, DWY) :- 401 mjd_to_dwy(MJD, monday, DWY). 402 403mjd_to_dwy(MJD, FirstWeekday, DoW/WoY/Y) :- 404 mjd_to_date(MJD, _/_/Y), 405 date_to_mjd(1/1/Y, Jan1st), 406 mjd_to_dow(Jan1st, FirstWeekday, Jan1stW), 407 mjd_to_dow(MJD, FirstWeekday, DoW), 408 WoY is fix((MJD-Jan1st) - (DoW-Jan1stW) + 7) // 7. 409 410dwy_to_mjd(DWY, MJD) :- 411 dwy_to_mjd(DWY, monday, MJD). 412 413dwy_to_mjd(DoW/WoY/Y, FirstWeekday, MJD) :- 414 date_to_mjd(1/1/Y, Jan1st), 415 mjd_to_dow(Jan1st, FirstWeekday, Jan1stW), 416 MJD is Jan1st + 7*WoY + (DoW-Jan1stW) - 7. 417 418 419% ISO8601 week numbering 420mjd_to_ywd(MJD, WY-WoY-DoW) :- 421 mjd_to_date(MJD, _/_/Y), 422 date_to_mjd(4/1/Y, Jan4th), 423 mjd_to_dow(Jan4th, monday, Jan4thW), % Week 1 contains Jan 4th 424 MondayWeek0 is Jan4th - Jan4thW - 6, 425 WoY0 is fix(MJD - MondayWeek0) // 7, 426 ( WoY0 == 0 -> 427 WY is Y-1, 428 date_to_mjd(4/1/WY, PrevJan4th), 429 mjd_to_dow(PrevJan4th, monday, PrevJan4thW), 430 PrevMondayWeek0 is PrevJan4th - PrevJan4thW - 6, 431 WoY is (MJD - PrevMondayWeek0) // 7 432 ; 433 WoY = WoY0, 434 WY is Y 435 ), 436 mjd_to_dow(MJD, monday, DoW). 437 438ywd_to_mjd(WY-WoY-DoW, MJD) :- 439 date_to_mjd(4/1/WY, Jan4th), % in week 1 440 mjd_to_dow(Jan4th, monday, Jan4thW), 441 MJD is Jan4th + 7*WoY + (DoW-Jan4thW) - 7. 442 443 444mjd_to_weekday(MJD, WD) :- 445 I is fix(MJD + 678881) mod 7 + 1, % avoid negative modulus! 446 arg(I, wd(wednesday, thursday, friday, 447 saturday, sunday, monday, tuesday), WD). 448 449 450%--------------------------------------------------------------------- 451% Easter 452%--------------------------------------------------------------------- 453 454easter_mjd(Y, MJD) :- 455 ( Y =< 1582 -> 456 % Julian 457 A is Y mod 4, 458 B is Y mod 7, 459 C is Y mod 19, 460 D is (19*C + 15) mod 30, 461 E is (2*A + 4*B - D + 34) mod 7, 462 MJD is D + E + ymd_to_mjd(Y-3-22) 463 ; 464 % Gregorian 465 A is Y mod 19, 466 B is integer(floor(Y / 100)), 467 C is Y mod 100, 468 D is integer(floor(B / 4)), 469 E is B mod 4, 470 F is integer(floor((B + 8) / 25)), 471 G is integer(floor((B - F + 1) / 3)), 472 H is (19*A + B - D - G + 15) mod 30, 473 I is integer(floor(C / 4)), 474 K is C mod 4, 475 L is (32 + 2*E + 2*I - H - K) mod 7, 476 M is integer(floor((A + 11*H + 22*L) / 451)), 477 MJD is H + L -7*M + ymd_to_mjd(Y-3-22) 478 ). 479 480 481%--------------------------------------------------------------------- 482 483/*** 484 485% Table of leap seconds 486 487leap(41498). % 1972-06-30 488leap(41682). % 1972-12-31 489leap(42047). % 1973-12-31 490leap(42412). % 1974-12-31 491leap(42777). % 1975-12-31 492leap(43143). % 1976-12-31 493leap(43508). % 1977-12-31 494leap(43873). % 1978-12-31 495leap(44238). % 1979-12-31 496leap(44785). % 1981-06-30 497leap(45150). % 1982-06-30 498leap(45515). % 1983-06-30 499leap(46246). % 1985-06-30 500leap(47160). % 1987-12-31 501leap(47891). % 1989-12-31 502leap(48256). % 1990-12-31 503leap(48803). % 1992-06-30 504leap(49168). % 1993-06-30 505leap(49533). % 1994-06-30 506leap(50082). % 1995-12-31 507leap(50629). % 1997-06-30 508leap(51178). % 1998-12-31 509leap(53735). % 2005-12-31 510leap(54831). % 2008-12-31 511leap(56108). % 2012-06-30 512 513 514%----------------------------------------------------------------------- 515% exhaustive test 516% should only print the 10 missing days 5..14 Oct 1582 517%----------------------------------------------------------------------- 518test_all :- 519 setval(mjd, -678577), between(1, 3267, 1, Y), 520% setval(mjd, -101117), between(1582, 3267, 1, Y), 521 522 put(13), write(Y), flush(output), 523 between(1, 12, 1, M), 524 (leap(Y) -> 525 arg(M, d(31,29,31,30,31,30,31,31,30,31,30,31), MD) 526 ; 527 arg(M, d(31,28,31,30,31,30,31,31,30,31,30,31), MD) 528 ), 529 between(1, MD, 1, D), 530 DateIn = D/M/Y, 531 date_to_mjd(DateIn, MJD), 532 mjd_to_date(MJD, DateOut), 533 ( DateOut == DateIn -> 534 getval(mjd, MJDexp), 535 ( MJD =:= MJDexp -> 536 incval(mjd) 537 ; 538 printf("wrong MJD generated: %w\n", date_to_mjd(DateIn, MJDexp) -> MJD) 539 ) 540 ; 541 printf("reverse conversion failed: %w\n", mjd_to_date(MJD, DateIn) -> DateOut) 542 ), 543 fail. 544test_all. 545 546leap(Y) :- 547 Y mod 4 =:= 0, 548 ( Y < 1582 -> 549 true 550 ; Y mod 100 =:= 0 -> 551 Y mod 400 =:= 0 552 ; 553 true 554 ). 555 556now :- 557 mjd_now(MJD), 558 mjd_to_date(MJD, Date), 559 mjd_to_time(MJD, Time), 560 mjd_to_weekday(MJD, W), 561 writeln((W,Date,Time,mjd=MJD)). 562***/ 563 564:- comment(eg, html(" 565What day of the week was the 29th of December 1959? 566<PRE> 567[eclipse 1]: lib(calendar). 568[eclipse 2]: date_to_mjd(29/12/1959, MJD), mjd_to_weekday(MJD,W). 569MJD = 36931 570W = tuesday 571</PRE> 572What date and time is it now? 573<PRE> 574[eclipse 3]: mjd_now(MJD), mjd_to_date(MJD,Date), mjd_to_time(MJD,Time). 575Date = 19 / 5 / 1999 576MJD = 51317.456238425926 577Time = 10 : 56 : 59.000000017695129 578</PRE> 579How many days are there in the 20th century? 580<PRE> 581[eclipse 4]: N is date_to_mjd(1/1/2001) - date_to_mjd(1/1/1901). 582N = 36525 583</PRE> 584The library code does not detect invalid dates, but this is easily done by converting a date to its MJD and 585back and checking whether they match: 586<PRE> 587[eclipse 5]: [user]. 588valid_date(Date) :- 589 date_to_mjd(Date,MJD), 590 mjd_to_date(MJD,Date). 591 592[eclipse 6]: valid_date(29/2/1900). % 1900 is not a leap year! 593no (more) solution. 594</PRE>" 595)). 596