[Development] Calendar Systems proposal

Soroush Rabiei soroush.rabiei at gmail.com
Thu Dec 15 11:30:33 CET 2016

1. Purpose

Nowadays, almost all major programming frameworks support calendar
globalization. We are a small group of developers working with
calendars and we believe Qt must support this too. This proposal discusses
details of our plan (and early implementation) to add support for multiple
calendar systems in Qt library.


Originally there was four plans described in [1] based on ideas discussed
[2]. These talks (dated back to 2011) were never implemented and eventually

3. Current Status

Currently there is no support of any calendar system other than Gregorian.
Gregorian calendar is widely used in western countries. However most
in Middle-east, Asia and Africa use other calendar systems. Unfortunately
is no support for them.

4. Requirements

Users of calendar systems expect same functionality provided by default
calendar for their local calendar (at least we do so). Following list
requirements of date/time API.

4.1. Such a feature must provide an API same as QDate, and all related
Adding convenient classed for each calendar type (QJalaliDate, QHebrewDate,
QIslamicDate, etc.) will be discussed, Tough we believe any API without
integration will not satisfy our needs.

4.2. Backward compatibility must be kept in both public API and semantics
QDate, QDateTime and related classes. Such a feature must not affect any
program that uses date, but has nothing to do with the calendar systems. A
program compiled with Qt 5.9 must behave the way it do with 5.8, whether it
utilizes calendar systems or not. There will be no assumption on user
nor the host system’s default calendar (as there is not any now).

4.3. The Public API and semantics of other parts of Qt that work with
should not be changed. For instance, regardless of the calendar system that
QDate object uses, a QSqlQuery must be able to use it. And QVarient must be
able to store/read it the way it did before. We prefer not to change
API and implementation as well, but if we had have to, (and it’s an
change) we will do it. For example we may need to change QtSql and QtCore
classes to overwrite calendar system of QDate object to work properly. We
we will find a way to avoid this. These examples are based on the
that QDate API will change, and calendar system support will be added
to QDate (See S5 below)

4.4. Calendar systems and Locales, are irrelevant except month naming.
There is
nothing to do with the locales while implementing calendar system: Adding a
calendar is not about naming months for some locale. Some calendars are
different in the math behind. For example, the year in Jalali calendar
in 21st March. First 6 months are 31 days and next 6 months are 30 except

Changing locale should not change calendar system. And there will be no
information on calendar system in supported locales. (There is no default
calendar in locale definition).

It’s necessary to have multiple calendars at the same time in an
and it’s necessary to have calendar system in all locales. Also calendar
and Time Zones are irrelevant. There were assumptions on previous plans
(described in [1]) that are totally misunderstanding of the calendar
A particular plan was about adding calendar integration to QLocale, which
we do
believe is wrong in all aspects. Because calendar system has nothing to do
culture. A calendar is an abstract astronomical concept.

5. API Candidates

The plan is to implement support for at least five most-used calendar
Each calendar’s implementation will be added into a new class named
`QCalendarSystem`. This class will provide information on calendar system
details, like how many days are in each month and if a year is leap year or

This class will also contain the calculation code needed for QDate. Most
importantly, how to convert between julian day and calendar date. Currently
these calculations are implemented in QDate class

There are several candidates for date object APIs. Following list discusses
these APIs.

5.1. Integrate calendar system semantics into QDate

This is what we plan to do. There are several ways to do this. All of
will preserve backward compatibility. First three options are based on John
Layt’s ideas described at [3]

5.1.1. Add a calculator class, add convenience methods to QDate

    QDate myDate = QDate::gregorianDate(2000, 1, 1);
    QDateCalculator myCalc;
    int localYear = myCalc.year(myDate);
    QString localString = QLocale::system().toString(myDate);
    QDateTimeEdit myEdit(myDate);
    int hebrewYear = myCalc.year();

There are several issues with this API. Most importantly it does not
backward-compatibility of QDate API. This also does not meet the
requirement of
having QDate-like API.

5.1.2. Add new methods to QDate

    QDate myDate = QDate::gregorianDate(2000,1,1);
    int localYear = myDate.localYear();
    QString localString = QLocale::system().toString(myDate);
    QDateTimeEdit myEdit(myDate);
    int hebrewYear = myDate.localYear(QLocale::HebrewCalendar);

This option has previous one’s problems. And there is something not clear
this option: How QLocale knows about calendar system of a given date
Is there a member added to QDate?

5.1.3. Add `calendar()` method to QDate, and return a QLocalDate

    QDate myDate = QDate::gregorianDate(2000,1,1);
    int localYear = myDate.calendar().year();
    QString localString = myDate.calendar().toString();
    QDateTimeEdit myEdit(myDate);
    int hebrewYear = myDate.calendar(QLocale::HebrewCalendar).year();

This is the best of above three, yet does not meet the requirements:
myDate.calendar(someCalendar).year() is not a good way to obtain year
number of
a date object. We prefer QDate::year().

5.1.4. Using an enumeration to perform the math

This option is complicated, and backward-compatible. We plan to implement
one if there is no problem.

Needed API changes are:

    * Adding an enum to the QLocale class:

        enum Calendar{Default, Gregorian=Default, Hebrew, Jalali,

    * Adding arguments of this enum with default values to `Gregorian` to
      member functions:

        QString monthName(int, FormatType format = LongFormat,
                          Calendar calendar = Default) const;
        QString standaloneMonthName(int, FormatType format = LongFormat,
                                    Calendar calendar = Default) const;
    * Adding `QCalendarSystem` class
    * Adding three member functions to the QDate:
        setCalendarSystem, calendarSyste and a constructor

The QCalendarSystem will contain information on calendar systems and the
This will help us to move calculations out of QDate, by providing a handle
private member) to a calendar system object. This object will have the
code of calendar system, and will keep QDate implementation cleaner.

So we will have something like:

    // qdatetime.cpp:
    void QDate::setCalendarSystem(QCalendarSystem::Type t){
    // This is the new API

    int QDate::year() const {
        // Previous implementation
        return d_calendar.year();

    // qcalendar_system.cpp:
    QCalendarSystem::year(quint64 jd){
            case CalendarSyste::Gregorian:
                return q_gregorianYearFromJulianDay(jd);
            case CalendarSyste::Jalali:
                return q_jalaliYearFromJulianDay(jd);

    // the calendar math (not optimized):
    jalaliDateFromJulianDay(quint64 julianDay) {
        quint64 depoch = julianDay - PERSIAN_EPOCH;
        quint64 cycle = depoch / 1029983;
        quint64 cyear = depoch % 1029983;
        quint64 ycycle ;
        quint64 aux1,aux2;
        } else{
            aux1 = cyear / 366;
            aux2 = cyear % 366;
            ycycle = (((2134 * aux1) + (2816 * aux2) + 2815) / 1028522)
                   + aux1 + 1;
        int year = ycycle + (2820 * cycle) + 474;
        int month;
        if(year <= 0){
        quint64 yday = (julianDay - persian_jdn(year, 1, 1)) + 1;
        if(yday <= 186){
            month = ::ceil(static_cast<float>(yday)/31.0);
        } else {
            month = ::ceil(static_cast<float>(yday-6.0)/30.0);
        int day = julianDay-persian_jdn(year, 1, 1)+1;
        const QCalendarSystemPrivate::ParsedDate result = {year,month,day};
        return result;

5.2. Provide separated date classes for each calendar system

So there will be QGregorianDate, QJalaliDate, QIslamicDate, QHebrewDate...
Possibly all inherited a common base. This solution is not reasonable. This
will not meet any of our requirements. And will not satisfy the need to
calendar system integrated into QDate (So that we can change the calnedar
easily). We prefer transparent API which is integrated to Qt itself, not
irrelevant classes. We already have them in several libraries, and our own
hand-made APIs that we currently use. However this API is open for
If you think we must go with this option, feel free to discuss about your

6. Known Problems

There are several issues with option 5.1.4 that are discussed here. I will
discuss other options (5.1.1 to 5.1.3) as I don't want to implement them.
you think they may fit the requirements, please let me know about the
and issues. And also if you see any other issues not mentioned here, please
me know about.

6.1. Missing calendar localization in CLDR

Currently, month names are provided by QLocale and being read from the CLDR
embedded in Qt source code. Unfortunately there is no data in CLDR for
non-Gregorian calendars. This problem is submited as a proposal in 2012 [4]
which is not added to CLDR yet. So how do we provide data for month names?
have implemented a workaround for this:

    QLocale::monthName(int month, FormatType type, Calendar calendar) const
        if (month < 1 || month > 12)
            return QString();

        switch (calendar) {
        case Gregorian:
            if (d->m_data == systemData()) {
                QVariant res =
                systemLocale()->query(type == LongFormat
                                            ? QSystemLocale::MonthNameLong:
                if (!res.isNull())
                    return res.toString();

            quint32 idx, size;
            switch (type) {
            case QLocale::LongFormat:
                idx = d->m_data->m_long_month_names_idx;
                size = d->m_data->m_long_month_names_size;
            case QLocale::ShortFormat:
                idx = d->m_data->m_short_month_names_idx;
                size = d->m_data->m_short_month_names_size;
            case QLocale::NarrowFormat:
                idx = d->m_data->m_narrow_month_names_idx;
                size = d->m_data->m_narrow_month_names_size;
                return QString();
            return getLocaleListData(months_data + idx, size, month - 1);
        case Jalali:
            switch (type) {
            case QLocale::LongFormat:
                // TODO: Add local month names at least for
                // Persian, Afghani, Pashtoo, English and Arabic
                return jalaliLongMonthNames[month];
            case QLocale::ShortFormat:
            case QLocale::NarrowFormat:
                // TODO: Add local month names at least for
                // Persian, Afghani, Pashtoo, English and Arabic
                return jalaliShortMonthNames[month];
                return QString();
            return QString();


It works, but it's not good enough. Can we add month names to the CLDR and
the calnedar type to query?

6.2. Calendar system support must be optional. Adding calendars following
option 5.1.4, will add a dependency to QDate. And qmake will also need to
compile that. I'm thinking of adding options to configure script to enable
calendar support. And let qmake be compiled without calendar object in it.
requires a lot of #ifdef preprocessors in QDate class, to keep previous
implementation. That will cause a dirty code in QDate...

6.3. ICU support must be in place when ICU is available. We can do calendar
math, conversions using ICU. Though not having ICU, we must have calendars
available. That also requires a lot of math in QCalendarSystem messed up
loads of #ifdefs.

7. References

[1] https://wiki.qt.io/Locale_Support_in_Qt_5
[2] https://wiki.qt.io/Qt_5_ICU#QCalendarSystem_Design

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.qt-project.org/pipermail/development/attachments/20161215/7e25a1f5/attachment.html>

More information about the Development mailing list