이 페이지에서

캘린더 백엔드 플러그인 예시

QCalendar 사용자가 직접 제공하는 사용자 정의 캘린더를 보여주는 예제입니다.

율리우스력에서 그레고리력으로 전환되는 기간의 공백이 반영된 1582년 10월을 표시하는 캘린더 위젯

소개

전 세계에는 매우 다양한 캘린더 시스템이 사용되고 있습니다. Qt는 그중 일부를 기본적으로 지원하지만( System 참조), 그 수가 너무 많기 때문에 전반적인 지원을 제공하지는 못합니다. 비공개 API인 사용자 정의 QCalendarBackend를 구현함으로써 추가적인 캘린더 시스템을 제공할 수 있습니다.

이 예제는 사용자 정의 달력 백엔드를 작성하는 방법과, 저수준 플러그인 API를 사용하여 애플리케이션을 확장해 사용자가 선택할 수 있는 달력을 지원하는 방법을 보여줍니다. 많은 국가들이 역사상 어느 시점에 율리우스력에서 그레고리력으로 전환했으며, 이 사용자 정의 달력 백엔드는 예시로 해당 달력을 구현할 것입니다. 사용자 정의 백엔드는 플러그인으로 컴파일되며, 런타임에 메인 애플리케이션에 의해 로드됩니다. 지역마다 다른 정확한 전환 날짜는 문자열 형태로 플러그인에 제공되며, 사용자가 직접 지정할 수 있습니다.

달력 백엔드

달력 백엔드 클래스는 ` QCalendarBackend `을 상속받아야 하며, 순수 가상 함수(pure virtual functions)를 ` 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 auto cid = myplugin->loadCalendar(args.at(0));
    if (!cid.isValid()) {
        qWarning() << "Invalid ID";
        parser.showHelp(1);
    }
    const QCalendar 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.