nathanreyes / v-calendar

An elegant calendar and datepicker plugin for Vue.
https://vcalendar.io
MIT License
4.41k stars 863 forks source link

"available-dates"-prop very slow on larger datasets in range mode #1010

Open MariusSpring opened 2 years ago

MariusSpring commented 2 years ago

Hello! I am building a Date Picker for selecting checkin and checkout dates. I have a list of available checkin dates and want to disable all dates except these.

I pass the following array to the "available-dates"-prop and it is working. However, it is VERY slow. Like it takes 30 seconds to select a date. Is there a more performant way to achieve the same thing? The dataset isn't very big, averages about 300 available checkin dates at the same time.

const availableDates = [
  {
    "start": "2021-10-18T00:00:00.000Z",
    "end": "2021-10-18T00:00:00.000Z"
  },
  {
    "start": "2021-10-19T00:00:00.000Z",
    "end": "2021-10-19T00:00:00.000Z"
  },
  {
    "start": "2021-10-20T00:00:00.000Z",
    "end": "2021-10-20T00:00:00.000Z"
  },
  {
    "start": "2021-10-21T00:00:00.000Z",
    "end": "2021-10-21T00:00:00.000Z"
  },
 // ... 300 more date ranges
]

I have also tried to pass individual dates like below, which is also working, but still extremely slow:

const availableDates = [
  '2021-10-18T00:00:00.000Z',
  '2021-10-19T00:00:00.000Z',
  '2021-10-20T00:00:00.000Z',
 // ... 300 more dates
]

Thanks in advance!

Update: I am also in range mode. Single mode performs slow, but acceptable. It is range mode that is extremely slow.

gng5216512 commented 2 years ago

same for me,how to do right?

MariusSpring commented 2 years ago

@gng5216512 Haven't quite figured that out yet. I believe this is an internal issue in how the library handles available dates. I have tried to pass the same data to "disabled-dates", which is working fine. It is only "available-dates" where some heavy computations are happening.

chuchiperriman commented 2 years ago

I need fix this problem. Can anyone help me to debug v-calendar? I cannot find any documentation about it.

I've downloaded the git repo and build the project with yarn build but I cannot run "yarn serve" to debug the library

chuchiperriman commented 2 years ago

I think the problem is not the availables-dates because if I add this configuration to show bars in a list of days, it is very very slow too:

calendarAttributes: [
        { key: 'today', dates: new Date(), dot: 'purple' },
        {
          bar: true,
          dates: [new Date('2021-09-08'), new Date('2021-09-08'), new Date('2021-09-10'), new Date('2021-09-10'), new Date('2021-09-13'), new Date('2021-09-13'), new Date('2021-09-14'), new Date('2021-09-14'), new Date('2021-09-15'), new Date('2021-09-15'), new Date('2021-09-17'), new Date('2021-09-17'), new Date('2021-09-20'), new Date('2021-09-20'), new Date('2021-09-21'), new Date('2021-09-21'), new Date('2021-09-22'), new Date('2021-09-22'), new Date('2021-09-24'), new Date('2021-09-24'), new Date('2021-09-27'), new Date('2021-09-27'), new Date('2021-09-28'), new Date('2021-09-28'), new Date('2021-09-29'), new Date('2021-09-29'), new Date('2021-10-01'), new Date('2021-10-01'), new Date('2021-10-04'), new Date('2021-10-04'), new Date('2021-10-05'), new Date('2021-10-05'), new Date('2021-10-06'), new Date('2021-10-06'), new Date('2021-10-08'), new Date('2021-10-08'), new Date('2021-10-11'), new Date('2021-10-11'), new Date('2021-10-12'), new Date('2021-10-12'), new Date('2021-10-13'), new Date('2021-10-13'), new Date('2021-10-15'), new Date('2021-10-15'), new Date('2021-10-18'), new Date('2021-10-18'), new Date('2021-10-19'), new Date('2021-10-19'), new Date('2021-10-20'), new Date('2021-10-20'), new Date('2021-10-22'), new Date('2021-10-22'), new Date('2021-10-25'), new Date('2021-10-25'), new Date('2021-10-26'), new Date('2021-10-26'), new Date('2021-10-27'), new Date('2021-10-27'), new Date('2021-10-29'), new Date('2021-10-29'), new Date('2021-11-01'), new Date('2021-11-01'), new Date('2021-11-02'), new Date('2021-11-02'), new Date('2021-11-03'), new Date('2021-11-03'), new Date('2021-11-05'), new Date('2021-11-05'), new Date('2021-11-08'), new Date('2021-11-08'), new Date('2021-11-09'), new Date('2021-11-09'), new Date('2021-11-10'), new Date('2021-11-10'), new Date('2021-11-12'), new Date('2021-11-12'), new Date('2021-11-15'), new Date('2021-11-15'), new Date('2021-11-16'), new Date('2021-11-16'), new Date('2021-11-17'), new Date('2021-11-17'), new Date('2021-11-19'), new Date('2021-11-19'), new Date('2021-11-22'), new Date('2021-11-22'), new Date('2021-11-23'), new Date('2021-11-23'), new Date('2021-11-24'), new Date('2021-11-24'), new Date('2021-11-26'), new Date('2021-11-26'), new Date('2021-11-29'), new Date('2021-11-29'), new Date('2021-11-30'), new Date('2021-11-30'), new Date('2021-12-01'), new Date('2021-12-01'), new Date('2021-12-03'), new Date('2021-12-03'), new Date('2021-12-06'), new Date('2021-12-07'), new Date('2021-12-08'), new Date('2021-12-10'), new Date('2021-12-10'), new Date('2021-12-13'), new Date('2021-12-13'), new Date('2021-12-14'), new Date('2021-12-14'), new Date('2021-12-15'), new Date('2021-12-15'), new Date('2021-12-17'), new Date('2021-12-17'), new Date('2021-12-20'), new Date('2021-12-20'), new Date('2021-12-21'), new Date('2021-12-21'), new Date('2021-12-22'), new Date('2021-12-22'), new Date('2021-12-24'), new Date('2021-12-24'), new Date('2021-12-27'), new Date('2021-12-27'), new Date('2021-12-28'), new Date('2021-12-28'), new Date('2021-12-29'), new Date('2021-12-29'), new Date('2021-12-31'), new Date('2021-12-31'), new Date('2022-01-03'), new Date('2022-01-03'), new Date('2022-01-04'), new Date('2022-01-04'), new Date('2022-01-05'), new Date('2022-01-05'), new Date('2022-01-07'), new Date('2022-01-07'), new Date('2022-01-10'), new Date('2022-01-10'), new Date('2022-01-11'), new Date('2022-01-11'), new Date('2022-01-12'), new Date('2022-01-12'), new Date('2022-01-14'), new Date('2022-01-14'), new Date('2022-01-17'), new Date('2022-01-17'), new Date('2022-01-18'), new Date('2022-01-18'), new Date('2022-01-19'), new Date('2022-01-19'), new Date('2022-01-21'), new Date('2022-01-21'), new Date('2022-01-24'), new Date('2022-01-24'), new Date('2022-01-25'), new Date('2022-01-25'), new Date('2022-01-26'), new Date('2022-01-26'), new Date('2022-01-28'), new Date('2022-01-28'), new Date('2022-01-31'), new Date('2022-01-31'), new Date('2022-02-01'), new Date('2022-02-01'), new Date('2022-02-02'), new Date('2022-02-02'), new Date('2022-02-04'), new Date('2022-02-04'), new Date('2022-02-07'), new Date('2022-02-07'), new Date('2022-02-08'), new Date('2022-02-08'), new Date('2022-02-09'), new Date('2022-02-09'), new Date('2022-02-11'), new Date('2022-02-11'), new Date('2022-02-14'), new Date('2022-02-14'), new Date('2022-02-15'), new Date('2022-02-15'), new Date('2022-02-16'), new Date('2022-02-16'), new Date('2022-02-18'), new Date('2022-02-18'), new Date('2022-02-21'), new Date('2022-02-21'), new Date('2022-02-22'), new Date('2022-02-22'), new Date('2022-02-23'), new Date('2022-02-23'), new Date('2022-02-25'), new Date('2022-02-25'), new Date('2022-02-28'), new Date('2022-02-28'), new Date('2022-03-01'), new Date('2022-03-01'), new Date('2022-03-02'), new Date('2022-03-02'), new Date('2022-03-04'), new Date('2022-03-04'), new Date('2022-03-07'), new Date('2022-03-07'), new Date('2022-03-08'), new Date('2022-03-08'), new Date('2022-03-09'), new Date('2022-03-09'), new Date('2022-03-11'), new Date('2022-03-11'), new Date('2022-03-14'), new Date('2022-03-14'), new Date('2022-03-15'), new Date('2022-03-15'), new Date('2022-03-16'), new Date('2022-03-16'), new Date('2022-03-18'), new Date('2022-03-18'), new Date('2022-03-21'), new Date('2022-03-21'), new Date('2022-03-22'), new Date('2022-03-22'), new Date('2022-03-23'), new Date('2022-03-23'), new Date('2022-03-25'), new Date('2022-03-25'), new Date('2022-03-28'), new Date('2022-03-28'), new Date('2022-03-29'), new Date('2022-03-29'), new Date('2022-03-30'), new Date('2022-03-30'), new Date('2022-04-01'), new Date('2022-04-01'), new Date('2022-04-04'), new Date('2022-04-04'), new Date('2022-04-05'), new Date('2022-04-05'), new Date('2022-04-06'), new Date('2022-04-06'), new Date('2022-04-08'), new Date('2022-04-08'), new Date('2022-04-11'), new Date('2022-04-11'), new Date('2022-04-12'), new Date('2022-04-12'), new Date('2022-04-13'), new Date('2022-04-13'), new Date('2022-04-15'), new Date('2022-04-15'), new Date('2022-04-18'), new Date('2022-04-18'), new Date('2022-04-19'), new Date('2022-04-19'), new Date('2022-04-20'), new Date('2022-04-20'), new Date('2022-04-22'), new Date('2022-04-22'), new Date('2022-04-25'), new Date('2022-04-25'), new Date('2022-04-26'), new Date('2022-04-26'), new Date('2022-04-27'), new Date('2022-04-27'), new Date('2022-04-29'), new Date('2022-04-29'), new Date('2022-05-02'), new Date('2022-05-02'), new Date('2022-05-03'), new Date('2022-05-03'), new Date('2022-05-04'), new Date('2022-05-04'), new Date('2022-05-06'), new Date('2022-05-06'), new Date('2022-05-09'), new Date('2022-05-09'), new Date('2022-05-10'), new Date('2022-05-10'), new Date('2022-05-11'), new Date('2022-05-11'), new Date('2022-05-13'), new Date('2022-05-13'), new Date('2022-05-16'), new Date('2022-05-16'), new Date('2022-05-17'), new Date('2022-05-17'), new Date('2022-05-18'), new Date('2022-05-18'), new Date('2022-05-20'), new Date('2022-05-20'), new Date('2022-05-23'), new Date('2022-05-23'), new Date('2022-05-24'), new Date('2022-05-24'), new Date('2022-05-25'), new Date('2022-05-25'), new Date('2022-05-27'), new Date('2022-05-27'), new Date('2022-05-30'), new Date('2022-05-30'), new Date('2022-05-31'), new Date('2022-05-31'), new Date('2022-06-01'), new Date('2022-06-01'), new Date('2022-06-03'), new Date('2022-06-03'), new Date('2022-06-06'), new Date('2022-06-06'), new Date('2022-06-07'), new Date('2022-06-07'), new Date('2022-06-08'), new Date('2022-06-08'), new Date('2022-06-10'), new Date('2022-06-10'), new Date('2022-06-13'), new Date('2022-06-13'), new Date('2022-06-14'), new Date('2022-06-14'), new Date('2022-06-15'), new Date('2022-06-15'), new Date('2022-06-17'), new Date('2022-06-17'), new Date('2022-06-20'), new Date('2022-06-20'), new Date('2022-06-21'), new Date('2022-06-21'), new Date('2022-06-22'), new Date('2022-06-22')],
        },
      ],
chuchiperriman commented 2 years ago

The problem starts when we add the timezone property. If we set the timezone, it takes 15 seconds, without timezone 2 seconds.

jondcampbell commented 2 years ago

You may be onto something. I spent hours yesterday reworking my structure so that I had only around 20 available dates and 20 calendar attribute dates instead of ~200 of each (made it so i used only a slice of my larger dates array based on what month we have "paged"). It seemed to make very little difference in the performance.

My date picking doesn't even need timezones, but if I don't have them then sometimes things show up on the wrong day because the system creates/stores timestamps.

flatsteve commented 1 year ago

@chuchiperriman you are a legend. I wasted far too much time on this before I found your comment. Remove the timezone prop and boom, 10x speed increase. Thank you.

Ricro commented 1 year ago

Hi guys, I have found a workaround to avoid slow performance in range mode.

The trick is to not use the native availableDates or disabledDates params, but rather apply a custom CSS class on these dates.

/* the main idea is to apply pointer-events: none to disabled dates, along with some styling  */
.vc-day.closed:not(.is-not-in-month) {
  background: #ffffff;
  pointer-events: none;
}
.vc-day.closed:not(.is-not-in-month) .vc-day-content {
  opacity: 0.7;
  color: #cbd5e0;
  pointer-events: none;
}
.vc-day.semi-open:not(.is-not-in-month) {
  background: #c9e7f4;
}
.vc-day.open:not(.is-not-in-month) {
  background: #004665;
}
// example code
const attributesDates = {
  "closed": [
    "2023-03-22",
    "2023-03-23",
    "2023-03-24"
  ],
  "semiOpen": [
    "2023-04-20",
    "2023-04-21",
    "2023-04-22"
  ],
  "open": [
    "2023-04-15",
    "2023-04-16",
    "2023-04-17",
    "2023-04-18",
    "2023-04-19"
  ]
}

// call this function each time you need to refresh the availabilities on the calendar
setCustomStyle() {
  const addClassOnDate = (className, day) => document.querySelectorAll(`.id-${day}`)?.forEach(e => e.classList.add(className))

  attributesDates.open.map(day => addClassOnDate('open', day))
  attributesDates.semiOpen.map(day => addClassOnDate('semi-open', day))
  attributesDates.closed.map(day => addClassOnDate('closed', day))
}

Using this technique, I was able to create the following Airbnb like calendar:

v-calendar-hack

I hope this helps!

eponcehenriquez commented 1 year ago

The problem starts when we add the timezone property. If we set the timezone, it takes 15 seconds, without timezone 2 seconds.

I have the same problem, it takes 10 seconds to load the calendar and another 10 seconds more when changing the month, it is necessary to use UTC since if it is not specified, the dates change.

`<v-date-picker :available-dates='diasDisponibleAgenda' :timezone="timezone" :disabled-dates='disableAllDates' tabindex="9" :min-date='fechaInicioAgenda' v-model="fechaCitacion" @input="buscarHorarioProfesional()" >

                    </v-date-picker>`

could you solve the problem?

linron commented 1 year ago

I have the same problem and would also appreciate a solution.

eponcehenriquez commented 1 year ago

unfortunately I have not been able to solve the performance problem. It is very necessary to use timezone, since otherwise, the selected dates are wrong (one day more or one day less). Has anyone been able to solve the performance while maintaining timezone?

BevanR commented 1 year ago

@nathanreyes How can we contact you regarding sponsoring a solution to this one?

BevanR commented 1 year ago

I identified a performance bottleneck in refreshDisabledDays() due to a nested loop, that causes quadratic time complexity when there are disabled dates.

https://github.com/nathanreyes/v-calendar/blob/4474e3676fca1e9cba565086d1bbd01b7bc433de/src/components/Calendar.vue#L619-L623

See https://github.com/nathanreyes/v-calendar/pull/1363 for my investigation code and suggested optimisation.

Timezone doesn't change this, but it appears to exaggerate it. Probably because checking for date intersections gets more complicated across different timezones.

This led me to realize that while V-Calendar only supports timezones for the visible features. It assumes that datetime objects supplied by propse are in the browser timezone. Further, DateInfo doesn't support a timezone.

Are there any plans to support timezone on the dates in props like :disabled-dates? Failing that we'd need to manually convert all our dates from the venue timezone to browser timezone at each point we interface with v-calendar for any dates going in and out of it. This is very prone to error.

BevanR commented 1 year ago

Actually I think timezones can be specified in dates provided to :disabled-dates. But only if the values are specified as strings.

I confirmed that specifying the timezone exaggerates the performance bottleneck. Commenting out just date.toLocaleString('en-US', { timeZone: timezone }), in this code reduces the runtime for my test case by a factor of about 3.

https://github.com/nathanreyes/v-calendar/blob/b480293d2c4139b8d9b47f47b7eaad9443c6d7de/src/utils/locale.js#L591-L597

I started looking at implementing a two-pass approach with a map, but quickly ran into difficulty with handling disabled dates that specify e.g. "every tuesday" with the repeat attribute of a DateRangeConfig. E.g. { weekdays: [1, 7] }.

Perhaps it is a reasonable assumption that typical use cases specify;

but not usually many repeat rules.

If so then a three pass approach might work well here;

  1. Build a map of displayed dates, keyed by YYYY-MM-DD format
    1. Mark each displayed date as enabled (d.isDisabled = false)
    2. Iterate only the repeating disabled dates/ranges and check for intersections
      1. This is the nested loop procedure with quadratic time complexity, but impact should be minimal if there are few repeating dates.
  2. Iterate non-repeating disabled dates/ranges and mark each date in the range as disabled using the map to lookup the affected date.

This would add to the complexity of the architecture of the this package reasonably significantly.