# How to Import and Export Todos Using the iCalendar Service in Lifetrace

> Learn how Lifetrace's iCalendar service imports and exports todos. Easily convert internal data to VTODO/VEVENT or parse .ics strings with full datetime, status, and recurrence support.

- Repository: [FreeU-group/lifetrace](https://github.com/freeu-group/lifetrace)
- Tags: how-to-guide
- Published: 2026-03-02

---

**The iCalendar service converts internal Todo dictionaries into VTODO or VEVENT components for export and parses .ics strings into TodoCreate objects for import, handling UTC datetime normalization, status mapping, and recurrence rules through specialized helper methods.**

The **iCalendar service** ([`lifetrace/services/icalendar_service.py`](https://github.com/freeu-group/lifetrace/blob/main/lifetrace/services/icalendar_service.py)) serves as the primary bridge between Lifetrace's internal task representation and standard iCalendar (ICS) formats used by external calendar applications. This implementation enables bidirectional data flow, allowing users to export their todo lists for use in third-party calendars and import tasks from existing .ics files. The service relies on a serialization layer in [`lifetrace/storage/todo_manager_ical.py`](https://github.com/freeu-group/lifetrace/blob/main/lifetrace/storage/todo_manager_ical.py) to convert SQLAlchemy models into plain dictionaries before processing.

## Exporting Todos to iCalendar Format

The export process transforms a list of Todo dictionaries into a valid iCalendar string through a multi-step pipeline that supports both task and event representations.

### Building the Calendar Container

Every export begins with `_build_calendar()` (lines 55-60), which instantiates a base `Calendar` object populated with standard PRODID, version, and calendar scale fields. This container serves as the root element that will eventually hold all converted todo components.

### Converting Todos to VTODO or VEVENT

The `export_todos` method (lines 97-106) iterates over the supplied dictionary list and determines component type based on the `item_type` field. For each entry, it dispatches to either `_todo_to_vtodo` (lines 67-106) or `_todo_to_vevent` (lines 109-143) depending on whether the item represents a task or a calendar event.

These conversion helpers selectively add optional fields—including summary, description, dates, status, priority, categories, and recurrence rules—only when the source dictionary contains non-null values. This approach ensures the resulting .ics file remains clean and free of empty properties.

### Serializing to .ics String

After all components are added via `cal.add_component()`, the service calls `cal.to_ical().decode("utf-8")` to produce a UTF-8 encoded string suitable for file storage or HTTP transmission.

## Importing Todos from iCalendar Files

The import workflow reverses the process, parsing raw iCalendar data into structured `TodoCreate` objects ready for database insertion.

### Parsing the .ics Content

The `import_todos` method (lines 11-18) begins by invoking `Calendar.from_ical(ics_content)` to build an `icalendar.Calendar` object from the raw string. It then walks through all sub-components using `cal.walk()`, filtering specifically for `VTODO` and `VEVENT` types while ignoring other calendar elements.

### Extracting and Normalizing Fields

For each valid component, the service extracts mandatory and optional fields:

- **Summary**: Required field used as the todo name
- **UID and Description**: Unique identifiers and detailed notes
- **Date/Time fields**: `dtstart`, `dtend`, `due`, and `completed` are converted to UTC via `_from_ical_dt` (lines 29-33)

### Mapping Status, Priority, and Categories

The service applies several normalization helpers to ensure data integrity:

- **Percent complete**: `_normalize_percent` (lines 25-32) clamps values to the 0-100 range
- **Status mapping**: `_status_from_ical` (lines 45-52) converts iCalendar status strings to the internal `TodoStatus` enum, defaulting to `COMPLETED` when percent-complete equals 100
- **Priority conversion**: `_priority_from_ical` (lines 52-66) maps numeric iCalendar priority values to the internal `TodoPriority` enum
- **Categories**: The code normalizes the component's `categories` property—whether presented as a list, tuple, set, or `icalendar.prop.vCategory` object—into a standardized list of strings (lines 55-63)
- **Recurrence rules**: The raw `rrule` value is converted to its string representation, with the original value preserved if parsing fails (lines 65-71)

All extracted values are packaged into `TodoCreate` schema instances (lines 73-85) and returned as a list for bulk insertion via the Todo manager.

## Code Example: Complete Import and Export Workflow

```python
from lifetrace.services.icalendar_service import ICalendarService
from lifetrace.schemas.todo import TodoCreate

# Export a set of Todo dicts (as produced by TodoIcalMixin._todo_to_dict)

todos = [
    {
        "uid": "12345",
        "name": "Buy groceries",
        "summary": "Buy groceries",
        "description": "Milk, Eggs, Bread",
        "item_type": "VTODO",
        "dtstart": None,
        "due": "2026-03-10T12:00:00Z",
        "status": "active",
        "priority": "high",
    },
]

ics_str = ICalendarService().export_todos(todos)
with open("mytodos.ics", "w", encoding="utf-8") as f:
    f.write(ics_str)

# Import an .ics file back into TodoCreate objects

with open("mytodos.ics", "r", encoding="utf-8") as f:
    raw_ics = f.read()

imported_todos: list[TodoCreate] = ICalendarService().import_todos(raw_ics)

# imported_todos can now be passed to the Todo manager for creation

```

## Key Implementation Files

| File | Role | Key Components |
|------|------|----------------|
| [`lifetrace/services/icalendar_service.py`](https://github.com/freeu-group/lifetrace/blob/main/lifetrace/services/icalendar_service.py) | Core import/export logic, conversion helpers, status/priority mapping | `export_todos` (L97-L106), `import_todos` (L11-L18), `_todo_to_vtodo` (L67-L106), `_todo_to_vevent` (L109-L143) |
| [`lifetrace/storage/todo_manager_ical.py`](https://github.com/freeu-group/lifetrace/blob/main/lifetrace/storage/todo_manager_ical.py) | Serializes SQLAlchemy Todo models to dictionaries consumable by the service | `_todo_to_dict` (L54-L122) |
| [`lifetrace/schemas/todo.py`](https://github.com/freeu-group/lifetrace/blob/main/lifetrace/schemas/todo.py) | Pydantic schema for newly imported todos | `TodoCreate` class definition |
| [`lifetrace/util/time_utils.py`](https://github.com/freeu-group/lifetrace/blob/main/lifetrace/util/time_utils.py) | UTC conversion utilities | `ensure_utc`, `naive_as_utc`, `to_local` |
| [`lifetrace/util/logging_config.py`](https://github.com/freeu-group/lifetrace/blob/main/lifetrace/util/logging_config.py) | Logging infrastructure | `get_logger` (line 13) |

## Summary

- The **iCalendar service** provides bidirectional conversion between Lifetrace's internal Todo representation and standard ICS format through `export_todos` and `import_todos` methods.
- **Export** supports both `VTODO` and `VEVENT` components, selectively including only non-null fields to generate clean .ics output.
- **Import** filters for VTODO and VEVENT components, normalizing dates to UTC, clamping percent-complete values, mapping status and priority enums, and handling complex category structures.
- The service depends on `TodoIcalMixin` for model serialization and returns `TodoCreate` objects ready for immediate database insertion.
- All datetime handling utilizes centralized utilities in [`lifetrace/util/time_utils.py`](https://github.com/freeu-group/lifetrace/blob/main/lifetrace/util/time_utils.py) to ensure consistent timezone management.

## Frequently Asked Questions

### What iCalendar components does Lifetrace support for todo import and export?

The service specifically handles **VTODO** and **VEVENT** components. During export, the `item_type` field in the Todo dictionary determines which component type is generated. During import, the `cal.walk()` method filters out all other component types, ensuring only tasks and events are processed while ignoring calendar metadata like timezones or daylight saving rules.

### How does the iCalendar service handle timezone conversion during import?

All datetime fields—including `dtstart`, `dtend`, `due`, and `completed`—are converted to UTC using the `_from_ical_dt` helper method (lines 29-33). This ensures consistent storage regardless of the timezone specified in the original .ics file. The service relies on utilities in [`lifetrace/util/time_utils.py`](https://github.com/freeu-group/lifetrace/blob/main/lifetrace/util/time_utils.py) to handle naive datetimes and timezone-aware conversions safely.

### Can the service distinguish between calendar events and tasks during the export process?

Yes. The `export_todos` method checks the `item_type` field in each Todo dictionary to determine the appropriate component type. If `item_type` indicates a task, it calls `_todo_to_vtodo` (lines 67-106) to generate a VTODO component. For calendar events, it invokes `_todo_to_vevent` (lines 109-143) to create a VEVENT component, allowing the same export pipeline to handle both appointment-style events and task-style todos.

### What happens to recurrence rules when importing todos from iCalendar files?

The service extracts the raw `rrule` property from each component and attempts to convert it to its string representation. If the recurrence rule parsing fails, the original value is preserved rather than discarded (lines 65-71). This ensures that complex recurrence patterns are maintained for storage and can be interpreted correctly when the todo is later exported or displayed in the Lifetrace interface.