Skip to main content
userTourKit
@tour-kit/schedulingUtilities

Recurring Pattern Utilities

Recurring schedule utilities: match daily, weekly, monthly, and custom recurrence patterns for periodic content display

domidex01Published

Check if a date matches a recurring pattern for repeating schedules.

matchesRecurringPattern

Check if a date matches a recurring pattern.

import { matchesRecurringPattern } from '@tour-kit/scheduling'

const pattern = {
  type: 'weekly',
  daysOfWeek: [1, 3], // Monday and Wednesday
  interval: 1,
}

const matches = matchesRecurringPattern(
  new Date(),
  pattern,
  'UTC',
  '2024-01-01' // Start date
)

API

function matchesRecurringPattern(
  date: Date,
  pattern: RecurringPattern,
  timezone: string,
  startDate?: DateString | Date
): boolean

Parameters

  • date - The date to check
  • pattern - The recurring pattern configuration
  • timezone - Timezone for date calculations
  • startDate - Optional start date for interval calculations

Pattern Types

Daily

Occurs every day or every N days.

// Every day
const pattern = {
  type: 'daily',
  interval: 1,
}

// Every 3 days
const pattern = {
  type: 'daily',
  interval: 3,
}

Weekly

Occurs on specific days of the week.

// Every Monday and Wednesday
const pattern = {
  type: 'weekly',
  daysOfWeek: [1, 3],
}

// Every other week on Friday
const pattern = {
  type: 'weekly',
  daysOfWeek: [5],
  interval: 2,
}

Monthly

Occurs on a specific day of the month.

// First day of every month
const pattern = {
  type: 'monthly',
  dayOfMonth: 1,
}

// 15th of every other month
const pattern = {
  type: 'monthly',
  dayOfMonth: 15,
  interval: 2,
}

Yearly

Occurs on a specific date each year.

// Every January 1st
const pattern = {
  type: 'yearly',
  month: 1,
  dayOfMonth: 1,
}

// Every July 4th, every other year
const pattern = {
  type: 'yearly',
  month: 7,
  dayOfMonth: 4,
  interval: 2,
}

Examples

Weekly Tips

import { useSchedule } from '@tour-kit/scheduling'

const schedule = {
  recurring: {
    type: 'weekly',
    daysOfWeek: [2], // Every Tuesday
  },
}

const { isActive } = useSchedule(schedule)

Monthly Newsletter

const schedule = {
  recurring: {
    type: 'monthly',
    dayOfMonth: 1, // First of each month
  },
  timeOfDay: { start: '09:00', end: '10:00' },
}

Quarterly Reviews

const schedule = {
  recurring: {
    type: 'monthly',
    dayOfMonth: 1,
    interval: 3, // Every 3 months
  },
}

Annual Event

const schedule = {
  recurring: {
    type: 'yearly',
    month: 12,
    dayOfMonth: 25, // Every Christmas
  },
}

Limited Occurrences

const schedule = {
  recurring: {
    type: 'weekly',
    daysOfWeek: [1], // Mondays
    maxOccurrences: 5, // Only 5 times
  },
}

maxOccurrences caps the recurrence: once the pattern has fired that many times from the schedule's startAt, matchesRecurringPattern stops matching. Occurrences are counted in the schedule's timezone, so the cap honors the same timezone as the rest of the schedule. It requires a start date to count from (the schedule's startAt).

End Date

const schedule = {
  recurring: {
    type: 'weekly',
    daysOfWeek: [1, 3, 5],
    endDate: '2024-12-31', // Stop recurring after this date
  },
}

With Start Date

The startDate parameter affects interval calculations:

// Starts Jan 1, 2024, every 2 weeks
const startDate = '2024-01-01'
const pattern = {
  type: 'weekly',
  interval: 2,
}

// Matches: Jan 1, Jan 15, Jan 29, Feb 12, etc.
matchesRecurringPattern(new Date('2024-01-15'), pattern, 'UTC', startDate)
// true

matchesRecurringPattern(new Date('2024-01-08'), pattern, 'UTC', startDate)
// false (not a 2-week interval from start)

Complex Schedule

// Every other Monday, during Q1 2024
const schedule = {
  startAt: '2024-01-01',
  endAt: '2024-03-31',
  recurring: {
    type: 'weekly',
    daysOfWeek: [1], // Monday
    interval: 2, // Every other week
  },
  timeOfDay: { start: '10:00', end: '11:00' },
}

Timezone Awareness

Recurring patterns respect timezones:

const pattern = {
  type: 'weekly',
  daysOfWeek: [1], // Monday
}

const date = new Date('2024-01-01T23:00:00Z') // Monday 11pm UTC

// In UTC - Monday
matchesRecurringPattern(date, pattern, 'UTC')
// true

// In Los Angeles (still Sunday) - not Monday
matchesRecurringPattern(date, pattern, 'America/Los_Angeles')
// false

Check Multiple Patterns

function matchesAnyPattern(
  date: Date,
  patterns: RecurringPattern[],
  timezone: string
): boolean {
  return patterns.some((pattern) => matchesRecurringPattern(date, pattern, timezone))
}

const patterns = [
  { type: 'weekly', daysOfWeek: [1] }, // Mondays
  { type: 'monthly', dayOfMonth: 1 }, // First of month
]

if (matchesAnyPattern(new Date(), patterns, 'UTC')) {
  showSpecialContent()
}

Pattern Validation

The function returns false for invalid patterns:

// Invalid: type not recognized
matchesRecurringPattern(
  new Date(),
  { type: 'custom' } as any,
  'UTC'
)
// false

// Invalid: monthly without dayOfMonth
matchesRecurringPattern(
  new Date(),
  { type: 'monthly' },
  'UTC'
)
// false (needs dayOfMonth to match specific date)

RecurringPattern Type

interface RecurringPattern {
  /** Type of recurrence */
  type: 'daily' | 'weekly' | 'monthly' | 'yearly'

  /** Interval between occurrences (default: 1) */
  interval?: number

  /** For weekly: which days of the week (0 = Sunday) */
  daysOfWeek?: DayOfWeek[]

  /** For monthly/yearly: which day of the month (1-31) */
  dayOfMonth?: number

  /** For yearly: which month (1-12) */
  month?: number

  /** Maximum number of occurrences */
  maxOccurrences?: number

  /** End date for the recurrence (YYYY-MM-DD) */
  endDate?: DateString
}
Free & open source

Ship onboarding, not config.

npm i @tour-kit/core is MIT and free. The Pro packages work unlicensed too — a one-time $99 license removes the production watermark when you ship.

MIT-licensed — no signup, no credit card. Pay once, only when you ship.