simple-slots
v1.0.1
Published
Simple and lightweight time slot calculator for booking systems
Maintainers
Readme
simple-slots
Simple and lightweight time slot calculator for booking systems
A zero-dependency, lightweight library for calculating available time slots in booking and reservation systems. Perfect for appointment scheduling, meeting room booking, and service reservations.
✨ Features
- 🚀 Zero dependencies - Pure JavaScript, no external libraries
- 📦 Lightweight - Only 3KB gzipped
- ⚡ Fast - Optimized algorithms for quick calculations
- 🎯 Simple API - Easy to use with minimal configuration
- 📅 Business hours - Support for complex business hour rules
- 🚫 Conflict detection - Automatic overlap checking with existing bookings
- ⏰ Buffer times - Configurable preparation and cleanup time
- 🕐 Flexible intervals - Any time interval (15min, 30min, 1hour, etc.)
- 📱 Framework agnostic - Works with React, Vue, Angular, or vanilla JS
- 💯 TypeScript - Full TypeScript support with type definitions
🚀 Quick Start
Installation
npm install simple-slotsBasic Usage
import { calculateAvailableSlots } from 'simple-slots';
const availableSlots = calculateAvailableSlots({
startDate: '2024-01-15',
endDate: '2024-01-21',
timeRange: { start: '09:00', end: '17:00' },
interval: 30, // 30-minute slots
businessHours: [
{ day: 1, start: '09:00', end: '17:00' }, // Monday
{ day: 2, start: '09:00', end: '17:00' }, // Tuesday
{ day: 3, start: '09:00', end: '17:00' }, // Wednesday
{ day: 4, start: '09:00', end: '17:00' }, // Thursday
{ day: 5, start: '09:00', end: '17:00' } // Friday
],
existingBookings: [
{
start: '2024-01-15T14:00:00',
end: '2024-01-15T15:00:00'
}
],
serviceDuration: 60, // 1-hour service
bufferTime: 15, // 15-minute buffer
minNoticeHours: 24 // 24-hour advance booking required
});
console.log(availableSlots);
// Output: Array of available time slots
// [
// { date: '2024-01-15', time: '09:00', datetime: Date, status: 'available' },
// { date: '2024-01-15', time: '09:30', datetime: Date, status: 'available' },
// ...
// ]📚 API Reference
calculateAvailableSlots(config)
Calculates available time slots based on the provided configuration.
Parameters:
config(Object) - Configuration object
Configuration Options:
| Option | Type | Required | Description |
|--------|------|----------|-------------|
| startDate | string | ✅ | Start date in YYYY-MM-DD format |
| endDate | string | ✅ | End date in YYYY-MM-DD format |
| timeRange | object | ✅ | { start: 'HH:MM', end: 'HH:MM' } |
| interval | number | ✅ | Time slot interval in minutes |
| businessHours | array | ✅ | Array of business hour objects |
| existingBookings | array | ❌ | Array of existing booking objects |
| serviceDuration | number | ❌ | Service duration in minutes |
| bufferTime | number | ❌ | Buffer time in minutes |
| minNoticeHours | number | ❌ | Minimum advance booking hours |
| maxAdvanceDays | number | ❌ | Maximum advance booking days |
Business Hours Format:
{
day: number, // 0 = Sunday, 1 = Monday, ..., 6 = Saturday
start: string, // 'HH:MM' format
end: string // 'HH:MM' format
}Existing Booking Format:
{
start: string, // ISO date string
end: string // ISO date string
}Returns:
Array of slot objects:
{
date: string, // 'YYYY-MM-DD'
time: string, // 'HH:MM'
datetime: Date, // JavaScript Date object
status: string // 'available'
}generateTimeSlots(config)
Generates all possible time slots without filtering.
import { generateTimeSlots } from 'simple-slots';
const allSlots = generateTimeSlots({
startDate: '2024-01-15',
endDate: '2024-01-15',
timeRange: { start: '09:00', end: '17:00' },
interval: 30,
businessHours: [
{ day: 1, start: '09:00', end: '17:00' }
]
});filterAvailableSlots(slots, filters)
Filters time slots based on availability criteria.
import { filterAvailableSlots } from 'simple-slots';
const availableSlots = filterAvailableSlots(allSlots, {
existingBookings: [...],
serviceDuration: 60,
bufferTime: 15,
minNoticeHours: 24
});📖 Examples
Healthcare Appointment System
const doctorSlots = calculateAvailableSlots({
startDate: '2024-02-01',
endDate: '2024-02-07',
timeRange: { start: '08:00', end: '18:00' },
interval: 15,
businessHours: [
{ day: 1, start: '08:00', end: '12:00' }, // Monday morning
{ day: 1, start: '13:00', end: '18:00' }, // Monday afternoon
{ day: 2, start: '08:00', end: '18:00' }, // Tuesday
{ day: 3, start: '08:00', end: '18:00' }, // Wednesday
{ day: 4, start: '08:00', end: '18:00' }, // Thursday
{ day: 5, start: '08:00', end: '16:00' } // Friday (shorter day)
],
existingBookings: [
{ start: '2024-02-01T10:00:00', end: '2024-02-01T10:30:00' },
{ start: '2024-02-01T15:00:00', end: '2024-02-01T16:00:00' }
],
serviceDuration: 30, // 30-minute appointments
bufferTime: 5, // 5-minute buffer between appointments
minNoticeHours: 2 // 2-hour advance booking
});Beauty Salon Booking
const salonSlots = calculateAvailableSlots({
startDate: '2024-02-15',
endDate: '2024-02-22',
timeRange: { start: '10:00', end: '20:00' },
interval: 60,
businessHours: [
{ day: 1, start: '10:00', end: '19:00' }, // Mon
{ day: 2, start: '10:00', end: '20:00' }, // Tue
{ day: 3, start: '10:00', end: '20:00' }, // Wed
{ day: 4, start: '10:00', end: '20:00' }, // Thu
{ day: 5, start: '10:00', end: '20:00' }, // Fri
{ day: 6, start: '09:00', end: '18:00' } // Sat
// Sunday closed
],
serviceDuration: 120, // 2-hour service
bufferTime: 30, // 30-minute cleanup
minNoticeHours: 48 // 48-hour advance booking
});Meeting Room Booking
const meetingRoomSlots = calculateAvailableSlots({
startDate: '2024-02-20',
endDate: '2024-02-20',
timeRange: { start: '08:00', end: '18:00' },
interval: 30,
businessHours: [
{ day: 2, start: '08:00', end: '18:00' } // Tuesday
],
existingBookings: [
{ start: '2024-02-20T09:00:00', end: '2024-02-20T10:30:00' }, // Morning meeting
{ start: '2024-02-20T14:00:00', end: '2024-02-20T15:00:00' } // Afternoon meeting
],
serviceDuration: 60, // 1-hour meetings
bufferTime: 15, // 15-minute setup/cleanup
minNoticeHours: 1 // 1-hour advance booking
});🔧 Framework Integration
React Hook
import { useState, useEffect } from 'react';
import { calculateAvailableSlots } from 'simple-slots';
function useAvailableSlots(config) {
const [slots, setSlots] = useState([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
setLoading(true);
const availableSlots = calculateAvailableSlots(config);
setSlots(availableSlots);
setLoading(false);
}, [config]);
return { slots, loading };
}
function BookingCalendar() {
const { slots, loading } = useAvailableSlots({
startDate: '2024-01-15',
endDate: '2024-01-21',
timeRange: { start: '09:00', end: '17:00' },
interval: 30,
businessHours: [
{ day: 1, start: '09:00', end: '17:00' }
]
});
if (loading) return <div>Loading...</div>;
return (
<div>
{slots.map(slot => (
<div key={`${slot.date}-${slot.time}`}>
{slot.date} {slot.time}
</div>
))}
</div>
);
}Vue.js Composition API
<template>
<div>
<div v-if="loading">Loading...</div>
<div v-else>
<div v-for="slot in slots" :key="`${slot.date}-${slot.time}`">
{{ slot.date }} {{ slot.time }}
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue';
import { calculateAvailableSlots } from 'simple-slots';
const loading = ref(false);
const slots = computed(() => {
loading.value = true;
const result = calculateAvailableSlots({
startDate: '2024-01-15',
endDate: '2024-01-21',
timeRange: { start: '09:00', end: '17:00' },
interval: 30,
businessHours: [
{ day: 1, start: '09:00', end: '17:00' }
]
});
loading.value = false;
return result;
});
</script>Express.js API
const express = require('express');
const { calculateAvailableSlots } = require('simple-slots');
const app = express();
app.get('/api/available-slots', async (req, res) => {
try {
const { startDate, endDate, serviceId } = req.query;
// Get configuration from database
const service = await getService(serviceId);
const businessHours = await getBusinessHours();
const existingBookings = await getBookings(startDate, endDate);
const slots = calculateAvailableSlots({
startDate,
endDate,
timeRange: { start: '09:00', end: '17:00' },
interval: 30,
businessHours,
existingBookings,
serviceDuration: service.duration,
bufferTime: service.bufferTime,
minNoticeHours: 24
});
res.json({ slots, count: slots.length });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
app.listen(3000);🎯 Performance Tips
Memoization
const memoize = (fn) => {
const cache = new Map();
return (...args) => {
const key = JSON.stringify(args);
if (cache.has(key)) return cache.get(key);
const result = fn(...args);
cache.set(key, result);
return result;
};
};
const memoizedCalculateSlots = memoize(calculateAvailableSlots);Batch Processing
function calculateSlotsForMonth(year, month, config) {
const results = {};
const daysInMonth = new Date(year, month + 1, 0).getDate();
for (let day = 1; day <= daysInMonth; day++) {
const date = `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
results[date] = calculateAvailableSlots({
...config,
startDate: date,
endDate: date
});
}
return results;
}🔍 Error Handling
import { calculateAvailableSlots } from 'simple-slots';
function safeCalculateSlots(config) {
try {
// Validate configuration
if (new Date(config.startDate) > new Date(config.endDate)) {
return { error: 'Start date must be before end date', slots: [] };
}
if (!config.businessHours?.length) {
return { error: 'Business hours must be provided', slots: [] };
}
const slots = calculateAvailableSlots(config);
return { error: null, slots };
} catch (error) {
return { error: error.message, slots: [] };
}
}
const result = safeCalculateSlots(config);
if (result.error) {
console.error('Error:', result.error);
} else {
console.log('Available slots:', result.slots);
}🧪 Testing
import { calculateAvailableSlots } from 'simple-slots';
describe('calculateAvailableSlots', () => {
test('should return available slots for a single day', () => {
const slots = calculateAvailableSlots({
startDate: '2024-01-15',
endDate: '2024-01-15',
timeRange: { start: '10:00', end: '12:00' },
interval: 30,
businessHours: [
{ day: 1, start: '10:00', end: '12:00' }
]
});
expect(slots).toHaveLength(4);
expect(slots[0]).toMatchObject({
date: '2024-01-15',
time: '10:00',
status: 'available'
});
});
test('should filter out overlapping bookings', () => {
const slots = calculateAvailableSlots({
startDate: '2024-01-15',
endDate: '2024-01-15',
timeRange: { start: '10:00', end: '12:00' },
interval: 30,
businessHours: [
{ day: 1, start: '10:00', end: '12:00' }
],
existingBookings: [
{ start: '2024-01-15T10:30:00', end: '2024-01-15T11:30:00' }
],
serviceDuration: 30
});
expect(slots).toHaveLength(2); // 10:00 and 11:30
});
});📊 Browser Support
- Chrome 90+
- Firefox 88+
- Safari 14+
- Edge 90+
🤝 Contributing
We welcome contributions! Please see our Contributing Guide for details.
Development Setup
# Clone the repository
git clone https://github.com/TomonariIkeda/simple-slots.git
cd simple-slots
# Install dependencies
npm install
# Run tests
npm test
# Build the package
npm run build
# Check bundle size
npm run size📄 License
MIT © Tomonari Ikeda
