Line data Source code
1 : /* 2 : Zipios -- a small C++ library that provides easy access to .zip files. 3 : 4 : Copyright (c) 2019-2022 Made to Order Software Corp. All Rights Reserved 5 : 6 : This library is free software; you can redistribute it and/or 7 : modify it under the terms of the GNU Lesser General Public 8 : License as published by the Free Software Foundation; either 9 : version 2.1 of the License, or (at your option) any later version. 10 : 11 : This library is distributed in the hope that it will be useful, 12 : but WITHOUT ANY WARRANTY; without even the implied warranty of 13 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 : Lesser General Public License for more details. 15 : 16 : You should have received a copy of the GNU Lesser General Public 17 : License along with this library; if not, write to the Free Software 18 : Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 : */ 20 : 21 : /** \file 22 : * 23 : * Zipios unit tests verify the dosdatetime.cpp/hpp implementation. 24 : * 25 : * Keep in mind that the date is saved as a local time date in a zip file. 26 : * It's crap. But that comes from the old days of DOS which chose that format 27 : * at the time. Sharing between people in the whole world was not viewed in 28 : * a way similar to today's world... 29 : * 30 : * \warning 31 : * This test will fail if run around a standard time to a saving time 32 : * or a saving time to a standard time changing period (i.e. during the 33 : * 1h or so when time changes in spring and autumn.) 34 : */ 35 : 36 : #include "catch_main.hpp" 37 : 38 : #include <zipios/dosdatetime.hpp> 39 : #include <zipios/zipiosexceptions.hpp> 40 : 41 : #include <fstream> 42 : 43 : #include <sys/stat.h> 44 : #include <unistd.h> 45 : 46 : #include <cstring> 47 : #include <iostream> 48 : 49 : 50 : 51 : 52 : // min = Jan 1, 1980 at 00:00:00 53 : std::time_t g_minimum_unix = -1; 54 : 55 : // max = Dec 31, 2107 at 23:59:59 56 : std::time_t g_maximum_unix = -1; 57 : 58 : 59 5 : void init_min_max() 60 : { 61 5 : if(g_minimum_unix == -1) 62 : { 63 1 : struct tm t = {}; 64 : 65 1 : t.tm_sec = 0; 66 1 : t.tm_min = 0; 67 1 : t.tm_hour = 0; 68 1 : t.tm_mday = 1; 69 1 : t.tm_mon = 0; 70 1 : t.tm_year = 1980 - 1900; 71 1 : t.tm_isdst = -1; 72 1 : g_minimum_unix = mktime(&t); 73 1 : CATCH_REQUIRE((g_minimum_unix & 1) == 0); 74 : } 75 : 76 5 : if(g_maximum_unix == -1) 77 : { 78 1 : struct tm t = {}; 79 : 80 1 : t.tm_sec = 58; 81 1 : t.tm_min = 59; 82 1 : t.tm_hour = 23; 83 1 : t.tm_mday = 31; 84 1 : t.tm_mon = 11; 85 1 : t.tm_year = 2107 - 1900; 86 1 : t.tm_isdst = -1; 87 1 : g_maximum_unix = mktime(&t); 88 1 : CATCH_REQUIRE((g_maximum_unix & 1) == 0); 89 : } 90 5 : } 91 : 92 : 93 : 94 2 : CATCH_TEST_CASE("DOS Date & Time Min/Max", "[dosdatetime]") 95 : { 96 2 : CATCH_START_SECTION("DOS time minimum") 97 : { 98 1 : zipios::DOSDateTime t; 99 1 : t.setYear(1980); 100 1 : t.setMonth(1); 101 1 : t.setMDay(1); 102 1 : t.setHour(0); 103 1 : t.setMinute(0); 104 1 : t.setSecond(0); 105 1 : CATCH_REQUIRE(zipios::DOSDateTime::g_min_dosdatetime == t.getDOSDateTime()); 106 : } 107 2 : CATCH_END_SECTION() 108 : 109 2 : CATCH_START_SECTION("DOS time maximum") 110 : { 111 1 : zipios::DOSDateTime t; 112 1 : t.setYear(2107); 113 1 : t.setMonth(12); 114 1 : t.setMDay(31); 115 1 : t.setHour(23); 116 1 : t.setMinute(59); 117 1 : t.setSecond(59); 118 1 : CATCH_REQUIRE(zipios::DOSDateTime::g_max_dosdatetime == t.getDOSDateTime()); 119 : } 120 2 : CATCH_END_SECTION() 121 2 : } 122 : 123 : 124 7 : CATCH_TEST_CASE("Invalid DOS Date & Time", "[dosdatetime]") 125 : { 126 7 : CATCH_START_SECTION("daysInMonth() when month is invalid") 127 : { 128 : // by default the date is 0 which means the month is 0 and 129 : // trying to call daysInMonth() fails with -1 130 : // 131 1 : zipios::DOSDateTime d; 132 1 : CATCH_REQUIRE(d.daysInMonth() == -1); 133 : } 134 7 : CATCH_END_SECTION() 135 : 136 7 : CATCH_START_SECTION("get/set seconds") 137 : { 138 61 : for(int i = 0; i < 60; ++i) 139 : { 140 60 : zipios::DOSDateTime t; 141 60 : t.setSecond(i); 142 60 : CATCH_REQUIRE(t.getSecond() == (i & -2)); 143 : } 144 21 : for(int i = -20; i < 0; ++i) 145 : { 146 20 : zipios::DOSDateTime t; 147 20 : int const r(rand() % 60); 148 20 : t.setSecond(r); 149 20 : CATCH_REQUIRE(t.getSecond() == (r & -2)); 150 20 : CATCH_REQUIRE_THROWS_AS(t.setSecond(i), zipios::InvalidException); 151 20 : CATCH_REQUIRE(t.getSecond() == (r & -2)); 152 : } 153 22 : for(int i = 60; i <= 80; ++i) 154 : { 155 21 : zipios::DOSDateTime t; 156 21 : int const r(rand() % 60); 157 21 : t.setSecond(r); 158 21 : CATCH_REQUIRE(t.getSecond() == (r & -2)); 159 21 : CATCH_REQUIRE_THROWS_AS(t.setSecond(i), zipios::InvalidException); 160 21 : CATCH_REQUIRE(t.getSecond() == (r & -2)); 161 : } 162 : } 163 7 : CATCH_END_SECTION() 164 : 165 7 : CATCH_START_SECTION("get/set minutes") 166 : { 167 61 : for(int i = 0; i < 60; ++i) 168 : { 169 60 : zipios::DOSDateTime t; 170 60 : t.setMinute(i); 171 60 : CATCH_REQUIRE(t.getMinute() == i); 172 : } 173 21 : for(int i = -20; i < 0; ++i) 174 : { 175 20 : zipios::DOSDateTime t; 176 20 : int const r(rand() % 60); 177 20 : t.setMinute(r); 178 20 : CATCH_REQUIRE(t.getMinute() == r); 179 20 : CATCH_REQUIRE_THROWS_AS(t.setMinute(i), zipios::InvalidException); 180 20 : CATCH_REQUIRE(t.getMinute() == r); 181 : } 182 22 : for(int i = 60; i <= 80; ++i) 183 : { 184 21 : zipios::DOSDateTime t; 185 21 : int const r(rand() % 60); 186 21 : t.setMinute(r); 187 21 : CATCH_REQUIRE(t.getMinute() == r); 188 21 : CATCH_REQUIRE_THROWS_AS(t.setMinute(i), zipios::InvalidException); 189 21 : CATCH_REQUIRE(t.getMinute() == r); 190 : } 191 : } 192 7 : CATCH_END_SECTION() 193 : 194 7 : CATCH_START_SECTION("get/set hours") 195 : { 196 25 : for(int i = 0; i < 24; ++i) 197 : { 198 24 : zipios::DOSDateTime t; 199 24 : t.setHour(i); 200 24 : CATCH_REQUIRE(t.getHour() == i); 201 : } 202 21 : for(int i = -20; i < 0; ++i) 203 : { 204 20 : zipios::DOSDateTime t; 205 20 : int const r(rand() % 24); 206 20 : t.setHour(r); 207 20 : CATCH_REQUIRE(t.getHour() == r); 208 20 : CATCH_REQUIRE_THROWS_AS(t.setHour(i), zipios::InvalidException); 209 20 : CATCH_REQUIRE(t.getHour() == r); 210 : } 211 22 : for(int i = 24; i <= 44; ++i) 212 : { 213 21 : zipios::DOSDateTime t; 214 21 : int const r(rand() % 24); 215 21 : t.setHour(r); 216 21 : CATCH_REQUIRE(t.getHour() == r); 217 21 : CATCH_REQUIRE_THROWS_AS(t.setHour(i), zipios::InvalidException); 218 21 : CATCH_REQUIRE(t.getHour() == r); 219 : } 220 : } 221 7 : CATCH_END_SECTION() 222 : 223 : // day is limited between 1 and 31 on a setMDay() 224 : // use the isValid() to know whether it is valid for the current month 225 : // and year 226 : // 227 7 : CATCH_START_SECTION("get/set day of the month") 228 : { 229 32 : for(int i = 1; i < 32; ++i) 230 : { 231 31 : zipios::DOSDateTime t; 232 31 : t.setMDay(i); 233 31 : CATCH_REQUIRE(t.getMDay() == i); 234 : } 235 22 : for(int i = -20; i <= 0; ++i) 236 : { 237 21 : zipios::DOSDateTime t; 238 21 : int const r(rand() % 31 + 1); 239 21 : t.setMDay(r); 240 21 : CATCH_REQUIRE(t.getMDay() == r); 241 21 : CATCH_REQUIRE_THROWS_AS(t.setMDay(i), zipios::InvalidException); 242 21 : CATCH_REQUIRE(t.getMDay() == r); 243 : } 244 22 : for(int i = 32; i <= 52; ++i) 245 : { 246 21 : zipios::DOSDateTime t; 247 21 : int const r(rand() % 31 + 1); 248 21 : t.setMDay(r); 249 21 : CATCH_REQUIRE(t.getMDay() == r); 250 21 : CATCH_REQUIRE_THROWS_AS(t.setMDay(i), zipios::InvalidException); 251 21 : CATCH_REQUIRE(t.getMDay() == r); 252 : } 253 : } 254 7 : CATCH_END_SECTION() 255 : 256 7 : CATCH_START_SECTION("get/set month") 257 : { 258 12 : for(int i = 1; i < 12; ++i) 259 : { 260 11 : zipios::DOSDateTime t; 261 11 : t.setMonth(i); 262 11 : CATCH_REQUIRE(t.getMonth() == i); 263 : } 264 22 : for(int i = -20; i <= 0; ++i) 265 : { 266 21 : zipios::DOSDateTime t; 267 21 : int const r(rand() % 12 + 1); 268 21 : t.setMonth(r); 269 21 : CATCH_REQUIRE(t.getMonth() == r); 270 21 : CATCH_REQUIRE_THROWS_AS(t.setMonth(i), zipios::InvalidException); 271 21 : CATCH_REQUIRE(t.getMonth() == r); 272 : } 273 22 : for(int i = 13; i <= 33; ++i) 274 : { 275 21 : zipios::DOSDateTime t; 276 21 : int const r(rand() % 12 + 1); 277 21 : t.setMonth(r); 278 21 : CATCH_REQUIRE(t.getMonth() == r); 279 21 : CATCH_REQUIRE_THROWS_AS(t.setMonth(i), zipios::InvalidException); 280 21 : CATCH_REQUIRE(t.getMonth() == r); 281 : } 282 : } 283 7 : CATCH_END_SECTION() 284 : 285 7 : CATCH_START_SECTION("get/set year") 286 : { 287 129 : for(int i = 1980; i <= 2107; ++i) 288 : { 289 128 : zipios::DOSDateTime t; 290 128 : t.setYear(i); 291 128 : CATCH_REQUIRE(t.getYear() == i); 292 : } 293 981 : for(int i = 1000; i < 1980; ++i) 294 : { 295 980 : zipios::DOSDateTime t; 296 980 : int const r(rand() % (2107 - 1980 + 1) + 1980); 297 980 : t.setYear(r); 298 980 : CATCH_REQUIRE(t.getYear() == r); 299 980 : CATCH_REQUIRE_THROWS_AS(t.setYear(i), zipios::InvalidException); 300 980 : CATCH_REQUIRE(t.getYear() == r); 301 : } 302 94 : for(int i = 2108; i <= 2200; ++i) 303 : { 304 93 : zipios::DOSDateTime t; 305 93 : int const r(rand() % (2107 - 1980 + 1) + 1980); 306 93 : t.setYear(r); 307 93 : CATCH_REQUIRE(t.getYear() == r); 308 93 : CATCH_REQUIRE_THROWS_AS(t.setYear(i), zipios::InvalidException); 309 93 : CATCH_REQUIRE(t.getYear() == r); 310 : } 311 : } 312 7 : CATCH_END_SECTION() 313 7 : } 314 : 315 : 316 : #if 0 317 : // this test is too long, which is why it is commented out by default 318 : // still, it is great if you want to verify all possible DOS Time & Date 319 : // 320 : CATCH_TEST_CASE("all_valid_dos_date_n_time", "[dosdatetime]") 321 : { 322 : if(sizeof(std::time_t) < sizeof(uint64_t)) 323 : { 324 : std::cerr << "warning: Unix to DOS time conversion is ignored on platform with a 32 bit time_t definition." << std::endl; 325 : return; 326 : } 327 : 328 : init_min_max(); 329 : 330 : CATCH_START_SECTION("all_valid_dos_date_n_time: all valid DOS Date Time values") 331 : { 332 : // make sure the maximum limit is checked properly 333 : // 334 : for(std::time_t t(g_minimum_unix); t <= g_maximum_unix; ++t) 335 : { 336 : std::time_t et((t + 1) & ~1); 337 : 338 : zipios::DOSDateTime td; 339 : CATCH_REQUIRE_FALSE(td.isValid()); 340 : td.setUnixTimestamp(t); 341 : CATCH_REQUIRE(td.isValid()); 342 : 343 : zipios::DOSDateTime::dosdatetime_t const d(td.getDOSDateTime()); 344 : 345 : zipios::DOSDateTime tu; 346 : CATCH_REQUIRE_FALSE(tu.isValid()); 347 : tu.setDOSDateTime(d); 348 : CATCH_REQUIRE(tu.isValid()); 349 : 350 : std::time_t const u(tu.getUnixTimestamp()); 351 : CATCH_REQUIRE(u == et); 352 : } 353 : } 354 : } 355 : #endif 356 : 357 : 358 2 : CATCH_TEST_CASE("small_dos_date_n_time", "[dosdatetime]") 359 : { 360 : if(sizeof(std::time_t) < sizeof(uint64_t)) 361 : { 362 : std::cerr << "warning: Unix to DOS time conversion is ignored on platform with a 32 bit time_t definition." << std::endl; 363 : return; 364 : } 365 : 366 2 : init_min_max(); 367 : 368 2 : CATCH_START_SECTION("small_dos_date_n_time: just under the minimum") 369 : { 370 : // make sure the minimum limit is checked properly 371 : // 372 : // the `g_minimum_unix - 1` is because the function uses the next 373 : // event date (i.e. odd seconds get a +1) 374 : // 375 20 : for(std::time_t t(g_minimum_unix - 20); t < g_minimum_unix - 1; ++t) 376 : { 377 19 : zipios::DOSDateTime td; 378 19 : CATCH_REQUIRE_FALSE(td.isValid()); 379 19 : CATCH_REQUIRE_THROWS_AS(td.setUnixTimestamp(t), zipios::InvalidException); 380 19 : CATCH_REQUIRE_FALSE(td.isValid()); 381 : } 382 : } 383 2 : CATCH_END_SECTION() 384 : 385 2 : CATCH_START_SECTION("small_dos_date_n_time: just around minimum, but valid") 386 : { 387 : // the "g_minimum_unix - 1" case is peculiar, the Unix date is not 388 : // usable as is because it is odd and we're going to use the next 389 : // second which means we're getting `minimum_unix` as the date, 390 : // which is valid! 391 : // 392 22 : for(std::time_t t(g_minimum_unix); t <= g_minimum_unix + 20; ++t) 393 : { 394 21 : std::time_t et((t + 1) & ~1); 395 : 396 21 : zipios::DOSDateTime td; 397 21 : CATCH_REQUIRE_FALSE(td.isValid()); 398 21 : td.setUnixTimestamp(t); 399 21 : CATCH_REQUIRE(td.isValid()); 400 : 401 21 : zipios::DOSDateTime::dosdatetime_t const d(td.getDOSDateTime()); 402 : 403 21 : zipios::DOSDateTime tu; 404 21 : CATCH_REQUIRE_FALSE(tu.isValid()); 405 21 : tu.setDOSDateTime(d); 406 21 : CATCH_REQUIRE(tu.isValid()); 407 : 408 21 : std::time_t const u(tu.getUnixTimestamp()); 409 21 : CATCH_REQUIRE(u == et); 410 : } 411 : } 412 2 : CATCH_END_SECTION() 413 : } 414 : 415 : 416 2 : CATCH_TEST_CASE("large_dos_date_n_time", "[dosdatetime]") 417 : { 418 : if(sizeof(std::time_t) < sizeof(uint64_t)) 419 : { 420 : std::cerr << "warning: Unix to DOS time conversion is ignored on platform with a 32 bit time_t definition." << std::endl; 421 : return; 422 : } 423 : 424 2 : init_min_max(); 425 : 426 2 : CATCH_START_SECTION("large_dos_date_n_time: just around maximum, but valid") 427 : { 428 : // make sure the maximum limit is checked properly 429 : // 430 22 : for(std::time_t t(g_maximum_unix - 20); t <= g_maximum_unix; ++t) 431 : { 432 21 : std::time_t et((t + 1) & ~1); 433 : 434 21 : zipios::DOSDateTime td; 435 21 : CATCH_REQUIRE_FALSE(td.isValid()); 436 21 : td.setUnixTimestamp(t); 437 21 : CATCH_REQUIRE(td.isValid()); 438 : 439 21 : zipios::DOSDateTime::dosdatetime_t const d(td.getDOSDateTime()); 440 : 441 21 : zipios::DOSDateTime tu; 442 21 : CATCH_REQUIRE_FALSE(tu.isValid()); 443 21 : tu.setDOSDateTime(d); 444 21 : CATCH_REQUIRE(tu.isValid()); 445 : 446 21 : std::time_t const u(tu.getUnixTimestamp()); 447 21 : CATCH_REQUIRE(u == et); 448 : } 449 : } 450 2 : CATCH_END_SECTION() 451 : 452 2 : CATCH_START_SECTION("large_dos_date_n_time: just a bit too large") 453 : { 454 : // make sure the maximum limit is checked properly 455 : // 456 21 : for(std::time_t t(g_maximum_unix + 1); t <= g_maximum_unix + 20; ++t) 457 : { 458 20 : zipios::DOSDateTime td; 459 20 : CATCH_REQUIRE(td.getDOSDateTime() == 0); 460 20 : CATCH_REQUIRE_FALSE(td.isValid()); 461 20 : CATCH_REQUIRE_THROWS_AS(td.setUnixTimestamp(t), zipios::InvalidException); 462 20 : CATCH_REQUIRE_FALSE(td.isValid()); 463 20 : CATCH_REQUIRE(td.getDOSDateTime() == 0); 464 20 : CATCH_REQUIRE(td.getUnixTimestamp() == 0); 465 : } 466 : } 467 2 : CATCH_END_SECTION() 468 : } 469 : 470 : 471 : #if INTPTR_MAX != INT32_MAX 472 : // at this time only check on 64 bit computers because the DOS date can 473 : // go out of range in a Unix date when we're on a 32 bit computer 474 1 : CATCH_TEST_CASE("random_dos_date_n_time", "[dosdatetime]") 475 : { 476 1 : init_min_max(); 477 : 478 1 : CATCH_START_SECTION("random_dos_date_n_time: more random tests") 479 : { 480 1 : std::time_t const max(sizeof(std::time_t) < sizeof(uint64_t) 481 : ? 0x7FFFFFFF // max. in 32 bits is less in Unix time_t than what the DOS Date Time supports 482 : : 0x104000000LL); // Dec 31, 2107 23:59:59 is 0x10391447F, so use something a little higher 483 : 484 266319 : for(std::time_t t(0); t <= max; t += rand() & 0x7FFF) 485 : { 486 266318 : if(t < g_minimum_unix 487 246954 : || t >= g_maximum_unix) 488 : { 489 19812 : zipios::DOSDateTime td; 490 19812 : CATCH_REQUIRE_THROWS_AS(td.setUnixTimestamp(t), zipios::InvalidException); 491 19812 : } 492 : else 493 : { 494 246506 : std::time_t et((t + 1) & ~1); 495 : 496 246506 : zipios::DOSDateTime td; 497 246506 : td.setUnixTimestamp(t); 498 : 499 246506 : zipios::DOSDateTime::dosdatetime_t const d(td.getDOSDateTime()); 500 : 501 246506 : zipios::DOSDateTime tu; 502 246506 : tu.setDOSDateTime(d); 503 : 504 246506 : std::time_t const u(tu.getUnixTimestamp()); 505 : 506 : // account for possible switch between local times (i.e. PST/PDT) 507 : // (I do not like it, but the Zip format saves dates using 508 : // local time and thus we need to use mktime() to be correct 509 : // but that means we can be off by +/- 1 hour when the time changes) 510 : // 511 : // note that part of the discrepancy is that mktime() can return 512 : // a different value depending what value was last returned because 513 : // tm_isdst = -1 means just that (i.e. there is an internal state) 514 : // 515 246506 : bool const valid(u == et || u == et + 3600 || u == et - 3600); 516 246506 : CATCH_REQUIRE(valid); 517 : } 518 : } 519 : } 520 1 : CATCH_END_SECTION() 521 1 : } 522 : #endif 523 : 524 : 525 : 526 : // Local Variables: 527 : // mode: cpp 528 : // indent-tabs-mode: nil 529 : // c-basic-offset: 4 530 : // tab-width: 4 531 : // End: 532 : 533 : // vim: ts=4 sw=4 et