[Development] Calendar Systems proposal

Edward Welbourne edward.welbourne at qt.io
Fri Feb 10 10:51:55 CET 2017


On Monday 02 January 2017 09:21:25 Lars Knoll wrote:
>>> I wonder whether we can't keep handling of different calendars
>>> completely outside of QDate. Something similar to what we've done
>>> with QString/QLocale. So QDate would continue unchanged and only
>>> support the standard Gregorian calendar. In addition, we have a
>>> QCalendar class, that can be constructed with a different calendar
>>> system, and can then return 'localized' date strings, days, months
>>> and years for this calendar system.
>>>
>>> Something like:
>>>
>>> QDate date;
>>> QCalendar c(QCalendar::Hebrew);
>>> QString hebrewDateString = c.toString(date);
>>> int hebrewYear = c.year(date);
>>>
>>> Maybe one could even integrate this into QLocale, that already provides
>>> support for localized month and day names?

The current work actually moves QLocale's calendar-related data *out* to
the calendar system classes - a step towards the dismembering of QLocle
that was contemplated some years ago, but on which no progress appears
to have been made.  This separation is one of the particularly
satisfying results of Soroush's work.  Choice of calendar system is
(kinda) orthogonal to choice of locale, anyway, so the calendar data
doesn't belong in the QLocale (which only knows about language, script
and country, although we *could* teach QLocale(const QString &) to parse
more complications out of a string - I doubt that'd be a win).

Note, by the way, that the locale data is the *only* data in most
calendar system classes, handled as globals (as in QLocale) of the
class's compilation unit.  Calendar objects themselves are usually
entirely algorithmic - they have no data members, just a vtable -
although one can conceive of eccentric ones (that we aren't about to
implement, but client code might) that could conceivably have member
data (e.g. a historian might use a hybrid Julian/Gregorian calendar with
one datum, the Julian day number at which to make the transition between
the two; the class for that could be instantiated with different
transition dates for the various jurisdictions that switched at
different dates).

On 02/01/17 12:01, "Frédéric Marchal" <frederic.marchal at wowtechnology.com> wrote:
>> There is more to it than converting a date to a string:
>>
>>  * Add N days to a date.
>>  * Find the number of days in a month.
>>  * Compare two dates.
>>  * Count the number of days between two dates.
>>
>> For instance a program wishing a happy new year to its users should
>> do it with as little modifications as possible.
>>
>> Using a plain QDate would have been the easiest way to reach more
>> users because it doesn't require to replace lots of QDate with a new,
>> very similar, class. As it is not possible to change QDate for now,
>> Soroush is looking for a temporary solution that would bridge the gap
>> until Qt6 is out.

Lars Knoll (2 January 2017 12:39) followed up with:
> Sure, that there’s more to do than just the examples I listed. Still,
> design wise it might be a good idea to have this functionality in a
> class separate from QDate. We’ve done the same design decision for
> QString (having no locale specific functionality in QString), and this
> worked out rather nicely. So I would encourage you to have a look
> whether and how a similar design could be done for calendar system
> support.

Actually, I do indeed want to move calendaring information - notably
that of the Gregorian calendar - out of QDate; even though QDate must
surely use Gregorian by default, moving the Gregorian logic (already
largely separated into static functions) out makes the QDate class more
straightforward.  That actually leaves little in QDate itself; and what
it does leave can readily be made calendar-system agnostic, although it
manipulates a calendar-system object to do the work.  So making
Gregorian the default in APIs that take a calendar system is in fact a
simplification of QDate, where I suppose locale-support complicated
QString and its removal simplified.

One big reason for wanting to integrate calendar into QDate is that,
without it, the overhead of supporting alternate calendars in any app
involves re-writing the app, reducing all use of QDate to a mere carrier
for its ->toJulianDay().  With QDate taking a calendar object (that
defaults Gregorian), it's fairly easy and painless to integrate calendar
support into an app.  This is rather well illustrated by Soroush's most
recent patch-set (11), which extends QCalendarWidget to have
QAbstractCalendar member (that, of course, shall default Gregorian),
which it duly passes to each QDate method:

https://codereview.qt-project.org#/c/182341/11/src/widgets/widgets/qcalendarwidget.cpp,unified

This is easy, straightforward and natural; one can readily see that it's
correct.  (The one wrinkle, QCalendarModel::referenceDate(), being due
to the existing comment looking suspiciously bogus, see my comments in
Gerrit; search for "Oct 1582" on PS 11.)

Now contrast this with what happens if we keep calendar systems out of
QDate.  There's a basic level of conversion that would go just as
straightforwardly - transforming date->method(args) into
cal->method(date, args) in most cases - but then there are places where
the code needs to add days or jump to the start of the week, month or
year, e.g. QCalendarView::moveCursor().  The calendar API can surely
have the methods to do that - but, if it does so, the calendar systems
would all be doing the same thing as QDate, so we'd end up with concrete
methods of QAbstractCalendar (to avoid duplicating *between* systems),
each of which duplicates code in a matching QDate method.  We could
eliminate this duplication by rewriting each QDate method as

ret QDate::method(args)
{
    return QGregorianCalendar().method(this, args);
}

or, indeed, for get-ish things

ret QDate::method(args)
{
    return QGregorianCalendar().method(this->toJulianDay(), args);
}

and, for set-ish things,

void QDate::setThing(args)
{
    this->fromJulianDay(QGregorianCalendar()->julianDayFromThing(args));
}

possibly with a this->toJulianDay() added to the args, as needed.  What
bothers me about this is that, at this point, I see no value left in
QDate.  It would just be a cumbersome way to package a Julian day number
- as the last two illustrate - that code using calendar systems other
than Gregorian would be obliged to go via in order to interact with the
rest of Qt.  That's actually worse than no value: it's an obstacle to
adding support for other calendars in apps using Qt.

QDate is the natural place for the algorithms that would, in this
scenario, be carried by QAbstractCalendar's non-abstract methods - they
are *date* manipulations, though they need to consult a calendar to work
out what to do, not *calendar* manipulations or conversions.

Now, as it happens, QDate's relevant methods at present mostly call out
to local static functions that would inevitably become methods of
QGregorianCalendar (and the exceptions inline relevant code in a QDate
method, duplicating what would be in QGregorianCalendar), making it
natural to implement them by having a local instance of that calendar
class in each method, so as to call its methods.  Once we do that,
though, the stop to having that instance come in as a parameter (that
defaults to a QGregorianCalendar instance) is very low indeed.

As a result, integrating calendar support into QDate would actually be
more natural than separating it - it would intrude scarcely at all into
the implementations (once re-written to use a QGregorianCalendar, to
save duplication) while being the natural way to share date-manipulation
code that would otherwise have to live in QAbstractCalendar, either as a
duplicate of QDate's code or as what QDate code ends up calling to do a
job that more naturally belongs to QDate,

	Eddy.



More information about the Development mailing list