The divide() Pattern
The divide() pattern is the revolutionary core feature that sets useTemporal apart from every other date library. It enables hierarchical time subdivision with perfect synchronization between parent and child time units.
What Makes It Revolutionary
Unlike traditional date libraries that treat time as isolated points, useTemporal introduces a hierarchical time model where any time period can be subdivided into smaller units while maintaining reactive synchronization.
Why This Matters
Traditional approaches require manual calculation and state management for time subdivisions. The divide() pattern automates this with a single, elegant function call.
Core Concept
Think of time as a tree structure where each node can be subdivided into smaller units:
graph TD
Y[Year 2024] --> M1[January]
Y --> M2[February]
Y --> M3[March]
M1 --> W1[Week 1]
M1 --> W2[Week 2]
M1 --> W3[Week 3]
M1 --> W4[Week 4]
W1 --> D1[Day 1]
W1 --> D2[Day 2]
W1 --> D3[Day 3]
D1 --> H1[Hour 0]
D1 --> H2[Hour 1]
D1 --> H3[Hour 2]Basic Usage
Level 1: Pure Functions
import { period, divide } from '@allystudio/usetemporal/operations'
import { createNativeAdapter } from '@allystudio/usetemporal/native'
const adapter = createNativeAdapter()
// Create a year period
const year = period(adapter, new Date(), 'year')
// Divide year into months - returns exactly 12 month periods
const months = divide(adapter, year, 'month')
console.log(months.length) // 12
// Each month is a Period object
const january = months[0]
console.log(january.date) // Date object for January 1st
console.log(january.start) // Start of January
console.log(january.end) // End of January
// Continue dividing - get all days in January
const days = divide(adapter, january, 'day')
console.log(days.length) // 31 (for January)Level 2: Builder API
import { createTemporal } from '@allystudio/usetemporal'
import { createNativeAdapter } from '@allystudio/usetemporal/native'
const temporal = createTemporal({
adapter: createNativeAdapter(),
date: new Date()
})
// Create and divide using builder methods
const year = temporal.period(new Date(), 'year')
const months = temporal.divide(year, 'month')
console.log(months.length) // 12
// Continue dividing
const days = temporal.divide(months[0], 'day')
console.log(days.length) // 31Level 3: Reactive Composables
import { computed } from '@vue/reactivity'
import { createTemporal, usePeriod } from '@allystudio/usetemporal'
import { divide } from '@allystudio/usetemporal/operations'
import { createNativeAdapter } from '@allystudio/usetemporal/native'
const temporal = createTemporal({
adapter: createNativeAdapter(),
date: new Date()
})
// Get the current year as a reactive period
const year = usePeriod(temporal, 'year')
// Reactive subdivision
const months = computed(() => divide(temporal.adapter, year.value, 'month'))
console.log(months.value.length) // 12Key Features
1. Infinite Subdivision
You can divide time units as deep as needed, creating a complete hierarchy:
import { period, divide } from '@allystudio/usetemporal/operations'
import { createNativeAdapter } from '@allystudio/usetemporal/native'
const adapter = createNativeAdapter()
const year = period(adapter, new Date(), 'year')
// Level 1: Year → Months
const months = divide(adapter, year, 'month')
// Level 2: Month → Days
const days = divide(adapter, months[0], 'day')
// Level 3: Day → Hours
const hours = divide(adapter, days[0], 'hour')
// Level 4: Hour → Minutes
const minutes = divide(adapter, hours[0], 'minute')
// Level 5: Minute → Seconds
const seconds = divide(adapter, minutes[0], 'second')2. Perfect Synchronization
All subdivisions automatically update when their parent changes:
import { watch } from '@vue/reactivity'
const month = usePeriod(temporal, 'month')
// Create reactive subdivisions
const days = computed(() => divide(temporal.adapter, month.value, 'day'))
// Initial state - January has 31 days
console.log(days.value.length) // 31
// Navigate to February
temporal.browsing.value = next(temporal.adapter, month.value)
// Days automatically update!
console.log(days.value.length) // 28 or 293. Type-Safe Operations
Every operation is fully type-safe:
import { divide } from '@allystudio/usetemporal/operations'
import { createNativeAdapter } from '@allystudio/usetemporal/native'
const adapter = createNativeAdapter()
// TypeScript knows these are valid units
const validDays = divide(adapter, month, 'day') // ✅
const validHours = divide(adapter, day, 'hour') // ✅
// TypeScript catches invalid operations
// const invalid = divide(adapter, month, 'invalid') // ❌ Type errorReal-World Examples
Building a Calendar View
import { createTemporal, usePeriod, divide } from '@allystudio/usetemporal'
import { createNativeAdapter } from '@allystudio/usetemporal/native'
function createCalendarMonth(temporal: Temporal) {
const month = usePeriod(temporal, 'month')
const days = computed(() => divide(temporal.adapter, month.value, 'day'))
const calendarGrid = computed(() => {
const allDays = days.value
const firstDay = allDays[0]
const startPadding = firstDay.date.getDay()
// Create calendar grid with padding
const grid = []
// Add empty cells for days before month starts
for (let i = 0; i < startPadding; i++) {
grid.push(null)
}
// Add all days of the month
allDays.forEach(day => {
grid.push({
number: day.date.getDate(),
isToday: isSame(temporal.adapter, day.date, new Date(), 'day'),
isWeekend: day.date.getDay() === 0 || day.date.getDay() === 6,
period: day
})
})
// Group into weeks
const weeks = []
for (let i = 0; i < grid.length; i += 7) {
weeks.push(grid.slice(i, i + 7))
}
return weeks
})
return { month, days, calendarGrid }
}Creating a Year Heatmap
function createYearHeatmap(temporal: Temporal, data: Map<string, number>) {
const year = usePeriod(temporal, 'year')
const months = computed(() => divide(temporal.adapter, year.value, 'month'))
const heatmap = computed(() =>
months.value.map(month => {
const days = divide(temporal.adapter, month, 'day')
return {
month: month.date.toLocaleDateString('en', { month: 'long' }),
weeks: groupIntoWeeks(days.map(day => ({
date: day.date,
value: data.get(day.date.toISOString().split('T')[0]) || 0,
weekDay: day.date.getDay()
})))
}
})
)
return { year, heatmap }
}Building a Time Picker
function createTimeSlots(temporal: Temporal, slotDuration = 30) {
const day = usePeriod(temporal, 'day')
const hours = computed(() => divide(temporal.adapter, day.value, 'hour'))
const timeSlots = computed(() => {
const slots = []
// Business hours only (9 AM - 5 PM)
const businessHours = hours.value.filter(hour => {
const h = hour.date.getHours()
return h >= 9 && h < 17
})
businessHours.forEach(hour => {
const minutes = divide(temporal.adapter, hour, 'minute')
// Create slots every 30 minutes
for (let i = 0; i < 60; i += slotDuration) {
const minute = minutes[i]
slots.push({
time: `${hour.date.getHours().toString().padStart(2, '0')}:${i.toString().padStart(2, '0')}`,
hour: hour.date.getHours(),
minute: i,
isPast: minute.date < new Date(),
isFuture: minute.date > new Date(),
period: minute
})
}
})
return slots
})
return { day, timeSlots }
}Advanced Patterns
Hierarchical Navigation
Create complex navigation structures with ease:
import { ref, computed } from '@vue/reactivity'
interface DateNavigator {
year: ComputedRef<Period>
months: ComputedRef<Period[]>
currentMonth: Ref<Period>
days: ComputedRef<Period[]>
selectMonth(index: number): void
selectDay(day: Period): void
goToToday(): void
}
function createDateNavigator(temporal: Temporal): DateNavigator {
const year = usePeriod(temporal, 'year')
const months = computed(() => divide(temporal.adapter, year.value, 'month'))
const currentMonth = ref(months.value[new Date().getMonth()])
const days = computed(() =>
divide(temporal.adapter, currentMonth.value, 'day')
)
return {
year,
months,
currentMonth,
days,
selectMonth(index: number) {
currentMonth.value = months.value[index]
temporal.browsing.value = months.value[index]
},
selectDay(day: Period) {
temporal.browsing.value = day
},
goToToday() {
temporal.browsing.value = temporal.period(new Date(), 'day')
}
}
}Comparative Analysis
Compare multiple time periods easily:
function compareYears(year1: number, year2: number) {
const temp1 = createTemporal({
adapter: createNativeAdapter(),
date: new Date(year1, 0, 1)
})
const temp2 = createTemporal({
adapter: createNativeAdapter(),
date: new Date(year2, 0, 1)
})
const y1 = usePeriod(temp1, 'year')
const y2 = usePeriod(temp2, 'year')
const months1 = divide(temp1.adapter, y1.value, 'month')
const months2 = divide(temp2.adapter, y2.value, 'month')
return months1.map((month1, index) => {
const month2 = months2[index]
const days1 = divide(temp1.adapter, month1, 'day')
const days2 = divide(temp2.adapter, month2, 'day')
return {
month: month1.date.toLocaleDateString('en', { month: 'long' }),
year1Days: days1.length,
year2Days: days2.length,
difference: days1.length - days2.length
}
})
}Working with Standard Units
The divide() pattern works seamlessly with all standard time units:
import { period, divide } from '@allystudio/usetemporal/operations'
import { createNativeAdapter } from '@allystudio/usetemporal/native'
const adapter = createNativeAdapter()
// Year divided into quarters
const year = period(adapter, new Date(), 'year')
const quarters = divide(adapter, year, 'quarter') // 4 quarters
// Each quarter can be further divided into months
const q1Months = divide(adapter, quarters[0], 'month') // 3 months
// And months into days
const janDays = divide(adapter, q1Months[0], 'day') // ~31 daysCustom Time Periods
For custom time ranges, use the period() function with start/end dates:
const customPeriod = period(adapter, {
start: new Date(2024, 0, 1),
end: new Date(2024, 2, 31)
})
const days = divide(adapter, customPeriod, 'day') // ~90 daysPerformance Optimization
Lazy Evaluation
Subdivisions are created on-demand for optimal performance:
const year = usePeriod(temporal, 'year')
const months = divide(temporal.adapter, year.value, 'month')
// Only 12 Period objects created
// Days are created only when accessed
const januaryDays = divide(temporal.adapter, months[0], 'day') // 31 objects
const februaryDays = divide(temporal.adapter, months[1], 'day') // 28/29 objectsEfficient Updates
When using computed properties, subdivisions update efficiently:
const month = usePeriod(temporal, 'month')
const days = computed(() => divide(temporal.adapter, month.value, 'day'))
// Navigate to next month
temporal.browsing.value = next(temporal.adapter, month.value)
// Only the days array is recalculated, not the entire time treeBest Practices
1. Use Computed for Reactive Subdivisions
// ✅ Good: Reactive subdivisions
const month = usePeriod(temporal, 'month')
const days = computed(() => divide(temporal.adapter, month.value, 'day'))
// ❌ Avoid: Manual subdivision
let days = divide(temporal.adapter, month.value, 'day')
watch(month, () => {
days = divide(temporal.adapter, month.value, 'day')
})2. Cache Static Subdivisions
// ✅ Good: Cache subdivisions that don't change
const year2024 = temporal.period(new Date(2024, 0, 1), 'year')
const months2024 = divide(temporal.adapter, year2024, 'month')
// Use cached months multiple times
const januaryDays = divide(temporal.adapter, months2024[0], 'day')
const februaryDays = divide(temporal.adapter, months2024[1], 'day')3. Use Appropriate Granularity
// ✅ Good: Only divide to the level you need
const hours = divide(temporal.adapter, day, 'hour')
// ❌ Avoid: Unnecessary deep subdivision
const seconds = hours
.flatMap(hour => divide(temporal.adapter, hour, 'minute'))
.flatMap(minute => divide(temporal.adapter, minute, 'second'))Framework Integration
The divide() pattern works seamlessly across all frameworks:
<template>
<div class="calendar">
<div v-for="(week, i) in weeks" :key="i" class="week">
<div
v-for="(day, j) in week"
:key="j"
class="day"
:class="{
today: day && isSame(temporal.adapter, day.date, new Date(), 'day'),
empty: !day
}"
>
{{ day?.date.getDate() }}
</div>
</div>
</div>
</template>
<script setup>
import { computed } from 'vue'
import { createTemporal, usePeriod, divide, isSame } from '@allystudio/usetemporal'
import { createNativeAdapter } from '@allystudio/usetemporal/native'
const temporal = createTemporal({
adapter: createNativeAdapter()
})
const month = usePeriod(temporal, 'month')
const days = computed(() => divide(temporal.adapter, month.value, 'day'))
const weeks = computed(() => {
const allDays = days.value
const firstDay = allDays[0]
const startPadding = (firstDay.date.getDay() + 6) % 7 // Monday = 0
const weeks = []
let week = Array(startPadding).fill(null)
allDays.forEach(day => {
week.push(day)
if (week.length === 7) {
weeks.push(week)
week = []
}
})
if (week.length > 0) {
weeks.push([...week, ...Array(7 - week.length).fill(null)])
}
return weeks
})
</script>import { useMemo } from 'react'
import { createTemporal, usePeriod, divide, isSame } from '@allystudio/usetemporal'
import { createNativeAdapter } from '@allystudio/usetemporal/native'
function Calendar() {
const temporal = useMemo(() => createTemporal({
adapter: createNativeAdapter()
}), [])
const month = usePeriod(temporal, 'month')
const days = useMemo(() =>
divide(temporal.adapter, month.value, 'day'),
[month.value]
)
const weeks = useMemo(() => {
const firstDay = days[0]
const startPadding = (firstDay.date.getDay() + 6) % 7
const weeks = []
let week = Array(startPadding).fill(null)
days.forEach(day => {
week.push(day)
if (week.length === 7) {
weeks.push(week)
week = []
}
})
if (week.length > 0) {
weeks.push([...week, ...Array(7 - week.length).fill(null)])
}
return weeks
}, [days])
return (
<div className="calendar">
{weeks.map((week, i) => (
<div key={i} className="week">
{week.map((day, j) => (
<div
key={j}
className={`day ${
day && isSame(temporal.adapter, day.date, new Date(), 'day')
? 'today'
: ''
} ${!day ? 'empty' : ''}`}
>
{day?.date.getDate()}
</div>
))}
</div>
))}
</div>
)
}<script>
import { createTemporal, usePeriod, divide, isSame } from '@allystudio/usetemporal'
import { createNativeAdapter } from '@allystudio/usetemporal/native'
const temporal = createTemporal({
adapter: createNativeAdapter()
})
const month = usePeriod(temporal, 'month')
$: days = divide(temporal.adapter, $month, 'day')
$: firstDay = days[0]
$: startPadding = (firstDay.date.getDay() + 6) % 7
$: weeks = (() => {
const weeks = []
let week = Array(startPadding).fill(null)
days.forEach(day => {
week.push(day)
if (week.length === 7) {
weeks.push(week)
week = []
}
})
if (week.length > 0) {
weeks.push([...week, ...Array(7 - week.length).fill(null)])
}
return weeks
})()
</script>
<div class="calendar">
{#each weeks as week, i}
<div class="week">
{#each week as day, j}
<div
class="day"
class:today={day && isSame(temporal.adapter, day.date, new Date(), 'day')}
class:empty={!day}
>
{day?.date.getDate() || ''}
</div>
{/each}
</div>
{/each}
</div>Why divide() is Revolutionary
No Manual Calculations: Traditional libraries require you to calculate subdivisions manually. With divide(), it's automatic.
Reactive by Default: Changes propagate through the hierarchy automatically.
Type-Safe: Full TypeScript support ensures you can't divide incorrectly.
Framework Agnostic: Works identically across all JavaScript frameworks.
Extensible: Works with custom units just as well as built-in ones.
The divide() pattern transforms how we work with time in JavaScript, making complex time-based UIs simple and intuitive to build.