Skip to content

Getting Started

Installation

Install PTW using your preferred package manager:

bash
npm install ptw
bash
pnpm install ptw
bash
yarn add ptw

Usage

Direct Expression Evaluation

For simple expressions without references:

typescript
import { parseExpression } from 'ptw'

// Parse a schedule expression for business hours
const expression = parseExpression('T[9:00..17:00] AND WD[1..5]')
if (!expression.ok)
  return

// Define evaluation domain (UTC timestamps)
const startTime = Date.UTC(2024, 0, 1) // January 1, 2024 UTC
const endTime = Date.UTC(2024, 0, 31, 23, 59, 59, 999) // January 31, 2024 UTC

// Evaluate directly - no Schedule instance needed
const result = expression.value.evaluate(startTime, endTime)
if (!result.ok)
  return

console.log('Business hours in January 2024:', result.value)
// Output: Array of time ranges matching business hours

// You can also check if a specific timestamp matches
const specificTime = Date.UTC(2024, 0, 15, 10, 30) // Jan 15, 2024 10:30 AM UTC
const timestampResult = expression.value.evaluateTimestamp(specificTime)
if (!timestampResult.ok)
  return

console.log('Is 10:30 AM on Jan 15 a business hour?', timestampResult.value)

Schedule Class (For References and Caching)

Use the Schedule class when you need:

  • References between expressions: Store and reuse expressions by name
  • Caching: Automatic optimization for repeated evaluations
  • Organization: Manage multiple related expressions together

Note: You can pass a Schedule to any block's evaluate() method, even if the block isn't stored in that schedule. This is useful for reference resolution.

typescript
import { parseExpression, Schedule } from 'ptw'

// Create a schedule instance for caching and references
const schedule = new Schedule()

// Define reusable expressions first
const businessHoursExpr = parseExpression('T[9:00..17:00] AND WD[1..5]')
const holidaysExpr = parseExpression('D[2024-01-01, 2024-07-04, 2024-12-25]')
if (!businessHoursExpr.ok || !holidaysExpr.ok)
  return

// Set expressions in schedule before using them
const setResult1 = schedule.setExpression('businesshours', 'Business Hours', businessHoursExpr.value)
const setResult2 = schedule.setExpression('holidays', 'Company Holidays', holidaysExpr.value)
if (!setResult1.ok || !setResult2.ok)
  return

// Now we can create expressions that reference the stored ones
const workingDaysExpr = parseExpression('REF[businesshours] AND NOT REF[holidays]')
if (!workingDaysExpr.ok)
  return

const startTime = Date.UTC(2024, 0, 1)
const endTime = Date.UTC(2024, 0, 31, 23, 59, 59, 999)

// Evaluate with schedule context for references and caching
const result = workingDaysExpr.value.evaluate(startTime, endTime, schedule)
if (!result.ok)
  return

console.log('Working days (excluding holidays):', result.value)

// Check specific timestamps using Schedule context
const newYearsDay = Date.UTC(2024, 0, 1, 10, 0) // Jan 1, 2024 10:00 AM UTC
const timestampResult = workingDaysExpr.value.evaluateTimestamp(newYearsDay, schedule)
if (!timestampResult.ok)
  return

console.log('Is New Years Day at 10 AM a working hour?', timestampResult.value) // false (holiday)

Using Schedule Context with Any Block

You can pass a Schedule to any block's evaluation, even if the block isn't stored in that schedule:

typescript
// Parse an expression that uses references
const dynamicExpr = parseExpression('REF[businesshours] OR T[20:00..22:00]') // Business hours OR evening
if (!dynamicExpr.ok)
  return

// Evaluate using the schedule context (block not stored in schedule)
const startTime = Date.UTC(2024, 0, 15, 21, 0) // Jan 15, 9 PM
const result = dynamicExpr.value.evaluateTimestamp(startTime, schedule)
if (!result.ok)
  return

console.log('Is 9 PM a valid time?', result.value) // true (evening time)

UTC Timestamps

PTW works exclusively with UTC timestamps:

typescript
// ✅ Correct: Using Date.UTC()
const utcStart = Date.UTC(2024, 0, 1, 9, 0, 0) // Jan 1, 2024 09:00:00 UTC

// ✅ Correct: Converting existing date to UTC
const localDate = new Date('2024-01-15T09:00:00')
const utcTimestamp = localDate.getTime() // Already in UTC milliseconds

// ❌ Incorrect: Using local date constructor for evaluation
const localStart = new Date(2024, 0, 1, 9, 0, 0).getTime() // Local timezone

Expression Syntax

typescript
// Time ranges (24-hour format)
parseExpression('T[9:00..17:00]') // 9:00:00.000 to 17:00:00.000 (exact times)
parseExpression('T[9>..17>]') // 9:59:59.999 to 17:59:59.999 (with padding)

// Time padding: Add > to pad incomplete times to maximum values
parseExpression('T[9..17]') // 9:00:00.000 to 17:00:00.000
parseExpression('T[9>..17>]') // 9:59:59.999 to 17:59:59.999
parseExpression('T[9:30>..12>]') // 9:30:59.999 to 12:59:59.999

// Weekdays (1=Monday, 7=Sunday)
parseExpression('WD[1..5]') // Monday through Friday

// Dates (YYYY-MM-DD)
parseExpression('D[2024-01-15]') // January 15, 2024

// Combining with AND
parseExpression('T[9:00..17:00] AND WD[1..5]') // Business hours

// Multiple values
parseExpression('WD[1,3,5]') // Monday, Wednesday, Friday

// References (Schedule class only)
parseExpression('REF[businesshours] AND NOT REF[holidays]')

Evaluation Methods

Range Evaluation (evaluate)

typescript
const expression = parseExpression('T[14:00..16:00] AND WD[1]') // 2-4 PM on Mondays
if (!expression.ok)
  return

const startTime = Date.UTC(2024, 0, 1)
const endTime = Date.UTC(2024, 0, 31, 23, 59, 59, 999)

const result = expression.value.evaluate(startTime, endTime)
if (!result.ok)
  return

result.value.forEach((range) => {
  const start = new Date(range.start)
  const end = new Date(range.end)
  console.log(`Range: ${start.toISOString()} to ${end.toISOString()}`)
})

Timestamp Evaluation (evaluateTimestamp)

typescript
const expression = parseExpression('T[9:00..17:00] AND WD[1..5]') // Business hours
if (!expression.ok)
  return

// Check if right now is during business hours
const now = Date.now()
const result = expression.value.evaluateTimestamp(now)
if (!result.ok)
  return

console.log('Is it currently business hours?', result.value)

// Check multiple specific times
const times = [
  Date.UTC(2024, 0, 15, 10, 30), // Monday 10:30 AM
  Date.UTC(2024, 0, 13, 10, 30), // Saturday 10:30 AM
  Date.UTC(2024, 0, 15, 20, 30), // Monday 8:30 PM
]

times.forEach((time) => {
  const result = expression.value.evaluateTimestamp(time)
  if (result.ok) {
    const date = new Date(time)
    console.log(`${date.toISOString()}: ${result.value}`)
  }
})

Error Handling

typescript
// Parse errors
const expression = parseExpression('invalid syntax')
if (!expression.ok) {
  console.error('Parse failed:', expression.error.message)
  return
}

// Success - use expression.value
const block = expression.value

// Schedule operations also return Results
const schedule = new Schedule()
const setResult = schedule.setExpression('test', 'Test', someBlock)
if (!setResult.ok) {
  console.error('Failed to set expression:', setResult.error.message)
  return
}

console.log('Expression set successfully')

// Evaluation errors
const evalResult = block.evaluate(startTime, endTime)
if (!evalResult.ok) {
  console.error('Evaluation failed:', evalResult.error.message)
  return
}

// Timestamp evaluation errors
const timestampResult = block.evaluateTimestamp(Date.now())
if (!timestampResult.ok) {
  console.error('Timestamp evaluation failed:', timestampResult.error.message)
  return
}

console.log('Timestamp matches:', timestampResult.value)

Next Steps

Released under the MIT License.