This post introduces timecond, a TypeScript library I created to represent, evaluate, and describe complex conditions on time ranges. The support for time ranges is what makes it different from many other popular time scheduling libraries that focus on events (points in time).

Note

In contrast to all my prior posts, I authored this with AI assistance (Gemini 2.5 Pro—even though I had to heavily iterate on several sections and rewrite a few sentences manually). This is the main reason why it does not follow my usual style; but I still like the result, it speaks the truth and I stand by it.

Introduction

Have you ever tried to schedule something that seemed simple on the surface but quickly became a nightmare of if statements? Imagine building an app that helps people schedule activities. The app needs to be smart. It knows “Spring Cleaning” is best done on the “first weekend of spring.” Meanwhile, your user, Alice, has her own schedule; she’s only free on “Saturday mornings or Sunday afternoons.”

How do you find the overlap?

Your brain does this calculation instantly. But in code? You start reaching for Date objects and your head starts to spin. You’re juggling date ranges, recurring days, and logical OR conditions. A simple event scheduler won’t cut it. An event is a point in time, but “Saturday morning” isn’t a point—it’s a period. It’s a recurring range of time.

This was the exact rabbit hole I fell down. I needed a way to express and combine these time periods in a modular, reusable way. My initial attempts were messy. I tried thinking in terms of discrete points in time, checking specific moments instead of handling the ranges themselves. This approach crumbled. The code was complex, buggy, and couldn’t answer the most important question: “When is a good time to schedule this?”

The “Aha!” Moment: Events vs. Periods

I knew from the start that my problem required a period-based solution. The challenge was that, as far as I knew at the time, no such solution existed. I had to invent it from first principles. My initial work was a solitary journey of trial and error. Ironically, in my search for a period-based model, I inadvertently stumbled upon and tried to apply several concepts based on discrete points in time, which naturally led to dead ends.

Eventually, after much effort, I developed a working period-based model that felt right. The core logic was sound, and it could handle the kinds of compositional rules I needed.

It was only after I had a satisfying solution that I did a deep dive into the wider world of scheduling libraries for comparison. That’s when I discovered a rich ecosystem of powerful tools like rSchedule, rrule.js, and later.js. For a moment, my heart sank. Had I just spent all this time reinventing a wheel?

But as I studied them, a different realization dawned on me—the real “Aha!” moment of the project. Every single one of them, despite their power, was fundamentally event-based. Their core abstraction was the point-in-time approach I had struggled against. They were brilliantly designed to calculate recurring points in time: “Every Monday at 9 AM.” “The 15th of every month.” They produce a stream of exact moments.

This discovery was a profound validation. It explained why my problem had felt so unique: periods vs events! Also, as far as I could see, there are no off-the-shelf libraries that work on periods directly. My gut feeling from the very beginning was correct. What I had built was genuinely new.

Forging a New Path: The Cond API

With the conceptual model of “periods” clear in my mind, the next challenge was designing the API. I knew from the start I wanted a compositional, object-oriented model where conditions could be combined. However, my first attempt at the core API contract had a fatal flaw, one that returned to a point-in-time mindset. It looked something like this:

// First-generation API attempt
abstract class Cond {
  // Returns the next time a condition is met
  abstract isAppropriateTime(now: Date): Date;
}

The problem is that isAppropriateTime returned a single Date object. It could tell me that the next “weekday” starts on Monday at 00:00, and the next “morning” starts on Monday at 08:00. But how do you combine these two points to find their intersection for "weekday AND morning"? You can’t. The moment the API returned a single Date, it had collapsed the period back into an event, throwing away the crucial information about the range’s duration.

The breakthrough was changing this fundamental contract. Instead of asking for the next start time, the API needed to provide the next set of valid ranges. This led to the final, robust Cond abstract class that powers the library today:

// My current, working abstraction
abstract class Cond {
  // Checks if a specific moment is inside a valid period
  abstract inRange(date: Date): boolean;

  // Finds the set of valid periods after a given moment
  abstract nextRanges(searchAfter: Date): DateRangeSet;

  // ... and other methods for range calculation
}

In this model, an AndCond or an OrCond doesn’t just combine booleans; it holds an array of other Cond objects. Its job is to intelligently query the ranges of its children and correctly calculate their intersection or union. This approach preserves the “period” concept at every level.

I finally had an API that felt right. My tests were passing. On the surface, it seemed like I had solved it. But I soon discovered that for period-based logic, the devil isn’t just in the details—he’s in the infinitely recurring, overlapping, and intersecting details.

You Can’t console.log Time: The Power of Visualization

My initial implementations of AND and OR were too simplistic. I thought I could just find the date ranges for each condition and perform a simple set union or intersection. This worked for basic cases, but failed on more complex, real-world scenarios.

The code for conditions like DateBetweenCond was also becoming bloated and fragile, partly because some of the initial boilerplate was AI-generated and subtly incorrect in ways that only surfaced under specific edge cases. My unit tests weren’t catching these conceptual errors. They would all pass, yet I had a nagging feeling that the logic was flawed. How do you debug a gut feeling?

I realized my problem: I was trying to debug time by looking at snapshots. I needed to see time as it was—a continuous flow.

So I put the library work on hold and built a tool for myself. This was a chance to deepen my knowledge of React, so I dove in and built an interactive test widget. It not only visualized a timecond expression on a graphical timeline, but to make it truly useful, I ended up inventing a novel UI algorithm for structural editing of the DSL text itself.

(You can access this tool here: https://knz.github.io/timecond )

This changed everything.

The visual feedback was an eye opener. I could see where my AndCond logic was producing incorrect ranges, or was missing ranges altogether. I could see how both morning and (either monday or friday) was being miscalculated on Thursdays but not on Saturdays. The time ribbon exposed many flaws in my reasoning and many bugs in my implementation. Using this widget, I was able to iteratively fix the algorithms and, just as importantly, build a more robust suite of unit tests that covered the tricky edge cases I had discovered visually.

A Tour of timecond: Expressive, Composable Time

After hardening the implementation with the visual tester, the timecond library was finally ready. The initial vision of a simple, expressive, and powerful way to handle period-based time was a reality.

The core of its power is the Domain-Specific Language (DSL). It allows you to write complex time conditions in a way that is remarkably close to natural language.

Simple combinations are trivial:

// Is it a morning on a work day?
both weekday and morning

You can group conditions logically, just as you would in your head:

// Active on Tuesday mornings and all day Friday
both morning and (either tuesday or friday)

But the real magic comes from its ability to handle relative and nested conditions. timecond truly understands the concept of “after.” For instance, how do you specify the “night” that belongs to Monday? It’s not the same as “Monday at midnight.”

// The night period that starts after Monday has begun
first night after start of monday exclusive

// For comparison, this is different:
// The intersection of the 24h "monday" period and the "night" period
both monday and night

Because every condition is a composable object, you can build up incredibly powerful expressions. Want to find the second Friday of spring? You can do it by nesting first after clauses:

// The 2nd Friday of spring
first friday after start of (first friday after start of spring inclusive) exclusive

This level of expressiveness is the direct result of the period-based model. Each part of that expression returns a range, which can then become the input for the next part of the expression.

A note on the DSL: as someone with a background in compilers, creating a parser for these English-like phrases felt like a natural way to make the library accessible. However, I’m aware of the potential pitfalls. A DSL can sometimes set user expectations higher than what it can deliver, leading them to try phrases that the grammar doesn’t support. This is a design trade-off I’m still contemplating, and I’m keen to hear feedback from users on whether the DSL is a help or a hindrance.

Try It, and Lessons from the Frontier

This entire journey, from a simple scheduling idea to a robust, period-based engine, was one of discovery. I experienced it as a reminder that even in a field as mature as time scheduling, there are still uncharted territories. For me, it was also a personal learning curve. timecond is my first TypeScript library—and only my second TypeScript project ever—so the path involved learning not just the problem domain, but also the ecosystem, right down to the details of package publishing.

It was also a lesson in the limits of our current tools, including AI. While AI assistants were helpful for boilerplate and simple algorithms, they consistently failed to produce correct solutions for the core, novel problems in timecond. The training data is rich with event-based scheduling examples, but for a period-based model, there was no prior art to draw from. This forced a return to first-principles thinking, debugging, and the creation of tools—like the visualizer—to bridge the gap between a human mental model and the code’s execution.

I encourage you to see it for yourself. The best way to understand timecond is to play with it.

Whether you’re building a smart scheduling app, a complex notification system, or just want to explore a new way of thinking about time in code, I hope you’ll find timecond useful. The project is open source, and I welcome any feedback, ideas, or contributions.

Like this post? Share on: TwitterHacker NewsRedditLinkedInEmail

Comments

So what do you think? Did I miss something? Is any part unclear? Leave your comments below.


Keep Reading


Reading Time

~8 min read

Published

Category

Research

Tags

Stay in Touch