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