Business Logic Patterns
Common patterns for implementing business logic with useTemporal.
Business Days
Checking Business Days
typescript
import { isWeekend, isWeekday } from '@allystudio/usetemporal'
function isBusinessDay(period: Period, holidays: Date[] = []): boolean {
// Not a business day if weekend
if (isWeekend(period)) return false
// Check if it's a holiday
const isHoliday = holidays.some(holiday =>
isSame(temporal.adapter,
temporal.period( holiday, 'day'),
period,
'day'
)
)
return !isHoliday
}Counting Business Days
typescript
function countBusinessDays(
period: Period,
temporal: Temporal,
holidays: Date[] = []
): number {
const days = divide(temporal.adapter, period, 'day')
return days.filter(day => isBusinessDay(day, holidays)).length
}
// Usage
const thisMonth = usePeriod(temporal, 'month')
const businessDays = countBusinessDays(thisMonth.value, temporal)Next Business Day
typescript
function nextBusinessDay(
day: Period,
temporal: Temporal,
holidays: Date[] = []
): Period {
let nextDay = next(temporal.adapter, day)
while (!isBusinessDay(nextDay, holidays)) {
nextDay = next(temporal.adapter, nextDay)
}
return nextDay
}Business Days Between Dates
typescript
function businessDaysBetween(
start: Date,
end: Date,
temporal: Temporal,
holidays: Date[] = []
): number {
const startDay = temporal.period( start, 'day')
const endDay = temporal.period( end, 'day')
let count = 0
let current = startDay
while (current.start <= endDay.start) {
if (isBusinessDay(current, holidays)) {
count++
}
current = next(temporal.adapter, current)
}
return count
}Time Slots
Generate Time Slots
typescript
function generateTimeSlots(
day: Period,
temporal: Temporal,
options: {
startHour: number
endHour: number
slotDuration: number // minutes
breakStart?: number
breakEnd?: number
}
): Period[] {
const hours = divide(temporal.adapter, day, 'hour')
const slots: Period[] = []
hours.forEach(hour => {
const h = hour.date.getHours()
// Skip hours outside business hours
if (h < options.startHour || h >= options.endHour) return
// Skip break hours
if (options.breakStart && options.breakEnd &&
h >= options.breakStart && h < options.breakEnd) return
// Generate slots within the hour
if (options.slotDuration === 60) {
slots.push(hour)
} else {
const minutes = divide(temporal.adapter, hour, 'minute')
for (let i = 0; i < 60; i += options.slotDuration) {
const slotStart = minutes[i]
const slotEnd = minutes[Math.min(i + options.slotDuration - 1, 59)]
slots.push({
type: 'custom',
date: slotStart.date,
start: slotStart.start,
end: slotEnd.end
})
}
}
})
return slots
}
// Usage
const today = usePeriod(temporal, 'day')
const slots = generateTimeSlots(today.value, temporal, {
startHour: 9,
endHour: 17,
slotDuration: 30,
breakStart: 12,
breakEnd: 13
})Available Slots with Bookings
typescript
interface Booking {
start: Date
end: Date
}
function getAvailableSlots(
day: Period,
temporal: Temporal,
bookings: Booking[],
slotDuration = 30
): Period[] {
const allSlots = generateTimeSlots(day, temporal, {
startHour: 9,
endHour: 17,
slotDuration
})
return allSlots.filter(slot => {
// Check if slot overlaps with any booking
const overlaps = bookings.some(booking =>
(slot.start < booking.end && slot.end > booking.start)
)
return !overlaps
})
}Recurring Events
Generate Recurring Dates
typescript
type RecurrencePattern =
| { type: 'daily', interval: number }
| { type: 'weekly', interval: number, daysOfWeek: number[] }
| { type: 'monthly', interval: number, dayOfMonth: number }
| { type: 'yearly', interval: number }
function generateRecurringDates(
start: Date,
pattern: RecurrencePattern,
count: number,
temporal: Temporal
): Date[] {
const dates: Date[] = [start]
let current = temporal.period( start, 'day')
for (let i = 1; i < count; i++) {
switch (pattern.type) {
case 'daily':
current = go(temporal.adapter, current, pattern.interval)
break
case 'weekly':
// Find next occurrence based on days of week
do {
current = next(temporal.adapter, current)
} while (!pattern.daysOfWeek.includes(current.date.getDay()))
break
case 'monthly':
// Navigate to same day next month
const monthPeriod = temporal.period( current.date, 'month')
const nextMonth = go(temporal.adapter, monthPeriod, pattern.interval)
const targetDate = new Date(nextMonth.date)
targetDate.setDate(pattern.dayOfMonth)
current = temporal.period( targetDate, 'day')
break
case 'yearly':
current = go(temporal.adapter, current, 365 * pattern.interval)
break
}
dates.push(current.date)
}
return dates
}Working Hours
Calculate Working Hours
typescript
function calculateWorkingHours(
period: Period,
temporal: Temporal,
options = {
startHour: 9,
endHour: 17,
excludeWeekends: true,
holidays: [] as Date[]
}
): number {
const days = divide(temporal.adapter, period, 'day')
let totalHours = 0
days.forEach(day => {
// Skip weekends if configured
if (options.excludeWeekends && isWeekend(day)) return
// Skip holidays
const isHoliday = options.holidays.some(holiday =>
isSame(temporal.adapter, temporal.period( holiday, 'day'), day, 'day')
)
if (isHoliday) return
// Add working hours for this day
totalHours += (options.endHour - options.startHour)
})
return totalHours
}Time Until Deadline
typescript
function timeUntilDeadline(
deadline: Date,
temporal: Temporal,
options = {
workingHoursOnly: false,
startHour: 9,
endHour: 17
}
): {
days: number
hours: number
minutes: number
isOverdue: boolean
} {
const now = temporal.now.value.date
const diff = deadline.getTime() - now.getTime()
if (!options.workingHoursOnly) {
// Simple calculation
const days = Math.floor(diff / (24 * 60 * 60 * 1000))
const hours = Math.floor((diff % (24 * 60 * 60 * 1000)) / (60 * 60 * 1000))
const minutes = Math.floor((diff % (60 * 60 * 1000)) / (60 * 1000))
return { days, hours, minutes, isOverdue: diff < 0 }
}
// Calculate working time only
const deadlinePeriod = temporal.period( deadline, 'day')
const todayPeriod = temporal.period( now, 'day')
const workingHours = calculateWorkingHours(
{ start: now, end: deadline, type: 'custom', date: now },
temporal,
options
)
const days = Math.floor(workingHours / 8)
const hours = workingHours % 8
return {
days,
hours,
minutes: 0,
isOverdue: diff < 0
}
}Fiscal Periods
Fiscal Year Calculations
typescript
// Define fiscal year (e.g., starts July 1)
const FISCAL_YEAR_START_MONTH = 6 // July (0-indexed)
function getFiscalYear(date: Date): number {
const year = date.getFullYear()
const month = date.getMonth()
// If before July, fiscal year is previous calendar year
return month < FISCAL_YEAR_START_MONTH ? year - 1 : year
}
function getFiscalQuarter(date: Date): number {
const month = date.getMonth()
const fiscalMonth = (month - FISCAL_YEAR_START_MONTH + 12) % 12
return Math.floor(fiscalMonth / 3) + 1
}
function createFiscalYearPeriod(
year: number,
temporal: Temporal
): Period {
const start = new Date(year, FISCAL_YEAR_START_MONTH, 1)
const end = new Date(year + 1, FISCAL_YEAR_START_MONTH, 0, 23, 59, 59, 999)
return {
type: 'custom',
date: start,
start,
end
}
}See Also
- divide() Pattern - Core time division concepts
- Navigation Patterns - Time navigation
- Business Days Recipe - Complete implementation
- Time Slots Recipe - Scheduling example