本页内容

日历后端插件示例

QCalendar 该示例演示了用户提供的自定义日历。

显示1582年10月的日历小部件,其中包含从儒略历到格里高利历过渡期间的空缺

简介

全球范围内有众多不同的日历系统在使用。Qt 内置了对其中部分日历系统的支持(参见System ),但由于日历系统数量庞大,无法提供通用的支持。可以通过实现自定义的 QCalendarBackend(这是一个私有 API)来提供额外的日历系统。

本示例演示了如何编写自定义日历后端,以及如何使用低级插件 API 将应用程序扩展为支持用户自选日历。许多国家在历史上某个时期都曾从儒略历过渡到格里高利历,本示例将通过实现相应的日历系统来展示这一过程。 自定义后端会被编译为插件,并在运行时由主应用程序加载。具体转换日期因地区而异,用户可自行决定,并以字符串形式提供给插件。

日历后端

日历后端类必须继承自QCalendarBackend ,并以thread-safe 的方式实现其纯虚函数。此外,它还可以根据需要重写其他一些虚函数。

实现示例

本示例继承自现有的QRomanCalendar ,而该类又继承自QCalendarBackend ,并实现了其中的一些虚函数。这样做是有意义的,因为过渡历与儒略历和格里高利历一样,都共享了罗马历提供的部分内容。

以下是JulianGregorianCalendar 类的声明:

class JulianGregorianCalendar : public QRomanCalendar
{
public:
    JulianGregorianCalendar(QDate endJulian, QAnyStringView name);
    QString name() const override;
    int daysInMonth(int month, int year = QCalendar::Unspecified) const override;
    bool isLeapYear(int year) const override;
    bool dateToJulianDay(int year, int month, int day, qint64 *jd) const override;
    QCalendar::YearMonthDay julianDayToDate(qint64 jd) const override;
private:
    static inline const QCalendar julian = QCalendar(QCalendar::System::Julian);
    static inline const QCalendar gregorian = QCalendar(QCalendar::System::Gregorian);
    QCalendar::YearMonthDay m_julianUntil;
    QCalendar::YearMonthDay m_gregorianSince;
    QString m_name;
};

传递给构造函数的QDate ——即endJulian ——是儒略历的最后一天。该日历会自动计算给定年份的日期偏移量,例如在1582年跳过了10天,但在1700年则必须跳过12天。 该日历后端注册在name 下,可通过该名称创建日历实例。该类仅重写了其组合的两个日历与罗马基准日历存在差异的函数。它拥有儒略历和格里高利历的实例,这些函数可将操作委托给它们。

儒略日转换

dateToJulianDay(int year, int month, int day, qint64 *jd) 计算与指定的year 、month 和day 对应的儒略日数。若该日历中存在该日期,则返回true 并设置jd ;否则,返回false 。

bool JulianGregorianCalendar::dateToJulianDay(int year, int month, int day, qint64 *jd) const
{
    if (year == m_julianUntil.year && month == m_julianUntil.month) {
        if (m_julianUntil.day < day && day < m_gregorianSince.day) {
            // Requested date is in the gap skipped over by the transition.
            *jd = 0;
            return false;
        }
    }
    QDate givenDate = gregorian.dateFromParts(year, month, day);
    QDate julianUntil = julian.dateFromParts(m_julianUntil);
    if (givenDate > julianUntil) {
        *jd = givenDate.toJulianDay();
        return true;
    }
    *jd = julian.dateFromParts(year, month, day).toJulianDay();
    return true;
}

julianDayToDate(qint64 jd) 根据给定的儒略日号jd 计算该日历中的年、月和日。如果给定的日期超出该日历的范围,则isValid() 的返回值为false 。在此示例中,如果给定的日期落在从儒略历转换为公历时被跳过的间隙内,则该日期超出范围。

QCalendar::YearMonthDay JulianGregorianCalendar::julianDayToDate(qint64 jd) const
{
    const qint64 jdForChange = julian.dateFromParts(m_julianUntil).toJulianDay();
    if (jdForChange < jd) {
        QCalendar gregorian(QCalendar::System::Gregorian);
        QDate date = QDate::fromJulianDay(jd);
        return gregorian.partsFromDate(date);
    } else if (jd <= jdForChange) {
        QCalendar julian(QCalendar::System::Julian);
        QDate date = QDate::fromJulianDay(jd);
        return julian.partsFromDate(date);
    }
    return QCalendar::YearMonthDay(QCalendar::Unspecified, QCalendar::Unspecified,
                                   QCalendar::Unspecified);
}
区域设置支持

通常,日历可能有其独特的月份和星期名称。这些名称必须经过适当的本地化处理,以便所有用户都能理解。默认情况下,后端基类会自动处理星期名称,这对这些儒略历/格里高利历过渡日历而言完全足够。

虽然后端可以直接覆盖月份命名方法,但也可以通过实现 `localeMonthData() ` 和 `localeMonthIndexData() ` 接口,提供本地化月份名称的表格,从而自定义基类中的这些方法。 由于儒略历和格里高利历使用相同的月份命名,它们从共同的基类QRomanCalendar 继承了该自定义设置。这也意味着自定义日历可以通过继承该基类来使用相同的名称。这样就解决了本地化问题。

插件

Qt 应用程序可通过插件进行扩展。这要求应用程序使用QPluginLoader 来检测和加载插件。

编写插件

要编写插件,首先需要创建一个纯虚类,用于定义插件与应用程序之间的接口。

在本例中,使用了以下接口:

class RequestedCalendarInterface
{
public:
    RequestedCalendarInterface() = default;
    virtual QCalendar::SystemId loadCalendar(QAnyStringView requested) = 0;
    virtual ~RequestedCalendarInterface() = default;
};

并将其注册到 Qt 元对象系统中:

#define RequestedCalendarInterface_iid \
"org.qt-project.Qt.Examples.CalendarBackend.RequestedCalendarInterface/1.0"
Q_DECLARE_INTERFACE(RequestedCalendarInterface, RequestedCalendarInterface_iid)

Q_DECLARE_INTERFACE() 宏用于将ClassName (此处为:RequestedCalendarInterface )与定义的Identifier (此处为:RequestedCalendarInterface_iid )关联起来。Identifier 必须是唯一的。该接口可由加载其他日历的插件实现,这些插件会以各种方式解释loadCalendar() 中的字符串参数。它并不局限于将使用该接口实现的特定插件,因此其名称是通用的,而非特定于某个后端。

随后,创建一个同时继承自QObject 和该接口的插件类。

class JulianGregorianPlugin : public QObject, public RequestedCalendarInterface
{
    Q_OBJECT
    Q_INTERFACES(RequestedCalendarInterface)
    Q_PLUGIN_METADATA(IID "org.qt-project.Qt.Examples."
                          "CalendarBackend."
                          "RequestedCalendarInterface/1.0")
public:
    JulianGregorianPlugin();
    QCalendar::SystemId loadCalendar(QAnyStringView request) override;
    ~JulianGregorianPlugin();
};

Q_PLUGIN_METADATA() 和Q_INTERFACES() 用于声明在接口类中也已声明的元数据,并告知 Qt 该类实现了哪个接口。

该插件会实例化并注册一个自定义日历后端,应用程序随后可以在任何时候使用该后端来实例化 `QCalendar `。

Qt 插件存储在一个共享库(DLL)中,而QPluginLoader 用于检测和动态加载插件文件(更多内容请参阅《如何创建 Qt 插件》)。

加载插件

QPluginLoader 会检查插件所对应的 Qt 版本是否与应用程序一致,并提供对 Qt 插件的直接访问。

以下是示例中QPluginLoader 的用法:

    QPluginLoader loader;
    loader.setFileName("../plugin/calendarPlugin");
    loader.load();
    if (!loader.isLoaded())
        return 1;
    auto *myplugin = qobject_cast<RequestedCalendarInterface*>(loader.instance());

首先,需要初始化一个QPluginLoader 对象的实例。接下来,通过向setFileName()传递DLL文件名来指定要加载的插件。然后,使用load()动态加载插件文件。 最后,通过调用qobject_cast()来测试插件是否实现了给定的接口。qobject_cast()使用instance()来访问插件中的根组件。如果插件已正确加载,其函数应可供使用。

实例化后端

在此示例中,插件中仅包含一个函数。loadCalendar() 负责在QCalendarRegistry 中注册自定义日历后端,并传入给定的过渡日期和名称。

QCalendar::SystemId JulianGregorianPlugin::loadCalendar(QAnyStringView request)
{
    Q_ASSERT(!request.isEmpty());
    QStringList names = request.toString().split(u';');
    if (names.size() < 1)
        return {};
    QString dateString = names.takeFirst();
    auto date = QDate::fromString(dateString, u"yyyy-MM-dd",
                                  QCalendar(QCalendar::System::Julian));
    if (!date.isValid())
        return {};
    QString primary = names.isEmpty() ?
            QString::fromStdU16String(u"Julian until ") + dateString : names[0];
    auto backend = new JulianGregorianCalendar(date, primary);
    names.emplaceFront(backend->name());
    auto cid = backend->registerCustomBackend(names);
    return cid;
}

JulianGregorianPlugin::~JulianGregorianPlugin()
{
}

loadCalendar() 的字符串参数由用户通过命令行参数提供。随后,通过拆分该字符串来提取从儒略历转换到格里高利历的日期。验证通过后,将创建一个自定义后端对象。 后端必须先通过registerCustomBackend() 方法在QCalendar 中注册,才能使用。后端注册完成后,即可使用相应的SystemId 或name 方法实例化QCalendar 对象。

以下是在main 中使用loadCalendar 的示例:

   const autocid= myplugin->loadCalendar(args.at(0));
    if(!cid.isValid()) {
        qWarning() << "Invalid ID";
        parser.showHelp(1);
    }
    constQCalendar calendar(cid);
扩展 QCalendarWidget

通过创建一个以特定日历作为后端的QCalendar 实例,可以将该后端提供给QCalendarWidget 并进行可视化显示。

    QCalendarWidget widget;
    widget.setCalendar(calendar);
    widget.show();
    QCalendar::YearMonthDay when = { 1582, 10, 4 };
    QCalendar julian = QCalendar(QCalendar::System::Julian);
    auto got = QDate::fromString(args.at(0).left(10), u"yyyy-MM-dd", julian);
    if (got.isValid())
        when = julian.partsFromDate(got);
    widget.setCurrentPage(when.year, when.month);

示例项目 @ code.qt.io

另请参阅 QCalendarWidget 、QCalendar 、QDate 、QLocale 、QtPlugin 以及QPluginLoader 。

© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.