Skip to content

Instantly share code, notes, and snippets.

@miljan-aleksic
Last active August 19, 2022 12:24
Show Gist options
  • Save miljan-aleksic/bd70452a3f0cd6a11545db9f6ab57df6 to your computer and use it in GitHub Desktop.
Save miljan-aleksic/bd70452a3f0cd6a11545db9f6ab57df6 to your computer and use it in GitHub Desktop.
Calendar Matrix (date-fns, ES6) inspired by https://github.com/bclinkinbeard/calendar-matrix
import getYear from 'date-fns/get_year'
import getMonth from 'date-fns/get_month'
import addDays from 'date-fns/add_days'
import startOfWeek from 'date-fns/start_of_week'
const rows = range(6)
const cols = range(7)
/**
* Returns a two-dimensional array with calendar represented dates
*/
export default function ({ year, month, weekStartsOn } = {
year: getYear(new Date()),
month: getMonth(new Date()),
weekStartsOn: 0
}) {
const matrix = []
const date = new Date(year, month)
let curDate = startOfWeek(date, { weekStartsOn })
rows.forEach(row => {
const week = []
cols.forEach(col => {
week.push(curDate)
curDate = addDays(curDate, 1)
})
matrix.push(week)
})
return matrix
}
/**
* Returns an array range from 0 to n
*/
function range (n) {
return [...Array(n).keys()]
}
@olegomon
Copy link

olegomon commented Jul 25, 2018

@miljan-aleksic thanks for the gist! I found it very useful. Here is a slightly different version:

import {addDays, startOfWeek} from 'date-fns'

/**
 * Returns a two-dimensional array with calendar represented dates
 */
export function matrix(year: number, month: number, weekStartsOn: 0 | 1 | 2 | 3 | 4 | 5 | 6 = 1) {
    const startDate = startOfWeek(new Date(year, month, 1), {weekStartsOn});
    const rows = 6;
    const cols = 7;
    const length = rows * cols;
    return Array.from({length})
        // create a list of dates
        .map((_, index) => addDays(startDate, index).getDate())
        // fold the array into a matrix
        .reduce((matrix, current, index, days) => !(index % cols !== 0) ? [...matrix, days.slice(index, index + cols)] : matrix, []);
}

console.log(matrix(2018, 7, 1));

@retyui
Copy link

retyui commented Nov 8, 2018

My useful variant, without (rows, cols) it's calculate automate

// @flow

import eachDayOfInterval from 'date-fns/eachDayOfInterval';
import endOfISOWeek from 'date-fns/endOfISOWeek';
import endOfMonth from 'date-fns/endOfMonth';
import isSameMonth from 'date-fns/isSameMonth';
import startOfISOWeek from 'date-fns/startOfISOWeek';
import startOfMonth from 'date-fns/startOfMonth';
import eachWeekOfInterval from 'date-fns/eachWeekOfInterval';

type Options = {|
  year: number,
  mount: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11,
|};
type convertFn = (Date, { isSameMonth: boolean }) => any;

export const getMountMatrix = (
  { year, mount }: Options,
  convertDate: convertFn = date => date,
) => {
  const date = new Date(year, mount);

  const matrix = eachWeekOfInterval(
    {
      start: startOfMonth(date),
      end: endOfMonth(date),
    },
    { weekStartsOn: 1 },
  );

  return matrix.map(weekDay =>
    eachDayOfInterval({
      start: startOfISOWeek(weekDay),
      end: endOfISOWeek(weekDay),
    }).map(day =>
      convertDate(day, {
        isSameMonth: isSameMonth(date, day),
      }),
    ),
  );
};

test

// @flow
import format from 'date-fns/format';

import { getMountMatrix } from '@/features/calendarScene/Calendar/uitls';

test('should create december mount days grid', () => {
  expect(
    getMountMatrix(
      { year: 2018, mount: 11 },
      (day, { isSameMonth }) => (isSameMonth ? format(day, 'dd') : '--'),
    ),
  ).toEqual([
    ['--', '--', '--', '--', '--', '01', '02'],
    ['03', '04', '05', '06', '07', '08', '09'],
    ['10', '11', '12', '13', '14', '15', '16'],
    ['17', '18', '19', '20', '21', '22', '23'],
    ['24', '25', '26', '27', '28', '29', '30'],
    ['31', '--', '--', '--', '--', '--', '--'],
  ]);
});

@indapublic
Copy link

indapublic commented Jan 20, 2020

@retyui awesome, thanks

@boazblake
Copy link

there is a small bug in the getMountMatrix function. You are actually returning back January and not December. You should be setting the date to the previous month:
(also I believe you meant month instead of mount)

const date = new Date(parseInt(year), parseInt(mount) - 1);

@notiv-nt
Copy link

notiv-nt commented Jan 3, 2022

Easier way, @retyui take a look

import { eachDayOfInterval, endOfWeek, startOfWeek } from 'date-fns'; // 2.2k (gzipped: 989)

function createCalendar(year: number, month: number): Date[] {
  const start = startOfWeek(new Date(year, month, 1), { weekStartsOn: 1 /* Monday */ });
  const end = endOfWeek(new Date(year, month + 1, 1), { weekStartsOn: 1 /* Monday */ });
  return eachDayOfInterval({ start, end });
}

const calendar = createCalendar(2022, 0 /* January */);

console.log(calendar); // Array<Date> between '27 Dec 2021' and '6 Feb 2022'

https://codesandbox.io/s/amazing-keldysh-7r41u

@anishdcruz
Copy link

@notiv-nt Your code was perfect, but I noticed that for some months there were only five rows rather than six.

Here is the fix :)

import {
  addWeeks,
  eachDayOfInterval,
  endOfWeek,
  startOfMonth,
  startOfWeek,
} from 'date-fns'

export function createCalendar(today: Date): Date[] {
  const start = startOfWeek(startOfMonth(today), {
    weekStartsOn: 1 /* Monday */,
  })
  const end = endOfWeek(addWeeks(start, 5), {
    weekStartsOn: 1 /* Monday */,
  })
  return eachDayOfInterval({ start, end })
}

const calendar = createCalendar(new Date());

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment