diff --git a/ql/time/calendars/newzealand.cpp b/ql/time/calendars/newzealand.cpp index 497abd8cc9..5ac0037562 100644 --- a/ql/time/calendars/newzealand.cpp +++ b/ql/time/calendars/newzealand.cpp @@ -21,13 +21,7 @@ namespace QuantLib { - NewZealand::NewZealand() { - // all calendar instances share the same implementation instance - static ext::shared_ptr impl(new NewZealand::Impl); - impl_ = impl; - } - - bool NewZealand::Impl::isBusinessDay(const Date& date) const { + bool NewZealand::CommonImpl::isBusinessDay(const Date& date) const { Weekday w = date.weekday(); Day d = date.dayOfMonth(), dd = date.dayOfYear(); Month m = date.month(); @@ -40,16 +34,14 @@ namespace QuantLib { // Day after New Year's Day (possibly moved to Mon or Tuesday) || ((d == 2 || (d == 4 && (w == Monday || w == Tuesday))) && m == January) - // Anniversary Day, Monday nearest January 22nd - || ((d >= 19 && d <= 25) && w == Monday && m == January) - // Waitangi Day. February 6th ("Mondayised" since 2013) + // Waitangi Day. February 6th (possibly moved to Monday since 2013) || (d == 6 && m == February) || ((d == 7 || d == 8) && w == Monday && m == February && y > 2013) // Good Friday || (dd == em-3) // Easter Monday || (dd == em) - // ANZAC Day. April 25th ("Mondayised" since 2013) + // ANZAC Day. April 25th (possibly moved to Monday since 2013) || (d == 25 && m == April) || ((d == 26 || d == 27) && w == Monday && m == April && y > 2013) // Queen's Birthday, first Monday in June @@ -81,9 +73,54 @@ namespace QuantLib { || (d == 14 && m == July && (y == 2023 || y == 2028)) || (d == 15 && m == July && (y == 2039 || y == 2050)) || (d == 18 && m == July && y == 2036) - || (d == 19 && m == July && (y == 2041 || y == 2047))) + || (d == 19 && m == July && (y == 2041 || y == 2047)) + // Queen Elizabeth's funeral + || (d == 26 && m == September && y == 2022)) return false; // NOLINT(readability-simplify-boolean-expr) return true; } + bool NewZealand::WellingtonImpl::isBusinessDay(const Date& date) const { + if (!NewZealand::CommonImpl::isBusinessDay(date)) + return false; + Weekday w = date.weekday(); + Day d = date.dayOfMonth(); + Month m = date.month(); + // Anniversary Day, Monday nearest January 22nd + if ((d >= 19 && d <= 25) && w == Monday && m == January) + return false; // NOLINT(readability-simplify-boolean-expr) + return true; + } + + bool NewZealand::AucklandImpl::isBusinessDay(const Date& date) const { + if (!NewZealand::CommonImpl::isBusinessDay(date)) + return false; + Weekday w = date.weekday(); + Day d = date.dayOfMonth(); + Month m = date.month(); + // Anniversary Day, Monday nearest January 29nd + if ((d >= 26 && w == Monday && m == January) + || (d == 1 && w == Monday && m == February)) + return false; // NOLINT(readability-simplify-boolean-expr) + return true; + } + + + NewZealand::NewZealand(Market market) { + // all calendar instances for a given market share the same implementation instance + static auto wellingtonImpl = ext::make_shared(); + static auto aucklandImpl = ext::make_shared(); + + switch (market) { + case Wellington: + impl_ = wellingtonImpl; + break; + case Auckland: + impl_ = aucklandImpl; + break; + default: + QL_FAIL("unknown market"); + } + } + } diff --git a/ql/time/calendars/newzealand.hpp b/ql/time/calendars/newzealand.hpp index 29abd657f8..d5a074d010 100644 --- a/ql/time/calendars/newzealand.hpp +++ b/ql/time/calendars/newzealand.hpp @@ -29,26 +29,33 @@ namespace QuantLib { //! New Zealand calendar - /*! Holidays: + /*! Common holidays:
  • Saturdays
  • Sundays
  • -
  • New Year's Day, January 1st (possibly moved to Monday or - Tuesday)
  • -
  • Day after New Year's Day, January 2st (possibly moved to - Monday or Tuesday)
  • -
  • Anniversary Day, Monday nearest January 22nd
  • -
  • Waitangi Day. February 6th
  • +
  • New Year's Day, January 1st (possibly moved to Monday or Tuesday)
  • +
  • Day after New Year's Day, January 2st (possibly moved to Monday or Tuesday)
  • +
  • Waitangi Day. February 6th (possibly moved to Monday since 2013)
  • Good Friday
  • Easter Monday
  • -
  • ANZAC Day. April 25th
  • +
  • ANZAC Day. April 25th (possibly moved to Monday since 2013)
  • Queen's Birthday, first Monday in June
  • Labour Day, fourth Monday in October
  • Christmas, December 25th (possibly moved to Monday or Tuesday)
  • -
  • Boxing Day, December 26th (possibly moved to Monday or - Tuesday)
  • +
  • Boxing Day, December 26th (possibly moved to Monday or Tuesday)
  • Matariki, in June or July, official calendar released for years 2022-2052
+ + Additional holidays for Wellington: +
    +
  • Anniversary Day, Monday nearest January 22nd
  • +
+ + Additional holidays for Auckland: +
    +
  • Anniversary Day, Monday nearest January 29nd
  • +
+ \note The holiday rules for New Zealand were documented by David Gilbert for IDB (http://www.jrefinery.com/ibd/) The Matariki holiday calendar has been released by the NZ Government @@ -58,13 +65,24 @@ namespace QuantLib { */ class NewZealand : public Calendar { private: - class Impl final : public Calendar::WesternImpl { + class CommonImpl : public Calendar::WesternImpl { + public: + bool isBusinessDay(const Date&) const override; + }; + class WellingtonImpl final : public CommonImpl { + public: + std::string name() const override { return "New Zealand (Wellington)"; } + bool isBusinessDay(const Date&) const override; + }; + class AucklandImpl final : public CommonImpl { public: - std::string name() const override { return "New Zealand"; } + std::string name() const override { return "New Zealand (Auckland)"; } bool isBusinessDay(const Date&) const override; }; public: - NewZealand(); + //! NZ calendars + enum Market { Wellington, Auckland }; + explicit NewZealand(Market market = Wellington); }; } diff --git a/test-suite/calendars.cpp b/test-suite/calendars.cpp index 1522f8189d..15eb261e3e 100644 --- a/test-suite/calendars.cpp +++ b/test-suite/calendars.cpp @@ -39,6 +39,7 @@ #include #include #include +#include #include #include #include @@ -3371,6 +3372,87 @@ BOOST_AUTO_TEST_CASE(testMexicoInaugurationDay) { } } + +BOOST_AUTO_TEST_CASE(testNewZealand) { + BOOST_TEST_MESSAGE("Testing a few holiday rules for New Zealand..."); + + auto auckland = NewZealand(NewZealand::Auckland); + auto wellington = NewZealand(NewZealand::Wellington); + + for (auto calendar: { auckland, wellington }) { + // mid-week New Year's day + BOOST_TEST(calendar.isHoliday({1, January, 2025})); + BOOST_TEST(calendar.isHoliday({2, January, 2025})); + BOOST_TEST(calendar.isBusinessDay({3, January, 2025})); + // New Year's day on Sunday + BOOST_TEST(calendar.isHoliday({1, January, 2023})); + BOOST_TEST(calendar.isHoliday({2, January, 2023})); + BOOST_TEST(calendar.isHoliday({3, January, 2023})); + BOOST_TEST(calendar.isBusinessDay({4, January, 2023})); + // New Year's day on Saturday + BOOST_TEST(calendar.isHoliday({1, January, 2022})); + BOOST_TEST(calendar.isHoliday({2, January, 2022})); + BOOST_TEST(calendar.isHoliday({3, January, 2022})); + BOOST_TEST(calendar.isHoliday({4, January, 2022})); + BOOST_TEST(calendar.isBusinessDay({5, January, 2022})); + // New Year's day on Friday + BOOST_TEST(calendar.isHoliday({1, January, 2027})); + BOOST_TEST(calendar.isHoliday({2, January, 2027})); + BOOST_TEST(calendar.isHoliday({3, January, 2027})); + BOOST_TEST(calendar.isHoliday({4, January, 2027})); + BOOST_TEST(calendar.isBusinessDay({5, January, 2027})); + + // mid-week Christmas day + BOOST_TEST(calendar.isHoliday({25, December, 2024})); + BOOST_TEST(calendar.isHoliday({26, December, 2024})); + BOOST_TEST(calendar.isBusinessDay({27, December, 2024})); + // Christmas day on Sunday + BOOST_TEST(calendar.isHoliday({25, December, 2022})); + BOOST_TEST(calendar.isHoliday({26, December, 2022})); + BOOST_TEST(calendar.isHoliday({27, December, 2022})); + BOOST_TEST(calendar.isBusinessDay({28, December, 2022})); + // Christmas day on Saturday + BOOST_TEST(calendar.isHoliday({25, December, 2021})); + BOOST_TEST(calendar.isHoliday({26, December, 2021})); + BOOST_TEST(calendar.isHoliday({27, December, 2021})); + BOOST_TEST(calendar.isHoliday({28, December, 2021})); + BOOST_TEST(calendar.isBusinessDay({29, December, 2021})); + // Christmas day on Friday + BOOST_TEST(calendar.isHoliday({25, December, 2026})); + BOOST_TEST(calendar.isHoliday({26, December, 2026})); + BOOST_TEST(calendar.isHoliday({27, December, 2026})); + BOOST_TEST(calendar.isHoliday({28, December, 2026})); + BOOST_TEST(calendar.isBusinessDay({29, December, 2026})); + + // Waitangi Day is moved to Monday but only since 2013 + BOOST_TEST(calendar.isHoliday({8, February, 2021})); + BOOST_TEST(calendar.isHoliday({7, February, 2022})); + BOOST_TEST(calendar.isBusinessDay({8, February, 2010})); + BOOST_TEST(calendar.isBusinessDay({7, February, 2011})); + + // The same goes for ANZAC Day + BOOST_TEST(calendar.isHoliday({27, April, 2020})); + BOOST_TEST(calendar.isHoliday({26, April, 2021})); + BOOST_TEST(calendar.isBusinessDay({27, April, 2009})); + BOOST_TEST(calendar.isBusinessDay({26, April, 2010})); + } + + // different Anniversary Day for the two calendars + BOOST_TEST(auckland.isBusinessDay({22, January, 2024})); + BOOST_TEST(wellington.isHoliday({22, January, 2024})); + BOOST_TEST(auckland.isHoliday({29, January, 2024})); + BOOST_TEST(wellington.isBusinessDay({29, January, 2024})); + BOOST_TEST(auckland.isBusinessDay({19, January, 2026})); + BOOST_TEST(wellington.isHoliday({19, January, 2026})); + BOOST_TEST(auckland.isHoliday({26, January, 2026})); + BOOST_TEST(wellington.isBusinessDay({26, January, 2026})); + BOOST_TEST(auckland.isBusinessDay({25, January, 2027})); + BOOST_TEST(wellington.isHoliday({25, January, 2027})); + BOOST_TEST(auckland.isHoliday({1, February, 2027})); + BOOST_TEST(wellington.isBusinessDay({1, February, 2027})); +} + + BOOST_AUTO_TEST_CASE(testStartOfMonth) { BOOST_TEST_MESSAGE("Testing start-of-month calculation...");