Metadata-Version: 2.5
Name: clockify-timesheet
Version: 0.5.1
Summary: Export time entries from Clockify to an Excel file (.xlsx) with a full detail sheet and aggregated summary sheets
Project-URL: Homepage, https://github.com/gborelli/clockify-timesheet
Project-URL: Repository, https://github.com/gborelli/clockify-timesheet
Project-URL: Changelog, https://github.com/gborelli/clockify-timesheet/blob/main/CHANGELOG.md
Author: Giorgio Borelli
License-Expression: MIT
License-File: LICENSE
Keywords: clockify,excel,time-tracking,timesheet
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Office/Business
Requires-Python: >=3.11
Requires-Dist: babel>=2.18.0
Requires-Dist: openpyxl>=3.1.5
Requires-Dist: python-dotenv>=1.2.2
Requires-Dist: requests>=2.33.1
Description-Content-Type: text/markdown

# Clockify Timesheet

Export time entries from [Clockify](https://clockify.me) to an Excel file (.xlsx) with 6 sheets: a full detail sheet with task descriptions and 5 aggregated summary sheets.

Can be used both as a **CLI command** and as an importable **Python module** in other projects.

## Requirements

- Python >= 3.11
- [uv](https://docs.astral.sh/uv/) (package manager)
- Clockify account with API key

## Installation

### As a standalone project

```bash
git clone <repo-url>
cd timesheet

cp .env.example .env
chmod 600 .env   # the file holds your API key: keep it readable only by you
# Edit .env with your API key

uv sync
```

### As a dependency in another project

Published on PyPI as [`clockify-timesheet`](https://pypi.org/project/clockify-timesheet/):

```bash
# with uv
uv add clockify-timesheet

# with pip
pip install clockify-timesheet
```

To track an unreleased change instead, install straight from the repository:

```bash
# with uv
uv add git+<repo-url>

# with pip
pip install git+<repo-url>
```

## Configuration

Environment variables in the `.env` file:

| Variable | Description | Default |
|---|---|---|
| `CLOCKIFY_API_KEY` | Personal Clockify API key | — (required) |
| `CLOCKIFY_OUTPUT_DIR` | Directory where Excel files are saved | `.` |
| `CLOCKIFY_CONNECT_TIMEOUT` | HTTP connect timeout (seconds), used only if `timeout` is not passed to `ClockifyClient` | `5` |
| `CLOCKIFY_READ_TIMEOUT` | HTTP read timeout (seconds), used only if `timeout` is not passed to `ClockifyClient` | `30` |

The `.env` file is looked up starting from the current working directory (and its parents), so run the commands from the folder that contains it. Variables already set in the environment take precedence over `.env`. Only `CLOCKIFY_*` variables are read from `.env`; anything else in it (for example `HTTPS_PROXY` or `REQUESTS_CA_BUNDLE`) is ignored on purpose, so that a `.env` found in an untrusted directory cannot redirect your API key elsewhere. Set such variables in your shell instead.

The API key is trimmed of surrounding whitespace (e.g. a trailing newline); a key with spaces or line breaks inside is rejected with an error that does not print it.

**How to get the API key:** Clockify → Profile settings → API → copy the key.

## CLI usage

After `uv sync` the `clockify-export` and `clockify-export-flat` commands are available:

```bash
# Export the current month (full 6-sheet report)
clockify-export

# Custom date range
clockify-export --start 2025-01-01 --end 2025-01-31

# Custom file name
clockify-export --start 2025-01-01 --end 2025-01-31 --output january.xlsx

# Flat export for automated import
clockify-export-flat
clockify-export-flat --start 2025-01-01 --end 2025-01-31
```

Alternatively, the old scripts are kept as backward-compatibility shims:

```bash
uv run python clockify_export.py --start 2025-01-01 --end 2025-01-31
uv run python clockify_export_flat.py --start 2025-01-01 --end 2025-01-31
```

### Parameters

| Parameter | Default | Description |
|---|---|---|
| `--start` | First day of the current month | Start date (YYYY-MM-DD) |
| `--end` | Last day of the current month | End date (YYYY-MM-DD) |
| `--output` | `YYYY-MM-DD-MonthName.xlsx` | Output Excel file name: a plain `.xlsx` name, not a path (the directory is set by `CLOCKIFY_OUTPUT_DIR`) |
| `--force` | off | Overwrite the output file if it already exists (otherwise the command fails) |

Exit codes: `0` success, `1` generic error (invalid input, output file, API error), `2` authentication failed, `3` Clockify unreachable or server error. Errors are printed to stderr.

## Usage as a Python module

```python
from clockify_timesheet import ClockifyClient, export_detailed, export_flat
from clockify_timesheet.utils import validate_dates

# The API key is read from CLOCKIFY_API_KEY if not passed explicitly
client = ClockifyClient(api_key="your-api-key")

start_iso, end_iso = validate_dates("2025-01-01", "2025-01-31")
entries = client.fetch_entries(start_iso, end_iso)

# Full report (6 sheets)
export_detailed(entries, "report.xlsx", "2025-01-01", "2025-01-31")

# Single sheet for automated import
export_flat(entries, "import.xlsx")
```

### Public API

#### `ClockifyClient`

```python
client = ClockifyClient(api_key=None, timeout=None)
# api_key: if omitted, reads the CLOCKIFY_API_KEY environment variable
# timeout: (connect, read) seconds passed to every HTTP request.
#          If omitted, reads CLOCKIFY_CONNECT_TIMEOUT / CLOCKIFY_READ_TIMEOUT
#          from the environment, falling back to (5, 30).

client.get_current_user()                              # → dict
client.get_workspaces()                                # → list
client.fetch_entries(start, end, workspace_id=None)    # → list[dict]
```

### Error handling

Every HTTP call made by `ClockifyClient` has a default timeout and translates
transport/HTTP errors into a dedicated exception hierarchy, so callers never
need to catch raw `requests` exceptions:

```python
from clockify_timesheet import ClockifyError, ClockifyAuthError, ClockifyConnectionError

try:
    entries = client.fetch_entries(start_iso, end_iso)
except ClockifyAuthError:
    ...  # invalid/expired API key (HTTP 401/403)
except ClockifyConnectionError:
    ...  # Clockify unreachable: timeout, connection error, or HTTP 5xx
except ClockifyError:
    ...  # any other Clockify API error (e.g. HTTP 4xx)
```

| Exception | Raised when |
|---|---|
| `ClockifyAuthError` | HTTP 401/403 |
| `ClockifyConnectionError` | request timeout, connection error, or HTTP 5xx |
| `ClockifyError` | any other non-2xx response (base class of the two above) |

#### Aggregation functions

```python
from clockify_timesheet import group_entries, parse_entries, group_entries_flat

rows        = group_entries(entries)       # aggregates by (date, client, project, task)
detail_rows = parse_entries(entries)       # one row per individual entry
flat_rows   = group_entries_flat(entries)  # aggregates by (date, project, task, note)
```

#### Export functions

```python
from clockify_timesheet import build_excel, build_excel_flat

build_excel(rows, "report.xlsx", "2025-01-01", "2025-01-31", detail_rows=detail_rows)
build_excel_flat(flat_rows, "import.xlsx")
```

#### Utilities

```python
from clockify_timesheet.utils import validate_dates, seconds_to_hhmm, parse_iso_duration

start_iso, end_iso = validate_dates("2025-01-01", "2025-01-31")
# → ("2025-01-01T00:00:00.000Z", "2025-01-31T23:59:59.999Z")
# Raises ValueError if the dates are invalid
```

## Output

The Excel file generated by `clockify-export` contains **6 sheets**, each with a final total row, frozen headers, and alternating row colors.

### Sheet 1 — "Details by project"

Hours for each day + project combination. Columns:

| Column | Format |
|---|---|
| **Date** | DD/MM/YYYY |
| **Total Hours** | HH:MM |
| **Client** |  |
| **Project** |  |

### Sheet 2 — "Details by task"

One row for each (date, client, project, task) combination with aggregated hours. Columns:

| Column | Format |
|---|---|
| **Date** | DD/MM/YYYY |
| **Hours Spent** | HH:MM |
| **Client** |  |
| **Project** |  |
| **Task** |  |

### Sheet 3 — "Total hours by task"

Total hours per task over the whole period, sorted by project → task. Columns:

| Column | Format |
|---|---|
| **Client** |  |
| **Project** |  |
| **Task** |  |
| **Total Hours** | HH:MM |

### Sheet 4 — "Total hours by project"

Total hours per project over the whole period. Columns:

| Column | Format |
|---|---|
| **Client** |  |
| **Project** |  |
| **Total Hours** | HH:MM |

### Sheet 5 — "Total hours by day"

Total hours per day, regardless of client/project/task. Columns:

| Column | Format |
|---|---|
| **Date** | DD/MM/YYYY |
| **Total Hours** | HH:MM |

### Sheet 6 — "Full report"

One row per individual time entry, with a description of the work performed. Columns:

| Column | Format |
|---|---|
| **Date** | DD/MM/YYYY |
| **Hours Spent** | HH:MM |
| **Client** |  |
| **Project** |  |
| **Task** |  |
| **Description** |  |

## Flat export for automated import

`clockify-export-flat` produces a **raw single-sheet** Excel file intended for automated import into other systems: one sheet only, just the column header row (no title, no period, no final total, no formatting).

It accepts the same parameters (`--start`, `--end`, `--output`); the default file name uses the `-import` suffix (`YYYY-MM-DD-MonthName-import.xlsx`) to avoid colliding with the full export.

**Aggregation:** one row for each (date, project, task, note) combination. Time entries sharing the same note are summed; if notes differ they remain separate rows.

### Single sheet columns

| Column | Format | Source |
|---|---|---|
| **Date** | YYYY-MM-DD | time entry start |
| **Project** |  | `projectName` |
| **Task** |  | `taskName` |
| **Hours** | HH:MM | aggregated duration |
| **Notes** |  | time entry `description` |
