Non-Gregorian Calendar Systems in Qt

For Iranian general public, working with date and time in a computer system has always been challenging. As opposed to - almost - entire population of the planet, we use a different calendar system (actually a better one!) but computer systems are not designed and developed with non-Gregorian calendaring in mind. So we always have to use unconventional methods and hacks to provide functionality of the official calendar of the country - Solar Hijri…

Personally, for me the absence of the Solar Hijri calendar in Qt has always been annoying. Whenever I wanted to work with date and time in my C++ GUI applications, I had two options: Either use my own custom widgets (which where not as good as Qt’s widgets), or simply use the Gregorian calendar (which is annoying for the final user).

Since version 4.6, right after I released Persian translation of Qt, I was thinking of implementing the solar calendar for this framework. Unfortunately, in that time it was not possible to implement it for many reasons. However, I made some prototypes, but it did not make it to Qt5. Qt5 had its own sad story, with the premature release of version 5.0; the opportunity created by changing the major version was also missed. Though finally, after 6 years and forgetting all about calendars, I had the chance to implement the solar Hijri calendar for Qt 5.10. The final version has been released with Qt 5.14 on December 12, 2019. This post describes challenges, and the process of the work, and also the API itself.

Running Example

Backward Compatibility Issues

One might wonder why it was not possible to implement the calendar systems for the 5.x versions? Well, because source compatibility and backward compatibility promises in Qt framework were not to be broken at all. This means that if you write software that links to Qt version 5.1, you must be able to upgrade to Qt version 5.2, without having to re-compile your software. In C++ terms, this means not to change the memory layout, nor the public API for any existing class. The updated version that keeps the described promise is sometimes called a drop-in replacement for its previous version. Meaning any given version of x.y.z form can be safely replaced with its successor version x.y+α.z+β, without causing any crash or link error.

To add non-Gregorian calendar support, I would have to introduce breaking changes in QtCore (QDate) and also in QtWiggets (QDateEdit and QCalendarWidget).

So I forgot about it and decided to wait for version 6, until last year… I was working on an unrelated project and dealing with similar problems of ABI compatibility. I realized that there might be actually a way to provide calendaring support without breaking binary and source compatibility! The idea was to add separate classes for each calendar system, without need to change QDate nor any other existing class. So we should add callbacks in methods of existing classes, adding a “calendar” argument, and no new member variables. Unfortunately when I discussed the matter with Qt developers in the mailing list, they kindly showed me my design had so many flaws and could not be implemented. Though then they suggested a few changes, and it looked generally acceptable! They agreed to have an initial implementation. I was so excited to finally have the Persian calendar in Qt!

I would love to have an API like this one:

QDate d = QDate::currentDate();
d.setCalendar(QCalendar::Jalali);
qDebug() << d.toString("ddddd d MMMM yyyy"); // Thursday 5 Mordad 1396
QLocale::setDefault("fa_IR");
qDebug() << d.toString("ddddd d MMMM yyyy"); // پنجشنبه ۵ مرداد ۱۳۹۶

But the final proposal looked like this:

QDate d = QDate::currentDate();
qDebug() << d.toString("ddddd d MMMM yyyy"); // Thursday 27 July 2017
qDebug() << d.toString("ddddd d MMMM yyyy", QCalendar{QCalendar::Jalali}); 
// Thursday 5 Mordad 1396
QLocale::setDefault("fa_IR");
qDebug() << d.toString("ddddd d MMMM yyyy", QCalendar{QCalendar::Jalali});
 // پنجشنبه ۵ مرداد ۱۳۹۶

I have to say that I believe there are many serious design issues with QDate class! These problems seems to be accumulated technical debt over a period of two decades, and cannot be resolved for the same reasons explained above. I would love to fix some of them for Qt6 so calendaring API would work better for me. (I will explain this issues in the following parts)

Calendaring and Julian Day

The concept of date (a point in time) in Qt framework (and many other software) is represented as the number of days past from a certain point in history. Only this number is stored in the memory and used for date arithmetic. It only gets converted to day, month and year, when needed. The epoch is usually set to be the beginning of the Julian period, thus the stored date value is Julian Day Number which is the number of days past since January 1st, 4713 BC. That is about 64 centuries ago. For example the JDN for January 1st, 2013, is 2456293.

The problem with this method is that whenever the program needs to get day, month or year number, it has to do the maths. There are no optimisation or caching mechanism for this conversion. So the valuable time of the processor, is wasted on conversions between JDN and broken down date. But why did they don’t cache the (y,m,d) so the conversion code only runs when needed? Maybe memory consumption was an issue. Though I still believe adding 5 more bytes to QDate instances, is worth reducing CPU load. On the other hand, if memory usage was a problem, there is another 4 bytes they could have saved!

The JDN in Qt’s implementation is stored as a signed 64 bit number. But why? I think it’s a big mistake. There is no need to store sign, and there is no need to store 8 bytes of data for a JDN. On my platform (amd64) the largest possible value for JDN is this number: $$2^{64-1} = 9,223,372,036,854,775,808$$ According to the Gregorian calendar arithmetic, the above day is this year: $$25,252,734,927,761,842$$ Which is the largest year number technically supported by the framework! So QDates API, is wasting 4 bytes to support date ranges from six thousands years ago, up to the upcoming twenty-five quadrillion two hundred fifty-two trillion seven hundred thirty-four billion nine hundred twenty-seven million seven hundred fifty-nine thousand eight hundred twenty-two years from now; though refuses to add 5 bytes to save a lot of calculations! That is certainly a bad design, dating back to decades ago, which is overlooked and no one took care of it. Anyway this matter needs another blog post, so let’s go back to the calendar system.

Julian Day Number to Solar Hijri Date Conversion

There is no known algorithm for converting JDN to Solar Hijri and vice versa. Almost all contemporary researchers have studied leap periods and the length of the solar year. There is no reference to the origin of the calendar in the form of JDN and thus there is no conversion method. Therefore, I had to find the day number for the beginning of the contemporary era (Farvardin 1st, 475 AH) and use the average year length – which was calculated very accurately in the studies of Dr. Musa Akrami –, as a basis for a new algorithm. It is not optimal in terms of calculations, but it is accurate enough to be used for modern times, as well as the ancient history.

This algorithm, however, had many challenges. Among other issues, the negative years and the periods before the current era were not representable in an accurate manner. There is no year zero in the Jalali calendar (later changed to Solar Hijri). Before year 1, there is year -1. The same is true of the Islamic Civil and Gregorian calendars. So this must be handled as an exception in the algorithm.

The algorithm works properly except for one or two leap years. It shows different results compared to the infamous 33-year algorithm. It shouldn’t be an issue since the 33-year algorithm itself is not the official reference and it contains errors in far past dates. Regarding the differences, I decided to use the algorithm based on 2028-year periods with leaps calculated by Dr Akrami. Because the 33-year algorithm is less precise and does not calculate the leap years correctly for past decades. However, the official calendar of the country is not based on either of these two! The official calendar has no call about the new leap years and whether or not the old years are leap years. (Probably due to historical errors in different calendars across the region). Nowrouz time and the number of days in a year are decided each year based on spring equinox observations. Of course, these observations in modern times make my algorithm 100% consistent with the following years until 1432 (that is about 33 years from now). The leap years of 1432 and 1433, according to the algorithm of the Institute of Geophysics of the University of Tehran, do not match the 33-year algorithm. This is of course not of much importance, since they may change their decision about those years as we approach them.

There is another big problem with my algorithm: It is untestable. There is no reliable historical reference to match dates against. There are several conflicting resource. There is only an algorithm (not a date to date reference) available. The 33-year algorithm developed by FarsiWeb Sharif team is only testable for modern times. That is inaccurate and does not work for negative year numbers, also has some issues with years before 475. Finally, after a lot of mundane correspondences with the Institute of Geophysics, they sent me an Excel file, containing carefully reviewed Gregorian and Solar dates for the past three centuries. My algorithm matches for all past dates and future years, again, with the exception of 1432 and 1433 years.

Comments

comments powered by Disqus