DEV Community

Eastkap
Eastkap

Posted on

How I built a free PTO optimizer that turns 5 days off into 18 — and the algorithm behind it

Most people treat PTO like a currency they spend one day at a time. What if you could treat it like a lever?

Here's the insight that sparked this project: the same Thursday off can be worth completely different amounts of actual time away from work, depending entirely on where it falls in the calendar.

Take a random Thursday in February: you get one day off. Nice.

Take Thursday, November 26, 2026 — Thanksgiving. Now that Thursday comes pre-loaded with a Friday (most employers give it), Saturday, and Sunday. Suddenly you're looking at a 4-day break for zero PTO spent. But if you also take Monday through Wednesday before it, you've spent 3 PTO days and earned 9 consecutive days off. That's a 3x multiplier.

I got obsessed with this math. So I built Holiday Optimizer — a free tool that finds every one of these opportunities for your country and year, then figures out the optimal combination given your PTO budget.


The Algorithm

The core idea is simple: public holidays and weekends create "anchors" of free time throughout the year. The optimizer's job is to find the gaps between those anchors where a small number of PTO days can bridge them into much longer stretches.

Here's how it works, step by step:

Step 1: Load the holiday calendar

The tool takes three inputs: country, year, and how many PTO days you have. It then loads all public holidays for that country/year using the date-holidays npm package — which is surprisingly comprehensive, covering hundreds of locales including state-level holidays for Germany.

Step 2: Identify bridge opportunities

For each holiday, I look at the days immediately surrounding it. The algorithm walks outward in both directions: if a holiday falls on a Tuesday, taking Monday off bridges it to the weekend — turning 1 PTO day into a 4-day break (Sat-Sun-Mon-Tue). If two holidays are close together (say, Christmas Day on Thursday and New Year's Day the following Thursday), the gap between them might only require 3 or 4 PTO days to string together nearly 2 weeks off.

Each "cluster" — a contiguous block of time off — gets scored:

efficiency = consecutive_days_gained / pto_days_used
Enter fullscreen mode Exit fullscreen mode

A 9-day break using 3 PTO days scores 3.0. A 4-day weekend using 1 PTO day scores 4.0. The algorithm favors high-efficiency clusters.

Step 3: Find the optimal non-overlapping combination

Here's where it gets interesting. Once you have a scored list of potential clusters, you need to find the best combination of non-overlapping clusters that fits within your PTO budget. This is structurally similar to the weighted interval scheduling problem — a classic DP setup.

I use a greedy approach with a twist: sort clusters by efficiency descending, then greedily select each one that doesn't overlap with already-selected clusters and doesn't exceed the remaining PTO budget. This isn't always globally optimal for maximizing total days off, but it reliably produces the highest-efficiency plan, which is usually what people actually want. Pure DP for maximizing total days would work too, but greedy-by-efficiency gives more intuitive results — you get the "best bang per day" combinations, not just the longest possible stretch.

Step 4: Output

The result is a ranked list of "PTO packages" — concrete date ranges with the specific days you should request off, how many PTO days each costs, and how many consecutive days off you'll get in return. It also shows what the full year looks like if you use all your budget on the top recommendations.


A Real 2026 Example (US)

Let me walk through what this looks like for a US employee with 10 PTO days in 2026.

Independence Day — July 4, 2026 (Saturday)
July 4 falls on a Saturday, so the federal observance shifts to Friday, July 3. That means most companies already give Friday off. Take Thursday July 2 with 1 PTO day → you get a 4-day weekend (Thu–Sun) for free, or 5 days if you count Thursday.

Wait, actually — if the federal holiday is observed on Friday, you might already have it off. So this one might cost 0 PTO depending on your employer. The optimizer handles this correctly.

Thanksgiving — November 26, 2026 (Thursday)
This is the motherload. Thanksgiving Thursday + Black Friday (many employers give it) + Sat/Sun = 4 days free. Add Monday Nov 23, Tuesday Nov 24, Wednesday Nov 25 = 3 PTO days. Result: 9 consecutive days off (Nov 21 Saturday through Nov 29 Sunday, or Nov 23–29 if your employer gives Black Friday). Efficiency score: 3.0.

Christmas + New Year Bridge
Christmas Day is December 25, 2026 (Friday). New Year's Day is January 1, 2027 (Friday). The gap between them is 6 calendar days. Take Dec 26 (Mon), Dec 29 (Mon), Dec 30 (Tue), Dec 31 (Wed) = 4 PTO days. Result: 17 consecutive days off (Dec 20 Saturday through January 4 Sunday). Efficiency: 4.25.

With just 10 PTO days split across these three opportunities, you walk away with ~30+ total days off including weekends.


The Stack

Nothing exotic here — I wanted to ship fast and keep it maintainable.

  • Next.js 15 with the App Router. Mostly a static site with some client-side state.
  • TypeScript throughout. The date math gets complex enough that types pay for themselves.
  • Tailwind CSS v4 — the new CSS-first config is genuinely nicer for component work.
  • date-holidays npm package for holiday data. Covers US, UK, France, Germany, Spain, and hundreds more. It's not perfect (more on that below), but it's remarkably complete.
  • Client-side only — no backend, no database, no API. Everything runs in the browser. Deployed on Vercel as a static export.

The entire optimization algorithm runs client-side in milliseconds. For a full year with ~15 holidays, the greedy pass over maybe 30-40 candidate clusters takes negligible time. Even a full DP approach would be trivial at this scale.


What Surprised Me

Regional holiday complexity is wild. Germany has 16 states (Bundesländer), and several holidays — like Corpus Christi, Assumption of Mary, Reformation Day — are only observed in some states. The same person living in Bavaria vs. Brandenburg gets a meaningfully different optimization result. I ended up adding a state selector for Germany (and regional selectors for other countries with similar complexity).

Holidays falling on weekends don't always roll over. In the US, if a federal holiday falls on Saturday, it's observed on Friday. Sunday → Monday. But the UK doesn't consistently do this, and France doesn't roll over at all. If Bastille Day (July 14) falls on a Sunday in France, you just... don't get a substitute day. This breaks naive optimizations that assume you can always capture the holiday's value.

Client-side DP is fast enough that I never needed to optimize it. I originally planned to run the heavy computation server-side or in a Web Worker. Totally unnecessary. Even the most aggressive combinations — trying every possible subset of clusters — complete in under 50ms in the browser. JavaScript date math is the actual bottleneck, and it's still imperceptible.

People really care about this. The response when I shared it was bigger than I expected. Turns out most people have no idea their PTO is this leverageable. A few people told me they'd been planning vacations for years the same way — picking dates they liked — without ever thinking about calendar efficiency. That was validating.


Try It Yourself

Holiday Optimizer →

Free, no signup, works for US, UK, France, Germany, and Spain. Takes about 30 seconds to find your best 2026 vacation plan.

2026 is a particularly good year for this in the US — several holidays fall on Fridays and Mondays, which means a lot of natural bridge opportunities. The Christmas/New Year combination alone is exceptional.

Pick your country, enter your PTO budget, and see what the calendar is hiding.


Built with Next.js, TypeScript, and a slight obsession with calendar math. If you find a bug or want to request a country, the feedback link is in the app.

Tags: #webdev #javascript #productivity #buildinpublic

Top comments (0)