An error fare to Iceland pops up. It's cheap enough to feel like a typo and most likely will be gone in minutes. I'm moderately obsessed with ways to travel on budget, so I keep an eye on these.
It isn’t one check, it’s a stack of small unfriendly ones, and takes around 20 minutes to process. Some bits are fun, like hunting for a seat upgrade, but mostly it’s counting midnights and expiry dates so a cheap weekend doesn’t become an expensive lesson.
I've been doing this dance for a decade now. In 2015 I made a spreadsheet for a US visa application that wanted ten years of travel history, down to the day. The spreadsheet grew: UK work visa, Indefinite Leave to Remain and citizenship applications, Canadian work permits. Any government form that asked "where have you been?" got its answer from the same battered CSV. It worked well enough, in the sense that I was never detained.
It also made me think that this was a solvable problem I was solving badly. I built a ledger to answer “where was I on 15 March 2023?” Instead, I ran simulations to check, “if I book this, what breaks later?”
The only question is whether the computer can answer all of faster than I do, and leave December, the next border control, and the end of the tax year blissfully uneventful.
That twenty-minute panic before buying a flight comes from one basic problem: none of the systems that judge you will tell you your state.
Schengen is running one check. The UK is running another. Tax residency is running a third. Your passport is running its own quiet clock in the background. None of them explain themselves, and none of them agree on what “a day” even is.
Schengen cares about presence across rolling windows. The UK counts how many midnights you were physically in the country in a tax year that, for historical reasons , starts on 6 April out of all options. Some countries track how many days you’ve spent in certain places and change the medical paperwork they expect from you once you cross a threshold. Meanwhile your passport might fail you with its expiry date, validity rules that may apply on arrival or departure depending on routing, and a finite number of blank facing pages that some countries require.
None of that is easily exposed. The officer at the desk can see it but you can’t. That's parsing the State—both kinds. The government's view of you, and the state machine that tracks it.
The rules aren't just complex—they're occasionally specific in ways that make you regret leaving the house in the first place.
To apply for British citizenship, you need to prove you were physically in the UK on your application date but five years ago. Not approximately five years, not that week—that exact day when you press "submit" on the form minus five years. Miss it by 24 hours and your application is reject after months of waiting, and you have to pay a hefty fee to re-apply.
Transiting through a UK airport? Leaving the terminal doesn't count as presence unless you do something "unrelated to your travel"—buy a sausage roll at Greggs, see a play in West End, meet a friend. The guidance doesn't even specify a minimum spend.
Morocco runs on UTC+1 most of the year but switches to UTC during Ramadan to shorten the fasting day. Which means "days spent in Morocco" depends on your timezone database version and whether you remembered to update it.
It would be alright with a single source of truth, but all these facts are scattered across (semi)official websites and PDFs, and you're supposed to figure it out yourself.
So the job isn't "log trips" (I already did that for ten years in a spreadsheet). The job is: given what I've already done and what I'm about to do, does this plan quietly break anything, and if so, where, and by how much.
"You're at 56 days because Amsterdam contributed 12, Prague 3, Barcelona 10, Iceland would add a month, and February doesn't count anymore" is something I can trust, argue with, or fix. "You're fine" isn't.
That's where this stops being a spreadsheet and starts being a linter. Apparently I want the compiler warning before I press Buy.
If I want a compiler warning I trust, the compiler has to agree with the officer about what a day is.
For the past five years I've been working on an app for people with epilepsy, managing timezones, medication reminders, and edge cases. We juggled multiple sources of truth and multiple storage styles (some records in UTC, some in local time with timezones stored separately—historical reasons, obviously).
This time, I tried to learn from that: facts are stored as instants, reasoning happens in local days of the jurisdiction that cares.
Take this routing: depart Dublin morning of November the 17th, brief Newark layover, a longer one in Mexico City, 23-hour Heathrow stop, then Tenerife. Ask five immigration systems "how many tax residency days?" and you get five answers:
Each stop has same or similar conditions, but different state machines are asking different questions. I pin the timezone database version that produced each result, and when rules or clocks shift, I recompute so I could show both answers if needed. Yesterday should stay reproducible even when tomorrow disagrees.
In other words, the linter is meant to answer the same question in various disguises: "what happens if I do this?"
Can I book Christmas in the Alps with three summer weekends planned in Europe? Does it matter if I leave UK before the tax year ends? What passport should I travel on? Does anything expire between booking and boarding?
Every question has the same shape: simulate forward, find what breaks, decide if you care. The goal isn't to convince border officers—it's to not make mistakes they'd catch. Trips get assembled from whatever I can verify later: geotagged photos, background location, manual entries. A resolver turns that into "present on this local day" and keeps track of why.
I don't hardcode rules, I ship interpretations instead: each jurisdiction has a small versioned blob that says what counts, how the window is measured, where that reading came from.
The paperwork gets the same treatment because documents are state machines too. A passport isn’t just means of identity; it has constraints and timers. Some requirements, like six months validity are legacy and usually exist to keep deportations possible without issuing emergency documents, but still need to be checked.
Before I buy anything the linter should tell me that I don't have the correct flavour of IDP (and man, getting those in Scotland since they delegated it from Post Offices to corner shops is tough), that a Dubai connection flips a “valid on arrival” buffer into “invalid on departure”. Quiet warnings, early enough to change dates or renew the right booklet, and clear enough that I won't have to improvise at a counter.
If the world changes—new examples, revised guidance, a delayed system finally launches—I don’t rewrite history. I version the assumption, keep both answers recorded, and move on.
Keeping all those rules up-to-date is hard, so rather than maintaining rules for every country, I parse a few databases, then let users configure their own tracking goals. A user emailed about Cyprus's fast-tracked tax residency scheme; another pointed out I'd missed a few countries entirely. The app gets better as people use it, which feels more honest than pretending to be a global authority on 195 countries' immigration rules.
The app is local by default. Calculations happen on-device, and if you're in airplane mode, it still works. Network is always the bottleneck, and I'm the person who spent a weekend reverse-engineering gym entry to save 44 seconds. I'm not adding server round-trips when you're standing at a gate.
Being local also means no liability. Personal immigration history is exactly the kind of data governments might want. Keeping it off my servers means nobody can demand I hand it over. Some friends asked about cloud sync: I keep saying no. Not because sync is hard—it is, though surely Claude Code can do that for me —but because the moment you add a server you add retention policies, jurisdiction questions, and a magnet for legal demands. If you want it on another device, export a file and move it yourself like the ancestors did it.
The first version just counted Schengen days, then I added the UK's midnight arithmetic because I needed it for my own calculations. Then documents with their expiry rules because I was tired of manually looking them up. Then the what-if layer because adding and deleting trips to see impact felt like manually diffing state. Then visa requirements and IDP rules: none of this was planned, it accumulated from use, the same way my fermentation tracker grew from "can I eat this?" to HACCP compliance documents.
I shipped it because keeping it private felt unfinished, and because I'd like fewer people spending twenty minutes researching whether a £62 return flight will cause problems six months later.
That Iceland error fare? I bought it. The app told me I wouldn't need an IDP, that the trip wouldn't push me over any Schengen threshold, that I'd leave with 34 days of margin in my 90/180 window, and—importantly—that booking it would mean I'd stop being a UK tax resident given my upcoming Canada move. Useful things to know before clicking purchase. The officer at Keflavík looked at his screen, agreed with his systems, and waved me through.
I called the app Residency and you can get it here. No subscriptions, costs less than an airport martini, and you'll likely regret it less a few hours later.
You can't cURL a border. But you can track your own state carefully enough that when the governments know the answer, so do you.
Working on problems where rules are complex and official documentation is garbage contradictory? I build systems that handle state when the state won't tell you your state. work@drobinin.com