Skip to content

Commit

Permalink
feat: add support for broadcast calendar (#2597)
Browse files Browse the repository at this point in the history
Co-authored-by: Zhao <[email protected]>
Co-authored-by: gpbl <[email protected]>
  • Loading branch information
3 people authored Nov 23, 2024
1 parent 5800719 commit 6833704
Show file tree
Hide file tree
Showing 9 changed files with 157 additions and 10 deletions.
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("broadcastCalendar", () => {
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("startOfBroadcastWeek 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("endOfBroadcastWeek 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>

0 comments on commit 6833704

Please sign in to comment.