# Plan: Implement Fixed-Offset Timezone Support for STF2 NetCDF Files

This plan implements timezone support in the efts-io package to handle various UTC offset timezones while explicitly rejecting daylight saving time (DST) zones.

## TL;DR

The current implementation always converts timestamps to UTC and hardcodes the `time_standard` attribute to "UTC+00:00". The new implementation will:
1. **Preserve** the original timezone information for fixed-offset timezones (UTC±HH:MM)
2. **Support** UTC aliases (UTC, GMT, Etc/UTC)
3. **Default** timezone-naive timestamps to UTC
4. **Reject** DST timezones with clear error messages explaining the limitation

The approach extracts timezone information from input timestamps, validates that they have fixed offsets (no DST), and embeds the offset in both the `time_standard` attribute and the CF time axis units string.

---

## **Steps**

### **Phase 1: Timezone Detection and Validation** *(Foundation)*

1. **Add timezone detection utility function** *(new function in [conventions.py](conventions.py))*
   - Create `detect_timezone_info(timestamps)` to extract timezone from pandas timestamps or DatetimeIndex
   - Return tuple: `(timezone_string, utc_offset_string)` 
   - Handle timezone-naive timestamps (treat as UTC)
   - Handle UTC aliases normalization

2. **Add DST timezone validation function** *(new function in [conventions.py](conventions.py))*
   - Create `validate_fixed_offset_timezone(timezone_string)` 
   - Check if timezone has daylight saving rules (DST)
   - Use `pytz` or `dateutil.tz` to detect if timezone transitions exist
   - Raise `NotImplementedError` for DST timezones with message: "Timezones with daylight saving time (DST) are not supported. STF2 format requires fixed UTC offsets. Please convert your data to a fixed UTC offset (e.g., 'UTC+10:00') before saving."
   - Return normalized timezone string and offset for valid fixed-offset timezones

3. **Add offset extraction utility** *(new function in [conventions.py](conventions.py))*
   - Create `extract_utc_offset_string(timestamps_or_tz)` 
   - Given timezone-aware timestamps, extract the UTC offset as string (e.g., "+10:00", "-05:00")
   - Handle edge cases: UTC+00:00, non-hour offsets (UTC+05:30, UTC+09:30)
   - Format consistently: "+HH:MM" or "-HH:MM"

### **Phase 2: Modify Time Axis Creation** *(Core implementation - parallel with Phase 3)*

4. **Update `_create_cf_time_axis()` function** *(modify [_ncdf_stf2.py](src/efts_io/_ncdf_stf2.py#L53-L98))*
   - **Stop** converting all timestamps to UTC
   - Detect original timezone using Phase 1 utilities
   - Validate timezone is fixed-offset (reject DST)
   - Use **original timezone** when creating CF time axis origin string
   - Keep timestamps in their original timezone for encoding
   - Format units string: `"{timestep_str} since {origin_date_time}{timezone_offset}"`
   - Return timezone offset string as 4th return value (extend signature)

5. **Update time coordinate encoding** *(modify [_ncdf_stf2.py](src/efts_io/_ncdf_stf2.py#L75-L98))*
   - Preserve timezone-aware origin when calling `times.encode_cf_datetime()`
   - Ensure encoded axis values are correct relative to origin with timezone
   - Test roundtrip: decoded times should match original times exactly

### **Phase 3: Update NetCDF Attribute Writing** *(Core implementation - parallel with Phase 2)*

6. **Update `write_nc_stf2()` time_standard attribute** *(modify [_ncdf_stf2.py](src/efts_io/_ncdf_stf2.py#L349))*
   - Replace hardcoded `"UTC+00:00"` with dynamic timezone offset from `_create_cf_time_axis()`
   - Set `time_var.setncattr(TIME_STANDARD_ATTR_KEY, f"UTC{offset_string}")`
   - Ensure format matches: "UTC+HH:MM" or "UTC-HH:MM" or "UTC+00:00" for UTC

7. **Ensure units attribute includes timezone** *(verify [_ncdf_stf2.py](src/efts_io/_ncdf_stf2.py#L353))*
   - The units string from `_create_cf_time_axis()` should already include timezone
   - Verify it's written correctly: `time_var.setncattr(UNITS_ATTR_KEY, time_units_str)`

### **Phase 4: Update Validation and Documentation** *(Finalization)*

8. **Update `exportable_to_stf2()` validation** *(modify [conventions.py](src/efts_io/conventions.py#L645))*
   - Add timezone validation before returning True
   - Check if time coordinate has timezone information
   - If timezone-aware, validate it's a fixed-offset timezone (no DST)
   - Return False for DST timezones with helpful message

9. **Add timezone documentation** *(update docstrings)*
   - Update `write_nc_stf2()` docstring to document timezone behavior
   - Update `xr_efts()` docstring to note timezone expectations
   - Add examples showing fixed-offset timezone usage
   - Document DST limitation clearly

10. **Add timezone examples and error messages** *(polish)*
    - Ensure error messages for DST timezones are user-friendly
    - Suggest workarounds: "Convert to fixed UTC offset or use UTC"
    - Add code comment explaining NetCDF time encoding limitation with DST

---

## **Relevant Files**

- [src/efts_io/_ncdf_stf2.py](src/efts_io/_ncdf_stf2.py) — Modify `_create_cf_time_axis()` function (lines 53-98) to detect and preserve timezone; update `write_nc_stf2()` to use dynamic timezone offset (line 349); reference `xarray.coding.times.encode_cf_datetime()` for timezone-aware encoding
- [src/efts_io/conventions.py](src/efts_io/conventions.py) — Add new functions: `detect_timezone_info()`, `validate_fixed_offset_timezone()`, `extract_utc_offset_string()`; update `exportable_to_stf2()` (line 645) to include timezone validation; reference existing `convert_to_datetime64_utc()` (line 625) as starting point for timezone utilities
- [src/efts_io/wrapper.py](src/efts_io/wrapper.py) — May need to update `xr_efts()` (line 798) docstring to document timezone expectations; verify `save_to_stf2()` (line 358) passes through timezone information correctly

---

## **Verification**

**Automated tests** (already written in [tests/test_write_stf.py](tests/test_write_stf.py)):
- Run `test_fixed_offset_timezones_preserved()` — parametrized test for all fixed offsets (positive, negative, non-hour)
- Run `test_utc_alias_timezones_preserved()` — verify UTC, GMT, Etc/UTC all work
- Run `test_timezone_naive_timestamps_localized_to_utc()` — verify naive timestamps default to UTC
- Run `test_dst_timezones_raise_appropriate_error()` — verify DST zones are rejected with proper error (currently marked xfail)
- Run existing tests to ensure no regression

**Manual verification**:
1. Create dataset with `UTC+10:00` timestamps
2. Save to STF2 file with `save_to_stf2()`
3. Open with `netCDF4` library and verify `time_standard` attribute equals "UTC+10:00"
4. Verify `units` attribute contains "+10:00" offset
5. Read back and verify timestamps match original

---

## **Decisions**

- **Fixed-offset only**: Support only timezones with constant UTC offsets; reject DST timezones because NetCDF's "time since ORIGIN +OFFSET" format cannot handle variable offsets
- **Error over silence**: Raise `NotImplementedError` for DST timezones rather than silently converting to UTC, making the limitation explicit
- **Preserve input timezone**: Store data in the timezone provided by the user (if fixed-offset), not automatically converting to UTC
- **Timezone-naive defaults to UTC**: Following common convention, treat timezone-naive timestamps as UTC
- **UTC normalization**: Treat UTC aliases (UTC, GMT, Etc/UTC) as equivalent to UTC+00:00
- **Format consistency**: Always format offsets as "+HH:MM" or "-HH:MM" (not "+HHMM") for readability and CF convention alignment
