@prefecthq/prefect-design
Version:
A collection of low-level Vue components.
113 lines (88 loc) • 3.88 kB
text/typescript
import { format, isSameDay, startOfDay, endOfDay, Duration, isSameYear } from 'date-fns'
import { secondsInDay, secondsInHour, secondsInMinute, secondsInWeek } from 'date-fns/constants'
import { DateRangeSelectAroundValue, DateRangeSelectPeriodValue, DateRangeSelectRangeValue, DateRangeSelectSpanValue, DateRangeSelectValue } from '@/types'
import { toPluralString } from '@/utilities'
type DateRange = {
startDate: Date,
endDate: Date,
}
const dateFormat = 'MMM do'
const dateAndYearFormat = 'MMM do, yyyy'
const timeFormat = 'hh:mm a'
const dateTimeFormat = `${dateFormat} 'at' ${timeFormat}`
const dateTimeAndYearFormat = `${dateAndYearFormat} 'at' ${timeFormat}`
export function getDateRangeSelectValueLabel(value: DateRangeSelectValue): string | null {
if (!value) {
return null
}
const { type } = value
switch (type) {
case 'span':
return getDateSpanLabel(value)
case 'range':
return getDateRangeLabel(value)
case 'around':
return getDateAroundLabel(value)
case 'period':
return getPeriodLabel(value)
default:
const exhaustive: never = type
throw new Error(`Label not found for date range type: ${exhaustive}`)
}
}
function getDateSpanLabel({ seconds }: DateRangeSelectSpanValue): string {
const absSeconds = Math.abs(seconds)
const duration: Duration = {
weeks: Math.floor(absSeconds / secondsInWeek),
days: Math.floor(absSeconds % secondsInWeek / secondsInDay),
hours: Math.floor(absSeconds % secondsInDay / secondsInHour),
minutes: Math.floor(absSeconds % secondsInHour / secondsInMinute),
seconds: Math.floor(absSeconds % secondsInMinute),
}
const reduced = Object.entries(duration).reduce<string[]>((durations, [key, value]) => {
if (value) {
const unit = key.slice(0, -1)
if (value === 1) {
durations.push(unit)
return durations
}
durations.push(`${value} ${toPluralString(unit, value)}`)
}
return durations
}, [])
const direction = seconds < 0 ? 'Past' : 'Next'
return `${direction} ${reduced.join(' ')}`
}
function getDateRangeLabel({ startDate, endDate }: DateRangeSelectRangeValue): string {
if (isPickerSingleDayRange({ startDate, endDate })) {
const startDateFormat = isSameYear(startDate, new Date()) ? dateFormat : dateAndYearFormat
return format(startDate, startDateFormat)
}
if (isFullDateRange({ startDate, endDate })) {
const startDateFormat = isSameYear(startDate, new Date()) ? dateFormat : dateAndYearFormat
const endDateFormat = isSameYear(endDate, new Date()) ? dateFormat : dateAndYearFormat
return `${format(startDate, startDateFormat)} - ${format(endDate, endDateFormat)}`
}
const startDateFormat = isSameYear(startDate, new Date()) ? dateTimeFormat : dateTimeAndYearFormat
const endDateFormat = isSameYear(endDate, new Date()) ? dateTimeFormat : dateTimeAndYearFormat
return `${format(startDate, startDateFormat)} - ${format(endDate, endDateFormat)}`
}
function getDateAroundLabel({ date, quantity, unit }: DateRangeSelectAroundValue): string {
const dateString = isStartOfDay(date) ? format(date, dateAndYearFormat) : format(date, dateTimeAndYearFormat)
return `${quantity} ${toPluralString(unit, quantity)} around ${dateString}`
}
function getPeriodLabel({ period }: DateRangeSelectPeriodValue): string {
return period
}
function isStartOfDay(date: Date): boolean {
return startOfDay(date).getTime() === date.getTime()
}
function isEndOfDay(date: Date): boolean {
return endOfDay(date).getTime() === date.getTime()
}
function isPickerSingleDayRange({ startDate, endDate }: DateRange): boolean {
return isFullDateRange({ startDate, endDate }) && isSameDay(startDate, endDate)
}
export function isFullDateRange({ startDate, endDate }: DateRange): boolean {
return isStartOfDay(startDate) && isEndOfDay(endDate)
}