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.
- Try the interactive demo: https://knz.github.io/timecond/
- Check out the code on GitHub: https://github.com/knz/timecond
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.