Modeling EASA Acclimatisation State Transitions
The exact task this guide solves is resolving a crew member’s EASA acclimatisation state — acclimatised to the departure reference time, acclimatised to local time, or in an unknown state — from the time-zone difference they have crossed and the time elapsed since they left their reference time. This is the piece of EASA FTL compliance with no FAA analogue: Part 117 uses the acclimatised theatre directly, but EASA computes a state that decides which maximum-FDP column applies, and getting it wrong shifts the entire duty-limit calculation. This page encodes the CS FTL.1.235 table as a state machine, transitions it across a rotation, and pins the boundaries. It supplies the acclimatisation input the duty time validation rule engines need and anchors its duty definitions to the crew duty time taxonomy.
Prerequisites
- Python 3.11+ with
zoneinfoand timezone-awaredatetime. - A reference time zone per crew member — the zone they are acclimatised to at the start of the rotation.
- The CS FTL.1.235 table seeded — the B/D/X result grid keyed on time-zone difference and elapsed-time band.
- The current ORO.FTL revision recorded as the ruleset effective date.
The acclimatisation states
EASA defines three states. B means the crew member remains acclimatised to the time zone of the point of departure (their reference time). D means they have become acclimatised to the local time of the point where they are now operating. X means they are in an unknown state of acclimatisation, which forces the more conservative FDP column. Which state applies depends on two numbers: the time-zone difference between the reference time and the local time at the reporting point, and the elapsed time since the crew member reported for the first duty in the reference time.
| Time-zone difference | < 48 h | 48–71:59 | 72–95:59 | 96–119:59 | ≥ 120 h |
|---|---|---|---|---|---|
| < 4 h | B | D | D | D | D |
| ≤ 6 h | B | X | D | D | D |
| ≤ 9 h | B | X | X | D | D |
| ≤ 12 h | B | X | X | X | D |
The pattern is legible once seen: within the first 48 hours the crew is always still acclimatised to departure (B); after five days they are always acclimatised to local (D); and the wider the time-zone jump, the longer they spend in the unknown state (X) in between.
Step 1 — Encode the table as a state resolver
The resolver reduces the two continuous inputs to their bands and reads the cell. Encoding the bands as ordered thresholds keeps the lookup a pair of small linear scans rather than a nest of conditionals.
from enum import Enum
class Acclim(Enum):
DEPARTURE = "B" # acclimatised to reference/departure time
LOCAL = "D" # acclimatised to local time
UNKNOWN = "X" # unknown state — conservative column
# Rows: time-zone-difference ceiling (hours). Columns: elapsed-time band index.
_TABLE = {
4: [Acclim.DEPARTURE, Acclim.LOCAL, Acclim.LOCAL, Acclim.LOCAL, Acclim.LOCAL],
6: [Acclim.DEPARTURE, Acclim.UNKNOWN, Acclim.LOCAL, Acclim.LOCAL, Acclim.LOCAL],
9: [Acclim.DEPARTURE, Acclim.UNKNOWN, Acclim.UNKNOWN, Acclim.LOCAL, Acclim.LOCAL],
12: [Acclim.DEPARTURE, Acclim.UNKNOWN, Acclim.UNKNOWN, Acclim.UNKNOWN, Acclim.LOCAL],
}
_ELAPSED_BANDS = [48, 72, 96, 120] # hour ceilings for columns 0..3; ≥120 is column 4
def resolve_state(tz_diff_hours: float, elapsed_hours: float) -> Acclim:
col = next((i for i, ceil in enumerate(_ELAPSED_BANDS) if elapsed_hours < ceil), 4)
for ceil in (4, 6, 9, 12):
if tz_diff_hours <= ceil:
return _TABLE[ceil][col]
# A time-zone difference above 12 hours is treated as the widest band.
return _TABLE[12][col]
Verify: resolve_state(8, 60) returns Acclim.UNKNOWN — an eight-hour zone difference at 60 elapsed hours falls in the ≤9 row, 48–71:59 column, which is X. resolve_state(3, 60) returns Acclim.LOCAL, because a sub-four-hour jump acclimatises to local after the first 48 hours.
Step 2 — Transition the state across a rotation
Acclimatisation is stateful: each duty updates the elapsed time and, on a time-zone change, resets the reference against which the difference is measured. Walking the rotation duty by duty keeps the state consistent with the crew member’s actual history rather than recomputing it from scratch each time.
from dataclasses import dataclass
from datetime import datetime
@dataclass
class AcclimState:
reference_offset_hours: float # UTC offset of the reference time
reference_since_utc: datetime # when the crew last reported in the reference time
def transition(state: AcclimState, report_utc: datetime, local_offset_hours: float) -> Acclim:
tz_diff = abs(local_offset_hours - state.reference_offset_hours)
elapsed = (report_utc - state.reference_since_utc).total_seconds() / 3600.0
resolved = resolve_state(tz_diff, elapsed)
# Once acclimatised to local, that local time becomes the new reference.
if resolved is Acclim.LOCAL:
state.reference_offset_hours = local_offset_hours
state.reference_since_utc = report_utc
return resolved
Verify: a crew member who flies from a UTC+0 base to a UTC+8 station and reports there 30 hours later resolves to DEPARTURE (still within 48 hours); reporting again after a further 80 hours resolves to LOCAL, and the state’s reference offset flips to +8.
Failure modes and troubleshooting
- Treating unknown as acclimatised. Defaulting X to the local or reference column overstates the FDP ceiling. Remediation: map X to the dedicated conservative column, never to B or D.
- Not resetting the reference on D. Once acclimatised to local, that local time is the new reference; failing to reset it keeps measuring the difference from the original base and never re-acclimatises on the next leg. Remediation: update the state’s reference offset and timestamp when D is reached.
- Signed time-zone difference. The table keys on the magnitude of the difference; a signed value sends an eastbound crew to the wrong row. Remediation: take the absolute difference.
- Elapsed time on local clock. Measuring elapsed time on the shifting local clock miscounts the band. Remediation: compute elapsed hours from UTC instants.
- Off-by-one on band ceilings. The bands are
< 48,48–71:59, and so on; a<= 48test moves the 48-hour boundary into the wrong column. Remediation: use strict<against the ceilings and test the exact boundary.
Frequently Asked Questions
Why does EASA need an acclimatisation state when the FAA does not?
The FAA assumes the crew member is acclimatised to a theatre and uses that time directly. EASA models the transition explicitly, recognising that after a large time-zone jump a crew member is neither on departure time nor yet on local time, and holds them in a conservative unknown state until enough time has passed.
What does the unknown state do to the FDP limit?
It selects the most conservative maximum-FDP column, generally reducing the permissible duty compared with either the departure or local column. This is deliberate: an unknown circadian state carries the highest fatigue risk, so the regulation grants the least duty.
How long until a crew acclimatises to a new time zone?
It depends on the size of the jump. Within four hours of difference, acclimatisation to local happens after 48 hours; for the widest jumps up to twelve hours, it takes a full 120 hours (five days) to reach the local state.
Does a rest period reset the elapsed-time clock?
No. Elapsed time runs from when the crew member last reported in their reference time, across rest periods, until they acclimatise to a new local time. Only reaching the local state resets the reference and the clock.
Related
- EASA FTL Compliance Frameworks — the parent section and rule set.
- Rest Requirements After a Long-Haul Rotation — where the acclimatisation shift changes the next FDP.
- Crew Duty Time Taxonomy Mapping — the duty definitions the elapsed-time clock measures.
- Duty Time Validation & Rule Engines — the engines that consume the resolved state.
Back to EASA FTL Compliance Frameworks.