尚未收集儲存庫資訊。
尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
製作者的介紹僅有上面的一句話。 更多詳情請閱讀儲存庫 README。 儲存庫 README ↗
正在載入完整 README。
檢視完整 README ↗Live site: leonkong-1.github.io/gb-railway-calendar
An open, reproducible implementation of the GB railway's own calendars: railway periods, possession weeks, and timetable years with period blocks.
Network Rail derives and maintains these calendars, but there has never been a good public source for them, and no published description of how the dates are generated. This repository provides:
The algorithm was entirely reverse engineered from published outputs. It was then systematically cross-checked against the Office of Rail and Road's published railway period dates and against Planancy's published rail weeks calendar. Every check matches. The method is described in full below so anyone can verify it, challenge it, or reuse it.
| Calendar | Used for | Weeks run | Year |
|---|---|---|---|
| Railway periods | General operational and financial reporting across the industry | Sunday to Saturday | 1 April to 31 March, 13 periods |
| Possession weeks | Engineering possession planning only | Saturday to Friday | Starts on the Saturday nearest 1 April, 52 or 53 weeks in 13 periods |
| Timetable | Timetable change dates and period blocks A to J | Sunday to Saturday | December to December |
Possession weeks start on a Saturday because possessions are taken over weekends: Saturday and Sunday need to fall in the same week.
A note on naming. railwaycalendar.com calls railway periods "finance periods" and possession weeks "NR weeks". This repository uses "railway periods" and "possession weeks", which is how most people in the industry refer to them, and matches ORR, which publishes the former as "railway period dates". Railway periods are used well beyond finance, and possession weeks are used for nothing except possessions.
The whole calendar hangs off three anchor dates. Everything else is counted from them.
53-week possession years between 1995/96 and 2059/60: 1997/98, 2003/04, 2008/09, 2014/15, 2019/20, 2025/26, 2031/32, 2036/37, 2042/43, 2047/48, 2053/54, and 2059/60.
In other words, railway periods are possession periods shifted forward by one day and clipped to the financial year.
Five financial years each. CP7 is 2024/25 to 2028/29. From 2029/30 they are funding periods: FP1 is 2029/30 to 2033/34, and so on. The labels match ORR's for every year from 1995/96.
The reference implementation is railcal.py, about 130 lines of standard-library Python.
railwaycalendar.com, built by Rizwan Khan, publishes year tables for railway periods, possession weeks, timetable years, and period blocks from 2004/05 to 2049/50. Its derivation method is deliberately not published. It was the first good public-facing source for these calendars, and this work would not have been possible without it.
The approach was:
Result: 4,700 out of 4,700 values match (see test results).
The Office of Rail and Road publishes railway period dates for every period from April 1995 to March 2029 (442 periods). The algorithm was not fitted to this data. The comparison is particularly strong for 1995/96 to 2003/04, which is earlier than anything on railwaycalendar.com and so is a genuinely out-of-sample test of the anchor rules.
Result: all 442 periods match exactly, and the control period labels match for all 34 years.
Planancy publishes a 2026 to 2030 Rail Weeks Calendar showing possession week numbers and period end dates. The PDF is image only, so the values were transcribed by eye from the rendered pages:
Result: all 185 values match, including week 53 in 2025/26 and the years where the possession year starts in March (2029 and 2030).
Planancy's key labels the yellow dates as the "NR Period End Date". They all fall on a Saturday, so they are railway period ends; the possession period ends on the Friday before. The calendar mixes railway period ends with possession week numbers. This is harmless, but worth knowing if the yellow dates are read as possession period boundaries.
The website runs a JavaScript port of the engine, docs/railcal.js. The data files are generated from the Python engine. The parity check compares the two for every year file, every timetable file, every day from 1 April 1996 to 31 March 2059, and a set of query-parser cases.
| Calendar | railwaycalendar.com | ORR | Planancy |
|---|---|---|---|
| Railway periods | ✔ | ✔ | ✔ |
| Possession weeks and periods | ✔ | (implied) | ✔ |
| Timetable years and blocks | ✔ | ||
| Control period labels | ✔ | ✔ |
Railway periods are confirmed by three independent sources and possession weeks by two. Possession periods are also confirmed indirectly by ORR, because railway periods are derived from them. Timetable years and blocks rest on railwaycalendar.com alone.
Latest run, from test-results/SUMMARY.md:
| Check | Checks | Mismatches | Result |
|---|---|---|---|
| railwaycalendar.com year tables, 2004/05 to 2049/50 | 4,700 | 0 | pass |
| ORR railway period dates, 1995/96 to 2028/29 | 476 | 0 | pass |
| Planancy 2026 to 2030 Rail Weeks Calendar | 185 | 0 | pass |
| JavaScript engine vs Python data files | 23,156 | 0 | pass |
Breakdown of the railwaycalendar.com checks:
| Item | Checks |
|---|---|
| Possession week (number, period/week, dates, railway period, timetable, block) | 2,446 |
| Timetable block | 828 |
| Railway period (start, end, days) | 598 |
| Possession period (end, week range) | 598 |
| Possession year start and length | 92 |
| Timetable year (length, Principal, Subsidiary) | 92 |
| Control period label | 46 |
Full output for each check is in test-results/.
Python 3.9 or later and, for the JavaScript parity check, Node.js. No packages need installing.
python run_tests.py
Each check can also be run on its own, for example python tests/check_orr.py. Each exits 0 on a full match, 1 on any mismatch, and 3 if its source data is not available locally.
The captured snapshot of railwaycalendar.com is not committed. The site's content is marked "all rights reserved", so it is not redistributed here. Without the snapshot, tests/check_railwaycalendar.py reports "skipped"; the committed result in test-results/railwaycalendar.txt is from a run with the snapshot present.
To reproduce it, render https://railwaycalendar.com/tables.html?fy=<year> for each year from 2004 to 2049 in a browser (the tables are generated client-side), and save tests/fixtures/railwaycalendar_snapshot.json as an object keyed by year, where each entry holds:
head: the line beginning "Financial year …";fin: the period table rows, cells joined by |;wk: the possession week table rows (first seven cells), joined by |;tt: the lines beginning "Timetable year" or "Principal";blocks: the block tables, as lists of rows joined by |.from datetime import date
import railcal
railcal.railway_periods(2026) # [(1, date(2026, 4, 1), date(2026, 5, 2)), ...]
railcal.possession_weeks(2026) # (week, period, week_in_period, start_sat, end_fri), ...
railcal.railway_period_of(date(2026, 10, 25)) # 8
railcal.timetable_of(date(2026, 10, 25)) # (2026, 'Subsidiary', 'H')
docs/railcal.js works in the browser (window.RailCal) and in Node (require).
const R = require("./docs/railcal.js");
R.describe(R.ymd(2026, 10, 25)); // everything about one date
R.lookup("P8W3 2026/27"); // free-text query: periods, weeks, dates, timetables
R.yearJSON(2026); // a whole financial year
lookup accepts the formats people actually type: P8, P08, 2026/27 P8, 2627P08, wk 31, P8W3, 25/12/2026, 2026-12-25, 25 Dec 2026, TT2027, TT2027 May, TT2027 block C, and 2027/28. Without a year, the current one is used.
docs/data/ holds one file per financial year and one per timetable year, listed in docs/data/index.json. All dates are ISO 8601 and all ranges are inclusive.
{
"financial_year": "2026/27",
"control_period": "CP7",
"railway_periods": [
{ "period": 8, "label": "2026/27 P08", "start": "2026-10-18", "end": "2026-11-14", "days": 28 }
],
"possession_year": {
"start": "2026-04-04", "end": "2027-04-02", "weeks": 52,
"periods": [ { "period": 8, "start": "2026-10-17", "end": "2026-11-13", "first_week": 29, "last_week": 32 } ],
"weeks_list": [ { "week": 30, "period": 8, "week_in_period": 2, "start": "2026-10-24", "end": "2026-10-30" } ]
}
}
(Abridged: each file lists all 13 periods and every week.) Regenerate with python gen_json.py.
docs/ is a self-contained static site with no build step and no server-side code. It has three views:
To run it locally:
python -m http.server 8000 --directory docs
It can be hosted as-is on any static host. For GitHub Pages: Settings → Pages → Build and deployment → Deploy from a branch, then choose main and /docs. The .nojekyll file stops Pages running Jekyll over it.
railcal.py Reference implementation of the algorithm
gen_json.py Writes docs/data/*.json from railcal.py
run_tests.py Runs every check and writes test-results/
tests/
check_railwaycalendar.py vs railwaycalendar.com (needs local snapshot)
check_orr.py vs ORR railway period dates
check_planancy.py vs Planancy 2026-2030 calendar (transcribed values inline)
check_js_parity.js JavaScript engine vs Python data files
fixtures/
orr_railway_period_dates.csv ORR data, Open Government Licence v3.0
test-results/ Output of the latest run
docs/
index.html The website
railcal.js JavaScript port of the engine
data/ Generated JSON, 1995/96 to 2059/60
tests/fixtures/orr_railway_period_dates.csv.The code, generated data, website, and test results are released under the MIT licence.
The exception is tests/fixtures/orr_railway_period_dates.csv, which is ORR data and remains under the Open Government Licence v3.0. See tests/fixtures/README.md.