Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for broadcast calendar #2597

Merged
merged 12 commits into from
Nov 23, 2024
7 changes: 7 additions & 0 deletions examples/BroadcastCalendar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import React from "react";

import { DayPicker } from "react-day-picker";

export function BroadcastCalendar() {
return <DayPicker showOutsideDays showWeekNumber broadcastCalendar />;
}
1 change: 1 addition & 0 deletions examples/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from "./AccessibleDatePicker";
export * from "./AutoFocus";
export * from "./BroadcastCalendar";
export * from "./ContainerAttributes";
export * from "./Controlled";
export * from "./ControlledSelection";
Expand Down
5 changes: 3 additions & 2 deletions src/DayPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export function DayPicker(props: DayPickerProps) {
const dateLib = new DateLib(
{
locale,
weekStartsOn: props.weekStartsOn,
weekStartsOn: props.broadcastCalendar ? 1 : props.weekStartsOn,
firstWeekContainsDate: props.firstWeekContainsDate,
useAdditionalWeekYearTokens: props.useAdditionalWeekYearTokens,
useAdditionalDayOfYearTokens: props.useAdditionalDayOfYearTokens
Expand All @@ -75,7 +75,8 @@ export function DayPicker(props: DayPickerProps) {
props.locale,
props.useAdditionalDayOfYearTokens,
props.useAdditionalWeekYearTokens,
props.weekStartsOn
props.weekStartsOn,
props.broadcastCalendar
]);

const {
Expand Down
36 changes: 36 additions & 0 deletions src/helpers/broadcastCalendar.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import {
getBroadcastWeeksInMonth,
startOfBroadcastWeek,
endOfBroadcastWeek
} from "./broadcastCalendar";

describe("Broadcast Date Functions", () => {
test("getBroadcastWeeksInMonth should return correct number of weeks", () => {
// Test for a month with 5 weeks
expect(getBroadcastWeeksInMonth(new Date(2023, 0, 1))).toBe(5); // January 2023
// Test for a month with 4 weeks
expect(getBroadcastWeeksInMonth(new Date(2023, 1, 1))).toBe(4); // February 2023
});

test("getBroadcastStartDate should return correct start date", () => {
// Test for a month starting on a Monday
expect(startOfBroadcastWeek(new Date(2023, 0, 1))).toEqual(
new Date(2022, 11, 26)
); // December 26 2022
// Test for a month starting on a Wednesday
expect(startOfBroadcastWeek(new Date(2020, 0, 1))).toEqual(
new Date(2019, 11, 30)
); // December 30 2019
});

test("getBroadcastEndDate should return correct end date", () => {
const startDate = startOfBroadcastWeek(new Date(2023, 0, 1));
expect(endOfBroadcastWeek(new Date(2023, 0, 1))).toEqual(
new Date(
startDate.getFullYear(),
startDate.getMonth(),
startDate.getDate() + 34
)
); // January 2023
});
});
58 changes: 58 additions & 0 deletions src/helpers/broadcastCalendar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/** Return the number of weeks to display in the broadcast calendar. */
export function getBroadcastWeeksInMonth(month: Date): number {
const NUMBER_OF_5_WEEKS = 5;
const NUMBER_OF_4_WEEKS = 4;

const firstDayOfMonth = new Date(month.getFullYear(), month.getMonth(), 1);
const dayOfWeekOfFirstDayOfMonth =
firstDayOfMonth.getDay() > 0 ? firstDayOfMonth.getDay() : 7;
const beginDate = new Date(
month.getFullYear(),
month.getMonth(),
month.getDate() - dayOfWeekOfFirstDayOfMonth + 1
);
const numberOfWeeks =
month.getMonth() ===
new Date(
beginDate.getFullYear(),
beginDate.getMonth(),
beginDate.getDate() + NUMBER_OF_5_WEEKS * 7 - 1
).getMonth()
? NUMBER_OF_5_WEEKS
: NUMBER_OF_4_WEEKS;
return numberOfWeeks;
}

/** Return the start date of the week in the broadcast calendar. */
export function startOfBroadcastWeek(date: Date): Date {
const year = date.getFullYear();
const month = date.getMonth();

const firstOfMonth = new Date(year, month, 1);
const dayOfWeek = firstOfMonth.getUTCDay(); // 0 = Sunday, 1 = Monday, etc.

// If the first of the month is a Monday, then the broadcast month starts on that day.
// Otherwise, the broadcast starts on the previous Monday, and if first of the month is Sunday, then the starts on the previous week's Monday.
if (dayOfWeek === 1) {
return firstOfMonth;
} else if (dayOfWeek === 0) {
const startDate = new Date(year, month, 1 - dayOfWeek + 1 - 7);
return startDate;
} else {
const startDate = new Date(year, month, 1 - dayOfWeek + 1);
return startDate;
}
}

/** Return the end date of the week in the broadcast calendar. */
export function endOfBroadcastWeek(date: Date): Date {
const startDate = startOfBroadcastWeek(date);
// end date based on the weeks to show, it is calculated by getBroadcastWeeksInMonth
const numberOfWeeks = getBroadcastWeeksInMonth(date);
const endDate = new Date(
startDate.getFullYear(),
startDate.getMonth(),
startDate.getDate() + numberOfWeeks * 7 - 1
);
return endDate;
}
26 changes: 18 additions & 8 deletions src/helpers/getMonths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ import type { DateLib } from "../classes/DateLib.js";
import { CalendarWeek, CalendarDay, CalendarMonth } from "../classes/index.js";
import type { DayPickerProps } from "../types/index.js";

import {
startOfBroadcastWeek,
endOfBroadcastWeek
} from "./broadcastCalendar.js";
import { NrOfDaysWithFixedWeeks } from "./getDates.js";

/** Return the months to display in the calendar. */
Expand All @@ -11,7 +15,10 @@ export function getMonths(
/** The dates to display in the calendar. */
dates: Date[],
/** Options from the props context. */
props: Pick<DayPickerProps, "fixedWeeks" | "ISOWeek" | "reverseMonths">,
props: Pick<
DayPickerProps,
"broadcastCalendar" | "fixedWeeks" | "ISOWeek" | "reverseMonths"
>,
dateLib: DateLib
): CalendarMonth[] {
const {
Expand All @@ -26,14 +33,17 @@ export function getMonths(
} = dateLib;
const dayPickerMonths = displayMonths.reduce<CalendarMonth[]>(
(months, month) => {
const firstDateOfFirstWeek = props.ISOWeek
? startOfISOWeek(month)
: startOfWeek(month);

const lastDateOfLastWeek = props.ISOWeek
? endOfISOWeek(endOfMonth(month))
: endOfWeek(endOfMonth(month));
const firstDateOfFirstWeek = props.broadcastCalendar
? startOfBroadcastWeek(month)
: props.ISOWeek
? startOfISOWeek(month)
: startOfWeek(month);

const lastDateOfLastWeek = props.broadcastCalendar
? endOfBroadcastWeek(month)
: props.ISOWeek
? endOfISOWeek(endOfMonth(month))
: endOfWeek(endOfMonth(month));
/** The dates to display in the month. */
const monthDates = dates.filter((date) => {
return date >= firstDateOfFirstWeek && date <= lastDateOfLastWeek;
Expand Down
8 changes: 8 additions & 0 deletions src/types/props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,14 @@ export interface PropsBase {
* @see https://daypicker.dev/docs/customization#showweeknumber
*/
showWeekNumber?: boolean;
/**
* Display the weeks in the month following the broadcast calendar. Setting
* this prop will ignore {@link weekStartsOn} and {@link fixedWeeks}.
*
* @see https://daypicker.dev/docs/customization#broadcast-calendar
* @see https://en.wikipedia.org/wiki/Broadcast_calendar
*/
broadcastCalendar?: boolean;
/**
* Use ISO week dates instead of the locale setting. Setting this prop will
* ignore `weekStartsOn` and `firstWeekContainsDate`.
Expand Down
1 change: 1 addition & 0 deletions src/useCalendar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ export function useCalendar(
| "month"
| "defaultMonth"
| "timeZone"
| "broadcastCalendar"
// Deprecated:
| "fromMonth"
| "fromYear"
Expand Down
25 changes: 25 additions & 0 deletions website/docs/docs/localization.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -135,3 +135,28 @@ export function Jalali() {
<BrowserWindow>
<Examples.Jalali />
</BrowserWindow>


## Broadcast Calendar

DayPicker supports the Broadcast Calendar. More Details in https://en.wikipedia.org/wiki/Broadcast_calendar

:::warning Experimental Feature

Support for the Broadcast calendar is an experimental feature. [Please report](https://github.com/gpbl/react-day-picker/issues) any issues you encounter. Thank you!

:::

```tsx title="./broadcastCalendar.jsx"
import React from "react";

import { DayPicker } from "react-day-picker";

export default function App() {
return <DayPicker showOutsideDays showWeekNumber broadcastCalendar />;
}
```

<BrowserWindow>
<Examples.BroadcastCalendar />
</BrowserWindow>