PageSourceSearch

https://www.emrvacationrentals.com/wp-content/plugins/homerunner/assets/js/calendar-utils.js?ver=2.5.1

js emrvacationrentals.com collected 2026-09-26 18:11:05 UTC 24,297 bytes, 915 lines download raw bytes

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.