| 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, 2005, 2006 Ferdinando Ametrano |
| 7 | Copyright (C) 2006 Katiuscia Manzoni |
| 8 | Copyright (C) 2006 Toyin Akin |
| 9 | Copyright (C) 2015 Klaus Spanderen |
| 10 | Copyright (C) 2020 Leonardo Arcari |
| 11 | Copyright (C) 2020 Kline s.r.l. |
| 12 | |
| 13 | This file is part of QuantLib, a free-software/open-source library |
| 14 | for financial quantitative analysts and developers - http://quantlib.org/ |
| 15 | |
| 16 | QuantLib is free software: you can redistribute it and/or modify it |
| 17 | under the terms of the QuantLib license. You should have received a |
| 18 | copy of the license along with this program; if not, please email |
| 19 | <quantlib-dev@lists.sf.net>. The license is also available online at |
| 20 | <http://quantlib.org/license.shtml>. |
| 21 | |
| 22 | This program is distributed in the hope that it will be useful, but WITHOUT |
| 23 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
| 24 | FOR A PARTICULAR PURPOSE. See the license for more details. |
| 25 | */ |
| 26 | |
| 27 | #include <ql/time/date.hpp> |
| 28 | #include <ql/utilities/dataformatters.hpp> |
| 29 | #include <ql/errors.hpp> |
| 30 | #include <boost/date_time/gregorian/gregorian.hpp> |
| 31 | #include <boost/date_time/posix_time/posix_time_types.hpp> |
| 32 | #include <functional> |
| 33 | #include <iomanip> |
| 34 | #include <ctime> |
| 35 | |
| 36 | #ifdef QL_HIGH_RESOLUTION_DATE |
| 37 | #if BOOST_VERSION < 106700 |
| 38 | #include <boost/functional/hash.hpp> |
| 39 | #else |
| 40 | #include <boost/container_hash/hash.hpp> |
| 41 | #endif |
| 42 | #endif |
| 43 | |
| 44 | #if defined(BOOST_NO_STDC_NAMESPACE) |
| 45 | namespace std { using ::time; using ::time_t; using ::tm; |
| 46 | using ::gmtime; using ::localtime; } |
| 47 | #endif |
| 48 | |
| 49 | #ifdef QL_HIGH_RESOLUTION_DATE |
| 50 | using boost::posix_time::ptime; |
| 51 | using boost::posix_time::time_duration; |
| 52 | #endif |
| 53 | |
| 54 | |
| 55 | namespace QuantLib { |
| 56 | #ifndef QL_HIGH_RESOLUTION_DATE |
| 57 | // constructors |
| 58 | Date::Date() |
| 59 | : serialNumber_(Date::serial_type(0)) {} |
| 60 | |
| 61 | Date::Date(Date::serial_type serialNumber) |
| 62 | : serialNumber_(serialNumber) { |
| 63 | checkSerialNumber(serialNumber); |
| 64 | } |
| 65 | |
| 66 | Date::Date(Day d, Month m, Year y) { |
| 67 | QL_REQUIRE(y > 1900 && y < 2200, |
| 68 | "year " << y << " out of bound. It must be in [1901,2199]" ); |
| 69 | QL_REQUIRE(Integer(m) > 0 && Integer(m) < 13, |
| 70 | "month " << Integer(m) |
| 71 | << " outside January-December range [1,12]" ); |
| 72 | |
| 73 | bool leap = isLeap(y); |
| 74 | Day len = monthLength(m,leapYear: leap), offset = monthOffset(m,leapYear: leap); |
| 75 | QL_REQUIRE(d <= len && d > 0, |
| 76 | "day outside month (" << Integer(m) << ") day-range " |
| 77 | << "[1," << len << "]" ); |
| 78 | |
| 79 | serialNumber_ = d + offset + yearOffset(y); |
| 80 | } |
| 81 | |
| 82 | Month Date::month() const { |
| 83 | Day d = dayOfYear(); // dayOfYear is 1 based |
| 84 | Integer m = d/30 + 1; |
| 85 | bool leap = isLeap(y: year()); |
| 86 | while (d <= monthOffset(m: Month(m),leapYear: leap)) |
| 87 | --m; |
| 88 | while (d > monthOffset(m: Month(m+1),leapYear: leap)) // NOLINT(misc-misplaced-widening-cast) |
| 89 | ++m; |
| 90 | return Month(m); |
| 91 | } |
| 92 | |
| 93 | Year Date::year() const { |
| 94 | Year y = (serialNumber_ / 365)+1900; |
| 95 | // yearOffset(y) is December 31st of the preceding year |
| 96 | if (serialNumber_ <= yearOffset(y)) |
| 97 | --y; |
| 98 | return y; |
| 99 | } |
| 100 | |
| 101 | Date& Date::operator+=(Date::serial_type days) { |
| 102 | Date::serial_type serial = serialNumber_ + days; |
| 103 | checkSerialNumber(serialNumber: serial); |
| 104 | serialNumber_ = serial; |
| 105 | return *this; |
| 106 | } |
| 107 | |
| 108 | Date& Date::operator+=(const Period& p) { |
| 109 | serialNumber_ = advance(d: *this,units: p.length(),p.units()).serialNumber(); |
| 110 | return *this; |
| 111 | } |
| 112 | |
| 113 | Date& Date::operator-=(Date::serial_type days) { |
| 114 | Date::serial_type serial = serialNumber_ - days; |
| 115 | checkSerialNumber(serialNumber: serial); |
| 116 | serialNumber_ = serial; |
| 117 | return *this; |
| 118 | } |
| 119 | |
| 120 | Date& Date::operator-=(const Period& p) { |
| 121 | serialNumber_ = advance(d: *this,units: -p.length(),p.units()).serialNumber(); |
| 122 | return *this; |
| 123 | } |
| 124 | |
| 125 | Date& Date::operator++() { |
| 126 | Date::serial_type serial = serialNumber_ + 1; |
| 127 | checkSerialNumber(serialNumber: serial); |
| 128 | serialNumber_ = serial; |
| 129 | return *this; |
| 130 | } |
| 131 | |
| 132 | Date& Date::operator--() { |
| 133 | Date::serial_type serial = serialNumber_ - 1; |
| 134 | checkSerialNumber(serialNumber: serial); |
| 135 | serialNumber_ = serial; |
| 136 | return *this; |
| 137 | } |
| 138 | |
| 139 | Date Date::advance(const Date& date, Integer n, TimeUnit units) { |
| 140 | switch (units) { |
| 141 | case Days: |
| 142 | return date + n; |
| 143 | case Weeks: |
| 144 | return date + 7*n; |
| 145 | case Months: { |
| 146 | Day d = date.dayOfMonth(); |
| 147 | Integer m = Integer(date.month())+n; |
| 148 | Year y = date.year(); |
| 149 | while (m > 12) { |
| 150 | m -= 12; |
| 151 | y += 1; |
| 152 | } |
| 153 | while (m < 1) { |
| 154 | m += 12; |
| 155 | y -= 1; |
| 156 | } |
| 157 | |
| 158 | QL_ENSURE(y >= 1900 && y <= 2199, |
| 159 | "year " << y << " out of bounds. " |
| 160 | << "It must be in [1901,2199]" ); |
| 161 | |
| 162 | Integer length = monthLength(m: Month(m), leapYear: isLeap(y)); |
| 163 | if (d > length) |
| 164 | d = length; |
| 165 | |
| 166 | return {d, Month(m), y}; |
| 167 | } |
| 168 | case Years: { |
| 169 | Day d = date.dayOfMonth(); |
| 170 | Month m = date.month(); |
| 171 | Year y = date.year()+n; |
| 172 | |
| 173 | QL_ENSURE(y >= 1900 && y <= 2199, |
| 174 | "year " << y << " out of bounds. " |
| 175 | << "It must be in [1901,2199]" ); |
| 176 | |
| 177 | if (d == 29 && m == February && !isLeap(y)) |
| 178 | d = 28; |
| 179 | |
| 180 | return {d, m, y}; |
| 181 | } |
| 182 | default: |
| 183 | QL_FAIL("undefined time units" ); |
| 184 | } |
| 185 | } |
| 186 | |
| 187 | bool Date::isLeap(Year y) { |
| 188 | static const bool YearIsLeap[] = { |
| 189 | // 1900 is leap in agreement with Excel's bug |
| 190 | // 1900 is out of valid date range anyway |
| 191 | // 1900-1909 |
| 192 | true,false,false,false, true,false,false,false, true,false, |
| 193 | // 1910-1919 |
| 194 | false,false, true,false,false,false, true,false,false,false, |
| 195 | // 1920-1929 |
| 196 | true,false,false,false, true,false,false,false, true,false, |
| 197 | // 1930-1939 |
| 198 | false,false, true,false,false,false, true,false,false,false, |
| 199 | // 1940-1949 |
| 200 | true,false,false,false, true,false,false,false, true,false, |
| 201 | // 1950-1959 |
| 202 | false,false, true,false,false,false, true,false,false,false, |
| 203 | // 1960-1969 |
| 204 | true,false,false,false, true,false,false,false, true,false, |
| 205 | // 1970-1979 |
| 206 | false,false, true,false,false,false, true,false,false,false, |
| 207 | // 1980-1989 |
| 208 | true,false,false,false, true,false,false,false, true,false, |
| 209 | // 1990-1999 |
| 210 | false,false, true,false,false,false, true,false,false,false, |
| 211 | // 2000-2009 |
| 212 | true,false,false,false, true,false,false,false, true,false, |
| 213 | // 2010-2019 |
| 214 | false,false, true,false,false,false, true,false,false,false, |
| 215 | // 2020-2029 |
| 216 | true,false,false,false, true,false,false,false, true,false, |
| 217 | // 2030-2039 |
| 218 | false,false, true,false,false,false, true,false,false,false, |
| 219 | // 2040-2049 |
| 220 | true,false,false,false, true,false,false,false, true,false, |
| 221 | // 2050-2059 |
| 222 | false,false, true,false,false,false, true,false,false,false, |
| 223 | // 2060-2069 |
| 224 | true,false,false,false, true,false,false,false, true,false, |
| 225 | // 2070-2079 |
| 226 | false,false, true,false,false,false, true,false,false,false, |
| 227 | // 2080-2089 |
| 228 | true,false,false,false, true,false,false,false, true,false, |
| 229 | // 2090-2099 |
| 230 | false,false, true,false,false,false, true,false,false,false, |
| 231 | // 2100-2109 |
| 232 | false,false,false,false, true,false,false,false, true,false, |
| 233 | // 2110-2119 |
| 234 | false,false, true,false,false,false, true,false,false,false, |
| 235 | // 2120-2129 |
| 236 | true,false,false,false, true,false,false,false, true,false, |
| 237 | // 2130-2139 |
| 238 | false,false, true,false,false,false, true,false,false,false, |
| 239 | // 2140-2149 |
| 240 | true,false,false,false, true,false,false,false, true,false, |
| 241 | // 2150-2159 |
| 242 | false,false, true,false,false,false, true,false,false,false, |
| 243 | // 2160-2169 |
| 244 | true,false,false,false, true,false,false,false, true,false, |
| 245 | // 2170-2179 |
| 246 | false,false, true,false,false,false, true,false,false,false, |
| 247 | // 2180-2189 |
| 248 | true,false,false,false, true,false,false,false, true,false, |
| 249 | // 2190-2199 |
| 250 | false,false, true,false,false,false, true,false,false,false, |
| 251 | // 2200 |
| 252 | false |
| 253 | }; |
| 254 | QL_REQUIRE(y>=1900 && y<=2200, "year outside valid range" ); |
| 255 | return YearIsLeap[y-1900]; |
| 256 | } |
| 257 | |
| 258 | |
| 259 | Integer Date::monthLength(Month m, bool leapYear) { |
| 260 | static const Integer MonthLength[] = { |
| 261 | 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 |
| 262 | }; |
| 263 | static const Integer MonthLeapLength[] = { |
| 264 | 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 |
| 265 | }; |
| 266 | return (leapYear? MonthLeapLength[m-1] : MonthLength[m-1]); |
| 267 | } |
| 268 | |
| 269 | Integer Date::monthOffset(Month m, bool leapYear) { |
| 270 | static const Integer MonthOffset[] = { |
| 271 | 0, 31, 59, 90, 120, 151, // Jan - Jun |
| 272 | 181, 212, 243, 273, 304, 334, // Jun - Dec |
| 273 | 365 // used in dayOfMonth to bracket day |
| 274 | }; |
| 275 | static const Integer MonthLeapOffset[] = { |
| 276 | 0, 31, 60, 91, 121, 152, // Jan - Jun |
| 277 | 182, 213, 244, 274, 305, 335, // Jun - Dec |
| 278 | 366 // used in dayOfMonth to bracket day |
| 279 | }; |
| 280 | return (leapYear? MonthLeapOffset[m-1] : MonthOffset[m-1]); |
| 281 | } |
| 282 | |
| 283 | Date::serial_type Date::yearOffset(Year y) { |
| 284 | // the list of all December 31st in the preceding year |
| 285 | // e.g. for 1901 yearOffset[1] is 366, that is, December 31 1900 |
| 286 | static const Date::serial_type YearOffset[] = { |
| 287 | // 1900-1909 |
| 288 | 0, 366, 731, 1096, 1461, 1827, 2192, 2557, 2922, 3288, |
| 289 | // 1910-1919 |
| 290 | 3653, 4018, 4383, 4749, 5114, 5479, 5844, 6210, 6575, 6940, |
| 291 | // 1920-1929 |
| 292 | 7305, 7671, 8036, 8401, 8766, 9132, 9497, 9862,10227,10593, |
| 293 | // 1930-1939 |
| 294 | 10958,11323,11688,12054,12419,12784,13149,13515,13880,14245, |
| 295 | // 1940-1949 |
| 296 | 14610,14976,15341,15706,16071,16437,16802,17167,17532,17898, |
| 297 | // 1950-1959 |
| 298 | 18263,18628,18993,19359,19724,20089,20454,20820,21185,21550, |
| 299 | // 1960-1969 |
| 300 | 21915,22281,22646,23011,23376,23742,24107,24472,24837,25203, |
| 301 | // 1970-1979 |
| 302 | 25568,25933,26298,26664,27029,27394,27759,28125,28490,28855, |
| 303 | // 1980-1989 |
| 304 | 29220,29586,29951,30316,30681,31047,31412,31777,32142,32508, |
| 305 | // 1990-1999 |
| 306 | 32873,33238,33603,33969,34334,34699,35064,35430,35795,36160, |
| 307 | // 2000-2009 |
| 308 | 36525,36891,37256,37621,37986,38352,38717,39082,39447,39813, |
| 309 | // 2010-2019 |
| 310 | 40178,40543,40908,41274,41639,42004,42369,42735,43100,43465, |
| 311 | // 2020-2029 |
| 312 | 43830,44196,44561,44926,45291,45657,46022,46387,46752,47118, |
| 313 | // 2030-2039 |
| 314 | 47483,47848,48213,48579,48944,49309,49674,50040,50405,50770, |
| 315 | // 2040-2049 |
| 316 | 51135,51501,51866,52231,52596,52962,53327,53692,54057,54423, |
| 317 | // 2050-2059 |
| 318 | 54788,55153,55518,55884,56249,56614,56979,57345,57710,58075, |
| 319 | // 2060-2069 |
| 320 | 58440,58806,59171,59536,59901,60267,60632,60997,61362,61728, |
| 321 | // 2070-2079 |
| 322 | 62093,62458,62823,63189,63554,63919,64284,64650,65015,65380, |
| 323 | // 2080-2089 |
| 324 | 65745,66111,66476,66841,67206,67572,67937,68302,68667,69033, |
| 325 | // 2090-2099 |
| 326 | 69398,69763,70128,70494,70859,71224,71589,71955,72320,72685, |
| 327 | // 2100-2109 |
| 328 | 73050,73415,73780,74145,74510,74876,75241,75606,75971,76337, |
| 329 | // 2110-2119 |
| 330 | 76702,77067,77432,77798,78163,78528,78893,79259,79624,79989, |
| 331 | // 2120-2129 |
| 332 | 80354,80720,81085,81450,81815,82181,82546,82911,83276,83642, |
| 333 | // 2130-2139 |
| 334 | 84007,84372,84737,85103,85468,85833,86198,86564,86929,87294, |
| 335 | // 2140-2149 |
| 336 | 87659,88025,88390,88755,89120,89486,89851,90216,90581,90947, |
| 337 | // 2150-2159 |
| 338 | 91312,91677,92042,92408,92773,93138,93503,93869,94234,94599, |
| 339 | // 2160-2169 |
| 340 | 94964,95330,95695,96060,96425,96791,97156,97521,97886,98252, |
| 341 | // 2170-2179 |
| 342 | 98617,98982,99347,99713,100078,100443,100808,101174,101539,101904, |
| 343 | // 2180-2189 |
| 344 | 102269,102635,103000,103365,103730,104096,104461,104826,105191,105557, |
| 345 | // 2190-2199 |
| 346 | 105922,106287,106652,107018,107383,107748,108113,108479,108844,109209, |
| 347 | // 2200 |
| 348 | 109574 |
| 349 | }; |
| 350 | return YearOffset[y-1900]; |
| 351 | } |
| 352 | |
| 353 | #else |
| 354 | |
| 355 | namespace { |
| 356 | const boost::gregorian::date& serialNumberDateReference() { |
| 357 | static const boost::gregorian::date dateReference( |
| 358 | 1899, boost::gregorian::Dec, 30); |
| 359 | return dateReference; |
| 360 | } |
| 361 | |
| 362 | |
| 363 | #define compatibleEnums ( int(boost::date_time::Monday) +1 == Monday \ |
| 364 | && int(boost::date_time::Tuesday) +1 == Tuesday \ |
| 365 | && int(boost::date_time::Wednesday)+1 == Wednesday \ |
| 366 | && int(boost::date_time::Thursday) +1 == Thursday \ |
| 367 | && int(boost::date_time::Friday) +1 == Friday \ |
| 368 | && int(boost::date_time::Saturday) +1 == Saturday \ |
| 369 | && int(boost::date_time::Sunday) +1 == Sunday \ |
| 370 | && int(boost::date_time::Jan) == January \ |
| 371 | && int(boost::date_time::Feb) == February \ |
| 372 | && int(boost::date_time::Mar) == March \ |
| 373 | && int(boost::date_time::Apr) == April \ |
| 374 | && int(boost::date_time::May) == May \ |
| 375 | && int(boost::date_time::Jun) == June \ |
| 376 | && int(boost::date_time::Jul) == July \ |
| 377 | && int(boost::date_time::Aug) == August \ |
| 378 | && int(boost::date_time::Sep) == September \ |
| 379 | && int(boost::date_time::Oct) == October \ |
| 380 | && int(boost::date_time::Nov) == November \ |
| 381 | && int(boost::date_time::Dec) == December ) |
| 382 | |
| 383 | template <bool compatible> |
| 384 | Weekday mapBoostDateType2QL(boost::gregorian::greg_weekday d) { |
| 385 | if (compatible) { |
| 386 | return Weekday(d.as_number() + 1); |
| 387 | } |
| 388 | else { |
| 389 | switch (d) { |
| 390 | case boost::date_time::Monday : return Monday; |
| 391 | case boost::date_time::Tuesday : return Tuesday; |
| 392 | case boost::date_time::Wednesday: return Wednesday; |
| 393 | case boost::date_time::Thursday : return Thursday; |
| 394 | case boost::date_time::Friday : return Friday; |
| 395 | case boost::date_time::Saturday : return Saturday; |
| 396 | case boost::date_time::Sunday : return Sunday; |
| 397 | default: |
| 398 | QL_FAIL("unknown boost date_time day of week given" ); |
| 399 | } |
| 400 | } |
| 401 | } |
| 402 | |
| 403 | template <bool compatible> |
| 404 | Month mapBoostDateType2QL(boost::gregorian::greg_month m) { |
| 405 | if (compatible) { |
| 406 | return Month(m.as_number()); |
| 407 | } |
| 408 | else { |
| 409 | switch (m) { |
| 410 | case boost::date_time::Jan : return January; |
| 411 | case boost::date_time::Feb : return February; |
| 412 | case boost::date_time::Mar : return March; |
| 413 | case boost::date_time::Apr : return April; |
| 414 | case boost::date_time::May : return May; |
| 415 | case boost::date_time::Jun : return June; |
| 416 | case boost::date_time::Jul : return July; |
| 417 | case boost::date_time::Aug : return August; |
| 418 | case boost::date_time::Sep : return September; |
| 419 | case boost::date_time::Oct : return October; |
| 420 | case boost::date_time::Nov : return November; |
| 421 | case boost::date_time::Dec : return December; |
| 422 | default: |
| 423 | QL_FAIL("unknown boost date_time month of week given" ); |
| 424 | } |
| 425 | } |
| 426 | } |
| 427 | |
| 428 | |
| 429 | template <bool compatible> |
| 430 | boost::gregorian::greg_month mapQLDateType2Boost(Month m) { |
| 431 | if (compatible) { |
| 432 | return boost::gregorian::greg_month(m); |
| 433 | } |
| 434 | else { |
| 435 | switch (m) { |
| 436 | case January : return boost::date_time::Jan; |
| 437 | case February : return boost::date_time::Feb; |
| 438 | case March : return boost::date_time::Mar; |
| 439 | case April : return boost::date_time::Apr; |
| 440 | case May : return boost::date_time::May; |
| 441 | case June : return boost::date_time::Jun; |
| 442 | case July : return boost::date_time::Jul; |
| 443 | case August : return boost::date_time::Aug; |
| 444 | case September: return boost::date_time::Sep; |
| 445 | case October : return boost::date_time::Oct; |
| 446 | case November : return boost::date_time::Nov; |
| 447 | case December : return boost::date_time::Dec; |
| 448 | default: |
| 449 | QL_FAIL("unknown boost date_time month of week given" ); |
| 450 | } |
| 451 | } |
| 452 | } |
| 453 | |
| 454 | void advance(ptime& dt, Integer n, TimeUnit units) { |
| 455 | using boost::gregorian::gregorian_calendar; |
| 456 | |
| 457 | switch (units) { |
| 458 | case Days: |
| 459 | dt += boost::gregorian::days(n); |
| 460 | break; |
| 461 | case Weeks: |
| 462 | dt += boost::gregorian::weeks(n); |
| 463 | break; |
| 464 | case Months: |
| 465 | case Years : { |
| 466 | const boost::gregorian::date date = dt.date(); |
| 467 | const Day eoM = gregorian_calendar::end_of_month_day( |
| 468 | date.year(), date.month()); |
| 469 | |
| 470 | if (units == Months) { |
| 471 | dt += boost::gregorian::months(n); |
| 472 | } |
| 473 | else { |
| 474 | dt += boost::gregorian::years(n); |
| 475 | } |
| 476 | |
| 477 | if (date.day() == eoM) { |
| 478 | // avoid snap-to-end-of-month |
| 479 | // behavior of boost::date_time |
| 480 | const Day newEoM |
| 481 | = gregorian_calendar::end_of_month_day( |
| 482 | dt.date().year(), dt.date().month()); |
| 483 | |
| 484 | if (newEoM > eoM) { |
| 485 | dt -= boost::gregorian::days(newEoM - eoM); |
| 486 | } |
| 487 | } |
| 488 | } |
| 489 | break; |
| 490 | case Hours: |
| 491 | dt += boost::posix_time::hours(n); |
| 492 | break; |
| 493 | case Minutes: |
| 494 | dt += boost::posix_time::minutes(n); |
| 495 | break; |
| 496 | case Seconds: |
| 497 | dt += boost::posix_time::seconds(n); |
| 498 | break; |
| 499 | case Milliseconds: |
| 500 | dt += boost::posix_time::milliseconds(n); |
| 501 | break; |
| 502 | case Microseconds: |
| 503 | dt += boost::posix_time::microseconds(n); |
| 504 | break; |
| 505 | default: |
| 506 | QL_FAIL("undefined time units" ); |
| 507 | } |
| 508 | } |
| 509 | |
| 510 | boost::gregorian::date gregorianDate(Year y, Month m, Day d) { |
| 511 | QL_REQUIRE(y > 1900 && y < 2200, |
| 512 | "year " << y << " out of bound. It must be in [1901,2199]" ); |
| 513 | QL_REQUIRE(Integer(m) > 0 && Integer(m) < 13, |
| 514 | "month " << Integer(m) |
| 515 | << " outside January-December range [1,12]" ); |
| 516 | |
| 517 | const boost::gregorian::greg_month bM |
| 518 | = mapQLDateType2Boost<compatibleEnums>(m); |
| 519 | |
| 520 | const Day len = |
| 521 | boost::gregorian::gregorian_calendar::end_of_month_day(y, bM); |
| 522 | QL_REQUIRE(d <= len && d > 0, |
| 523 | "day outside month (" << Integer(m) << ") day-range " |
| 524 | << "[1," << len << "]" ); |
| 525 | |
| 526 | return boost::gregorian::date(y, bM, d); |
| 527 | } |
| 528 | } |
| 529 | |
| 530 | |
| 531 | Date::Date() |
| 532 | : dateTime_(serialNumberDateReference()) {} |
| 533 | |
| 534 | Date::Date(const ptime& dateTime) |
| 535 | : dateTime_(dateTime) {} |
| 536 | |
| 537 | Date::Date(Day d, Month m, Year y) |
| 538 | : dateTime_(gregorianDate(y, m, d)) {} |
| 539 | |
| 540 | Date::Date(Day d, Month m, Year y, |
| 541 | Hour hours, Minute minutes, Second seconds, |
| 542 | Millisecond millisec, Microsecond microsec) |
| 543 | : dateTime_( |
| 544 | gregorianDate(y, m, d), |
| 545 | boost::posix_time::time_duration( |
| 546 | hours, minutes, seconds, |
| 547 | millisec*(time_duration::ticks_per_second()/1000) |
| 548 | + microsec*(time_duration::ticks_per_second()/1000000))) {} |
| 549 | |
| 550 | Date::Date(Date::serial_type serialNumber) |
| 551 | : dateTime_( |
| 552 | serialNumberDateReference() + |
| 553 | boost::gregorian::days(serialNumber)) { |
| 554 | checkSerialNumber(serialNumber); |
| 555 | } |
| 556 | |
| 557 | Weekday Date::weekday() const { |
| 558 | return mapBoostDateType2QL<compatibleEnums>( |
| 559 | dateTime_.date().day_of_week()); |
| 560 | } |
| 561 | |
| 562 | Day Date::dayOfMonth() const { |
| 563 | return dateTime_.date().day(); |
| 564 | } |
| 565 | |
| 566 | Day Date::dayOfYear() const { |
| 567 | return dateTime_.date().day_of_year(); |
| 568 | } |
| 569 | |
| 570 | Month Date::month() const { |
| 571 | return mapBoostDateType2QL<compatibleEnums>(dateTime_.date().month()); |
| 572 | } |
| 573 | |
| 574 | Year Date::year() const { |
| 575 | return dateTime_.date().year(); |
| 576 | } |
| 577 | |
| 578 | Hour Date::hours() const { |
| 579 | return dateTime_.time_of_day().hours(); |
| 580 | } |
| 581 | |
| 582 | Minute Date::minutes() const { |
| 583 | return dateTime_.time_of_day().minutes(); |
| 584 | } |
| 585 | |
| 586 | Second Date::seconds() const { |
| 587 | return dateTime_.time_of_day().seconds(); |
| 588 | } |
| 589 | |
| 590 | Time Date::fractionOfDay() const { |
| 591 | const time_duration t = dateTime().time_of_day(); |
| 592 | |
| 593 | const Time seconds |
| 594 | = (t.hours()*60.0 + t.minutes())*60.0 + t.seconds() |
| 595 | + Real(t.fractional_seconds())/ticksPerSecond(); |
| 596 | |
| 597 | return seconds/86400.0; // ignore any DST hocus-pocus |
| 598 | } |
| 599 | |
| 600 | Time Date::fractionOfSecond() const { |
| 601 | return dateTime_.time_of_day().fractional_seconds() |
| 602 | /Real(ticksPerSecond()); |
| 603 | } |
| 604 | |
| 605 | Millisecond Date::milliseconds() const { |
| 606 | return dateTime_.time_of_day().fractional_seconds() |
| 607 | /(ticksPerSecond()/1000); |
| 608 | } |
| 609 | |
| 610 | Microsecond Date::microseconds() const { |
| 611 | return (dateTime_.time_of_day().fractional_seconds() |
| 612 | - milliseconds()*(time_duration::ticks_per_second()/1000)) |
| 613 | /(ticksPerSecond()/1000000); |
| 614 | } |
| 615 | |
| 616 | time_duration::tick_type Date::ticksPerSecond() { |
| 617 | return time_duration::ticks_per_second(); |
| 618 | } |
| 619 | |
| 620 | Date::serial_type Date::serialNumber() const { |
| 621 | const Date::serial_type n = (dateTime_.date() |
| 622 | - serialNumberDateReference()).days(); |
| 623 | checkSerialNumber(n); |
| 624 | |
| 625 | return n; |
| 626 | } |
| 627 | |
| 628 | const ptime& Date::dateTime() const { return dateTime_; } |
| 629 | |
| 630 | Date& Date::operator+=(Date::serial_type d) { |
| 631 | dateTime_ += boost::gregorian::days(d); |
| 632 | return *this; |
| 633 | } |
| 634 | |
| 635 | Date& Date::operator+=(const Period& p) { |
| 636 | advance(dateTime_, p.length(), p.units()); |
| 637 | return *this; |
| 638 | } |
| 639 | |
| 640 | Date& Date::operator-=(Date::serial_type d) { |
| 641 | dateTime_ -= boost::gregorian::days(d); |
| 642 | return *this; |
| 643 | } |
| 644 | Date& Date::operator-=(const Period& p) { |
| 645 | advance(dateTime_, -p.length(), p.units()); |
| 646 | return *this; |
| 647 | } |
| 648 | |
| 649 | Date& Date::operator++() { |
| 650 | dateTime_ +=boost::gregorian::days(1); |
| 651 | return *this; |
| 652 | } |
| 653 | |
| 654 | Date& Date::operator--() { |
| 655 | dateTime_ -=boost::gregorian::days(1); |
| 656 | return *this; |
| 657 | } |
| 658 | |
| 659 | Date Date::operator+(Date::serial_type days) const { |
| 660 | Date retVal(*this); |
| 661 | retVal+=days; |
| 662 | |
| 663 | return retVal; |
| 664 | } |
| 665 | |
| 666 | Date Date::operator-(Date::serial_type days) const { |
| 667 | Date retVal(*this); |
| 668 | retVal-=days; |
| 669 | |
| 670 | return retVal; |
| 671 | } |
| 672 | |
| 673 | Date Date::operator+(const Period& p) const { |
| 674 | Date retVal(*this); |
| 675 | retVal+=p; |
| 676 | |
| 677 | return retVal; |
| 678 | } |
| 679 | |
| 680 | Date Date::operator-(const Period& p) const { |
| 681 | Date retVal(*this); |
| 682 | retVal-=p; |
| 683 | |
| 684 | return retVal; |
| 685 | } |
| 686 | |
| 687 | Date Date::localDateTime() { |
| 688 | return Date(boost::posix_time::microsec_clock::local_time()); |
| 689 | } |
| 690 | |
| 691 | Date Date::universalDateTime() { |
| 692 | return Date(boost::posix_time::microsec_clock::universal_time()); |
| 693 | } |
| 694 | |
| 695 | bool Date::isLeap(Year y) { |
| 696 | return boost::gregorian::gregorian_calendar::is_leap_year(y); |
| 697 | } |
| 698 | |
| 699 | Date Date::endOfMonth(const Date& d) { |
| 700 | const Month m = d.month(); |
| 701 | const Year y = d.year(); |
| 702 | const Day eoM = boost::gregorian::gregorian_calendar::end_of_month_day( |
| 703 | d.year(), mapQLDateType2Boost<compatibleEnums>(d.month())); |
| 704 | |
| 705 | return Date(eoM, m, y); |
| 706 | } |
| 707 | |
| 708 | bool Date::isEndOfMonth(const Date& d) { |
| 709 | return d.dayOfMonth() == |
| 710 | boost::gregorian::gregorian_calendar::end_of_month_day( |
| 711 | d.year(), mapQLDateType2Boost<compatibleEnums>(d.month())); |
| 712 | } |
| 713 | |
| 714 | |
| 715 | Date::serial_type operator-(const Date& d1, const Date& d2) { |
| 716 | return (d1.dateTime().date() - d2.dateTime().date()).days(); |
| 717 | } |
| 718 | |
| 719 | Time daysBetween(const Date& d1, const Date& d2) { |
| 720 | const Date::serial_type days = d2 - d1; |
| 721 | return days + d2.fractionOfDay() - d1.fractionOfDay(); |
| 722 | } |
| 723 | |
| 724 | bool operator<(const Date& d1, const Date& d2) { |
| 725 | return (d1.dateTime() < d2.dateTime()); |
| 726 | } |
| 727 | |
| 728 | bool operator<=(const Date& d1, const Date& d2) { |
| 729 | return (d1.dateTime() <= d2.dateTime()); |
| 730 | } |
| 731 | |
| 732 | bool operator>(const Date& d1, const Date& d2) { |
| 733 | return (d1.dateTime() > d2.dateTime()); |
| 734 | } |
| 735 | |
| 736 | bool operator>=(const Date& d1, const Date& d2) { |
| 737 | return (d1.dateTime() >= d2.dateTime()); |
| 738 | } |
| 739 | |
| 740 | bool operator==(const Date& d1, const Date& d2) { |
| 741 | return (d1.dateTime() == d2.dateTime()); |
| 742 | } |
| 743 | |
| 744 | bool operator!=(const Date& d1, const Date& d2) { |
| 745 | return (d1.dateTime() != d2.dateTime()); |
| 746 | } |
| 747 | #endif |
| 748 | |
| 749 | Date::serial_type Date::minimumSerialNumber() { |
| 750 | return 367; // Jan 1st, 1901 |
| 751 | } |
| 752 | |
| 753 | Date::serial_type Date::maximumSerialNumber() { |
| 754 | return 109574; // Dec 31st, 2199 |
| 755 | } |
| 756 | |
| 757 | void Date::checkSerialNumber(Date::serial_type serialNumber) { |
| 758 | QL_REQUIRE(serialNumber >= minimumSerialNumber() && |
| 759 | serialNumber <= maximumSerialNumber(), |
| 760 | "Date's serial number (" << serialNumber << ") outside " |
| 761 | "allowed range [" << minimumSerialNumber() << |
| 762 | "-" << maximumSerialNumber() << "], i.e. [" << |
| 763 | minDate() << "-" << maxDate() << "]" ); |
| 764 | } |
| 765 | |
| 766 | Date Date::minDate() { |
| 767 | static const Date minimumDate(minimumSerialNumber()); |
| 768 | return minimumDate; |
| 769 | } |
| 770 | |
| 771 | Date Date::maxDate() { |
| 772 | static const Date maximumDate(maximumSerialNumber()); |
| 773 | return maximumDate; |
| 774 | } |
| 775 | |
| 776 | Date Date::operator++(int ) { |
| 777 | Date old(*this); |
| 778 | ++*this; // use the pre-increment |
| 779 | return old; |
| 780 | } |
| 781 | |
| 782 | Date Date::operator--(int ) { |
| 783 | Date old(*this); |
| 784 | --*this; // use the pre-decrement |
| 785 | return old; |
| 786 | } |
| 787 | |
| 788 | Date Date::todaysDate() { |
| 789 | std::time_t t; |
| 790 | |
| 791 | if (std::time(timer: &t) == std::time_t(-1)) // -1 means time() didn't work |
| 792 | return {}; |
| 793 | std::tm *lt = std::localtime(timer: &t); |
| 794 | return {Day(lt->tm_mday), Month(lt->tm_mon + 1), Year(lt->tm_year + 1900)}; |
| 795 | } |
| 796 | |
| 797 | Date Date::nextWeekday(const Date& d, Weekday dayOfWeek) { |
| 798 | Weekday wd = d.weekday(); |
| 799 | return d + ((wd>dayOfWeek ? 7 : 0) - wd + dayOfWeek); |
| 800 | } |
| 801 | |
| 802 | Date Date::nthWeekday(Size nth, Weekday dayOfWeek, |
| 803 | Month m, Year y) { |
| 804 | QL_REQUIRE(nth>0, |
| 805 | "zeroth day of week in a given (month, year) is undefined" ); |
| 806 | QL_REQUIRE(nth<6, |
| 807 | "no more than 5 weekday in a given (month, year)" ); |
| 808 | Weekday first = Date(1, m, y).weekday(); |
| 809 | Size skip = nth - (dayOfWeek>=first ? 1 : 0); |
| 810 | return {Day((1 + dayOfWeek + skip * 7) - first), m, y}; |
| 811 | } |
| 812 | |
| 813 | // month formatting |
| 814 | |
| 815 | std::ostream& operator<<(std::ostream& out, Month m) { |
| 816 | switch (m) { |
| 817 | case January: |
| 818 | return out << "January" ; |
| 819 | case February: |
| 820 | return out << "February" ; |
| 821 | case March: |
| 822 | return out << "March" ; |
| 823 | case April: |
| 824 | return out << "April" ; |
| 825 | case May: |
| 826 | return out << "May" ; |
| 827 | case June: |
| 828 | return out << "June" ; |
| 829 | case July: |
| 830 | return out << "July" ; |
| 831 | case August: |
| 832 | return out << "August" ; |
| 833 | case September: |
| 834 | return out << "September" ; |
| 835 | case October: |
| 836 | return out << "October" ; |
| 837 | case November: |
| 838 | return out << "November" ; |
| 839 | case December: |
| 840 | return out << "December" ; |
| 841 | default: |
| 842 | QL_FAIL("unknown month (" << Integer(m) << ")" ); |
| 843 | } |
| 844 | } |
| 845 | |
| 846 | std::size_t hash_value(const Date& d) { |
| 847 | #ifdef QL_HIGH_RESOLUTION_DATE |
| 848 | std::size_t seed = 0; |
| 849 | boost::hash_combine(seed, d.serialNumber()); |
| 850 | boost::hash_combine(seed, d.dateTime().time_of_day().total_nanoseconds()); |
| 851 | return seed; |
| 852 | #else |
| 853 | return std::hash<Date::serial_type>()(d.serialNumber()); |
| 854 | #endif |
| 855 | } |
| 856 | |
| 857 | // date formatting |
| 858 | |
| 859 | std::ostream& operator<<(std::ostream& out, const Date& d) { |
| 860 | return out << io::long_date(d); |
| 861 | } |
| 862 | |
| 863 | namespace detail { |
| 864 | |
| 865 | struct FormatResetter { // NOLINT(cppcoreguidelines-special-member-functions) |
| 866 | // An instance of this object will have undefined behaviour |
| 867 | // if the object out passed in the constructor is destroyed |
| 868 | // before this instance |
| 869 | struct nopunct : std::numpunct<char> { |
| 870 | std::string do_grouping() const override { return "" ; } |
| 871 | }; |
| 872 | explicit FormatResetter(std::ostream &out) |
| 873 | : out_(&out), flags_(out.flags()), filler_(out.fill()), |
| 874 | loc_(out.getloc()) { |
| 875 | std::locale loc (out.getloc(),new nopunct); |
| 876 | out.imbue(loc: loc); |
| 877 | out << std::resetiosflags( |
| 878 | mask: std::ios_base::adjustfield | std::ios_base::basefield | |
| 879 | std::ios_base::floatfield | std::ios_base::showbase | |
| 880 | std::ios_base::showpos | std::ios_base::uppercase); |
| 881 | out << std::right; |
| 882 | } |
| 883 | ~FormatResetter() { |
| 884 | out_->flags(fmtfl: flags_); |
| 885 | out_->fill(ch: filler_); |
| 886 | out_->imbue(loc: loc_); |
| 887 | } |
| 888 | std::ostream *out_; |
| 889 | std::ios_base::fmtflags flags_; |
| 890 | char filler_; |
| 891 | std::locale loc_; |
| 892 | }; |
| 893 | |
| 894 | std::ostream& operator<<(std::ostream& out, |
| 895 | const short_date_holder& holder) { |
| 896 | const Date& d = holder.d; |
| 897 | if (d == Date()) { |
| 898 | out << "null date" ; |
| 899 | } else { |
| 900 | FormatResetter resetter(out); |
| 901 | Integer dd = d.dayOfMonth(), mm = Integer(d.month()), |
| 902 | yyyy = d.year(); |
| 903 | char filler = out.fill(); |
| 904 | out << std::setw(2) << std::setfill('0') << mm << "/" ; |
| 905 | out << std::setw(2) << std::setfill('0') << dd << "/" ; |
| 906 | out << yyyy; |
| 907 | out.fill(ch: filler); |
| 908 | } |
| 909 | return out; |
| 910 | } |
| 911 | |
| 912 | std::ostream& operator<<(std::ostream& out, |
| 913 | const long_date_holder& holder) { |
| 914 | const Date& d = holder.d; |
| 915 | if (d == Date()) { |
| 916 | out << "null date" ; |
| 917 | } else { |
| 918 | FormatResetter resetter(out); |
| 919 | out << d.month() << " " ; |
| 920 | out << io::ordinal(n: d.dayOfMonth()) << ", " ; |
| 921 | out << d.year(); |
| 922 | } |
| 923 | return out; |
| 924 | } |
| 925 | |
| 926 | std::ostream& operator<<(std::ostream& out, |
| 927 | const iso_date_holder& holder) { |
| 928 | const Date& d = holder.d; |
| 929 | if (d == Date()) { |
| 930 | out << "null date" ; |
| 931 | } else { |
| 932 | FormatResetter resetter(out); |
| 933 | Integer dd = d.dayOfMonth(), mm = Integer(d.month()), |
| 934 | yyyy = d.year(); |
| 935 | out << yyyy << "-" ; |
| 936 | out << std::setw(2) << std::setfill('0') << mm << "-" ; |
| 937 | out << std::setw(2) << std::setfill('0') << dd; |
| 938 | } |
| 939 | return out; |
| 940 | } |
| 941 | |
| 942 | std::ostream& operator<<(std::ostream& out, |
| 943 | const formatted_date_holder& holder) { |
| 944 | using namespace boost::gregorian; |
| 945 | const Date& d = holder.d; |
| 946 | if (d == Date()) { |
| 947 | out << "null date" ; |
| 948 | } else { |
| 949 | FormatResetter resetter(out); |
| 950 | date boostDate(d.year(), d.month(), d.dayOfMonth()); |
| 951 | out.imbue(loc: std::locale(std::locale(), |
| 952 | new date_facet(holder.f.c_str()))); |
| 953 | out << boostDate; |
| 954 | } |
| 955 | return out; |
| 956 | } |
| 957 | |
| 958 | #ifdef QL_HIGH_RESOLUTION_DATE |
| 959 | std::ostream& operator<<(std::ostream& out, |
| 960 | const iso_datetime_holder& holder) { |
| 961 | const Date& d = holder.d; |
| 962 | |
| 963 | out << io::iso_date(d) << "T" ; |
| 964 | FormatResetter resetter(out); |
| 965 | const Hour hh= d.hours(); |
| 966 | const Minute mm = d.minutes(); |
| 967 | const Second s = d.seconds(); |
| 968 | const Millisecond millis = d.milliseconds(); |
| 969 | const Microsecond micros = d.microseconds(); |
| 970 | |
| 971 | out << std::setw(2) << std::setfill('0') << hh << ":" |
| 972 | << std::setw(2) << std::setfill('0') << mm << ":" |
| 973 | << std::setw(2) << std::setfill('0') << s << "," |
| 974 | << std::setw(3) << std::setfill('0') << millis |
| 975 | << std::setw(3) << std::setfill('0') << micros; |
| 976 | |
| 977 | return out; |
| 978 | } |
| 979 | #endif |
| 980 | } |
| 981 | |
| 982 | namespace io { |
| 983 | detail::short_date_holder short_date(const Date& d) { |
| 984 | return detail::short_date_holder(d); |
| 985 | } |
| 986 | |
| 987 | detail::long_date_holder long_date(const Date& d) { |
| 988 | return detail::long_date_holder(d); |
| 989 | } |
| 990 | |
| 991 | detail::iso_date_holder iso_date(const Date& d) { |
| 992 | return detail::iso_date_holder(d); |
| 993 | } |
| 994 | |
| 995 | detail::formatted_date_holder formatted_date(const Date& d, |
| 996 | const std::string& f) { |
| 997 | return detail::formatted_date_holder(d, f); |
| 998 | } |
| 999 | |
| 1000 | #ifdef QL_HIGH_RESOLUTION_DATE |
| 1001 | detail::iso_datetime_holder iso_datetime(const Date& d) { |
| 1002 | return detail::iso_datetime_holder(d); |
| 1003 | } |
| 1004 | #endif |
| 1005 | } |
| 1006 | } |
| 1007 | |