pollRevision of How should we compute the result of DATE.is_leap_hear? from Tue, 06/01/2010 - 19:31

The revisions let you track differences between multiple versions of a post.

Using Gregorian calendar even for date older than 1582
57% (4 votes)
Using Julian calendar for dates up to 1582, and then Gregorian calendar
14% (1 vote)
Remove `is_leap_year' from date and adds some calendar classes
29% (2 votes)
Total votes: 7

Comments

I'm posting this poll because

manus_eiffel's picture

I'm posting this poll because someone reported that {DATE}.is_leap_year was buggy. It turns out that it actually mostly depends on which calendar you are basing the computation. In our case, we assume the Gregorian calendar even for dates before 1582 (year it was actually created). This is the behavior you can find in the Microsoft .NET Date class. I haven't checked Java.

Now even the Gregorian calendar was not adopted right away in most countries, thus the meaning is highly region dependent.

I'm proposing three alternatives on how we could go to resolve this issue.

What do you think? Anyone being a date expert that would like to provide some insights?

Note that option 3 (remove

colin-adams's picture

Note that option 3 (remove is_leap_year), would not fix class DATE. Features such as days_from also given the wrong answer if the class is supposed to correspond with the gregorian calendar rather than the proleptic gregorian calendar. Currently the documentation and contracts of the class does not say what calendar is in operation, but the implementation corresponds to the proleptic gregorian calendar.

It would be misleading to use

peter7723's picture

It would be misleading to use calendars which do not apply. For example, the Gregorian Calendar was not adopted in Great Britain and its colonies until 1752. So, option 1 is OK in much of Europe, excluding protestant Germany, but not elsewhere. Option 2 is better, but still not adequate because of the varying dates of adoption of the Gregorian calendar.

Perhaps a single class could be used in which the date of change could be set, defaulting to 1582 in French format, 1700 in German format and 1752 in English format! Alternatively, implement a basic DATE class based on the astronomical Julian date and supply adapter classes (not child classes) to get the correct date for the locale.

So, a particular date can then be displayed and minipulated through the adapter for the locale in question. This would allow Wednesday 1 Jan 1600 in England to be displayed as Wednesday 12 Jan 1600 in France. Note that the days of the week were not shifted. (N.B., I am fairly certain 1/12 Jan 1600 was Wednesday, and I think the change was still 11 days in 1600. A nice thing about the Gregorian Calendar is that it from 1 Jan 1600 to 1 Jan 2000 is an exact number of weeks).

Use the adapter pattern

peter7723's picture

The reason I proposed the adapter pattern in my first message was to exploit a single supplier date class (perhaps based on the Julian day as used in astronomy or equivalent). Then an adapter class, as client, wraps the date class and maps dates to and from the required value for the locale and epoch (pre or post the critical date). The adapter would handle cases of dates which never occurred in that locale.

For example, one could have classes such as JULIAN_DATE, GREGORIAN_PROLEPTIC_DATE, ENGLISH_DATE, etc. all clients of JULIAN_DAY.

Then,

e_d: ENGLISH_DATE
f_d: FRENCH_DATE
...
   create e_d.make_from_string ("1 Jan 1600")
   create f_d.make_from_julian_day(e_d.julian_day)
   io.put_string(f_d.out)         -- writes "12 Jan 1600"
where the date adapters declare
feature
   julian_day: JULIAN_DAY
All the date adapters would inherit from deferred interface class DATE.

The first option on this poll is wrong.

hotzenplotz's picture

I'm the one who started the discussion about the critical method is_leap_year in the DATE class in irc.freenode.net - #eiffelstudio. That is the way Java and .NET does it. But it is 100% definitely wrong! If it should stay that way then you should stop any operation before 1582! As Peter Horan said there are differences between the states in europe and the world in general, from when this scheme applies. Surely this is solvable, one could make a date regionally dependent. See http://en.wikipedia.org/wiki/Inter_gravissimas as reference. It would mean a lot of work to build it all. I think it would go beyond the scope of a standard framework. There should be an external solution to be programmed. A Date Framework. I say stop any critical date operations and source it out. The standard library should be right in any case! So i vote for the third one.

This is my solution for a (hopefully) correct leap year operation for catholic states in europe:

is_leap_year (a_year: INTEGER): BOOLEAN is
			-- Checks whether the given year is a leap year.
		do
			Result :=(((a_year \\ 4) = 0) and
				(a_year <= 1582)) or
				(a_year \\ 4) = 0 and
				(((a_year \\ 100) /= 0) or
				((a_year \\ 400) = 0))
		end

I have not found a function,

hotzenplotz's picture

I have not found a function, to get the days since 1.1.1. It is needed for many functions I have. Like get_weekday_by_name, get_weekday and so on. I'm not 100% sure if this code is right ;-) My tests were right - so far. But I'm not sure. This is not stable. And there is a Fixme. Some like this would be nice for a DATE class.

get_days_past (a_year, a_month, a_day: INTEGER): INTEGER is
			-- Returns the days passed since 1.1.1.
			-- Notice that day 1 is 1.1.1 not day 0. This is a function for the
			-- Catholic states in europe.
			-- Fixme: Exception handling! A date in 1582.10.5 - 1582.10.15 is wrong!
		local
			dummy_1: INTEGER
			dummy_2: INTEGER
		do
			days_past := 0
 
			from dummy_1 := 1
			until dummy_1 > a_year
			loop
				days_past := days_past + 365 + is_leap_year(dummy_1).to_integer
				dummy_1 := dummy_1 + 1
			end
 
			days_past := days_past - 365
 
			from dummy_2 := 1
			until dummy_2 > a_month
			loop
				days_past := days_past + days_in_month(a_year, dummy_2)
				dummy_2 := dummy_2 + 1
			end
 
			days_past := days_past - 31
 
			days_past := days_past + a_day
 
			-- Is date after 1582.10.14 then decrement 10 days.
			if days_past > 577736 then
				days_past := days_past - 10
			end
 
			Result := days_past
		end

The simplest approach is to

colin-adams's picture

The simplest approach is to document the class (by default) as applying to the proleptic gregorian calendar (i.e. the gregorian calendar extended backwards). Then `is_leap_year' isn't wrong (but might not be useful). Coupling this with adding calendars (some of which may have an `is_leap_year' feature, gives clients full flexibility.

So I voted for option 1 as the nearest to the above.

This solution is wrong.

hotzenplotz's picture

That can you turn and rotate as you want, a date before 1582 is wrong, with the gregorian calendar. In the year 1582 they have skipped 10 days. So all operations before 1582 would be wrong with this class. The only possibility I see is an "out of range" exception for years before 1582. If you shouldn't want to change the code in generally.

You mis-read. I said the

colin-adams's picture

You mis-read.

I said the proleptic gregorian calendar. Not the gregorian calendar.

Sorry.

hotzenplotz's picture

Sorry that was not my intention.

It is certain that it is

hotzenplotz's picture

It is certain that it is wrong as it is. What do you think of the astronomical time as standard? This is very close to my first ideas. But with a year 0. This can be used as default DATE. It is a standard date format. http://en.wikipedia.org/wiki/Astronomical_year_numbering

ISO 8601

colin-adams's picture

It is certainly correct as it stands.

The standard for computer dates (and therefore a suitable default for the DATE class) is ISO 8601 which requires the proleptic gregorian calendar. This is what DATE delivers.

Ok. Then dates before 1582

hotzenplotz's picture

Ok. Then dates before 1582 should be negated. And it should be written in the documentation.

No. That's not right.

colin-adams's picture

No. That's not right.

Syndicate content