Skip to content

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 

Repository files navigation

schengen-calculator

A small command-line tool that reads a calendar feed and tells you how many days you have left in the Schengen Area under the 90/180 rule. Pure Python standard library, no dependencies.

What it does

  • Reads an .ics file or subscribed calendar URL (iCloud, Google Calendar, Outlook, etc.).
  • Counts your Schengen days in the rolling 180-day window ending on a given date.
  • Handles overlapping trips correctly (no double-counting shared days).
  • Forecasts your balance at future dates so you can plan trips.
  • Lets you test hypothetical trips without modifying your calendar.
  • Scans your full date range for any 90/180 breaches.

This matches the official EU short-stay calculator — both entry and exit days count, and the window is the 180 days ending on your reference date.

What it doesn't do

  • It's not a substitute for legal advice. Border officers exercise discretion.
  • It doesn't track non-Schengen Europe (UK, Ireland, Cyprus). If your calendar contains trips to those, exclude them from your feed before counting.
  • It doesn't sync, integrate with anything, or run in the background. You run it, it tells you a number, you close the terminal.

Setup

You'll need Python 3.10 or later. Clone the repo:

git clone https://github.com/YOUR_USERNAME/schengen-calculator.git
cd schengen-calculator

That's it — there's nothing to install.

Use

The first time you run it, it'll ask for a calendar source and offer to save it for next time:

python schengen.py
# Calendar source: webcal://p01-calendars.icloud.com/published/2/...
# Save this URL for future runs? [Y/n] y

After that, run with no arguments to get the standard report:

python schengen.py

Common commands

# Default: rolling-180 status as of today
python schengen.py

# What will my balance be N days from now?
python schengen.py --in 60

# Forecast table at +0/+30/+60/+90/+180 days
python schengen.py --forecast

# Status on a specific date
python schengen.py --on 2026-09-15

# Test a hypothetical trip (does not save)
python schengen.py --check 2026-08-01 2026-08-20

# Add hypothetical trips that count toward all calculations (repeatable)
python schengen.py --add 2026-06-15 2026-06-22 --add 2026-07-10 2026-07-20

# Combine: with these added trips, can I also fit a 2-week trip in late August?
python schengen.py --add 2026-06-15 2026-06-22 --check 2026-08-15 2026-08-29

# Filter by event title (e.g. if you don't keep a dedicated Schengen calendar)
python schengen.py --tag "schengen"

# Manage the saved feed
python schengen.py --show-saved-feed
python schengen.py --clear-saved-feed

You can also pass a path or URL explicitly, which overrides the saved one:

python schengen.py /path/to/calendar.ics
python schengen.py "webcal://p01-calendars.icloud.com/published/2/..."

Calendar source resolution

In order:

  1. Positional argument (path or URL)
  2. SCHENGEN_FEED environment variable
  3. Saved config at ~/.config/schengen/feed
  4. Interactive prompt

The saved feed file is created with 0600 permissions (readable only by you). The URL itself is functionally a read-credential for your calendar — anyone with it can subscribe to your events. Don't commit it to a repo, paste it on Slack, or include it in screenshots.

Getting a calendar URL

Apple Calendar / iCloud

  1. Right-click your Schengen-trips calendar in the sidebar.
  2. "Sharing settings…" → check "Public Calendar".
  3. Click the share-arrow icon next to the calendar name → "Copy URL".

You'll get a webcal:// URL. The script auto-rewrites this to https:// before fetching.

Google Calendar

  1. Settings → "Settings for my calendars" → pick the calendar.
  2. Scroll to "Integrate calendar" → "Secret address in iCal format".
  3. Copy the URL ending in .ics.

⚠️ Google Calendar feeds update slowly (hours-to-days behind real edits). For frequent edits, Apple's feeds are noticeably more responsive.

How counting works

  • Trips are read from the calendar feed. By default, every event in the feed counts as a Schengen trip — so you probably want a dedicated calendar for Schengen travel rather than your main one. Use --tag to filter by event-title substring if you can't.
  • All-day events use exclusive end dates per RFC 5545 — an event with DTSTART=20260503 and DTEND=20260511 counts as 3-10 May (8 days), not 11.
  • Overlapping or touching events are merged so a shared day is only counted once.
  • The 180-day window is inclusive on both ends, so it covers exactly 180 days. Both your entry and exit days count as days in Schengen.

Sharing one binary command

To make it accessible from anywhere as schengen, drop a wrapper somewhere on your PATH:

mkdir -p ~/bin
cat > ~/bin/schengen <<'EOF'
#!/bin/bash
exec python3 "$HOME/path/to/schengen-calculator/schengen.py" "$@"
EOF
chmod +x ~/bin/schengen

Make sure ~/bin is on your PATH (export PATH="$HOME/bin:$PATH" in your shell config). Then schengen --forecast works from any directory.

Caveats and things to know

  1. Romania and Bulgaria joined Schengen for air/sea borders in March 2024 and land borders in January 2025. Trips before those dates don't count toward the 90/180 limit. Croatia joined in January 2023.
  2. Cyprus and Ireland are not in Schengen. Don't include them.
  3. The script counts your departure and arrival days both as Schengen days. This matches the EU calculator. A trip 3-10 May counts as 8 days even if you only spent half of those days physically inside Schengen.
  4. Border officers can be stricter. The 90/180 rule has the force of law but is enforced day-by-day by humans with discretion. Don't run yourself down to zero on the assumption you have an absolute right to the 90th day.
  5. No caching. Each run fetches the URL fresh. Fast enough for occasional use; inefficient if you're scripting repeated calls.

Privacy

The only data this script reads or transmits:

  • Reads your calendar feed (locally, or via HTTPS GET to the URL you give it).
  • Writes the URL to ~/.config/schengen/feed if you opt in to saving.
  • Writes nothing else, sends nothing anywhere else.

No analytics, no telemetry, no third parties.

Licence

MIT. See LICENSE.

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages