Coverage Report

Created: 2026-03-31 07:01

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/quantlib/ql/time/calendar.cpp
Line
Count
Source
1
/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3
/*
4
 Copyright (C) 2000, 2001, 2002, 2003 RiskMap srl
5
 Copyright (C) 2003, 2004, 2005, 2006, 2007 StatPro Italia srl
6
 Copyright (C) 2004 Jeff Yu
7
 Copyright (C) 2014 Paolo Mazzocchi
8
 Copyright (C) 2020 Leonardo Arcari
9
 Copyright (C) 2020 Kline s.r.l.
10
11
 This file is part of QuantLib, a free-software/open-source library
12
 for financial quantitative analysts and developers - http://quantlib.org/
13
14
 QuantLib is free software: you can redistribute it and/or modify it
15
 under the terms of the QuantLib license.  You should have received a
16
 copy of the license along with this program; if not, please email
17
 <quantlib-dev@lists.sf.net>. The license is also available online at
18
 <https://www.quantlib.org/license.shtml>.
19
20
 This program is distributed in the hope that it will be useful, but WITHOUT
21
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
22
 FOR A PARTICULAR PURPOSE.  See the license for more details.
23
*/
24
25
#include <ql/time/calendar.hpp>
26
#include <ql/errors.hpp>
27
28
namespace QuantLib {
29
30
    namespace {
31
32
        // Requires: from < to.
33
        Date::serial_type daysBetweenImpl(const Calendar& cal,
34
                                          const Date& from, const Date& to,
35
0
                                          bool includeFirst, bool includeLast) {
36
0
            auto res = static_cast<Date::serial_type>(includeLast && cal.isBusinessDay(to));
37
0
            for (Date d = includeFirst ? from : from + 1; d < to; ++d) {
38
0
                res += static_cast<Date::serial_type>(cal.isBusinessDay(d));
39
0
            }
40
0
            return res;
41
0
        }
42
43
    }
44
45
0
    void Calendar::addHoliday(const Date& d) {
46
0
        QL_REQUIRE(impl_, "no calendar implementation provided");
47
48
#ifdef QL_HIGH_RESOLUTION_DATE
49
        const Date _d(d.dayOfMonth(), d.month(), d.year());
50
#else
51
0
        const Date& _d = d;
52
0
#endif
53
54
        // if d was a genuine holiday previously removed, revert the change
55
0
        impl_->removedHolidays.erase(_d);
56
        // if it's already a holiday, leave the calendar alone.
57
        // Otherwise, add it.
58
0
        if (impl_->isBusinessDay(_d))
59
0
            impl_->addedHolidays.insert(_d);
60
0
    }
61
62
0
    void Calendar::removeHoliday(const Date& d) {
63
0
        QL_REQUIRE(impl_, "no calendar implementation provided");
64
65
#ifdef QL_HIGH_RESOLUTION_DATE
66
        const Date _d(d.dayOfMonth(), d.month(), d.year());
67
#else
68
0
        const Date& _d = d;
69
0
#endif
70
71
        // if d was an artificially-added holiday, revert the change
72
0
        impl_->addedHolidays.erase(_d);
73
        // if it's already a business day, leave the calendar alone.
74
        // Otherwise, add it.
75
0
        if (!impl_->isBusinessDay(_d))
76
0
            impl_->removedHolidays.insert(_d);
77
0
    }
78
79
0
    void Calendar::resetAddedAndRemovedHolidays() {
80
0
        impl_->addedHolidays.clear();
81
0
        impl_->removedHolidays.clear();
82
0
    }
83
84
    Date Calendar::adjust(const Date& d,
85
129M
                          BusinessDayConvention c) const {
86
129M
        QL_REQUIRE(d != Date(), "null date");
87
88
129M
        if (c == Unadjusted)
89
103M
            return d;
90
91
25.8M
        Date d1 = d;
92
25.8M
        if (c == Following || c == ModifiedFollowing 
93
25.8M
            || c == HalfMonthModifiedFollowing) {
94
25.8M
            while (isHoliday(d1))
95
0
                ++d1;
96
25.8M
            if (c == ModifiedFollowing 
97
25.8M
                || c == HalfMonthModifiedFollowing) {
98
0
                if (d1.month() != d.month()) {
99
0
                    return adjust(d, Preceding);
100
0
                }
101
0
                if (c == HalfMonthModifiedFollowing) {
102
0
                    if (d.dayOfMonth() <= 15 && d1.dayOfMonth() > 15) {
103
0
                        return adjust(d, Preceding);
104
0
                    }
105
0
                }
106
0
            }
107
25.8M
        } else if (c == Preceding || c == ModifiedPreceding) {
108
0
            while (isHoliday(d1))
109
0
                --d1;
110
0
            if (c == ModifiedPreceding && d1.month() != d.month()) {
111
0
                return adjust(d,Following);
112
0
            }
113
0
        } else if (c == Nearest) {
114
0
            Date d2 = d;
115
0
            while (isHoliday(d1) && isHoliday(d2))
116
0
            {
117
0
                ++d1;
118
0
                --d2;
119
0
            }
120
0
            if (isHoliday(d1))
121
0
                return d2;
122
0
            else
123
0
                return d1;
124
0
        } else {
125
0
            QL_FAIL("unknown business-day convention");
126
0
        }
127
25.8M
        return d1;
128
25.8M
    }
129
130
    Date Calendar::advance(const Date& d,
131
                           Integer n, TimeUnit unit,
132
                           BusinessDayConvention c,
133
51.8M
                           bool endOfMonth) const {
134
51.8M
        QL_REQUIRE(d!=Date(), "null date");
135
51.8M
        if (n == 0) {
136
25.8M
            return adjust(d,c);
137
25.9M
        } else if (unit == Days) {
138
0
            Date d1 = d;
139
0
            if (n > 0) {
140
0
                while (n > 0) {
141
0
                    ++d1;
142
0
                    while (isHoliday(d1))
143
0
                        ++d1;
144
0
                    --n;
145
0
                }
146
0
            } else {
147
0
                while (n < 0) {
148
0
                    --d1;
149
0
                    while(isHoliday(d1))
150
0
                        --d1;
151
0
                    ++n;
152
0
                }
153
0
            }
154
0
            return d1;
155
25.9M
        } else if (unit == Weeks) {
156
0
            Date d1 = d + n*unit;
157
0
            return adjust(d1,c);
158
25.9M
        } else {
159
25.9M
            Date d1 = d + n*unit;
160
161
            // we are sure the unit is Months or Years
162
25.9M
            if (endOfMonth) {
163
0
                if (c == Unadjusted) {
164
                    // move to the last calendar day if d is the last calendar day
165
0
                    if (Date::isEndOfMonth(d)) return Date::endOfMonth(d1);
166
0
                } else {
167
                    // move to the last business day if d is the last business day
168
0
                    if (isEndOfMonth(d)) return Calendar::endOfMonth(d1);
169
0
                }
170
0
            }
171
25.9M
            return adjust(d1, c);
172
25.9M
        }
173
51.8M
    }
174
175
    Date Calendar::advance(const Date & d,
176
                           const Period & p,
177
                           BusinessDayConvention c,
178
25.9M
                           bool endOfMonth) const {
179
25.9M
        return advance(d, p.length(), p.units(), c, endOfMonth);
180
25.9M
    }
181
182
    Date::serial_type Calendar::businessDaysBetween(const Date& from,
183
                                                    const Date& to,
184
                                                    bool includeFirst,
185
0
                                                    bool includeLast) const {
186
0
        return (from < to) ? daysBetweenImpl(*this, from, to, includeFirst, includeLast) :
187
0
               (from > to) ? -daysBetweenImpl(*this, to, from, includeLast, includeFirst) :
188
0
               Date::serial_type(includeFirst && includeLast && isBusinessDay(from));
189
0
    }
190
191
192
193
   // Western calendars
194
195
0
    bool Calendar::WesternImpl::isWeekend(Weekday w) const {
196
0
        return w == Saturday || w == Sunday;
197
0
    }
198
199
0
    Day Calendar::WesternImpl::easterMonday(Year y) {
200
0
        static const unsigned char EasterMonday[] = {
201
0
                  98,  90, 103,  95, 114, 106,  91, 111, 102,   // 1901-1909
202
0
             87, 107,  99,  83, 103,  95, 115,  99,  91, 111,   // 1910-1919
203
0
             96,  87, 107,  92, 112, 103,  95, 108, 100,  91,   // 1920-1929
204
0
            111,  96,  88, 107,  92, 112, 104,  88, 108, 100,   // 1930-1939
205
0
             85, 104,  96, 116, 101,  92, 112,  97,  89, 108,   // 1940-1949
206
0
            100,  85, 105,  96, 109, 101,  93, 112,  97,  89,   // 1950-1959
207
0
            109,  93, 113, 105,  90, 109, 101,  86, 106,  97,   // 1960-1969
208
0
             89, 102,  94, 113, 105,  90, 110, 101,  86, 106,   // 1970-1979
209
0
             98, 110, 102,  94, 114,  98,  90, 110,  95,  86,   // 1980-1989
210
0
            106,  91, 111, 102,  94, 107,  99,  90, 103,  95,   // 1990-1999
211
0
            115, 106,  91, 111, 103,  87, 107,  99,  84, 103,   // 2000-2009
212
0
             95, 115, 100,  91, 111,  96,  88, 107,  92, 112,   // 2010-2019
213
0
            104,  95, 108, 100,  92, 111,  96,  88, 108,  92,   // 2020-2029
214
0
            112, 104,  89, 108, 100,  85, 105,  96, 116, 101,   // 2030-2039
215
0
             93, 112,  97,  89, 109, 100,  85, 105,  97, 109,   // 2040-2049
216
0
            101,  93, 113,  97,  89, 109,  94, 113, 105,  90,   // 2050-2059
217
0
            110, 101,  86, 106,  98,  89, 102,  94, 114, 105,   // 2060-2069
218
0
             90, 110, 102,  86, 106,  98, 111, 102,  94, 114,   // 2070-2079
219
0
             99,  90, 110,  95,  87, 106,  91, 111, 103,  94,   // 2080-2089
220
0
            107,  99,  91, 103,  95, 115, 107,  91, 111, 103,   // 2090-2099
221
0
             88, 108, 100,  85, 105,  96, 109, 101,  93, 112,   // 2100-2109
222
0
             97,  89, 109,  93, 113, 105,  90, 109, 101,  86,   // 2110-2119
223
0
            106,  97,  89, 102,  94, 113, 105,  90, 110, 101,   // 2120-2129
224
0
             86, 106,  98, 110, 102,  94, 114,  98,  90, 110,   // 2130-2139
225
0
             95,  86, 106,  91, 111, 102,  94, 107,  99,  90,   // 2140-2149
226
0
            103,  95, 115, 106,  91, 111, 103,  87, 107,  99,   // 2150-2159
227
0
             84, 103,  95, 115, 100,  91, 111,  96,  88, 107,   // 2160-2169
228
0
             92, 112, 104,  95, 108, 100,  92, 111,  96,  88,   // 2170-2179
229
0
            108,  92, 112, 104,  89, 108, 100,  85, 105,  96,   // 2180-2189
230
0
            116, 101,  93, 112,  97,  89, 109, 100,  85, 105    // 2190-2199
231
0
        };
232
0
        return EasterMonday[y-1901];
233
0
    }
234
235
    // Orthodox calendars
236
237
0
    bool Calendar::OrthodoxImpl::isWeekend(Weekday w) const {
238
0
        return w == Saturday || w == Sunday;
239
0
    }
240
241
0
    Day Calendar::OrthodoxImpl::easterMonday(Year y) {
242
0
        static const unsigned char EasterMonday[] = {
243
0
                 105, 118, 110, 102, 121, 106, 126, 118, 102,   // 1901-1909
244
0
            122, 114,  99, 118, 110,  95, 115, 106, 126, 111,   // 1910-1919
245
0
            103, 122, 107,  99, 119, 110, 123, 115, 107, 126,   // 1920-1929
246
0
            111, 103, 123, 107,  99, 119, 104, 123, 115, 100,   // 1930-1939
247
0
            120, 111,  96, 116, 108, 127, 112, 104, 124, 115,   // 1940-1949
248
0
            100, 120, 112,  96, 116, 108, 128, 112, 104, 124,   // 1950-1959
249
0
            109, 100, 120, 105, 125, 116, 101, 121, 113, 104,   // 1960-1969
250
0
            117, 109, 101, 120, 105, 125, 117, 101, 121, 113,   // 1970-1979
251
0
             98, 117, 109, 129, 114, 105, 125, 110, 102, 121,   // 1980-1989
252
0
            106,  98, 118, 109, 122, 114, 106, 118, 110, 102,   // 1990-1999
253
0
            122, 106, 126, 118, 103, 122, 114,  99, 119, 110,   // 2000-2009
254
0
             95, 115, 107, 126, 111, 103, 123, 107,  99, 119,   // 2010-2019
255
0
            111, 123, 115, 107, 127, 111, 103, 123, 108,  99,   // 2020-2029
256
0
            119, 104, 124, 115, 100, 120, 112,  96, 116, 108,   // 2030-2039
257
0
            128, 112, 104, 124, 116, 100, 120, 112,  97, 116,   // 2040-2049
258
0
            108, 128, 113, 104, 124, 109, 101, 120, 105, 125,   // 2050-2059
259
0
            117, 101, 121, 113, 105, 117, 109, 101, 121, 105,   // 2060-2069
260
0
            125, 110, 102, 121, 113,  98, 118, 109, 129, 114,   // 2070-2079
261
0
            106, 125, 110, 102, 122, 106,  98, 118, 110, 122,   // 2080-2089
262
0
            114,  99, 119, 110, 102, 115, 107, 126, 118, 103,   // 2090-2099
263
0
            123, 115, 100, 120, 112,  96, 116, 108, 128, 112,   // 2100-2109
264
0
            104, 124, 109, 100, 120, 105, 125, 116, 108, 121,   // 2110-2119
265
0
            113, 104, 124, 109, 101, 120, 105, 125, 117, 101,   // 2120-2129
266
0
            121, 113,  98, 117, 109, 129, 114, 105, 125, 110,   // 2130-2139
267
0
            102, 121, 113,  98, 118, 109, 129, 114, 106, 125,   // 2140-2149
268
0
            110, 102, 122, 106, 126, 118, 103, 122, 114,  99,   // 2150-2159
269
0
            119, 110, 102, 115, 107, 126, 111, 103, 123, 114,   // 2160-2169
270
0
             99, 119, 111, 130, 115, 107, 127, 111, 103, 123,   // 2170-2179
271
0
            108,  99, 119, 104, 124, 115, 100, 120, 112, 103,   // 2180-2189
272
0
            116, 108, 128, 119, 104, 124, 116, 100, 120, 112    // 2190-2199
273
0
        };
274
0
        return EasterMonday[y-1901];
275
0
    }
276
277
    std::vector<Date> Calendar::holidayList(
278
0
        const Date& from, const Date& to, bool includeWeekEnds) const {
279
280
0
        std::vector<Date> result;
281
0
        for (Date d = from; d <= to; ++d) {
282
0
            if (isHoliday(d) && (includeWeekEnds || !isWeekend(d.weekday())))
283
0
                result.push_back(d);
284
0
       }
285
0
       return result;
286
0
    }
287
288
    std::vector<Date> Calendar::businessDayList(
289
0
        const Date& from, const Date& to) const {
290
291
0
        std::vector<Date> result;
292
0
        for (Date d = from; d <= to; ++d) {
293
0
            if (isBusinessDay(d))
294
0
                result.push_back(d);
295
0
       }
296
0
       return result;
297
0
    }
298
}