DEFINITION MODULE JulianDays;
(*******************************************************************
Module JulianDays (Version 3.0)
Copyright (c) 1989-2006 by Andreas Fischlin and ETH Zurich.
Purpose Translate back and forth dates into a number of
days (Julian days) in order to allow the computing
with dates.
Remarks Julian days measure time continuously in days; where
the integral part defines the number of days and the
fractional part defines hours, minutes and seconds
of the day not yet completed. The unit is J.D.,
which stands for (unmodified) Julian Days. The origin
of the time scale can be set arbitrarily to any
supported date.
This implementation supports the Gregorian calendar
(Gregorian calendar correction by Pope Gregor
XIII), which is valid after 15.Oct.1582 up to the
present and much of the foreseeable future. Note
that this date followed immediately after
4.Oct.1582 to correct for accumulated errors in the
previously used Julian calendar. The latter has
been introduced by Julius Caesar "ab urbe
condiata", the foundation of Rome, i.e. 753 BC and
accounted already for months with variable day
numbers and leap years to accomodate the actual
length of the year, which was estimated to be
365.25 days (see below, tropical year). Note the
10 day gap between the Julian and Gregorian
calendar where the Julian calendar ends on October
4, 1582 (JD = 2299160), and the Gregorian calendar
starts immediately thereafter on October 15, 1582.
The Gregorian calendar will need no corrections for
3333 years. Afterwards, the deviation of the
calendar from the true tropical year will have
accumulated to more than a day and another calendar
correction will be required.
Note, Julian Days sensu stricto all fall within the
so-called Julian Period. The latter is used in
astronomy and has been proposed by Joseph Justus
Scaliger (1581). It begins with the First Julian
Date (J.D.) = middle noon (12h00 standard world
time or Greenwich time), 1.Jan.4713 BC (= year
-4712). Note, there is also the so-called modified
Julian Date (M.J.D.) in use today (much used in
space travel) which starts at 17.Nov.1858 00h00'00"
=> 0.0 M.J.D. = 2'400'000.5 J.D.
The fractional part of a julian days value can
represent any time as a fraction of 24 hours, i.e.
(i) UT (Universal Time or Greenwich civil time
based on the Earth's rotation and being 0.0 at
midnight); (iii TAI (International Atomic Time);
(iii) UTC (Coordinated Universal Time, the legal
time, which is kept within 0.9 seconds of UT by
introducing leap second as necessary); or (iv)
standard time (often referred to as local time and
defined by a fixed offset (in multiples of half
hours) from UT). In case your location is close to
the international date line (~180° longitude) there
is little risk to obtain a wrong date with any of
above listed time systems. If in doubt first
convert to UT by subtracting the fixed offset before
using any of the algorithms from this module.
Note, this offset may vary with seasons as daylight
saving time rules are now applied within most
standard time zones (cf.
http://en.wikipedia.org/wiki/Local_time).
IMPLEMENTATION RESTRICTION: This module does not
support the full Julian period and is fully valid
only after 15th October 1582, since it's current
implementation is restricted and does not correctly
support the Gregorian calendar correction. Instead
it supports the often needed setting of an
arbitrary set calendar range, where the minimum of
the range corresponds to the origin of the time
scale as measured in julian days (for details see
routine SetCalendarRange).
The year as actually perceived on Earth is the
tropical year, i.e. the actual number of days (not
really a constant of course): 365 days, 5 h, 48
min, 46.98 sec
tropical year = 365.242210416667 d
The tropical year is different from the so-called
sidereal year (siderisches Jahr). It is the time
the sun needs to complete a revolution measured
relative to stars on the sun's orbit as seen on
Earth: 365 days, 6 h, 9 min, 9.54 sec
sidereal year = 365.256360416667 d
Programming
o Design
Andreas Fischlin 24/09/1989
o Implementation
Andreas Fischlin 24/09/1989
ETH Zurich
Systems Ecology
CHN E 35.1
Universitaetstrasse 16
8092 Zurich
SWITZERLAND
URLs:
<mailto:RAMSES@env.ethz.ch>
<http://www.sysecol.ethz.ch>
<http://www.sysecol.ethz.ch/SimSoftware/RAMSES>
Last revision of definition: 20/01/2000 AF
*******************************************************************)
CONST
Jan = 1; Feb = 2; Mar = 3; Apr = 4; Mai = 5; Jun = 6;
Jul = 7; Aug = 8; Sep = 9; Oct = 10; Nov = 11; Dec = 12;
Sun = 1; Mon = 2; Tue = 3; Wed = 4; Thur = 5; Fri = 6; Sat = 7;
TYPE
Month = [Jan..Dec];
WeekDay = [Sun..Sat];
DateAndTime = RECORD
year: INTEGER; (* e.g. 1582,...,1994,...,2040 etc.*)
month: Month;
day: INTEGER; (* [1..31] (depends on month) *)
hour, (* [0..23] *)
min: INTEGER; (* [0..59] *)
sec: INTEGER; (* [0..59] *)
dayOfWeek: WeekDay; (* e.g. Sun *)
secFrac: REAL; (* fraction of a second,
e.g. 0.13 for 13 hundredth of a second *)
END;
PROCEDURE DateTimeToJulDay(dt: DateAndTime): LONGREAL;
PROCEDURE JulDayToDateTime(jd: LONGREAL; VAR dt: DateAndTime);
(*
Above two routines allow to convert between a julian day given
as a real number and an ordinary calendar date plus the time of
the day.
*)
PROCEDURE DateToJulDay(day,month,year: INTEGER): LONGINT;
PROCEDURE JulDayToDate(jd: LONGINT; VAR day: INTEGER;
VAR month: Month;
VAR year: INTEGER;
VAR dayOfWeek: WeekDay);
(*
Above two routines allow to convert between a julian day and an
ordinary calendar date. Hereby ignoring the time of the day.
*)
PROCEDURE IsLeapYear(yr: INTEGER): BOOLEAN;
PROCEDURE SetCalendarRange(firstYear,lastYear,firstSunday: INTEGER);
(*
This procedure allows to set the calendar range for which
the algorithms of this module shall work. Basically they
work correctly within the Gregorian Calendar, which starts
from the date 15.Oct.1582 and needs no correction for the
next 3333 years, i.e. till 4915.
However, implementation restrictions reduce somewhat the
actually usable range and require to satisfy strictly the
following constraints: Parameter firstYear must be an year
following immediately a leap year. The day of the first
Sunday in January in the first year (firstSunday) must be
specified, otherwise weekdays can't be computed correctly
and some algorithms will fail. The origin of the time
scale (0.0) as defined by a call to SetCalendarRange is the
begin of the day with date 31st December of the year, which
preceeds firstYear, i.e. 1.Jan.firstYear 0h00 = 1.0 J.D.
The days between the origin of the time scale till the day
before firstSunday are to be excluded (at most 7 days, i.e.
the interval [0.0 .. firstSunday) ). The value for
lastYear is by 1 smaller than 4915. SetCalendarRange
attempts to detect faulty values in the actual parameters.
However, not all are checked fully, to allow for the use of
this module within a calendar range before 15.Oct.1582
(results without any warranty for correctness!). If errors
are detected, they will lead to an error condition (HALT)
and the previous calendar range is preserved. Correctness
of the implementation has only been tested for the largest
part of the Gregorian calendar, i.e. the range [6.Jan.1585
.. 31.Dez.4914] (see below).
For efficiency reasons, no tests are made in the routines
JulDayToDate or JulDayToDateTime whether the actual julian
day argument falls within the current calendar range. If
in doubt and to avoid the inevitable calendar range run
time errors within JulDayToDate or JulDayToDateTime, use
procedure JulDayInCalendar to check wether the actual
julian day argument is valid.
The default range is firstYear = 1949, lastYear = 2099,
firstSunday = 2, since the 2nd January 1949 is a Sunday,
which corresponds to the following call to SetCalendarRange:
SetCalendarRange(1949,2099,2)
Other possibilities within the supported Gregorian calendar
range:
Sunday, 6.Jan.1585 (1st possible date within Gregorian calendar)
Sunday, 6.Jan.1805
Sunday, 1.Jan.1809
Sunday, 6.Jan.1861 (1st possible date within modified J.D. period)
Sunday, 1.Jan.1905
Sunday, 2.Jan.1949 (default used by this module)
Sunday, 1.Jan.1967 (Unix systems use 1.1.1970 as origin)
Sunday, 3.Jan.1971 (Unix systems use 1.1.1970 as origin)
Sunday, 5.Jan.1997
Sunday, 2.Jan.2000
For your information, the 5th January -4711 (1.Jan.4712 BC)
is a Sunday, i.e. it is the first Sunday in January within
the period, which is covered by (unmodified) Julian days.
Noon of this day, i.e. 12h00 world standard or Greenwich
time [ST], corresponds to 370.0 J.D. This day started at
0h00 ST ~ 369.5 J.D. and ended at 24h00 ST ~ 370.5 J.D.
Note, this implementation does not prevent its use before
the Gregorian calendar correction and may actually
correctly function if only used for ranges before or after
the 15th October 1582. However, only little testing was
done for the period before 15th October 1582.
Note that calling this procedure may be useful in order to
use Julian days of type INTEGER instead of LONGINT. Then
the calendar routines can cover fully 137 years without
causing an overflow when assigning the LONGINT result of
procedure DateToJulDay to an INTEGER variable. If using
LONGINT the full Gregorian calendar period (15.Oct.1582
till 4915) is covered.
*)
PROCEDURE GetCalendarRange(VAR firstYear,lastYear,firstSunday: INTEGER);
(*
Allows to inquire the currently set calendar range.
*)
PROCEDURE JulDayInCalendar(jd: LONGINT): BOOLEAN;
(*
Checks wether the julian day jd, falls within the
currently set calendar range. For efficiency reasons
this test is neither done by routine JulDayToDate nor
JulDayToDateTime. If you wish to check it, you have to
call JulDayInCalendar before calling one of above
routines in order to avoid a program halt. To check a
julian day jd of type LONGREAL, call JulDayInCalendar
as follows:
JulDayInCalendar(LITRUNC(jd))
where LITRUNC is from module DMPortab.
*)
END JulianDays.