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