QDate, KCalendarSystem, KDatePicker, and some API change proposals (long)
John Layt
johnlayt at yahoo.com.au
Fri Jul 6 20:18:52 BST 2007
I want to pick up on the earlier threads over QT4's extended range and hybrid
calendar, which didn't seem to reach any conclusion other than the status
quo, and suggest some small additions to the API to make life easier on
application and plasmoid developers.
Before going into my (rather long winded) reasoning, here's what I propose to
do for 4.0:
1) Add some new methods to KCalendarClass API to match QDate4, to support
proper date range validation, and help app programmers implement their own
calendar systems.
2) Change the date range limits hard-coded into KCalendarSystem to match
QDate4 or each calendars epoch, as appropriate.
3) Clean up the KCalendarSystem implementations to be simpler, more
consistant, and to share more code (and add unit tests!)
4) Modify KDateWidget, KDateTable and KDatePicker API (& BIC?) to allow
multiple calender systems to be displayed at the same time, as well as do
code clean-up.
I have code that can quickly be ported to make the required API changes before
the freeze, followed by the clean-up tasks at a slightly more leisurely pace.
I've attached the proposed new API methods at the bottom of the mail.
What I'd like to know is:
1) Do people agree with my proposals?
2) Do I have enough time before the API freeze to make the proposed changes,
or do we hold everything over to 4.1?
3) Are BIC breakages still allowed before the API freeze?
4) What's the policy after the API freeze for changes to kdelibs
implementations for clean-up purposes?
5) Is there a current maintainer, or someone else willing to receive patches
for these changes?
Now for the long part :-)
First some brief background on me. A few years ago I wrote a KDE3 app called
Kalends (http://www.kalends.org), a universal & historical date calculator
which currently implements 14 different calendar systems, which I've recently
resurrected and am polishing up for a 0.1 release. I had to work around the
infamous QDate3 range problem by forking KCalendarSystem and replacing QDate
with my own JDate, similar to KStars (no, I'm not proposing we do that). As
such I've become very familiar with the KDE3 KCalendarSystem, KDateTable, and
KDatePicker code and its current limitations.
There's 3 main areas I see that need addressing: the changes to QDate4, code
quality issues in general including code duplication, and issues with the
widgets that prevent multiple calender systems being displayed on the same
desktop.
As discussed on the list previously, QDate now has an extended date range, and
a hybrid of the Julian and Gregorian calendars with a fixed switchover date.
Another important change some may have missed are the new public methods
toJulianDay() and fromJulianDay(). While I share the misgivings about the
unfortunate lower date limit and historic accuracy of the switchover date,
the fact is we are stuck with it for KDE4 and need to be pragmatic about
making the best of the situation.
It is key to remember that within KCalendarSystem, QDate is merely a container
for the Julian Day number (jd), how we choose to interpret that jd is up to
the individual calendar system implementations. We are also free to add new
calendar system implementations for specialised historic or astronomical
uses, but the core function of KCalendarSytem is to provide the user with the
correct calendar system for their locale.
The lower limit on the date range is disappointing, but it's far better than
the old limit as it covers most of recorded history which the majority of
apps and users will be interested in. I see no harm in changing the current
hard-coded KCalendarSystem limitations to match QDate4, especially in the
Hijri and Jalali calendars where we can now offer our users support for all
dates since their calendars epoch. This can only be a good thing and would
be pointless to deny them. We could hold back on doing so for the Gregorian
until the hybrid/switchover issue is resolved, but this would leave
KCalendarSystem inconsistent with QDate and would cause issues if an earlier
date was passed in, so we should just go with it. We can also move the upper
limit to be the end of year 9999 in the given calendar system as I believe
our date formats only support 4 digit years.
This however creates the problem of the ambiguity of years 00 to 99. In
QDate3, these were interpreted as 1900 to 1999. In KDE3 they were rejected
by setYMD() and interpreted by yearStringToInteger() as 1969 to 2068. In
QDate4 01 to 99 are interpreted literally and the year 0 rejected (i.e. BC/AD
Julian implementation, not BCE/CE) by obsoleting setYMD() and introducing
setDate(). The only real choice for the calendar systems is to do the same,
otherwise those years would be impossible to set.
On the hybrid/switchover issue, a proleptic Gregorian calendar has no uses in
the real world. The QDate implementation actually reflects how people
experienced the calendar in real life, even if calling the result Gregorian
is a confusing misnomer. What's important is for locale to return what
people actually call any given jd in their locale, which was Julian until the
switchover then Gregorian. For 4.0 we should ensure KCalendarSystem matches
the QDate API and implementation for consistency with those apps that use
QDate instead or interchangeably. Later we can see if reimplementing
KCalendarSystemGregorian with historically accurate switchover dates set up
by the locale or manual override is a desirable thing. The vast majority of
apps don't care about dates that historic and so won't notice any difference.
Apps that do care will probably be aware of the issues and can choose to use
whatever implementation is suitable instead of what locale returns, e.g
implement their own KCalendarSystemGregorianProleptic class.
Just as an aside, I'd like to come up with a better name for the
weekDayOfPray() method, but weekdayOfReligiousObservance() seems a little
long :-) Suggestions??? Actually, I'm not even sure it belongs in
KCalendarSystem, for civil calendars KLocale might be more appropriate. The
Gregorian calender is after all the civil calendar used in Japan, China, and
Thailand, so it's probably returning the wrong value for them. Some way of
indicating weekends and holidays for display on the widgets would also be
useful, but that's getting close to PIM libs material.
From a code quality viewpoint, there's a lot that could be cleaned up. The
current calendar and widget implementations are poorly documented,
non-conforming to various library standards, lacking unit test cases, have
lots of hard-coded magic numbers, accessability issues, and even leftover
code from previous implementations that appears to be unused. This is not
surprising given different people implemented each of the calendar systems
and had goes at fixing the widgets. Within each sub-class implementation,
there is also a lot of duplicated code. While I understand the BIC
preference to implement virtual methods in the sub-class if there is any
chance of their needing their own implementation later on, and most of the
methods are only a couple of lines, a complex method like the ISO
weekNumber() really should be implemented in the base class for the
sub-classes to call to make maintenance easier. The exposing of QDate
methods toJulianDay() and fromJulianDay() will also allow the simplification
of many of the conversion formulas in the calendar implementations by doing
away with the intermediate conversion step to/from Gregorian previously
required to get/set QDate, improving efficiency and reliability.
Probably the biggest changes need to be made to the KDateWidget, KDateTable
and KDatePicker widgets, which are currently limited by their implementation
to only using the calendar system returned by KGlobal::locale()->calendar().
This prevents the use case where someone wants widgets for two different
calendar systems displayed in their app or on their desktop at the same time,
e.g. using Gregorian for their locales civil calendar and at the same time
displaying a plasmoid of their religious calendar like Jewish or Islamic.
This can be solved by adding a KCalendarSystem pointer member in KDateWidget
and KDateTable which by default will point to the KGlobal calendar system, or
can be set to the required calendar system. New constructors will be
required to accept a KCalendarSystem and get/set methods in all of
KDateWidget, KDateTable, and KDatePicker, and to fix the code to call them.
The one hitch I see is that any KCalendarSystem passed in can have its own
KLocale set to enable it to use different translations which the widgets
would have to respect in place of the KGlobal translations. The widgets will
need to call the KCalendarSystem protected method locale() for this purpose,
which would suggest making them friends, but this is not an area I know much
about. There's also quite a lot of code clean-up that can be done to improve
quality, usability, accessability, and add support for non-7 day weeks.
For 4.1, I would investigate the posibility of :
1) Extend the API to include more methods for calculations, special dates, and
information
2) Implement various new calendar systems, such as Julian, Gregorian
Proleptic, etc, either in libs or keg.
3) Add the Gregorian switchover date to KLocale and the KCM to allow user
override.
4) Reimplement KCalendarGregorian to obey the switchover date, but otherwise
remain consistent with QDate
Well, that's been rather long-winded, but I hope it indicates that I have put
some thought into the changes, and believe these are worth doing in the 4.0
timeframe, but I welcome your thoughts.
Cheers!
John.
-------------- next part --------------
----------------------- CHANGES TO KCalendarSystem* API ---------------------
public:
/*
QDate has extended year range from -4713 to 9999, so it is now possible
to obtain a QDate that lies before the start of a particular calendar
system (e.g. Islamic starts 622AD), so we need to provide methods to
check if a QDate or YMD is valid in a calendar system. These were in
QT3, don't know why KCalendarSystem doesn't already have them.
QDate provides a static for this, should we as well?
*/
virtual bool isValid(int y, int m, int d) const; //In QDate is static
virtual bool isValid(const QDate &date) const;
/*
New in QDate, all K implementations already have hidden versions they
use internally, so expose for public use. Second one isn't in QDate, but
is for consistancy with rest of API
QDate provides a static for this, should we as well?
*/
virtual bool isLeapYear(int year) const; //In QDate is static
virtual bool isLeapYear(const QDate &date) const;
/*
setYMD() has been obsoleted in QDate as it interprets years in the range
00 to 99 as 1900 to 1999, but QDates new extended year range of -4713 to
9999 means 01 to 99 should be treated literally, which setDate() does.
In KDE, setYMD() rejects any year in 00 to 99 as invalid, which is now
wrong behaviour, so should be marked as obsolete, or even removed
completely to force correct usage.
*/
bool setDate(const QDate &date, int year, int month, int day);
/*
QDates extended range now covers start (epoch) and end (terminus?) of some
calendar systems, it would be useful to know these dates for validation,
widget building and limit setting. Note these are the real start and end
dates for a calendar system, the implemented calculation may not cover
these dates.
*/
virtual QDate epoch() const;
virtual QDate terminus() const;
/*
The implemented calculation may not cover the full date range of a given
calendar, or if proleptic may extend beyond the epoch and terminus. These
functions return the real limits of an implementation.
*/
virtual QDate validFrom() const;
virtual QDate validTo() const;
/*
Returns if calculation implemented is proleptic. Not really needed for
4.0, but could be useful for people implementing their own systems on top
*/
virtual bool isProleptic() const;
/*
Already have an 'int weeksInYear(int y)' method, but all other aInB type
methods (monthsInYear, etc) take a QDate, so add this for consistancy.
Keep old one for compatability, or remove? Does not exist in QDate.
*/
virtual int weeksInYear(const QDate &date) const;
/*
This would allow future support for calendars with weeks that don't have
7 days in them. All current uses of the magic number 7 would call this
instead, resulting in more self-documenting code and avoidng potential
confusions such as in KDateTable which has 7 columns for days and 7 rows
for weeks and the code isn't always clear which is which.
*/
virtual int daysInWeek (const QDate &date) const;
------------------------- CHANGES TO KDateWidget API ------------------------
public:
/*
New Constructor for explicitly setting the calendar system you want the
widget to support
*/
explicit KDateWidget( const KCalendarSystem &calendar, const QDate &date, QWidget *parent=0 );
/*
New methods to allow changing the current calendar system. Need the KCS
version to allow use of non-KGlobal locale for translations. QString
version will use KGlobal translations.
*/
void setCalendar( const KCalendarSystem &calendar );
void setCalendar( const QString &calendarType );
/*
New method returns the currently used calendar system, copied from KLocale
*/
const KCalendarSystem *calendar() const;
/*
Existing function with void return value, but should return bool if
invalid date is passed in, consistant behaviour with other date widgets.
*/
bool setDate( const QDate &date );
private: or in d-> if BIC breakage not allowed
KCalendarSystem *calendar;
------------------------- CHANGES TO KDateTable API ------------------------
public:
/*
Constructor for explicitly setting the calendar system you want the widget
to support
*/
explicit KDateTable( const KCalendarSystem &calendar, const QDate &date, QWidget *parent=0 );
/*
Methods to allow changing the current calendar system. Need the KCS
version to allow use of non-KGlobal locale for translations. QString
version will use KGlobal translations.
*/
void setCalendar( const KCalendarSystem &calendar );
void setCalendar( const QString &calendarType );
/*
Returns the currently used calendar system
Footprint copied from KLocale
*/
const KCalendarSystem *calendar() const;
private: or in d-> if BIC breakage not allowed
KCalendarSystem *calendar;
------------------------- CHANGES TO KDatePicker API ------------------------
public:
/*
Constructor for explicitly setting the calendar system you want the widget
to support
*/
explicit KDatePicker( const KCalendarSystem &calendar, const QDate &date, QWidget *parent=0 );
/*
Methods to allow changing the current calendar system. Need the KCS
version to allow use of non-KGlobal locale for translations. QString
version will use KGlobal translations.
*/
void setCalendar( const KCalendarSystem &calendar );
void setCalendar( const QString &calendarType );
/*
Returns the currently used calendar system
Footprint copied from KLocale
*/
const KCalendarSystem *calendar() const;
More information about the kde-core-devel
mailing list