/* * Copyright 2020 Adobe. All rights reserved. * This file is licensed to you under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. You may obtain a copy * of the License at http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS * OF ANY KIND, either express or implied. See the License for the specific language * governing permissions and limitations under the License. */ import {CalendarDate, DateFormatter, endOfMonth, isSameDay, startOfMonth} from '@internationalized/date'; import {CalendarState, RangeCalendarState} from '@react-stately/calendar'; // @ts-ignore import intlMessages from '../intl/*.json'; import type {LocalizedStringFormatter} from '@internationalized/string'; import {useDateFormatter, useLocalizedStringFormatter} from '@react-aria/i18n'; import {useMemo} from 'react'; interface HookData { ariaLabel?: string, ariaLabelledBy?: string, errorMessageId: string, selectedDateDescription: string } export const hookData: WeakMap = new WeakMap(); export function getEraFormat(date: CalendarDate | undefined): 'short' | undefined { return date?.calendar.identifier === 'gregory' && date.era === 'BC' ? 'short' : undefined; } export function useSelectedDateDescription(state: CalendarState | RangeCalendarState): string { let stringFormatter = useLocalizedStringFormatter(intlMessages, '@react-aria/calendar'); let start: CalendarDate | undefined, end: CalendarDate | undefined; if ('highlightedRange' in state) { ({start, end} = state.highlightedRange || {}); } else { start = end = state.value ?? undefined; } let dateFormatter = useDateFormatter({ weekday: 'long', month: 'long', year: 'numeric', day: 'numeric', era: getEraFormat(start) || getEraFormat(end), timeZone: state.timeZone }); let anchorDate = 'anchorDate' in state ? state.anchorDate : null; return useMemo(() => { // No message if currently selecting a range, or there is nothing highlighted. if (!anchorDate && start && end) { // Use a single date message if the start and end dates are the same day, // otherwise include both dates. if (isSameDay(start, end)) { let date = dateFormatter.format(start.toDate(state.timeZone)); return stringFormatter.format('selectedDateDescription', {date}); } else { let dateRange = formatRange(dateFormatter, stringFormatter, start, end, state.timeZone); return stringFormatter.format('selectedRangeDescription', {dateRange}); } } return ''; }, [start, end, anchorDate, state.timeZone, stringFormatter, dateFormatter]); } export function useVisibleRangeDescription(startDate: CalendarDate, endDate: CalendarDate, timeZone: string, isAria: boolean): string { let stringFormatter = useLocalizedStringFormatter(intlMessages, '@react-aria/calendar'); let era: any = getEraFormat(startDate) || getEraFormat(endDate); let monthFormatter = useDateFormatter({ month: 'long', year: 'numeric', era, calendar: startDate.calendar.identifier, timeZone }); let dateFormatter = useDateFormatter({ month: 'long', year: 'numeric', day: 'numeric', era, calendar: startDate.calendar.identifier, timeZone }); return useMemo(() => { // Special case for month granularity. Format as a single month if only a // single month is visible, otherwise format as a range of months. if (isSameDay(startDate, startOfMonth(startDate))) { let startMonth = startDate; let endMonth = endDate; if (startDate.calendar.getFormattableMonth) { startMonth = startDate.calendar.getFormattableMonth(startDate); } if (endDate.calendar.getFormattableMonth) { endMonth = endDate.calendar.getFormattableMonth(endDate); } if (isSameDay(endDate, endOfMonth(startDate))) { return monthFormatter.format(startMonth.toDate(timeZone)); } else if (isSameDay(endDate, endOfMonth(endDate))) { return isAria ? formatRange(monthFormatter, stringFormatter, startMonth, endMonth, timeZone) : monthFormatter.formatRange(startMonth.toDate(timeZone), endMonth.toDate(timeZone)); } } return isAria ? formatRange(dateFormatter, stringFormatter, startDate, endDate, timeZone) : dateFormatter.formatRange(startDate.toDate(timeZone), endDate.toDate(timeZone)); }, [startDate, endDate, monthFormatter, dateFormatter, stringFormatter, timeZone, isAria]); } function formatRange(dateFormatter: DateFormatter, stringFormatter: LocalizedStringFormatter, start: CalendarDate, end: CalendarDate, timeZone: string) { let parts = dateFormatter.formatRangeToParts(start.toDate(timeZone), end.toDate(timeZone)); // Find the separator between the start and end date. This is determined // by finding the last shared literal before the end range. let separatorIndex = -1; for (let i = 0; i < parts.length; i++) { let part = parts[i]; if (part.source === 'shared' && part.type === 'literal') { separatorIndex = i; } else if (part.source === 'endRange') { break; } } // Now we can combine the parts into start and end strings. let startValue = ''; let endValue = ''; for (let i = 0; i < parts.length; i++) { if (i < separatorIndex) { startValue += parts[i].value; } else if (i > separatorIndex) { endValue += parts[i].value; } } return stringFormatter.format('dateRange', {startDate: startValue, endDate: endValue}); }