mui / material-ui-pickers

Date & Time pickers for Material UI (support from v1 to v4)
https://github.com/mui/material-ui-pickers/issues/2157
MIT License
2.32k stars 832 forks source link

Allow overriding the start of the week #1270

Closed alexplumb closed 5 years ago

alexplumb commented 5 years ago

original

Is your feature request related to a problem? Please describe. Our application uses Luxon for date/time management. Luxon uses the ISO standard for setting the start of the week - Monday. Our users, however, are almost entirely in US/Canada, where the standard for the start of the week is Sunday.

Describe the solution you'd like I would like to see a property added to material-ui-pickers that allows us to override the first day of the week.

Describe alternatives you've considered The only other solution is to switch to a different date utility

dmtrKovalenko commented 5 years ago

yeah, got what you mean. Really Intl really doesn't allow localized weekdays out of the box. So you can use something like this https://codesandbox.io/s/material-ui-pickers-usage-demo-w9n8i

A hack to generate a custom set of days. The implementation definitely can be improved, but the idea is right

alexplumb commented 5 years ago

@dmtrKovalenko Thanks for the response. I think the implementation could be improved by using the Luxon method for getting the weekday names and then just moving the items around - that way it still works when localized.

import { Info } from 'luxon';

getWeekdays() {
    const days = [...Info.weekdaysFormat('narrow', this.locale)];
    days.unshift(days.pop());

    return days;
}

edit: Thanks for the heads-up @leereamsnyder - I fixed this in my code but neglected to update my comment here

leereamsnyder commented 4 years ago

should anyone run into this down the road, @alexplumb's suggestion is good, but make sure you work from a copy of the Info.weekdaysFormat result so you're not modifying the original (you'll end up shifting the days constantly)

getWeekdays() {
  // need to copy the existing, and use Info to preserve localization
  const days = [...Info.weekdaysFormat('narrow', this.locale)];
  // remove Sun from end of list and move to start of list
  days.unshift(days.pop());
  return days;
}
alexplumb commented 4 years ago

@dmtrKovalenko I've discovered an issue with this solution: if the first day of the month is Sunday (as it was in September and December), the date picker renders an extra row at the top. Any idea how to resolve this?

Screen Shot 2019-12-18 at 10 41 11 AM
dmtrKovalenko commented 4 years ago

@alexplumb checkout that you are not returning empty arrays. I suppose your matrix values are

[
  [],
  [day1, day2,day3...],
  ...
]
alexplumb commented 4 years ago

@dmtrKovalenko To clarify, that row is populated with days from the previous month. The buttons for some reason have a class of "MuiPickersDay-hidden", which is giving them an opacity of 0. If I adjust the styles for the "hidden" class you can see what's actually going on.

Screen Shot 2019-12-18 at 10 51 30 AM
dmtrKovalenko commented 4 years ago

Haha, but it doesn't matter

Our code is

 {utils.getWeekArray(currentMonth).map(week => (

So if you are seeing this row it means that for September your method override returned not needed week from the previous month

alexplumb commented 4 years ago

Well the reason I'm asking you for support is because you wrote the week override method - I (and I suspect others) are just using what you put in the code sandbox. Any suggestions for where I should start for fixing this? The getWeekArray method in the sandbox is pretty dense and TBH I don't really understand what's going on.

leereamsnyder commented 4 years ago

@alexplumb i hit that same problem with the "week full of days from another month"

I had to tweak the getWeekArray method from @dmtrKovalenko's example code to only include weeks where some day was in the target month. Here's that trick:

  return weeks.filter(w => w.some(d => d.month === date.month));

Here's the whole utils class I'm using right now:

import {Info} from 'luxon';
import LuxonUtils from '@date-io/luxon';

class CustomLuxonUtils extends LuxonUtils {
  getWeekdays() {
    // need to copy the existing, and use Info to preserve localization
    const days = [...Info.weekdaysFormat('narrow', this.locale)];
    // remove Sun from end of list and move to start of list
    days.unshift(days.pop());
    return days;
  }

  getWeekArray(date) {
    const endDate = date
      .endOf('month')
      // if a month ends on sunday, luxon will consider it already the end of the week
      // but we need to get the _entire_ next week to properly lay that out
      // so we add one more day to cover that before getting the end of the week
      .plus({days: 1})
      .endOf('week');
    const startDate = date
      .startOf('month')
      .startOf('week')
      // must subtract 1, because startOf('week') will be Mon, but we want weeks to start on Sun
      // this is the basis for every day in a our calendar
      .minus({days: 1});

    const {days} = endDate.diff(startDate, 'days').toObject();

    const weeks = [];
    new Array(Math.round(days))
      .fill(0)
      .map((_, i) => i)
      .map(day => startDate.plus({days: day}))
      .forEach((v, i) => {
        if (i === 0 || (i % 7 === 0 && i > 6)) {
          weeks.push([v]);
          return;
        }

        weeks[weeks.length - 1].push(v);
      });

    // a consequence of all this shifting back/forth 1 day is that you might end up with a week
    // where all the days are actually in the previous or next month.
    // this happens when the first day of the month is Sunday (Dec 2019 or Mar 2020 are examples)
    // or the last day of the month is Sunday (May 2020 or Jan 2021 is one example)
    // so we're only including weeks where ANY day is in the correct month to handle that
    return weeks.filter(w => w.some(d => d.month === date.month));
  }
}
dmtrKovalenko commented 4 years ago

Here is working sandbox https://codesandbox.io/s/material-ui-pickers-usage-demo-w9n8i UPD: a little bit more performant then previous variant because comparing only the first and last element

alexplumb commented 4 years ago

@leereamsnyder Wow, thanks - and so well commented too!

BrianWhiting commented 1 year ago

Since 2021, I've been using the workaround noted in this issue to force Luxon to use Sunday as the first day of the week and just noticed that it no longer works, probably due to this issue: https://github.com/mui/mui-x/issues/9984. I was able to fix it by adding an override for the startOfWeek method as follows:

  startOfWeek = (value: DateTime) => {
    return value.startOf('week').minus({ days: 1 });
  }
hokwanhung commented 11 months ago

The workaround provided by dmtrKovalenko seems to be working fine; though in case anyone needs a TypeScript version, here is the code sandbox:

Edit material-ui-pickers-usage-demo (forked, with TypeScript)