1class HomeLocalCalendarUtils { 2 constructor(settings, options, prices = null) { 3 this.dailyPrice = {}; 4 this.string = settings.selection || {}; 5 this.timezone = options.timezone; 6 this.minStay = parseInt(options.min_stay, 10) || 1; 7 this.maxStay = parseInt(options.max_stay, 10) || 365; 8 this.maxNumDays = parseInt(options.max_num_days, 10) || 365; 9 this.minDate = this.getDateZero(); 10 this.maxDate = this.minDate.plus({ days: this.maxNumDays }); 11 this.maxIterations = this.maxNumDays + 60; // Safety limit for loops 12 this.preload = options.preload || false; 13 14 // Cache for various calculations 15 this._cache = { 16 isUnavailable: {}, 17 canCheckin: {}, 18 canCheckout: {}, 19 getAvailableCheckoutDays: {}, 20 selectableRanges: {}, 21 minstay: {}, 22 maxstay: {}, 23 trapped: {}, 24 }; 25 26 // Pre-calculation state tracking 27 this._calculationState = { 28 isInProgress: false, 29 visibleStartDate: null, 30 visibleEndDate: null, 31 nextDateToProcess: null, 32 priorityDatesProcessed: false, 33 }; 34 35 if (prices) { 36 this.setPriceData(prices); 37 } 38 } 39 40 // Prioritize calculations for visible dates (next 60-90 days) 41 _immediateCalculations() { 42 const today = this.getDateZero(); 43 const visibleRangeEnd = today.plus({ days: 90 }); // Calculate ~3 months immediately 44 45 this._calculationState.visibleStartDate = today; 46 this._calculationState.visibleEndDate = visibleRangeEnd; 47 this._calculationState.nextDateToProcess = today.plus({ days: 91 }); 48 49 // Process the immediately visible dates 50 let currentDate = today; 51 52 while (currentDate <= visibleRangeEnd) { 53 const dateStr = this.dateYmd(currentDate); 54 55 // Essential calculations for UI rendering 56 this._cache.isUnavailable[dateStr] = 57 this._calculateUnavailability(dateStr); 58 this._cache.minstay[dateStr] = this._calculateMinstay(dateStr); 59 60 // Pre-calculate minimal checkin data for initial visible dates 61 if (!this._cache.isUnavailable[dateStr] && this._canCheckin(dateStr)) { 62 this._cache.canCheckin[dateStr] = this._calculateCanCheckin(dateStr); 63 } 64 65 // Move to next day 66 currentDate = currentDate.plus({ days: 1 }); 67 } 68 69 this._calculationState.priorityDatesProcessed = true; 70 } 71 72 // Process remaining dates in small chunks to avoid UI blocking 73 _scheduledPreCalculations() { 74 // If calculation is already in progress, don't start another one 75 if (this._calculationState.isInProgress) { 76 return; 77 } 78 79 this._calculationState.isInProgress = true; 80 81 // Process in small chunks using requestAnimationFrame for better UI responsiveness 82 this._processNextChunk(); 83 } 84 85 _processNextChunk() { 86 const CHUNK_SIZE = 10; // Process 10 days at a time 87 const startTime = performance.now(); 88 const MAX_CHUNK_TIME = 16; // Try to stay under 16ms (60fps) per chunk 89 90 // Check if we've reached the end of our date range 91 if (this._calculationState.nextDateToProcess > this.maxDate) { 92 this._calculationState.isInProgress = false; 93 return; // We're done 94 } 95 96 let currentDate = this._calculationState.nextDateToProcess; 97 let daysProcessed = 0; 98 99 while ( 100 currentDate <= this.maxDate && 101 daysProcessed < CHUNK_SIZE && 102 performance.now() - startTime < MAX_CHUNK_TIME 103 ) { 104 const dateStr = this.dateYmd(currentDate); 105 106 // Skip if already processed 107 if (!this._cache.minstay[dateStr]) { 108 this._cache.isUnavailable[dateStr] = 109 this._calculateUnavailability(dateStr); 110 this._cache.minstay[dateStr] = this._calculateMinstay(dateStr); 111 } 112 113 // Only calculate checkin status for available dates 114 if ( 115 !this._cache.isUnavailable[dateStr] && 116 this._canCheckin(dateStr) && 117 !this._cache.canCheckin[dateStr] 118 ) { 119 this._cache.canCheckin[dateStr] = this._calculateCanCheckin(dateStr); 120 121 // For valid checkin dates, calculate checkout days and selectable range 122 if (this._cache.canCheckin[dateStr].status) { 123 // These are more expensive calculations, so only do them if needed 124 this._cache.getAvailableCheckoutDays[dateStr] = 125 this._calculateAvailableCheckoutDays(dateStr); 126 this._cache.selectableRanges[dateStr] = { 127 minDate: dateStr, 128 maxDate: 129 this._cache.getAvailableCheckoutDays[dateStr][ 130 this._cache.getAvailableCheckoutDays[dateStr].length - 1 131 ], 132 }; 133 } 134 } 135 136 // Move to next day 137 currentDate = currentDate.plus({ days: 1 }); 138 daysProcessed++; 139 } 140 141 // Update the next date to process 142 this._calculationState.nextDateToProcess = currentDate; 143 144 // Schedule the next chunk if we're not done 145 if (currentDate <= this.maxDate) { 146 requestAnimationFrame(() => this._processNextChunk()); 147 } else { 148 this._calculationState.isInProgress = false;
149 } 150 } 151 152 // Method to prioritize calculations for a specific date range (e.g., when user navigates to a month) 153 prioritizeCalculations(startDate, endDate) { 154 // Convert to DateTime if needed 155 const start = this.getDateZero(startDate); 156 const end = this.getDateZero(endDate); 157 158 // Queue up this range for immediate processing 159 setTimeout(() => { 160 let currentDate = start; 161 162 while (currentDate <= end) { 163 const dateStr = this.dateYmd(currentDate); 164 165 // Skip if already processed 166 if (!this._cache.minstay[dateStr]) { 167 this._cache.isUnavailable[dateStr] = 168 this._calculateUnavailability(dateStr); 169 this._cache.minstay[dateStr] = this._calculateMinstay(dateStr); 170 this._cache.maxstay[dateStr] = this._calculateMaxstay(dateStr); 171 } 172 173 // Process checkin information 174 if ( 175 !this._cache.isUnavailable[dateStr] && 176 this._canCheckin(dateStr) && 177 !this._cache.canCheckin[dateStr] 178 ) { 179 this._cache.canCheckin[dateStr] = this._calculateCanCheckin(dateStr); 180 181 if (this._cache.canCheckin[dateStr].status) { 182 this._cache.getAvailableCheckoutDays[dateStr] = 183 this._calculateAvailableCheckoutDays(dateStr); 184 this._cache.selectableRanges[dateStr] = { 185 minDate: dateStr, 186 maxDate: 187 this._cache.getAvailableCheckoutDays[dateStr][ 188 this._cache.getAvailableCheckoutDays[dateStr].length - 1 189 ], 190 }; 191 } 192 } 193 194 currentDate = currentDate.plus({ days: 1 }); 195 } 196 }, 0); 197 } 198 199 setPriceData(prices) { 200 this.dailyPrice = prices; 201 202 // Clear caches 203 this._cache = { 204 isUnavailable: {}, 205 canCheckin: {}, 206 canCheckout: {}, 207 getAvailableCheckoutDays: {}, 208 selectableRanges: {}, 209 minstay: {}, 210 maxstay: {}, 211 trapped: {}, 212 }; 213 214 // Instead of calculating everything at once, do essential calculations immediately 215 // and schedule the rest for later 216 if (this.hasPriceData() && this.preload) { 217 this._immediateCalculations(); 218 219 // Schedule remaining calculations to occur in small chunks 220 setTimeout(() => this._scheduledPreCalculations(), 0); 221 } 222 } 223 224 hasPriceData() { 225 return Object.keys(this.dailyPrice).length > 0; 226 } 227 228 getFirstWeekday(year, month) { 229 const { DateTime } = luxon; 230 231 // Get today's date in the specified timezone 232 const date = DateTime.fromObject({ year, month }, { zone: this.timezone }); 233 234 // Return the weekday as a number (1-7, where 1 is Monday and 7 is Sunday) 235 return date.weekday; 236 } 237 238 getMonthDayCount(year, month) { 239 const { DateTime } = luxon; 240 241 // Create a DateTime object for the first day of the month 242 const firstDay = DateTime.fromObject( 243 { year, month }, 244 { zone: this.timezone }, 245 ); 246 247 return firstDay.daysInMonth; 248 } 249 250 dateYmd(date) { 251 const { DateTime } = luxon; 252 253 if (date instanceof DateTime) { 254 return date.toFormat("yyyy-MM-dd"); 255 } 256 257 console.warn("dateYmd: received non-Luxon date, fallback used"); 258 259 return ( 260 date.getFullYear() + 261 "-" + 262 ("0" + (date.getMonth() + 1)).slice(-2) + 263 "-" + 264 ("0" + date.getDate()).slice(-2) 265 ); 266 } 267 268 getDateZero(dateStr = null) { 269 const { DateTime } = luxon; 270 271 const dt = dateStr 272 ? DateTime.fromISO(dateStr, { zone: this.timezone }) 273 : DateTime.now().setZone(this.timezone); 274 275 return dt.startOf("day"); 276 } 277 278 prevDay(dateStr) { 279 return luxon.DateTime.fromISO(dateStr, { 280 zone: this.timezone, 281 }) 282 .plus({ days: -1 }) 283 .toFormat("yyyy-MM-dd"); 284 } 285 286 nextDay(dateStr) { 287 return luxon.DateTime.fromISO(dateStr, { 288 zone: this.timezone, 289 }) 290 .plus({ days: 1 }) 291 .toFormat("yyyy-MM-dd"); 292 } 293 294 getDateToday() { 295 const { DateTime } = luxon; 296 const today = DateTime.now().setZone(this.timezone); 297 return today.startOf("day"); 298 } 299 300 getDateNow() { 301 const { DateTime } = luxon; 302 return DateTime.now().setZone(this.timezone); 303 } 304 305 // Optimized dateOptions with caching 306 dateOptions(date) { 307 // Ensure this.dailyPrice is defined and is an object. 308 if (typeof this.dailyPrice !== "object" || this.dailyPrice === null) { 309 return null; 310 } 311 312 if (date instanceof Date) { 313 date = this.dateYmd(date); 314 } 315 316 const { DateTime } = luxon; 317 if (date instanceof DateTime) {
318 date = date.toFormat("yyyy-MM-dd"); 319 } 320 321 if (date in this.dailyPrice) { 322 return this.dailyPrice[date]; 323 } 324 325 return null; 326 } 327 328 _calculateDateOptions(date) { 329 // Ensure this.dailyPrice is defined and is an object. 330 if (typeof this.dailyPrice !== "object" || this.dailyPrice === null) { 331 return null; 332 } 333 334 if (date in this.dailyPrice) { 335 return this.dailyPrice[date]; 336 } 337 338 return null; 339 } 340 341 getMaxstay(day) { 342 // Check cache first 343 if (this._cache.maxstay[day] !== undefined) { 344 return this._cache.maxstay[day]; 345 } 346 347 // Calculate and cache the result 348 const result = this._calculateMaxstay(day); 349 this._cache.maxstay[day] = result; 350 return result; 351 } 352 353 _calculateMaxstay(day) { 354 const opts = this.dateOptions(day); 355 if (opts) { 356 // Add a safety check for max_stay to prevent unreasonably large values 357 const maxStay = parseInt(opts.max_stay, 10); 358 return isNaN(maxStay) || maxStay <= 0 ? this.maxStay : maxStay; // Cap at 365 for safety 359 } 360 361 if (this.maxStay) { 362 return this.maxStay; // Cap at 365 for safety 363 } 364 365 return 365; // Default max stay 366 } 367 368 // Optimized getMinstay with caching 369 getMinstay(day) { 370 // Check cache first 371 if (this._cache.minstay[day] !== undefined) { 372 return this._cache.minstay[day]; 373 } 374 375 // Calculate and cache the result 376 const result = this._calculateMinstay(day); 377 this._cache.minstay[day] = result; 378 return result; 379 } 380 381 _calculateMinstay(day) { 382 const opts = this.dateOptions(day); 383 if (opts) { 384 // Add a safety check for min_stay to prevent unreasonably large values 385 const minStay = parseInt(opts.min_stay, 10); 386 return isNaN(minStay) || minStay <= 0 ? 1 : Math.min(minStay, 100); // Cap at 100 for safety 387 } 388 389 if (this.minStay) { 390 return this.minStay; // Cap at 100 for safety 391 } 392 393 return 1; 394 } 395 396 calcNights(date1, date2) { 397 const { DateTime } = luxon; 398 399 if (!(date1 instanceof DateTime)) { 400 date1 = this.getDateZero(date1); 401 } 402 if (!(date2 instanceof DateTime)) { 403 date2 = this.getDateZero(date2); 404 } 405 406 return Math.ceil(Math.abs(date1.diff(date2, ["days"]).days)); 407 } 408 409 // Optimized isUnavailable with caching 410 isUnavailable(day) { 411 // Check cache first 412 if (this._cache.isUnavailable[day] !== undefined) { 413 return this._cache.isUnavailable[day]; 414 } 415 416 // Calculate and cache the result 417 const result = this._calculateUnavailability(day); 418 this._cache.isUnavailable[day] = result; 419 return result; 420 } 421 422 // Optimized to be lightweight by avoiding DateTime operations where possible 423 _calculateUnavailability(day) { 424 const date = this.getDateZero(day); 425 const today = this.getDateZero(); 426 427 // check if date is in the past, today or too far in the future 428 if (date < today) { 429 return true; 430 } 431 432 if (date > this.maxDate) { 433 return true; 434 } 435 436 // Directly check if the day exists in the daily_price object 437 // This avoids the more expensive dateOptions call 438 if (!(day in this.dailyPrice)) { 439 return true; 440 } 441 442 return false; 443 } 444 445 isToday(dateStr) { 446 return ( 447 this.getDateZero(dateStr).toISODate() === this.getDateZero().toISODate() 448 ); 449 } 450 451 _canCheckin(day) { 452 const dateOpt = this.dateOptions(day); 453 if (dateOpt && dateOpt.checkin === 1) { 454 return true; 455 } 456 457 return false; 458 } 459 460 _canCheckout(day) { 461 const dateOpt = this.dateOptions(day); 462 if (dateOpt && dateOpt.checkout === 1) { 463 return true; 464 } 465 466 return false; 467 } 468 469 _checkinBooked(day) { 470 const dateOpt = this.dateOptions(day); 471 if (dateOpt && dateOpt.checkin === 2) { 472 return true; 473 } 474 475 return false; 476 } 477 478 _checkoutBooked(day) { 479 const dateOpt = this.dateOptions(day); 480 if (dateOpt && dateOpt.checkout === 2) { 481 return true; 482 } 483 484 return false; 485 } 486 487 _canStay(day) { 488 const dateOpt = this.dateOptions(day); 489 if (dateOpt && dateOpt.available === 1) { 490 return true; 491 } 492 493 return false; 494 } 495 496 // Optimize isTraped with cached results from other methods 497 isTraped(day) { 498 // Check cache first 499 if (this._cache.trapped[day] !== undefined) { 500 return this._cache.trapped[day]; 501 } 502 503 // Calculate and cache the result 504 const result = this._calculateIsTraped(day); 505 506 this._cache.trapped[day] = result; 507 508 return result; 509 } 510 511 // Optimize isTraped with cached results from other methods 512 _calculateIsTraped(day) { 513 if ( 514 this.isUnavailable(day) || 515 !(this._canStay(day) || this._canCheckin(day) || this._canCheckout(day)) 516 ) { 517 return false;
518 } 519 520 if ( 521 (this._canCheckin(day) && this.canCheckin(day).status) || 522 (this._canCheckout(day) && this.canCheckout(day).status) 523 ) { 524 return false; 525 } 526 527 // get least checkin day. 528 let prevDay = day; 529 let loopCounter = 0; 530 531 while ( 532 this.getDateZero(prevDay) > this.minDate && 533 loopCounter < this.maxIterations 534 ) { 535 loopCounter++; 536 prevDay = this.prevDay(prevDay); 537 if (this._canCheckin(prevDay)) { 538 if (this.canCheckin(prevDay).status) { 539 const dateRange = this.getSelectableRange(prevDay); 540 if (dateRange && dateRange.minDate < day && dateRange.maxDate > day) { 541 return false; 542 } 543 544 return true; 545 } 546 } 547 548 if (!this._canStay(prevDay)) { 549 return true; 550 } 551 } 552 553 return loopCounter >= this.maxIterations ? false : true; 554 } 555 556 // Optimized canCheckin with caching 557 canCheckin(dateStr) { 558 // Check cache first 559 if (this._cache.canCheckin[dateStr] !== undefined) { 560 return this._cache.canCheckin[dateStr]; 561 } 562 563 // Calculate and cache the result 564 const result = this._calculateCanCheckin(dateStr); 565 this._cache.canCheckin[dateStr] = result; 566 return result; 567 } 568 569 _calculateCanCheckin(dateStr) { 570 if (!this._canCheckin(dateStr)) { 571 return { 572 status: false, 573 message: this.string.checkin_unavailable, 574 }; 575 } 576 577 const givenDay = this.getDateZero(dateStr); 578 const today = this.getDateZero(); 579 580 if (givenDay < today) { 581 return { 582 status: false, 583 message: this.string.unavailable, 584 }; 585 } 586 587 const minStay = this.getMinstay(dateStr); 588 const maxStay = this.getMaxstay(dateStr); 589 590 for (let nights = 1; nights < minStay; nights++) { 591 let checkDay = givenDay.plus({ days: nights }); 592 const checkDayStr = this.dateYmd(checkDay); 593 594 if (!this._canStay(checkDayStr)) { 595 return { 596 status: false, 597 message: this.string.checkin_unavailable_min_stay.replace( 598 "{night}", 599 minStay, 600 ), 601 date: checkDayStr, 602 }; 603 } 604 605 if (nights > maxStay) { 606 return { 607 status: false, 608 message: this.string.checkin_unavailable_max_stay.replace( 609 "{night}", 610 maxStay, 611 ), 612 }; 613 } 614 } 615 616 const checkoutDates = this.getAvailableCheckoutDays(dateStr); 617 if (checkoutDates.length < 1) { 618 return { 619 status: false, 620 message: this.string.checkin_unavailable, 621 }; 622 } 623 624 return { 625 status: true, 626 }; 627 } 628 629 // check if checkout date is valid. 630 // if checkin date is provided, check if min stay is met 631 canCheckinout(checkin, checkout) { 632 const checkinStatus = this.canCheckin(checkin); 633 if (!checkinStatus.status) { 634 return checkinStatus; 635 } 636 637 if (!this._canCheckout(checkout)) { 638 return { 639 status: false, 640 message: this.string.checkout_unavailable, 641 }; 642 } 643 644 const today = this.getDateZero(); 645 const checkinDate = this.getDateZero(checkin); 646 const checkoutDate = this.getDateZero(checkout); 647 648 if (checkinDate < today || checkoutDate < checkinDate) { 649 return { 650 status: false, 651 message: this.string.checkout_unavailable, 652 }; 653 } 654 655 // when checkin date is provided, check if min stay is met 656 const minStay = this.getMinstay(checkin); 657 const maxStay = this.getMaxstay(checkin); 658 const nights = this.calcNights(checkinDate, checkoutDate); 659 660 if (nights > maxStay) { 661 return { 662 status: false, 663 message: this.string.checkout_unavailable_max_stay.replace( 664 "{night}", 665 maxStay, 666 ), 667 }; 668 } 669 670 if (nights < minStay) { 671 return { 672 status: false, 673 message: this.string.checkout_unavailable_min_stay.replace( 674 "{night}", 675 minStay, 676 ), 677 }; 678 } 679 680 let passed = true; 681 for (let i = 1; i < nights && i < 366; i++) { 682 // Add safety limit of 366 days 683 const nextDate = checkinDate.plus({ days: i }); 684 const nextDateStr = this.dateYmd(nextDate); 685 686 if (!this._canStay(nextDateStr)) { 687 passed = false;
688 break; 689 } 690 } 691 692 if (!passed) { 693 return { 694 status: false, 695 message: this.string.checkout_unavailable, 696 }; 697 } 698 699 return { 700 status: true, 701 }; 702 } 703 704 // Optimized canCheckout with caching 705 canCheckout(checkout) { 706 // Check cache first 707 if (this._cache.canCheckout[checkout] !== undefined) { 708 return this._cache.canCheckout[checkout]; 709 } 710 711 // Calculate and cache the result 712 const result = this._calculateCanCheckout(checkout); 713 this._cache.canCheckout[checkout] = result; 714 return result; 715 } 716 717 _calculateCanCheckout(checkout) { 718 if (!this._canCheckout(checkout)) { 719 return { 720 status: false, 721 at: "0", 722 message: this.string.checkout_unavailable, 723 }; 724 } 725 726 const today = this.getDateToday(); 727 const checkoutDate = this.getDateZero(checkout); 728 729 if (checkoutDate < today) { 730 return { 731 status: false, 732 at: "1", 733 message: this.string.checkout_unavailable, 734 }; 735 } 736 737 // Create a clone of the date to avoid modifying the original date object 738 let loopCounter = 0; 739 let prevDay = checkoutDate; 740 741 while (today < prevDay && loopCounter < this.maxIterations) { 742 loopCounter++; 743 744 prevDay = prevDay.plus({ days: -1 }); 745 const prevDayStr = this.dateYmd(prevDay); 746 747 if (!this._canCheckin(prevDayStr) && !this._canStay(prevDayStr)) { 748 return { 749 status: false, 750 at: "2", 751 prevDay: prevDayStr, 752 message: this.string.checkout_unavailable, 753 }; 754 } 755 756 if (this.canCheckin(prevDayStr).status) { 757 const checkoutDates = this.getAvailableCheckoutDays(prevDayStr); 758 if (checkoutDates.indexOf(checkout) > -1) { 759 return { 760 status: true, 761 at: "3", 762 prevDay: prevDayStr, 763 }; 764 } 765 } 766 } 767 768 // If we've reached the maximum number of iterations, return with a safety limit message 769 if (loopCounter >= this.maxIterations) { 770 return { 771 status: false, 772 at: "safety_limit", 773 message: this.string.checkout_unavailable + " (Safety limit reached)", 774 }; 775 } 776 777 return { 778 status: false, 779 at: "last", 780 message: this.string.checkout_unavailable, 781 }; 782 } 783 784 getFirstCheckinDay() { 785 let nextDate = this.minDate.plus({ days: -1 }); 786 let nextAvailableDay; 787 let loopCounter = 0; 788 789 while (nextDate < this.maxDate && loopCounter < this.maxIterations) { 790 loopCounter++; 791 nextDate = nextDate.plus({ days: 1 }); 792 const nextDateStr = this.dateYmd(nextDate); 793 794 if (this.canCheckin(nextDateStr).status) { 795 nextAvailableDay = nextDateStr; 796 break; 797 } 798 } 799 800 return nextAvailableDay; 801 } 802 803 getSelectableRange(checkinStr) { 804 // Check cache first 805 if (this._cache.selectableRanges[checkinStr] !== undefined) { 806 return this._cache.selectableRanges[checkinStr]; 807 } 808 809 // Calculate and cache the result 810 const result = this._calculateSelectableRange(checkinStr); 811 this._cache.selectableRanges[checkinStr] = result; 812 return result; 813 } 814 815 // Optimized version of getSelectableRange that doesn't recalculate checkout days 816 _calculateSelectableRange(checkinStr) { 817 if (!this._canCheckin(checkinStr)) { 818 return false; 819 } 820 821 // Get or calculate available checkout days 822 let checkoutDays; 823 if (this._cache.getAvailableCheckoutDays[checkinStr]) { 824 checkoutDays = this._cache.getAvailableCheckoutDays[checkinStr]; 825 } else { 826 checkoutDays = this._calculateAvailableCheckoutDays(checkinStr); 827 this._cache.getAvailableCheckoutDays[checkinStr] = checkoutDays; 828 } 829 830 if (checkoutDays.length < 1) { 831 return false; 832 } 833 834 return { 835 minDate: checkinStr, 836 maxDate: checkoutDays[checkoutDays.length - 1], 837 }; 838 } 839 840 // Optimized getAvailableCheckoutDays with caching 841 getAvailableCheckoutDays(checkinStr) { 842 // Check cache first 843 if (this._cache.getAvailableCheckoutDays[checkinStr] !== undefined) { 844 return this._cache.getAvailableCheckoutDays[checkinStr]; 845 } 846 847 // Calculate and cache the result 848 const result = this._calculateAvailableCheckoutDays(checkinStr); 849 this._cache.getAvailableCheckoutDays[checkinStr] = result; 850 return result; 851 } 852 853 _calculateAvailableCheckoutDays(checkinStr) { 854 const { DateTime } = luxon; 855 const days = []; 856 857 // Normalize check-in date 858 const checkinDate = this.getDateZero(checkinStr); 859 860 // Calculate first checkout date using min stay 861 const minStay = this.getMinstay(checkinStr); 862 const maxStay = this.getMaxstay(checkinStr); 863
864 let checkoutDate = checkinDate.plus({ days: minStay }); 865 866 // if (this._canCheckout(checkoutDate)) { 867 // days.push(checkoutDate.toFormat("yyyy-MM-dd")); 868 // } 869 870 let loopCounter = 0; 871 // Limit the maximum checkout calculation to 90 days from checkin 872 // This significantly improves performance while still covering most use cases 873 const maxCalculationDate = DateTime.min( 874 this.maxDate, 875 checkinDate.plus({ days: maxStay }), 876 ); 877 878 while ( 879 checkoutDate <= maxCalculationDate && 880 loopCounter < this.maxIterations 881 ) { 882 const opts = this.dateOptions(checkoutDate); 883 if (!opts) { 884 break; 885 } 886 887 if (this._checkoutBooked(checkoutDate)) { 888 break; 889 } 890 891 if (!this._canCheckout(checkoutDate) && !this._canStay(checkoutDate)) { 892 break; 893 } 894 895 const nights = loopCounter + minStay; 896 if (nights > maxStay) { 897 break; 898 } 899 900 if (this._canCheckout(checkoutDate)) { 901 days.push(checkoutDate.toFormat("yyyy-MM-dd")); 902 } 903 904 loopCounter++; 905 checkoutDate = checkoutDate.plus({ days: 1 }); 906 } 907 908 return days; 909 } 910} 911 912// export only if running in node.js 913if (typeof window === "undefined") { 914 module.exports = { HomeLocalCalendarUtils }; 915}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.