| 1 | /* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
| 2 | |
| 3 | /* |
| 4 | Copyright (C) 2004, 2009 Ferdinando Ametrano |
| 5 | Copyright (C) 2006 Katiuscia Manzoni |
| 6 | Copyright (C) 2003 RiskMap srl |
| 7 | Copyright (C) 2015 Maddalena Zanzi |
| 8 | Copyright (c) 2015 Klaus Spanderen |
| 9 | Copyright (C) 2020 Leonardo Arcari |
| 10 | Copyright (C) 2020 Kline s.r.l. |
| 11 | |
| 12 | This file is part of QuantLib, a free-software/open-source library |
| 13 | for financial quantitative analysts and developers - http://quantlib.org/ |
| 14 | |
| 15 | QuantLib is free software: you can redistribute it and/or modify it |
| 16 | under the terms of the QuantLib license. You should have received a |
| 17 | copy of the license along with this program; if not, please email |
| 18 | <quantlib-dev@lists.sf.net>. The license is also available online at |
| 19 | <http://quantlib.org/license.shtml>. |
| 20 | |
| 21 | This program is distributed in the hope that it will be useful, but WITHOUT |
| 22 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
| 23 | FOR A PARTICULAR PURPOSE. See the license for more details. |
| 24 | */ |
| 25 | |
| 26 | #include "dates.hpp" |
| 27 | #include "utilities.hpp" |
| 28 | #include <ql/time/date.hpp> |
| 29 | #include <ql/time/timeunit.hpp> |
| 30 | #include <ql/time/imm.hpp> |
| 31 | #include <ql/time/ecb.hpp> |
| 32 | #include <ql/time/asx.hpp> |
| 33 | #include <ql/utilities/dataparsers.hpp> |
| 34 | |
| 35 | #include <sstream> |
| 36 | #include <unordered_set> |
| 37 | |
| 38 | using namespace QuantLib; |
| 39 | using namespace boost::unit_test_framework; |
| 40 | |
| 41 | void DateTest::ecbDates() { |
| 42 | BOOST_TEST_MESSAGE("Testing ECB dates..." ); |
| 43 | |
| 44 | std::set<Date> knownDates = ECB::knownDates(); |
| 45 | if (knownDates.empty()) |
| 46 | BOOST_FAIL("empty EBC date vector" ); |
| 47 | |
| 48 | Size n = ECB::nextDates(d: Date::minDate()).size(); |
| 49 | if (n != knownDates.size()) |
| 50 | BOOST_FAIL("nextDates(minDate) returns " << n << |
| 51 | " instead of " << knownDates.size() << " dates" ); |
| 52 | |
| 53 | std::set<Date>::const_iterator i; |
| 54 | Date previousEcbDate = Date::minDate(), |
| 55 | currentEcbDate, ecbDateMinusOne; |
| 56 | for (i=knownDates.begin(); i!=knownDates.end(); ++i) { |
| 57 | |
| 58 | currentEcbDate = *i; |
| 59 | if (!ECB::isECBdate(d: currentEcbDate)) |
| 60 | BOOST_FAIL(currentEcbDate << " fails isECBdate check" ); |
| 61 | |
| 62 | ecbDateMinusOne = currentEcbDate-1; |
| 63 | if (ECB::isECBdate(d: ecbDateMinusOne)) |
| 64 | BOOST_FAIL(ecbDateMinusOne << " fails isECBdate check" ); |
| 65 | |
| 66 | if (ECB::nextDate(d: ecbDateMinusOne)!=currentEcbDate) |
| 67 | BOOST_FAIL("next EBC date following " << ecbDateMinusOne << |
| 68 | " must be " << currentEcbDate); |
| 69 | |
| 70 | if (ECB::nextDate(d: previousEcbDate)!=currentEcbDate) |
| 71 | BOOST_FAIL("next EBC date following " << previousEcbDate << |
| 72 | " must be " << currentEcbDate); |
| 73 | |
| 74 | previousEcbDate = currentEcbDate; |
| 75 | } |
| 76 | |
| 77 | Date knownDate = *knownDates.begin(); |
| 78 | ECB::removeDate(d: knownDate); |
| 79 | if (ECB::isECBdate(d: knownDate)) |
| 80 | BOOST_FAIL("unable to remove an EBC date" ); |
| 81 | ECB::addDate(d: knownDate); |
| 82 | if (!ECB::isECBdate(d: knownDate)) |
| 83 | BOOST_FAIL("unable to add an EBC date" ); |
| 84 | } |
| 85 | |
| 86 | void DateTest::immDates() { |
| 87 | BOOST_TEST_MESSAGE("Testing IMM dates..." ); |
| 88 | |
| 89 | const std::string IMMcodes[] = { |
| 90 | "F0" , "G0" , "H0" , "J0" , "K0" , "M0" , "N0" , "Q0" , "U0" , "V0" , "X0" , "Z0" , |
| 91 | "F1" , "G1" , "H1" , "J1" , "K1" , "M1" , "N1" , "Q1" , "U1" , "V1" , "X1" , "Z1" , |
| 92 | "F2" , "G2" , "H2" , "J2" , "K2" , "M2" , "N2" , "Q2" , "U2" , "V2" , "X2" , "Z2" , |
| 93 | "F3" , "G3" , "H3" , "J3" , "K3" , "M3" , "N3" , "Q3" , "U3" , "V3" , "X3" , "Z3" , |
| 94 | "F4" , "G4" , "H4" , "J4" , "K4" , "M4" , "N4" , "Q4" , "U4" , "V4" , "X4" , "Z4" , |
| 95 | "F5" , "G5" , "H5" , "J5" , "K5" , "M5" , "N5" , "Q5" , "U5" , "V5" , "X5" , "Z5" , |
| 96 | "F6" , "G6" , "H6" , "J6" , "K6" , "M6" , "N6" , "Q6" , "U6" , "V6" , "X6" , "Z6" , |
| 97 | "F7" , "G7" , "H7" , "J7" , "K7" , "M7" , "N7" , "Q7" , "U7" , "V7" , "X7" , "Z7" , |
| 98 | "F8" , "G8" , "H8" , "J8" , "K8" , "M8" , "N8" , "Q8" , "U8" , "V8" , "X8" , "Z8" , |
| 99 | "F9" , "G9" , "H9" , "J9" , "K9" , "M9" , "N9" , "Q9" , "U9" , "V9" , "X9" , "Z9" |
| 100 | }; |
| 101 | |
| 102 | Date counter = { 1, January, 2000 }; |
| 103 | Date last = { 1, January, 2040 }; |
| 104 | Date imm; |
| 105 | |
| 106 | while (counter<=last) { |
| 107 | imm = IMM::nextDate(d: counter, mainCycle: false); |
| 108 | |
| 109 | // check that imm is greater than counter |
| 110 | if (imm<=counter) |
| 111 | BOOST_FAIL(imm.weekday() << " " << imm |
| 112 | << " is not greater than " |
| 113 | << counter.weekday() << " " << counter); |
| 114 | |
| 115 | // check that imm is an IMM date |
| 116 | if (!IMM::isIMMdate(d: imm, mainCycle: false)) |
| 117 | BOOST_FAIL(imm.weekday() << " " << imm |
| 118 | << " is not an IMM date (calculated from " |
| 119 | << counter.weekday() << " " << counter << ")" ); |
| 120 | |
| 121 | // check that imm is <= to the next IMM date in the main cycle |
| 122 | if (imm>IMM::nextDate(d: counter, mainCycle: true)) |
| 123 | BOOST_FAIL(imm.weekday() << " " << imm |
| 124 | << " is not less than or equal to the next future in the main cycle " |
| 125 | << IMM::nextDate(counter, true)); |
| 126 | |
| 127 | // check that for every date IMMdate is the inverse of IMMcode |
| 128 | if (IMM::date(immCode: IMM::code(immDate: imm), referenceDate: counter) != imm) |
| 129 | BOOST_FAIL(IMM::code(imm) |
| 130 | << " at calendar day " << counter |
| 131 | << " is not the IMM code matching " << imm); |
| 132 | |
| 133 | // check that for every date the 120 IMM codes refer to future dates |
| 134 | for (int i=0; i<40; ++i) { |
| 135 | if (IMM::date(immCode: IMMcodes[i], referenceDate: counter)<counter) |
| 136 | BOOST_FAIL(IMM::date(IMMcodes[i], counter) |
| 137 | << " is wrong for " << IMMcodes[i] |
| 138 | << " at reference date " << counter); |
| 139 | } |
| 140 | |
| 141 | counter = counter + 1; |
| 142 | } |
| 143 | } |
| 144 | |
| 145 | void DateTest::asxDates() { |
| 146 | BOOST_TEST_MESSAGE("Testing ASX dates..." ); |
| 147 | |
| 148 | const std::string ASXcodes[] = { |
| 149 | "F0" , "G0" , "H0" , "J0" , "K0" , "M0" , "N0" , "Q0" , "U0" , "V0" , "X0" , "Z0" , |
| 150 | "F1" , "G1" , "H1" , "J1" , "K1" , "M1" , "N1" , "Q1" , "U1" , "V1" , "X1" , "Z1" , |
| 151 | "F2" , "G2" , "H2" , "J2" , "K2" , "M2" , "N2" , "Q2" , "U2" , "V2" , "X2" , "Z2" , |
| 152 | "F3" , "G3" , "H3" , "J3" , "K3" , "M3" , "N3" , "Q3" , "U3" , "V3" , "X3" , "Z3" , |
| 153 | "F4" , "G4" , "H4" , "J4" , "K4" , "M4" , "N4" , "Q4" , "U4" , "V4" , "X4" , "Z4" , |
| 154 | "F5" , "G5" , "H5" , "J5" , "K5" , "M5" , "N5" , "Q5" , "U5" , "V5" , "X5" , "Z5" , |
| 155 | "F6" , "G6" , "H6" , "J6" , "K6" , "M6" , "N6" , "Q6" , "U6" , "V6" , "X6" , "Z6" , |
| 156 | "F7" , "G7" , "H7" , "J7" , "K7" , "M7" , "N7" , "Q7" , "U7" , "V7" , "X7" , "Z7" , |
| 157 | "F8" , "G8" , "H8" , "J8" , "K8" , "M8" , "N8" , "Q8" , "U8" , "V8" , "X8" , "Z8" , |
| 158 | "F9" , "G9" , "H9" , "J9" , "K9" , "M9" , "N9" , "Q9" , "U9" , "V9" , "X9" , "Z9" |
| 159 | }; |
| 160 | |
| 161 | Date counter = { 1, January, 2000 }; |
| 162 | Date last = { 1, January, 2040 }; |
| 163 | Date asx; |
| 164 | |
| 165 | while (counter <= last) { |
| 166 | asx = ASX::nextDate(d: counter, mainCycle: false); |
| 167 | |
| 168 | // check that asx is greater than counter |
| 169 | if (asx <= counter) |
| 170 | BOOST_FAIL(asx.weekday() << " " << asx |
| 171 | << " is not greater than " |
| 172 | << counter.weekday() << " " << counter); |
| 173 | |
| 174 | // check that asx is an ASX date |
| 175 | if (!ASX::isASXdate(d: asx, mainCycle: false)) |
| 176 | BOOST_FAIL(asx.weekday() << " " << asx |
| 177 | << " is not an ASX date (calculated from " |
| 178 | << counter.weekday() << " " << counter << ")" ); |
| 179 | |
| 180 | // check that asx is <= to the next ASX date in the main cycle |
| 181 | if (asx>ASX::nextDate(d: counter, mainCycle: true)) |
| 182 | BOOST_FAIL(asx.weekday() << " " << asx |
| 183 | << " is not less than or equal to the next future in the main cycle " |
| 184 | << ASX::nextDate(counter, true)); |
| 185 | |
| 186 | // check that for every date ASXdate is the inverse of ASXcode |
| 187 | if (ASX::date(asxCode: ASX::code(asxDate: asx), referenceDate: counter) != asx) |
| 188 | BOOST_FAIL(ASX::code(asx) |
| 189 | << " at calendar day " << counter |
| 190 | << " is not the ASX code matching " << asx); |
| 191 | |
| 192 | // check that for every date the 120 ASX codes refer to future dates |
| 193 | for (const auto& ASXcode : ASXcodes) { |
| 194 | if (ASX::date(asxCode: ASXcode, referenceDate: counter) < counter) |
| 195 | BOOST_FAIL(ASX::date(ASXcode, counter) << " is wrong for " << ASXcode |
| 196 | << " at reference date " << counter); |
| 197 | } |
| 198 | |
| 199 | counter = counter + 1; |
| 200 | } |
| 201 | } |
| 202 | |
| 203 | void DateTest::testConsistency() { |
| 204 | |
| 205 | BOOST_TEST_MESSAGE("Testing dates..." ); |
| 206 | |
| 207 | Date::serial_type minDate = Date::minDate().serialNumber()+1, |
| 208 | maxDate = Date::maxDate().serialNumber(); |
| 209 | |
| 210 | Date::serial_type dyold = Date(minDate-1).dayOfYear(), |
| 211 | dold = Date(minDate-1).dayOfMonth(), |
| 212 | mold = Date(minDate-1).month(), |
| 213 | yold = Date(minDate-1).year(), |
| 214 | wdold = Date(minDate-1).weekday(); |
| 215 | |
| 216 | for (Date::serial_type i=minDate; i<=maxDate; i++) { |
| 217 | Date t(i); |
| 218 | Date::serial_type serial = t.serialNumber(); |
| 219 | |
| 220 | // check serial number consistency |
| 221 | if (serial != i) |
| 222 | BOOST_FAIL("inconsistent serial number:\n" |
| 223 | << " original: " << i << "\n" |
| 224 | << " date: " << t << "\n" |
| 225 | << " serial number: " << serial); |
| 226 | |
| 227 | Integer dy = t.dayOfYear(), |
| 228 | d = t.dayOfMonth(), |
| 229 | m = t.month(), |
| 230 | y = t.year(), |
| 231 | wd = t.weekday(); |
| 232 | |
| 233 | // check if skipping any date |
| 234 | if (!((dy == dyold+1) || |
| 235 | (dy == 1 && dyold == 365 && !Date::isLeap(y: yold)) || |
| 236 | (dy == 1 && dyold == 366 && Date::isLeap(y: yold)))) |
| 237 | BOOST_FAIL("wrong day of year increment: \n" |
| 238 | << " date: " << t << "\n" |
| 239 | << " day of year: " << dy << "\n" |
| 240 | << " previous: " << dyold); |
| 241 | dyold = dy; |
| 242 | |
| 243 | if (!((d == dold+1 && m == mold && y == yold) || |
| 244 | (d == 1 && m == mold+1 && y == yold) || |
| 245 | (d == 1 && m == 1 && y == yold+1))) |
| 246 | BOOST_FAIL("wrong day,month,year increment: \n" |
| 247 | << " date: " << t << "\n" |
| 248 | << " day,month,year: " |
| 249 | << d << "," << Integer(m) << "," << y << "\n" |
| 250 | << " previous: " |
| 251 | << dold<< "," << Integer(mold) << "," << yold); |
| 252 | dold = d; mold = m; yold = y; |
| 253 | |
| 254 | // check month definition |
| 255 | if (m < 1 || m > 12) |
| 256 | BOOST_FAIL("invalid month: \n" |
| 257 | << " date: " << t << "\n" |
| 258 | << " month: " << Integer(m)); |
| 259 | |
| 260 | // check day definition |
| 261 | if (d < 1) |
| 262 | BOOST_FAIL("invalid day of month: \n" |
| 263 | << " date: " << t << "\n" |
| 264 | << " day: " << d); |
| 265 | if (!((m == 1 && d <= 31) || |
| 266 | (m == 2 && d <= 28) || |
| 267 | (m == 2 && d == 29 && Date::isLeap(y)) || |
| 268 | (m == 3 && d <= 31) || |
| 269 | (m == 4 && d <= 30) || |
| 270 | (m == 5 && d <= 31) || |
| 271 | (m == 6 && d <= 30) || |
| 272 | (m == 7 && d <= 31) || |
| 273 | (m == 8 && d <= 31) || |
| 274 | (m == 9 && d <= 30) || |
| 275 | (m == 10 && d <= 31) || |
| 276 | (m == 11 && d <= 30) || |
| 277 | (m == 12 && d <= 31))) |
| 278 | BOOST_FAIL("invalid day of month: \n" |
| 279 | << " date: " << t << "\n" |
| 280 | << " day: " << d); |
| 281 | |
| 282 | // check weekday definition |
| 283 | if (!((Integer(wd) == Integer(wdold+1)) || |
| 284 | (Integer(wd) == 1 && Integer(wdold) == 7))) |
| 285 | BOOST_FAIL("invalid weekday: \n" |
| 286 | << " date: " << t << "\n" |
| 287 | << " weekday: " << Integer(wd) << "\n" |
| 288 | << " previous: " << Integer(wdold)); |
| 289 | wdold = wd; |
| 290 | |
| 291 | // create the same date with a different constructor |
| 292 | Date s(d,Month(m),y); |
| 293 | // check serial number consistency |
| 294 | serial = s.serialNumber(); |
| 295 | if (serial != i) |
| 296 | BOOST_FAIL("inconsistent serial number:\n" |
| 297 | << " date: " << t << "\n" |
| 298 | << " serial number: " << i << "\n" |
| 299 | << " cloned date: " << s << "\n" |
| 300 | << " serial number: " << serial); |
| 301 | } |
| 302 | |
| 303 | } |
| 304 | |
| 305 | void DateTest::isoDates() { |
| 306 | BOOST_TEST_MESSAGE("Testing ISO dates..." ); |
| 307 | std::string input_date("2006-01-15" ); |
| 308 | Date d = DateParser::parseISO(str: input_date); |
| 309 | if (d.dayOfMonth() != 15 || |
| 310 | d.month() != January || |
| 311 | d.year() != 2006) { |
| 312 | BOOST_FAIL("Iso date failed\n" |
| 313 | << " input date: " << input_date << "\n" |
| 314 | << " day of month: " << d.dayOfMonth() << "\n" |
| 315 | << " month: " << d.month() << "\n" |
| 316 | << " year: " << d.year()); |
| 317 | } |
| 318 | } |
| 319 | |
| 320 | void DateTest::parseDates() { |
| 321 | BOOST_TEST_MESSAGE("Testing parsing of dates..." ); |
| 322 | |
| 323 | std::string input_date("2006-01-15" ); |
| 324 | Date d = DateParser::parseFormatted(str: input_date, fmt: "%Y-%m-%d" ); |
| 325 | if (d != Date(15, January, 2006)) { |
| 326 | BOOST_FAIL("date parsing failed\n" |
| 327 | << " input date: " << input_date << "\n" |
| 328 | << " parsed date: " << d); |
| 329 | } |
| 330 | |
| 331 | input_date = "12/02/2012" ; |
| 332 | d = DateParser::parseFormatted(str: input_date, fmt: "%m/%d/%Y" ); |
| 333 | if (d != Date(2, December, 2012)) { |
| 334 | BOOST_FAIL("date parsing failed\n" |
| 335 | << " input date: " << input_date << "\n" |
| 336 | << " parsed date: " << d); |
| 337 | } |
| 338 | d = DateParser::parseFormatted(str: input_date, fmt: "%d/%m/%Y" ); |
| 339 | if (d != Date(12, February, 2012)) { |
| 340 | BOOST_FAIL("date parsing failed\n" |
| 341 | << " input date: " << input_date << "\n" |
| 342 | << " parsed date: " << d); |
| 343 | } |
| 344 | |
| 345 | input_date = "20011002" ; |
| 346 | d = DateParser::parseFormatted(str: input_date, fmt: "%Y%m%d" ); |
| 347 | if (d != Date(2, October, 2001)) { |
| 348 | BOOST_FAIL("date parsing failed\n" |
| 349 | << " input date: " << input_date << "\n" |
| 350 | << " parsed date: " << d); |
| 351 | } |
| 352 | } |
| 353 | |
| 354 | void DateTest::intraday() { |
| 355 | #ifdef QL_HIGH_RESOLUTION_DATE |
| 356 | |
| 357 | BOOST_TEST_MESSAGE("Testing intraday information of dates..." ); |
| 358 | |
| 359 | const Date d1 = Date(12, February, 2015, 10, 45, 12, 1234, 76253); |
| 360 | |
| 361 | BOOST_CHECK_MESSAGE(d1.year() == 2015, "failed to reproduce year" ); |
| 362 | BOOST_CHECK_MESSAGE(d1.month() == February, "failed to reproduce month" ); |
| 363 | BOOST_CHECK_MESSAGE(d1.dayOfMonth() == 12, "failed to reproduce day" ); |
| 364 | BOOST_CHECK_MESSAGE(d1.hours() == 10, "failed to reproduce hour of day" ); |
| 365 | BOOST_CHECK_MESSAGE(d1.minutes() == 45, |
| 366 | "failed to reproduce minute of hour" ); |
| 367 | BOOST_CHECK_MESSAGE(d1.seconds() == 13, |
| 368 | "failed to reproduce second of minute" ); |
| 369 | |
| 370 | if (Date::ticksPerSecond() == 1000) |
| 371 | BOOST_CHECK_MESSAGE(d1.fractionOfSecond() == 0.234, |
| 372 | "failed to reproduce fraction of second" ); |
| 373 | else if (Date::ticksPerSecond() >= 1000000) |
| 374 | BOOST_CHECK_MESSAGE(d1.fractionOfSecond() == (234000 + 76253)/1000000.0, |
| 375 | "failed to reproduce fraction of second" ); |
| 376 | |
| 377 | if (Date::ticksPerSecond() >= 1000) |
| 378 | BOOST_CHECK_MESSAGE(d1.milliseconds() == 234 + 76, |
| 379 | "failed to reproduce number of milliseconds" ); |
| 380 | |
| 381 | if (Date::ticksPerSecond() >= 1000000) |
| 382 | BOOST_CHECK_MESSAGE(d1.microseconds() == 253, |
| 383 | "failed to reproduce number of microseconds" ); |
| 384 | |
| 385 | const Date d2 = Date(28, February, 2015, 50, 165, 476, 1234, 253); |
| 386 | BOOST_CHECK_MESSAGE(d2.year() == 2015, "failed to reproduce year" ); |
| 387 | BOOST_CHECK_MESSAGE(d2.month() == March, "failed to reproduce month" ); |
| 388 | BOOST_CHECK_MESSAGE(d2.dayOfMonth() == 2, "failed to reproduce day" ); |
| 389 | BOOST_CHECK_MESSAGE(d2.hours() == 4, "failed to reproduce hour of day" ); |
| 390 | BOOST_CHECK_MESSAGE(d2.minutes() == 52, |
| 391 | "failed to reproduce minute of hour" ); |
| 392 | BOOST_CHECK_MESSAGE(d2.seconds() == 57, |
| 393 | "failed to reproduce second of minute" ); |
| 394 | |
| 395 | if (Date::ticksPerSecond() >= 1000) |
| 396 | BOOST_CHECK_MESSAGE(d2.milliseconds() == 234, |
| 397 | "failed to reproduce number of milliseconds" ); |
| 398 | if (Date::ticksPerSecond() >= 1000000) |
| 399 | BOOST_CHECK_MESSAGE(d2.microseconds() == 253, |
| 400 | "failed to reproduce number of microseconds" ); |
| 401 | |
| 402 | std::ostringstream s; |
| 403 | s << io::iso_datetime(Date(7, February, 2015, 1, 4, 2, 3, 4)); |
| 404 | |
| 405 | BOOST_CHECK_MESSAGE(s.str() == std::string("2015-02-07T01:04:02,003004" ), |
| 406 | "datetime to string failed to reproduce expected result" ); |
| 407 | |
| 408 | const Date d3 = Date(10, April, 2023, 11, 43, 13, 234, 253); |
| 409 | |
| 410 | BOOST_CHECK_MESSAGE(d3 + Period(23, Hours) == |
| 411 | Date(11, April, 2023, 10, 43, 13, 234, 253), "failed to add hours" ); |
| 412 | |
| 413 | BOOST_CHECK_MESSAGE(d3 + Period(2, Minutes) == |
| 414 | Date(10, April, 2023, 11, 45, 13, 234, 253), "failed to add minutes" ); |
| 415 | |
| 416 | BOOST_CHECK_MESSAGE(d3 + Period(-2, Seconds) == |
| 417 | Date(10, April, 2023, 11, 43, 11, 234, 253), "failed to add seconds" ); |
| 418 | |
| 419 | BOOST_CHECK_MESSAGE(d3 + Period(-20, Milliseconds) == |
| 420 | Date(10, April, 2023, 11, 43, 13, 214, 253), "failed to add milliseconds" ); |
| 421 | |
| 422 | BOOST_CHECK_MESSAGE(d3 + Period(20, Microseconds) == |
| 423 | Date(10, April, 2023, 11, 43, 13, 234, 273), "failed to add microseconds" ); |
| 424 | |
| 425 | #endif |
| 426 | } |
| 427 | |
| 428 | void DateTest::canHash() { |
| 429 | BOOST_TEST_MESSAGE("Testing hashing of dates..." ); |
| 430 | |
| 431 | Date start_date = Date(1, Jan, 2020); |
| 432 | int nb_tests = 500; |
| 433 | |
| 434 | std::hash<Date> hasher; |
| 435 | |
| 436 | // Check hash values |
| 437 | for (int i = 0; i < nb_tests; ++i) { |
| 438 | for (int j = 0; j < nb_tests; ++j) { |
| 439 | Date lhs = start_date + i; |
| 440 | Date rhs = start_date + j; |
| 441 | |
| 442 | if (lhs == rhs && hasher(lhs) != hasher(rhs)) { |
| 443 | BOOST_FAIL("Equal dates are expected to have same hash value\n" |
| 444 | << "rhs = " << lhs << '\n' |
| 445 | << "lhs = " << rhs << '\n' |
| 446 | << "hash(lhs) = " << hasher(lhs) << '\n' |
| 447 | << "hash(rhs) = " << hasher(rhs) << '\n'); |
| 448 | } |
| 449 | |
| 450 | if (lhs != rhs && hasher(lhs) == hasher(rhs)) { |
| 451 | BOOST_FAIL("Different dates are expected to have different hash value\n" |
| 452 | << "rhs = " << lhs << '\n' |
| 453 | << "lhs = " << rhs << '\n' |
| 454 | << "hash(lhs) = " << hasher(lhs) << '\n' |
| 455 | << "hash(rhs) = " << hasher(rhs) << '\n'); |
| 456 | } |
| 457 | } |
| 458 | } |
| 459 | |
| 460 | // Check if Date can be used as unordered_set key |
| 461 | std::unordered_set<Date> set; |
| 462 | set.insert(x: start_date); |
| 463 | |
| 464 | if (set.count(x: start_date) == 0) { |
| 465 | BOOST_FAIL("Expected to find date " << start_date << " in unordered_set\n" ); |
| 466 | } |
| 467 | } |
| 468 | |
| 469 | |
| 470 | test_suite* DateTest::suite(SpeedLevel speed) { |
| 471 | auto* suite = BOOST_TEST_SUITE("Date tests" ); |
| 472 | |
| 473 | suite->add(QUANTLIB_TEST_CASE(&DateTest::testConsistency)); |
| 474 | suite->add(QUANTLIB_TEST_CASE(&DateTest::ecbDates)); |
| 475 | suite->add(QUANTLIB_TEST_CASE(&DateTest::immDates)); |
| 476 | suite->add(QUANTLIB_TEST_CASE(&DateTest::asxDates)); |
| 477 | suite->add(QUANTLIB_TEST_CASE(&DateTest::isoDates)); |
| 478 | #ifndef QL_PATCH_SOLARIS |
| 479 | suite->add(QUANTLIB_TEST_CASE(&DateTest::parseDates)); |
| 480 | #endif |
| 481 | #ifdef QL_HIGH_RESOLUTION_DATE |
| 482 | suite->add(QUANTLIB_TEST_CASE(&DateTest::intraday)); |
| 483 | #endif |
| 484 | suite->add(QUANTLIB_TEST_CASE(&DateTest::canHash)); |
| 485 | |
| 486 | return suite; |
| 487 | } |
| 488 | |