En esta página

Ejemplo de plugin de calendario

QCalendar ejemplo que ilustra los calendarios personalizados proporcionados por el usuario.

Introducción

Existen numerosos sistemas de calendario en uso en todo el mundo. Qt tiene soporte integrado para algunos de ellos (ver System), pero no puede proporcionar soporte general debido a su elevado número. Se pueden proporcionar sistemas de calendario adicionales implementando un QCalendarBackend personalizado, que es una API privada.

Este ejemplo demuestra cómo escribir un backend de calendario personalizado y cómo usar el plugin API de bajo nivel para extender una aplicación a calendarios seleccionables por el usuario. Muchos países pasaron del calendario juliano al gregoriano en algún momento de su historia, y este backend de calendario personalizado implementará el calendario respectivo como ejemplo. El backend personalizado se compila en un plugin y se carga en tiempo de ejecución por la aplicación principal. La fecha exacta de la transición, diferente para cada región, se proporciona como una cadena al plugin y puede ser determinada por el usuario.

Calendario

La clase de calendario debe heredar de QCalendarBackend e implementar sus funciones virtuales puras en thread-safe. También puede sustituir algunas otras funciones virtuales según sea necesario.

Ejemplo de implementación

Este ejemplo hereda del ya existente QRomanCalendar, que a su vez hereda de QCalendarBackend e implementa algunas de sus funciones virtuales. Es constructivo hacer esto porque el calendario de transición comparte, tanto con el calendario juliano como con el gregoriano, partes proporcionadas por el calendario romano.

He aquí la declaración de clase de 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;
};

El QDate pasado al constructor - endJulian - es la fecha del último día del calendario juliano. El calendario calculará automáticamente el desplazamiento para un año determinado, por ejemplo, en 1582 se omitieron 10 días, pero en 1700 hubo que omitir 12 días. El backend del calendario está registrado en name y se puede crear una instancia del calendario utilizando ese nombre. La clase sólo anula funciones cuando los dos calendarios que combina difieren de la base romana. Tiene instancias de los calendarios juliano y gregoriano en las que estas funciones pueden delegar.

Conversiones de días julianos

dateToJulianDay(int year, int month, int day, qint64 *jd) calcula el número de día juliano correspondiente a los especificados year, month y day. Devuelve true y establece jd si existe tal fecha en este calendario; en caso contrario, devuelve 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) calcula el año, mes y día en este calendario para el número de día juliano dado, jd. Si el día dado cae fuera del ámbito de este calendario, el valor devuelto por isValid() es false. En este ejemplo, si la fecha dada cae en el hueco saltado por la transición del calendario juliano al gregoriano, está fuera del ámbito.

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);
}
Soporte de configuración regional

En general, un calendario puede tener su propia nomenclatura para los meses del año y los días de la semana. Estos deben estar convenientemente localizados para que sean inteligibles para todos los usuarios. Por defecto, el backend baseclass se encarga de los nombres de los días de la semana por nosotros, lo que es totalmente suficiente para estos calendarios de transición Juliano/Gregoriano.

Aunque un backend puede anular directamente los métodos de nomenclatura de meses, la versión baseclass de éstos puede personalizarse implementando localeMonthData() y localeMonthIndexData() para proporcionar tablas de nombres de meses localizados. Dado que los calendarios juliano y gregoriano utilizan la misma nomenclatura de meses, heredan esa personalización de una base común, QRomanCalendar. Esto también significa que el calendario personalizado puede utilizar los mismos nombres, de nuevo heredando de esa base. Esto se ocupa de la localización.

Plugin

Las aplicaciones Qt pueden ampliarse mediante plugins. Esto requiere que la aplicación detecte y cargue plugins usando QPluginLoader.

Escribir un plugin

Para escribir un plugin, lo primero que hay que hacer es crear una clase virtual pura que defina la interfaz entre el plugin y la aplicación.

En este ejemplo se ha utilizado la siguiente interfaz:

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

y registrarla en el sistema de meta-objetos de Qt:

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

Q_DECLARE_INTERFACE() se utiliza para asociar el ClassName (aquí: RequestedCalendarInterface) con el Identifier definido (aquí: RequestedCalendarInterface_iid). El Identifier debe ser único. Esta interfaz puede ser implementada por plugins que cargan otros calendarios, interpretando el parámetro de cadena loadCalendar()'s de varias maneras. No está limitado a este plugin en particular que será implementado usándolo, por lo que tiene un nombre genérico, no uno específico para este backend en particular.

Luego se crea una clase plugin que hereda de QObject y de la interfaz.

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() y Q_INTERFACES() se usan para declarar metadatos que también fueron declarados en la clase de la interfaz y para decirle a Qt qué interfaz implementa la clase.

Este plugin instancia y registra un backend de calendario personalizado que puede a su vez ser usado para instanciar QCalendar por la aplicación en cualquier punto.

Los plugins de Qt se almacenan en una única librería compartida (una DLL) y QPluginLoader se utiliza para detectar y cargar dinámicamente el archivo del plugin (para más información ver Cómo crear plugins de Qt).

Cargando el plugin

QPluginLoader comprueba si la versión de Qt del plugin es la misma que la de la aplicación y proporciona acceso directo a un plugin Qt.

Este es el uso de QPluginLoader en el ejemplo:

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

En primer lugar, hay que inicializar una instancia de un objeto QPluginLoader. A continuación, hay que especificar qué plugin cargar pasando un nombre de archivo DLL a setFileName(). A continuación, mediante load(), se carga dinámicamente el archivo del plugin. Al final, una llamada a qobject_cast() comprueba si un plugin implementa una interfaz determinada. qobject_cast() utiliza instance() para acceder al componente raíz del plugin. Si el plugin se ha cargado correctamente, sus funciones deberían estar disponibles.

Instanciar el backend

En este ejemplo sólo hay una función en el plugin. loadCalendar() se encarga de registrar el backend del calendario personalizado en QCalendarRegistry con la fecha de la transición y los nombres dados.

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()
{
}

El argumento de cadena para loadCalendar() es suministrado por el usuario a través de argumentos de línea de comandos. A continuación, se extrae la fecha de transición del calendario juliano al gregoriano dividiendo la cadena dada. Tras la validación, se crea un objeto backend personalizado. El backend debe registrarse antes de poder utilizarse en QCalendar, utilizando el método registerCustomBackend(). Una vez registrado un backend, se puede instanciar un QCalendar con los respectivos SystemId o name.

A continuación se muestra el uso de loadCalendar en main:

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

Creando una instancia de QCalendar con un calendario específico como backend, es posible proporcionar a QCalendarWidget ese backend y visualizarlo.

    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);

Proyecto de ejemplo @ code.qt.io

Ver también QCalendarWidget, QCalendar, QDate, QLocale, QtPlugin, y 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.