How the Ireland annual leave calculator is verified
Inputs
| Input | Field | Type |
|---|---|---|
| Days you usually work per week | daysPerWeek | number |
| Hours you usually work per week | hoursPerWeek | number |
| Months worked in the leave year (0–12) | monthsWorked | number |
| I changed employment during this leave year | changedEmployment | boolean |
| Days already taken | daysTaken | number |
The formula
Each derived value below is computed by the Valem engine from the expression shown, in this order. Nothing else runs — there is no hidden code path.
| Derived value | Field | Formula (JSONata) |
|---|---|---|
| Hours Per Day | hoursPerDay | ($d := $n(daysPerWeek); $d > 0 ? $n(hoursPerWeek) / $d : 0) |
| Hours Per Month | hoursPerMonth | $n(hoursPerWeek) * $const.weeksPerYear / $const.monthsPerYear |
| Hours Worked | hoursWorked | hoursPerMonth * $n(monthsWorked) |
| Full Working Weeks Days | fullWorkingWeeksDays | $const.workingWeeks * $n(daysPerWeek) |
| Method ADays | methodADays | (changedEmployment = true or hoursWorked < $const.annualHoursThreshold ? 0 : fullWorkingWeeksDays) |
| Qualifying Months | qualifyingMonths | (hoursPerMonth >= $const.monthlyHoursThreshold ? $floor($n(monthsWorked)) : 0) |
| Method BDays | methodBDays | qualifyingMonths * $n(daysPerWeek) / $const.monthWeekDivisor |
| Method CHours | methodCHours | hoursWorked * $const.hoursPercentage |
| Method CDays | methodCDays | (hoursPerDay > 0 ? $clampFloorCap(methodCHours / hoursPerDay, null, fullWorkingWeeksDays) : 0) |
| Days Entitled | daysEntitled | ($a := $n(methodADays); $b := $n(methodBDays); $c := $n(methodCDays); $ab := $a > $b ? $a : $b; $g := $ab > $c ? $ab : $c; $round($clampFloorCap($g, null, fullWorkingWeeksDays), 1)) |
| Days Remaining | daysRemaining | ($r := daysEntitled - $n(daysTaken); $r < 0 ? 0 : $round($r, 1)) |
| Method Used | methodUsed | ($m := daysEntitled; $a := $round($n(methodADays), 1) = $m; $b := $round($n(methodBDays), 1) = $m; $c := $round($n(methodCDays), 1) = $m; $m <= 0 ? 'none yet — no qualifying hours in this leave year' : ($a and $b and $c ? 'all three methods give the same answer' : ($a and $b ? 'methods (a) and (b)' : ($a and $c ? 'methods (a) and (c)' : ($b and $c ? 'methods (b) and (c)' : ($a ? 'method (a) — you passed the 1,365-hour threshold' : ($b ? 'method (b) — one-third of a working week per qualifying month' : 'method (c) — 8% of the hours you worked'))))))) |
Rates and thresholds
The expressions above read these as $const. They are the figures the annual refresh replaces, so they live as data rather than being written into the formulas — which is what makes a year-on-year change a one-line diff instead of an edit to arithmetic.
| Constant | Value |
|---|---|
workingWeeks | 4 |
annualHoursThreshold | 1365 |
monthlyHoursThreshold | 117 |
monthWeekDivisor | 3 |
hoursPercentage | 0.08 |
weeksPerYear | 52 |
monthsPerYear | 12 |
Test vectors (10)
Every case is executed against this model on each build, by the Java engine and independently by a JavaScript one. A mismatch of a single cent fails the build, so the page cannot ship advertising a figure the model no longer produces.
Case 1 — D1 a full-year five-day week is 20 days, and all three methods agree on it
given daysPerWeek | 5 |
|---|---|
given hoursPerWeek | 39 |
given monthsWorked | 12 |
given changedEmployment | no |
given daysTaken | 0 |
expect methodADays | 20 |
expect methodBDays | 20 |
expect methodCDays | 20 |
expect daysEntitled | 20 |
expect methodUsed | all three methods give the same answer |
Case 2 — D2 a three-day part-timer fails both hour tests, so only method (c) carries them
given daysPerWeek | 3 |
|---|---|
given hoursPerWeek | 24 |
given monthsWorked | 12 |
given changedEmployment | no |
given daysTaken | 0 |
expect methodADays | 0 |
expect methodBDays | 0 |
expect methodCDays | 12 |
expect daysEntitled | 12 |
expect methodUsed | method (c) — 8% of the hours you worked |
Case 3 — D3 half a leave year: method (c)'s 10.4 beats method (b)'s 10, which is what the proviso is for
given daysPerWeek | 5 |
|---|---|
given hoursPerWeek | 39 |
given monthsWorked | 6 |
given changedEmployment | no |
given daysTaken | 0 |
expect hoursWorked | 1014 |
expect methodADays | 0 |
expect methodBDays | 10 |
expect methodCDays | 10.4 |
expect daysEntitled | 10.4 |
Case 4 — D4 eleven months on a six-day week clears 1,365 hours, so method (a) pays the full 4 weeks
given daysPerWeek | 6 |
|---|---|
given hoursPerWeek | 45 |
given monthsWorked | 11 |
given changedEmployment | no |
given daysTaken | 0 |
expect methodADays | 24 |
expect methodBDays | 22 |
expect daysEntitled | 24 |
expect methodUsed | method (a) — you passed the 1,365-hour threshold |
Case 5 — D5 the same year, but employment changed: s.19(1)(a) is unavailable and (c) wins at 22.9
given daysPerWeek | 6 |
|---|---|
given hoursPerWeek | 45 |
given monthsWorked | 11 |
given changedEmployment | yes |
given daysTaken | 0 |
expect methodADays | 0 |
expect daysEntitled | 22.9 |
expect methodUsed | method (c) — 8% of the hours you worked |
Case 6 — D6 the four-working-week ceiling holds against a 100-hour week
given daysPerWeek | 5 |
|---|---|
given hoursPerWeek | 100 |
given monthsWorked | 12 |
given changedEmployment | no |
given daysTaken | 0 |
expect fullWorkingWeeksDays | 20 |
expect methodCDays | 20 |
expect daysEntitled | 20 |
Case 7 — D7 no months worked is no entitlement, and the method label says so rather than naming one
given daysPerWeek | 5 |
|---|---|
given hoursPerWeek | 39 |
given monthsWorked | 0 |
given changedEmployment | no |
given daysTaken | 0 |
expect daysEntitled | 0 |
expect methodUsed | none yet — no qualifying hours in this leave year |
Case 8 — D8 a two-day week is 8 days, the (c) cap biting on 8.32
given daysPerWeek | 2 |
|---|---|
given hoursPerWeek | 16 |
given monthsWorked | 12 |
given changedEmployment | no |
given daysTaken | 0 |
expect methodCDays | 8 |
expect daysEntitled | 8 |
Case 9 — D9 days already taken come off the remainder and it never goes negative
given daysPerWeek | 5 |
|---|---|
given hoursPerWeek | 39 |
given monthsWorked | 12 |
given changedEmployment | no |
given daysTaken | 25 |
expect daysEntitled | 20 |
expect daysRemaining | 0 |
Case 10 — D10 days taken below the entitlement leave the balance
given daysPerWeek | 5 |
|---|---|
given hoursPerWeek | 39 |
given monthsWorked | 12 |
given changedEmployment | no |
given daysTaken | 8 |
expect daysEntitled | 20 |
expect daysRemaining | 12 |
Sources
- Organisation of Working Time Act 1997, s.19 — revised (the three methods, their thresholds, the greater-of proviso, and what a working week is) · checked 2026-10-11
- Workplace Relations Commission — Annual Leave (the same three methods, the greater-of rule, and the four-working-week ceiling) · checked 2026-10-11
- Organisation of Working Time Act 1997, s.2(1) — revised (“leave year” begins on a 1st day of April) · checked 2026-10-11
Check it yourself — and reuse it
The whole model as JSON — inputs, outputs, formulas, vectors and sources in one re-runnable document.
This model is published under CC BY 4.0: use it, adapt it, build on it, with attribution. The licence covers the model — the expression of each rule and the arrangement of the catalog. The rates and thresholds are law, and nobody licenses those; the sources above are where they come from.
← Back to the Ireland annual leave calculator
Built with Valem — a live, verifiable calculator you can fork and embed.