Skip to content

About

Zeiterfassung — Desktop-App zur Arbeitszeiterfassung mit Kalenderansicht, PDF-Reports, SMTP-, Webhook- und Gmail-Versand

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

Zeiterfassung

Note

Hier wird die Zeiterfassung weiterentwickelt. Bis einschließlich 1.21.0 lag das Projekt unter margenheld/Zeiterfassung; dort bleiben die älteren Releases samt Downloads und die Issue-Historie dauerhaft erreichbar.

Wer noch eine Version vor 1.21.0 nutzt, aktualisiert einmalig über das alte Repository — ab 1.21.0 meldet die App neue Versionen automatisch von hier. Neue Releases erscheinen ausschließlich in diesem Repository.

Desktop-App zur Erfassung von Arbeitszeiten: im Kern Kalenderansicht und PDF-Bericht, dazu optional Versand per Gmail, SMTP oder Webhook, Sync zwischen mehreren Geräten über Google Drive und ein Abgleich mit dem Google Kalender.

Release Python Platform License: MIT

Monatsansicht der Zeiterfassung mit erfassten Arbeitszeiten

Aus welcher Version ein Screenshot stammt, steht in seinem Dateinamen (…-v<version>.png), siehe docs/screenshots/.

Inhalt

Features · Installation · Gmail API · SMTP · Multi-Device-Sync · Google-Kalender · Lokale API · Handy-Erfassung · Einstellungen · Plattform-Kompatibilität · Datenspeicherung · Entfernen · Mitentwickeln · Lizenz

Features

Diese Seite beschreibt den Stand auf master. (ab X.Y.Z) nennt die Version, mit der ein Feature erscheint — liegt sie über dem neuesten Release, ist es fertig, aber noch nicht ausgeliefert. Steht dort statt einer Zahl --VERSION--, ist das Feature fertig, aber die Release-Version noch nicht festgelegt. Was die installierte Version kann, steht im CHANGELOG.

Zeiten erfassen

  • Kalenderansicht — Monats- und Wochenansicht mit Tageseinträgen (Start, Ende, Pause) und Netto-Stunden je Tag; Stunden durchgehend in Stunden/Minuten statt dezimal
  • Kategorien — Mehrere Zeitblöcke pro Tag mit eigenen Kategorien; Standard-Start/-Ende pro Kategorie, optional pro Wochentag
  • Reservierungen & Google-Kalender — Zukünftige Arbeitszeiten pro Tag reservieren (eigenes Konzept neben den Ist-Zeiten, im Kalender als violetter Eck-Punkt markiert); optionaler Abgleich mit einem wählbaren Google Kalender
  • Reservierungs-Erinnerungen — Optionale Toast-Benachrichtigung, wenn ein für heute reservierter Slot fällig wird und noch keine Ist-Zeit erfasst ist (konfigurierbare Vorlaufzeit)
  • Urlaub — Urlaubszeiträume als Einheit eintragen (Name, Von/Bis, Stunden pro Tag oder Gesamtstunden auf die Arbeitstage verteilt); im Kalender ein durchgehender türkiser Block über Wochenenden und Feiertage hinweg, einzelne Tage nachträglich anpassbar (halbe Urlaubstage). Urlaub und Arbeitszeit schließen sich am selben Tag aus — ein Urlaubstag nimmt keine Arbeitszeit an, und ein Urlaub lässt sich nicht über bereits erfasste Tage legen. Die Stundenzeile in der Kalenderzelle („4:00 h") lässt sich im Dialog „Urlaub verwalten" abschalten (ab 1.23.0) — der Zeitraum bleibt dann als Block mit „Urlaub" stehen, die Stunden stehen weiterhin im Tooltip. Optional als Ganztags-Termine im Google Kalender — im Dialog „Urlaub verwalten" ein- und ausschaltbar; beim Ausschalten räumt die App ihre Termine auf Wunsch wieder weg. Gerätelokal — Urlaub reist nicht über den Drive-Sync mit
  • Feiertage — Feiertage des gewählten Bundeslands sind im Kalender markiert und werden beim Anlegen eines Eintrags nachgefragt
  • Nur Werktage — Optional lässt sich das Wochenende komplett deaktivieren: Sa/So verschwinden aus Kalender, Standardzeiten, Bericht, Mailversand und PDF-Export. Vorhandene Wochenend-Einträge bleiben gespeichert und sind sofort wieder da, wenn die Einstellung zurückgenommen wird
  • Wochenstunden-Limit — Optionales Werkstudenten-Limit über einen konfigurierbaren Zeitraum mit Warnung beim Überschreiten
  • Pausenpflicht-Warnung — Hinweis beim Speichern, wenn die eingetragene Pause die gesetzliche Mindestpause nach § 4 ArbZG unterschreitet (30 Min ab >6 h, 45 Min ab >9 h); standardmäßig aktiv, abschaltbar. Grobe Näherung, keine rechtliche Bewertung

Tages-Dialog mit Arbeitszeit, Reservierung und Erinnerung

Ein Tag im Detail: erfasste Ist-Zeit, geplante Reservierung und die Erinnerung ans Verschicken. Linksklick auf einen Kalendertag öffnet den Dialog; gelöscht wird im Kalender selbst (Rechtsklick, unter macOS über das ✕ in der Tageszelle).

Berichten & verschicken

  • PDF-Report — Automatische Generierung als druckfreundliches PDF, gruppiert pro ISO-Kalenderwoche; Kategorie-Aufschlüsselung optional
  • E-Mail-Versand — HTML-E-Mail mit PDF-Anhang über Gmail API (OAuth2)
  • SMTP-Versand (ab 1.23.0) — Berichte über einen eigenen Mail-Server statt über die Gmail-API verschicken; mehrere Konten mit je eigenem Empfänger möglich, im Sende-Dialog einzeln anwählbar (die Ziele stehen dort nach Kanal gruppiert: Gmail, SMTP, Webhooks)
  • Webhook-Versand — Der Bericht lässt sich zusätzlich zur E-Mail an konfigurierbare HTTP-Endpunkte senden (JSON und/oder PDF, optional mit Token oder HMAC-Signatur); gerätelokal konfiguriert
  • PDF-Export — Bericht für einen frei gewählten Zeitraum direkt als PDF lokal speichern (ohne Mail-Versand)
  • Urlaub im Bericht — Optionales Häkchen „Urlaub ausweisen“: der Bericht bekommt einen eigenen Urlaubs-Block je Zeitraum und die Zeile „Zu vergüten gesamt“. „Gesamt“ bleibt die reine Ist-Zeit. Liegt an einem Tag ausnahmsweise doch beides — Urlaub und erfasste Arbeitszeit, etwa nach einem Import oder einem Sync vom Zweitgerät —, zählt der Tag nicht doppelt: der Urlaub wird dort um die erfassten Stunden gekürzt, und eine Zeile im Sende-/Export-Dialog sowie eine Fußnote im Bericht sagen, an welchem Tag das passiert ist (ab 1.23.0)
  • Zeitraumwahl — Flexibler Datumsbereich für Reports, mit Filter auf einzelne Kategorien
  • Sende-Erinnerung — Optionale Toast-Erinnerung, die Arbeitszeiten zu verschicken: monatlich an einem frei wählbaren Tag (auf Wunsch von Wochenenden und Feiertagen weg verschoben) und/oder tagesbezogen, wenn ein dafür markierter Reservierungs-Slot ausläuft. Der Sende-Dialog schlägt den Zeitraum seit der letzten Erinnerung vor
  • Teilen & Importieren — Eigene Arbeitszeiten als JSON-Anhang per Mail an eine zweite Person teilen — wahlweise über Gmail oder ein SMTP-Konto, dessen hinterlegter Empfänger dann vorbelegt und überschreibbar ist (ab 1.23.0); der Empfänger importiert sie mit Zeitraum-Filter und drei Konflikt-Modi (alles importieren / alles lokal / pro Tag entscheiden)

Sende-Dialog mit Zeitraum- und Kategorieauswahl

Der Sende-Dialog: Zeitraum, Kategorie-Filter und die Gesamtstunden vor dem Absenden.

Erzeugter PDF-Bericht

Der erzeugte PDF-Bericht — pro ISO-Kalenderwoche gruppiert, mit Tages- und Wochensummen.

App & Umgebung

  • Multi-Device-Sync — Optionale Synchronisation von Zeiteinträgen und Mail-Vorlagen über Google Drive (appDataFolder), inklusive Konflikt-Auflösung wenn dasselbe Datum offline auf mehreren Geräten bearbeitet wurde — per Linksklick direkt auf den betroffenen Kalendertag oder gesammelt in den Einstellungen
  • Einstellungen — In Tabs gegliedert (Arbeitszeit / Erinnerungen / Versand / Google / API / Mobil / App / Updates); Standardzeiten und Pause, Erinnerungen, E-Mail-Vorlagen mit Platzhaltern, Empfänger, SMTP-Konten und Webhooks, Update-Einstellungen
  • Lokale HTTP-API (ab --VERSION--) — Optional (Standard: aus): ein Server nur auf 127.0.0.1, über den Skripte, Taskplaner oder andere Programme auf diesem Rechner deine Zeiten lesen und eintragen können — nur mit Token (Einstellungen → API), nie aus dem Browser; siehe Lokale HTTP-API
  • Handy-Erfassung (ab --VERSION--) — Optional (Standard: aus): Zeiten unterwegs auf dem Handy nachtragen — auch ohne Verbindung — und im selben WLAN mit der App abgleichen. Läuft als Web-App (PWA) im Browser des Handys (Chrome auf Android), gekoppelt per QR-Code; keine App-Installation aus einem Store. Die Verbindung ist unverschlüsselt (nur in vertrauenswürdigen Netzen nutzen), die Desktop-App muss laufen; siehe Handy-Erfassung
  • Autostart & Einzelinstanz — Optionaler minimierter Start bei Anmeldung (Windows, macOS, Linux); es läuft immer nur eine Instanz — ein zweiter Start holt das vorhandene Fenster nach vorn
  • Entfernen aus der App (ab 1.24.0) — Unter macOS und Linux räumt „Zeiterfassung entfernen“ (Einstellungen → App) Schlüsselbund, Zugangsdaten, Autostart und Menüeintrag ab, auf Wunsch auch Zeiten und Einstellungen; die Programmdatei löschst du danach selbst
  • Update-Check — Konfigurierbare Hintergrund-Prüfung auf neue Releases; Updates-Tab mit manuellem Check, Changelog und Direkt-Download, bei aktivem Tray als einmaliger Toast statt Banner. Läuft die App im Infobereich, stößt „Nach Updates suchen" im Tray-Menü die Prüfung direkt an — das Ergebnis kommt als Toast, auch wenn alles aktuell ist. Optional lassen sich auch Vorabversionen (Pre-Releases) anbieten — Testbuilds vor dem echten Release
  • Update aus der App (ab 1.23.0) — Unter Windows und Linux lädt die App ein Update selbst, prüft es gegen die Prüfsummen des Releases und installiert es; auf Wunsch automatisch beim nächsten Beenden. Unter macOS öffnet der Knopf weiterhin den Download im Browser
  • Dark Mode UI — Modernes dunkles Design, für alle Dialoge einheitlich
  • UI-Skalierung — Stufenloser Skalierungsfaktor für die Oberfläche (gerätelokal). Unter Windows und Linux folgt die App zusätzlich der Anzeigeskalierung des Systems (ab 1.24.0) — unter Windows bleibt sie dabei scharf, unter Linux ist sie auf skalierten X11-Desktops nicht mehr winzig; der Faktor gilt dann obendrauf
  • Cross-Platform-Installer — Per PyInstaller gebaut, als Setup-Exe (Windows), DMG (macOS) und AppImage (Linux) paketierbar

Einstellungs-Dialog, Tab Arbeitszeit

Die Einstellungen, hier der Tab „Arbeitszeit" mit Standardzeiten, Pausenpflicht und Werkstudenten-Limit.

Installation

Vorgefertigte Installer für alle drei Plattformen gibt es unter Releases:

Windows Lade Zeiterfassung_Setup.exe und führe den Installer aus. App installiert nach %LOCALAPPDATA%\Programs\Zeiterfassung\.

macOS (Apple Silicon) Lade Zeiterfassung-<ver>-arm64.dmg herunter. Öffne das DMG und ziehe die App in den Applications-Ordner. Beim ersten Start: Rechtsklick auf die App → „Öffnen" (Gatekeeper-Warnung bestätigen), oder im Terminal:

xattr -dr com.apple.quarantine /Applications/Zeiterfassung.app

Der Build ist nicht signiert — dieser Schritt ist einmalig nötig.

Linux Lade Zeiterfassung-<ver>-x86_64.AppImage herunter:

chmod +x Zeiterfassung-<ver>-x86_64.AppImage
./Zeiterfassung-<ver>-x86_64.AppImage

Voraussetzung: libfuse2 installiert (sudo apt install libfuse2 unter Debian/Ubuntu).

Beim ersten Start legt die App einen Eintrag im Anwendungsmenü an (~/.local/share/applications/Zeiterfassung.desktop) und hält ihn danach automatisch aktuell. Dasselbe gilt für den Autostart, falls aktiviert: beide zeigen nach einem Update von selbst auf die neue AppImage — vorausgesetzt, du startest die neue Datei einmal. Ein Integrationswerkzeug wie appimaged wird nicht gebraucht.

Gmail API einrichten

Damit die App E-Mails über Gmail versenden kann, muss einmalig ein Google Cloud Projekt mit Gmail API eingerichtet werden. Wer keinen Google-Account als Absender nutzen will oder kein Cloud-Projekt anlegen möchte, kann stattdessen auf einen eigenen SMTP-Server ausweichen — siehe E-Mail-Versand ohne Google (SMTP) weiter unten.

1. Google Cloud Projekt erstellen

  1. Google Cloud Console öffnen
  2. Projekt-Dropdown → Neues Projekt → Name: "Zeiterfassung" → Erstellen

2. Gmail API aktivieren

  1. APIs & Dienste → Bibliothek
  2. Nach "Gmail API" suchen → Aktivieren

3. OAuth-Zustimmungsbildschirm (Google Auth Platform)

Google hat die OAuth-Konfiguration 2025 unter Google Auth Platform zusammengezogen (früher „OAuth-Zustimmungsbildschirm" unter APIs & Dienste).

  1. Menü ☰ → Google Auth Platform öffnen (oder direkt console.cloud.google.com/auth/overview)
  2. Falls noch nicht konfiguriert: Jetzt starten / Get Started klicken
  3. Branding: App-Name "Zeiterfassung" + Support-E-Mail (deine Gmail-Adresse)
  4. Zielgruppe / Audience: Nutzertyp Extern wählen
  5. Kontaktinformationen: deine E-Mail-Adresse für Benachrichtigungen
  6. Datenschutzbedingungen akzeptieren → Erstellen / Finish
  7. Anschließend unter Zielgruppe / Audience → Testnutzer → Nutzer hinzufügen: deine Gmail-Adresse eintragen

Die Scopes werden nicht hier, sondern unter Data Access vergeben — entweder fügt sie der Consent-Flow beim ersten Versand automatisch hinzu, oder du trägst sie manuell ein (siehe Hinweis unten und den Sync-/Kalender-Abschnitt). Für reinen Gmail-Versand reichen gmail.send und userinfo.email.

  • userinfo.email wird benötigt, damit die App die Absender-E-Mail-Adresse automatisch aus dem Google-Konto übernehmen kann (non-sensitive, keine Verifizierung nötig)

4. OAuth2 Client-ID erstellen

  1. Menü ☰ → Google Auth Platform → Clients → Client erstellen (alternativ weiterhin unter APIs & Dienste → Anmeldedaten → Anmeldedaten erstellen → OAuth-Client-ID)
  2. Anwendungstyp: Desktop-App → Name: "Zeiterfassung" → Erstellen
  3. JSON herunterladen → als credentials.json speichern:
    • Entwicklung (aus dem Source): im Projekt-Root
    • Windows (installiert): %LOCALAPPDATA%\Programs\Zeiterfassung\
    • macOS (installiert): ~/Library/Application Support/Zeiterfassung/
    • Linux (AppImage): ~/.local/share/Zeiterfassung/ (oder $XDG_DATA_HOME/Zeiterfassung/)

5. Erster Versand

  1. App starten
  2. Unter Einstellungen (⚙) → Versand → Gmail den Empfänger eintragen (der Absender ist das Google-Konto)
  3. Arbeitszeiten senden klicken
  4. Browser öffnet sich → mit Google anmelden → Zugriff erlauben (bei unverifizierter App: Erweitert → „Zu Zeiterfassung (unsicher)")
  5. token.json wird automatisch erstellt

Hinweise

  • Test-Modus läuft alle 7 Tage ab: Solange das Cloud-Projekt den Veröffentlichungsstatus „Testing" hat und Scopes über userinfo.email/profile hinaus anfordert (Gmail, Drive, Kalender — also bei dieser App immer), widerruft Google den Refresh-Token nach 7 Tagen (Google-Doku). Folge: ungefähr wöchentlich öffnet sich der Anmelde-Browser erneut.
    • Abhilfe: Unter Google Auth Platform → Zielgruppe / Audience den Status auf „In Produktion" setzen. Dann bleibt der Refresh-Token langlebig. Für rein private Nutzung ist keine Google-Verifizierung nötig — die App bleibt „nicht verifiziert" (Warnscreen beim ersten Login, Limit 100 Nutzer), funktioniert aber dauerhaft ohne wöchentliche Neuanmeldung.
  • Im Test-Modus können sich nur eingetragene Testnutzer authentifizieren (deine eigene Gmail-Adresse zählt mit)
  • Innerhalb der Token-Gültigkeit wird der Access-Token automatisch erneuert; läuft der Refresh-Token ab, öffnet sich der Browser erneut
  • Welche Berechtigungen dein Konto der App tatsächlich gewährt hat, zeigt die App unter Einstellungen → Google → Berechtigungen — die Kontrolle nach dem Setup und nach jedem Zuschalten von Sync oder Kalender
  • credentials.json und token.json gehören nicht ins Repository

E-Mail-Versand ohne Google (SMTP)

Statt der Gmail-API kann die App Berichte über einen ganz normalen Mail-Server verschicken — dann wird kein Google-Cloud-Projekt und keine credentials.json gebraucht.

Einstellungen → Versand → SMTP-Konten → Hinzufügen:

Feld Bedeutung
Name Frei wählbar, erscheint so im Sende-Dialog
Server / Port z. B. mail.gmx.net / 587
Verschlüsselung STARTTLS (Port 587) oder SSL/TLS (Port 465)
Benutzer / Passwort Zugangsdaten des Postfachs; darf bei internen Relays ohne Anmeldung leer bleiben
Absender Die Adresse, die als Absender erscheint
Empfänger Wohin dieses Konto den Bericht schickt; im Teilen-Dialog ist diese Adresse vorbelegt und überschreibbar

Verbindung testen prüft Server und Zugangsdaten, ohne eine Mail zu verschicken.

Das Passwort wird im Schlüsselbund des Betriebssystems abgelegt (Windows Credential Manager, macOS Keychain, Linux Secret Service). Steht keiner zur Verfügung, wird es lokal in smtp.json gespeichert — die App sagt das dann beim Speichern. Der Schlüsselbund bindet den Zugriff an die installierte App; wird die App aus dem Repo heraus gestartet (python -m src.main), hängt er dagegen am Python-Interpreter und schützt entsprechend weniger.

Was nicht geht: Microsoft-Konten (Outlook.com, Microsoft 365). Microsoft hat SMTP mit Passwort 2026 abgeschaltet; auch App-Passwörter funktionieren dort nicht mehr. Für Gmail wird ein App-Passwort benötigt — nicht das Kontopasswort —, das eine aktive Zwei-Faktor-Anmeldung voraussetzt.

SMTP-Konten gelten nur auf diesem Gerät und reisen nicht per Multi-Device-Sync.

Multi-Device-Sync einrichten (optional)

Wer die App auf mehreren Geräten (z. B. Büro-PC und Privat-Laptop) mit demselben Google-Konto nutzt, kann Zeiteinträge und Mail-Vorlagen automatisch synchronisieren. Die Sync-Datei liegt in einem versteckten App-Ordner in deinem Google Drive (appDataFolder) — sie taucht nicht in der normalen Drive-Ansicht auf und ist nur für diese App lesbar.

Voraussetzung: Gmail API ist bereits eingerichtet (siehe Abschnitt oben). Die Sync-Funktion erweitert das bestehende OAuth-Setup nur um einen zusätzlichen Scope.

1. Google Drive API aktivieren

  1. Google Cloud Console öffnen, dein bestehendes Zeiterfassungs-Projekt wählen
  2. APIs & Dienste → Bibliothek
  3. Nach "Google Drive API" suchen → Aktivieren

2. drive.appdata-Scope hinzufügen

Google hat die OAuth-Konfiguration 2025 unter Google Auth Platform zusammengezogen. Direkt-Link zur Scope-Seite:

https://console.cloud.google.com/auth/scopes

Oder manuell: Menü ☰ → Google Auth Platform → Data Access.

  1. Bereiche hinzufügen oder entfernen klicken
  2. Im Filter drive.appdata eintippen
  3. Häkchen bei .../auth/drive.appdata (Google Drive API) setzen — Beschreibung: „Eigene Konfigurationsdaten in Google Drive abrufen, erstellen und löschen"
  4. Aktualisieren klicken → der Scope landet unter „Nicht vertrauliche Bereiche" (keine Verifizierung nötig — drive.appdata ist Non-Sensitive)

3. Bestehendes Token verwerfen

Solange die alte token.json (nur mit gmail.send-Scope) existiert, läuft kein neuer Consent-Flow.

Empfohlener Weg (ab 1.23.2): Einstellungen → Google → „Google neu verbinden" — das räumt dabei auch einen eventuellen Schlüsselbund-Eintrag des Refresh-Tokens mit ab.

Alternativ die Datei direkt löschen. Ein Schlüsselbund-Eintrag des Refresh-Tokens bleibt dann verwaist stehen — die nächste Anmeldung legt einen neuen an; den alten (Name beginnt mit Zeiterfassung:google-oauth:) bei Bedarf von Hand in der Anmeldeinformationsverwaltung bzw. Schlüsselbundverwaltung entfernen:

  • Windows (installiert): %LOCALAPPDATA%\Programs\Zeiterfassung\token.json
  • macOS (installiert): ~/Library/Application Support/Zeiterfassung/token.json
  • Linux (AppImage): ~/.local/share/Zeiterfassung/token.json
  • Entwicklung (Source): token.json im Projekt-Root

4. Sync in der App aktivieren

  1. App starten → Einstellungen (⚙) öffnen
  2. Sektion Synchronisation ganz unten → Checkbox „Mit Google Drive synchronisieren" anhaken
  3. Browser öffnet sich → mit Google anmelden → der Consent-Screen zeigt jetzt zwei Berechtigungen:
    • „E-Mails über dein Konto senden" (Gmail, bestehend)
    • „Eigene Konfigurationsdaten in deinem Google Drive einsehen und verwalten" (Drive appdata, neu)
  4. Beiden zustimmen → im Header erscheint rechts ein ⟳-Button und ein Status-Label

Wiederhole Schritte 3-4 auf jedem weiteren Gerät mit demselben Google-Konto.

Wie der Sync funktioniert

  • Pull beim App-Start — sobald Sync aktiv und Netz da ist, werden Drive-Änderungen anderer Geräte im Hintergrund eingespielt
  • Push beim App-Schließen — lokale Änderungen werden vor dem Beenden hochgeladen (5s Timeout)
  • Manueller Sync — der ⟳-Button im Header triggert sofortigen Push
  • Konflikte — wird ein Tag offline auf zwei Geräten unterschiedlich bearbeitet, erscheint nach dem Sync ein Warn-Icon auf dem Tag und ein „⚠ N Konflikte"-Status. Klick auf Konflikte ansehen in den Einstellungen öffnet einen Dialog, in dem du Version A, B oder einen eigenen Wert übernehmen kannst

Hinweise zum Sync

  • Geräte-ID — jede Installation bekommt beim ersten Start eine eindeutige ID. Installierte Builds leiten sie aus einer stabilen System-ID des Rechners ab (gehasht, siehe src/device_id.py) — sie übersteht damit eine Neuinstallation der App; im Repo-/Skript-Modus bleibt es bei einer in settings.json gespeicherten Zufalls-UUID. Im Konflikt-Dialog siehst du, von welchem Gerät die jeweilige Version kommt.
  • Gerätename — weil die Geräte-ID nur ein Hex-Wert ist, trägt jedes Gerät zusätzlich einen lesbaren Namen (Einstellungen → Google, beim ersten Start aus dem Hostnamen vorbelegt). Er reist über die Sync-Datei mit, sodass der Konflikt-Dialog „Laptop Arbeit · 6800a51a…" statt nur der ID zeigt. Das Feld darf leer bleiben — dann steht dort weiter nur die gekürzte ID; dasselbe gilt für Geräte, die seit dem Update noch nicht gesynct haben.
  • Was synchronisiert wird: Zeiteinträge + Mail-Vorlagen-Settings (Empfänger, Name, Stundensatz, Betreff, Begrüßung, Inhalt, Grußformel). Gerätespezifisches (Autostart, Standardzeiten pro Wochentag, Update-Einstellungen/-Status) bleibt lokal.
  • Wo die Sync-Datei liegt: Im versteckten appDataFolder deines Google Drives — nicht über drive.google.com einsehbar, nur diese App kommt dran.
  • Test-Modus: Solange dein Cloud-Projekt im Test-Modus bleibt, müssen alle Nutzer (deine eigenen Geräte zählen mit deiner E-Mail) als Testnutzer eingetragen sein. Verifizierung durch Google ist für rein private Nutzung nicht nötig.
  • Tombstones wachsen bei aktivem Sync — gelöschte Einträge bleiben als Marker im Sync-File, damit Löschungen sich gegen veraltete Speicherungen anderer Geräte durchsetzen. Aufgeräumt werden sie erst durch die Aktion „Sync-Daten kompaktieren" (Einstellungen → Google); bei normalem Gebrauch ist das über Jahre unproblematisch. Auf Geräten, die nie gesynct haben, räumt die App diese Marker seit 1.19.1 beim Start automatisch weg. Details: docs/known-limitations.md.

Google-Kalender für Reservierungen einrichten (optional)

Die Reservierungs-Funktion (zukünftige Soll-Zeiten) kann optional mit einem wählbaren Google Kalender abgeglichen werden. Dafür braucht die App zwei zusätzliche Scopes — analog zum Drive-Sync.

Voraussetzung: Gmail API ist bereits eingerichtet (siehe Abschnitt oben).

1. Google Calendar API aktivieren

  1. Google Cloud Console öffnen, dein bestehendes Zeiterfassungs-Projekt wählen
  2. APIs & Dienste → Bibliothek
  3. Nach "Google Calendar API" suchen → Aktivieren

2. Kalender-Scopes hinzufügen

Menü ☰ → Google Auth Platform → Data Access → Bereiche hinzufügen oder entfernen. Zwei Scopes setzen:

  • .../auth/calendar.events — Reservierungs-Events im gewählten Kalender lesen/schreiben
  • .../auth/calendar.calendarlist.readonly — Liste deiner Kalender zur Auswahl abrufen

Beide sind sensitive Scopes (im Gegensatz zu drive.appdata). Für rein private Nutzung gilt dasselbe wie beim Versand: kein Verifizierungs-Review nötig, aber siehe den 7-Tage-Hinweis oben — bei aktivem Kalender-Abgleich greift er erst recht.

3. Bestehendes Token verwerfen

Ein bereits gespeicherter Token ohne die Kalender-Scopes löst keinen neuen Consent-Flow aus. token.json löschen (Pfade siehe Sync-Abschnitt oben) — beim nächsten Start fordert die App den Consent inkl. Kalender neu an. Die App erkennt fehlende Scopes auch selbst und erzwingt dann einen frischen Flow.

4. Kalender in der App wählen

Reservierungen anlegen und den Abgleich über die App-Oberfläche aktivieren; beim ersten Zugriff zeigt der Consent-Screen die beiden Kalender-Berechtigungen zusätzlich an.

Lokale HTTP-API (optional)

Lokale HTTP-API (ab --VERSION--) — Eine kleine Schnittstelle nur auf deinem Rechner (127.0.0.1), über die Skripte, Taskplaner oder andere Programme deine Zeiten lesen und eintragen können. Standardmäßig aus.

  1. Einstellungen → API → „Lokale API aktivieren“, speichern. Der Standard-Port ist 17653; die Statuszeile zeigt, ob die API läuft.
  2. Token kopieren — jeder Zugriff braucht es als Authorization: Bearer <Token>. „Neu erzeugen …“ sperrt alle Programme aus, die das alte Token benutzen.
  3. Abfragen, zum Beispiel:
curl -H "Authorization: Bearer <Token>" http://127.0.0.1:17653/v1/status
curl -H "Authorization: Bearer <Token>" "http://127.0.0.1:17653/v1/entries?from=2026-10-01&to=2026-10-31"
curl -H "Authorization: Bearer <Token>" http://127.0.0.1:17653/v1/entries/2026-10-06
curl -H "Authorization: Bearer <Token>" http://127.0.0.1:17653/v1/summary/month/2026-10
Pfad Antwort
GET /v1/status App-Version, Gerätename, API-Version, Zeit
GET /v1/entries?from=…&to=… Ist-Zeiten im Zeitraum (ohne Angabe: alle), je Tag die Slots {start, end, pause, kategorie}
GET /v1/entries/{YYYY-MM-DD} ein Tag (404, wenn es keinen Eintrag gibt)
PUT /v1/entries/{YYYY-MM-DD} Tag ersetzen, Body {"slots": [...]}; Antwort mit changed (ein identischer Tag wird nicht neu geschrieben) und warnings (Wochenlimit, Pausenpflicht — blockieren nie)
DELETE /v1/entries/{YYYY-MM-DD} Tag löschen (404, wenn es keinen Eintrag gibt)
GET /v1/summary/week/{YYYY-Www} Auswertung einer ISO-Woche (z. B. 2026-W41)
GET /v1/summary/month/{YYYY-MM} Auswertung eines Monats (z. B. 2026-10)
GET /v1/categories die in den Einstellungen konfigurierten Kategorien
GET /v1/holidays/{YYYY} Feiertage des eingestellten Bundeslands (leer, solange keins gesetzt ist)

Einen Tag eintragen (ersetzt den ganzen Tag; pause in Minuten und kategorie sind optional):

curl -X PUT -H "Authorization: Bearer <Token>" -H "Content-Type: application/json" \
     -d '{"slots": [{"start": "08:00", "end": "12:00", "pause": 0, "kategorie": "Projekt"}]}' \
     http://127.0.0.1:17653/v1/entries/2026-10-07

Fehler: 400 kaputtes JSON oder falsche Form, 422 ungültiger Slot oder Datum (Jahr 2000–2100, höchstens 50 Slots, Zeiten genau HH:MM), 409 der Tag hat einen ungelösten Sync-Konflikt oder Urlaub (dann in der App lösen). Eine leere Slot-Liste speichert nichts — zum Löschen DELETE benutzen.

Die Antwort enthält warnings, die nie blockieren: {"code": "weekly_limit", "iso_year", "iso_week", "total_minutes", "limit_minutes"} (Werkstudenten-Limit) und {"code": "pause_requirement", "worked_minutes", "actual_pause_minutes", "required_pause_minutes"} (§ 4 ArbZG). kategorie ist frei (höchstens 100 Zeichen, ohne Steuer- und ungültige Unicode-Zeichen, umgebende Leerzeichen werden entfernt), pause muss eine ganze Zahl sein (30.0 ist ein Fehler). Der Body muss UTF-8 sein, ein BOM ist erlaubt. DELETE entfernt nur die Ist-Zeit des Tages, keine Reservierung und keinen Urlaub.

Die Auswertungen rechnen wie die App in ganzen Minuten: total_minutes (Ist-Zeit), days[] (je Tag mit Ist-Zeit), by_category[] (absteigend; "" heißt „ohne Kategorie“), weeks[] (jede ISO-Woche, die der Zeitraum berührt, mit der Summe der ganzen Woche und limit_minutes (null ohne aktives Werkstudenten-Limit) und exceeded) und pause_warnings[]. Dazu der Urlaub dieses Geräts: vacation_minutes (um die am selben Tag erfasste Ist-Zeit gekappt, die betroffenen Tage stehen in vacation_capped_days) und payable_minutes („Zu vergüten gesamt“, Ist-Zeit plus Urlaub). Es gelten die Jahre 2000–2100: Jahre außerhalb davon sind 422, ungültige Pfade 400 (bei Wochen und Monaten gehören Jahr 0000 und 9999 dazu; /v1/holidays/0000 und /9999 sind 422). Bei einem Monat kann weeks[] eine Woche des Vorjahres enthalten (KW 52/1999 bei 2000-01), die /v1/summary/week/… selbst ablehnt.

Unter Windows PowerShell heißt der Aufruf curl.exe statt curl: dort ist curl ein Alias für Invoke-WebRequest, und -H funktioniert nicht. In Windows PowerShell 5.1 den JSON-Body am besten aus einer Datei übergeben (-d @tag.json), die Anführungszeichen in -d '…' gehen dort verloren, und den Aufruf in eine Zeile schreiben (\ ist dort kein Zeilenumbruch).

Die API läuft nur, solange die App läuft (Autostart hilft), nimmt nur Anfragen von diesem Rechner mit Token an und keine Browser-Anfragen. Grenzen: docs/known-limitations.md.

Handy-Erfassung (optional)

Handy-Erfassung (ab --VERSION--) — Du trägst Arbeitszeiten auf dem Handy ein, auch unterwegs ohne Verbindung. Im selben WLAN gleicht das Handy sie mit dieser App ab. Standardmäßig aus.

Voraussetzungen: Handy und Rechner im selben WLAN, die Desktop-App läuft, auf dem Handy ein aktueller Chrome (Android). Die Web-App liegt unter https://xveyn.github.io/Zeiterfassung/; der Rechner stellt nur die Abgleich-Schnittstelle bereit, nicht die Seite.

  1. Einstellungen → Mobil → „Handy-Erfassung aktivieren". Beim ersten Einschalten erklärt die App die Folgen (unverschlüsselt, App muss laufen). Unter Windows fragt die Firewall nach; erlaube den Zugriff für dein WLAN (stuft Windows es als „öffentlich“ ein, muss es dafür freigegeben sein — das wird vor dem Release auf echter Hardware geprüft). Der Standard-Port ist 17654; die Adresse wählt die App aus deinen Netzwerken („Automatisch“), du kannst sie fest vorgeben. Danach speichern: „Gerät koppeln“ ist erst aktiv, wenn der Server läuft.
  2. „Gerät koppeln" zeigt einen QR-Code und darunter Adresse und Kopplungscode (28 Zeichen, in Gruppen zu vier) als Text. Scanne den QR-Code mit dem Handy (Kamera-App oder in der Web-App) oder tippe Adresse und Kopplungscode ein. Der Code ist fünf Minuten gültig, gilt einmal und verschlüsselt die Verbindung — zeige ihn nur dem eigenen Handy.
  3. Auf dem Handy erscheint die Wochenansicht. Tag antippen → Zeiten, Pause und Kategorie eintragen → „Speichern". Der Abgleich läuft beim Start, beim Zurückkehren in die App, bei wiederhergestellter Verbindung, kurz nach dem Speichern und auf Knopfdruck („Jetzt abgleichen"). Die Statuszeile zeigt, ob Änderungen noch nicht übertragen sind.
  4. Optional zur Startseite hinzufügen (Chrome-Menü → „App installieren“): dann startet die Web-App wie eine App und lässt sich auch ohne Verbindung öffnen. Beides, Installation und QR-Scan in der Web-App, wird vor dem Release auf echten Geräten geprüft; die Kamera-App des Handys und die Eingabe von Adresse und Code funktionieren unabhängig davon.

Was abgeglichen wird: nur Ist-Zeiten. Das Handy sieht standardmäßig die letzten 90 Tage und alle Kategorien; ältere Zeiten lädt es auf Antippen nach („Ältere Zeiten laden“), bis zur Obergrenze, die du unter Einstellungen → Mobil einstellst (höchstens 365 Tage); Einstellungen, Zugangsdaten, Reservierungen und Urlaub bleiben auf dem Rechner. Dasselbe Zusammenführen wie beim Drive-Sync: hat nur eine Seite einen Tag geändert, gilt ihr Stand; haben beide ihn geändert, entsteht ein Konflikt in der Konfliktliste der App (Einstellungen → Google → „Konflikte ansehen (n)“, erscheint nur bei offenen Konflikten), bis zur Auflösung gilt der neuere Stand. Die Web-App zeigt Konflikte nur an.

Geräte verwalten: Einstellungen → Mobil zeigt die gekoppelten Handys mit „zuletzt …“ und „gültig bis …“. Ein Gerät ist 30 Tage ab seinem letzten Abgleich gekoppelt, danach koppelst du neu (die Einträge auf dem Handy bleiben dabei erhalten). „Widerrufen" und „Alle widerrufen" sperren Handys sofort aus; widerrufene Geräte verschwinden aus der Liste (das Handy meldet beim nächsten Abgleich, dass es neu gekoppelt werden muss).

Sicherheit in Kürze: Die Verbindung im WLAN ist nicht per HTTPS gesichert, aber Ende-zu-Ende verschlüsselt (AES-256-GCM): wer im selben WLAN mitliest, sieht weder Einträge noch Kategorien noch das Gerätetoken im Klartext und kann Anfragen weder fälschen noch wiedereinspielen. Sichtbar bleibt, dass ein Handy mit deinem Rechner spricht, wann und wie viel. Der Schlüssel entsteht beim Koppeln und liegt nur auf Handy und Rechner — am Rechner im Schlüsselbund des Betriebssystems, ohne Schlüsselbund in einer nur für dein Benutzerkonto lesbaren Datei. Der Rechner speichert vom Gerätetoken nur einen Hash, lauscht nur auf der gewählten Adresse, und die Funktion lässt sich jederzeit ausschalten. Grenzen: docs/known-limitations.md.

Einstellungen

Über das Zahnrad-Symbol (⚙) im Header konfigurierbar:

Einstellung Beschreibung
Absender Eigene Gmail-Adresse — kommt aus dem Google-Konto (Tab Google), nicht einzutragen
Empfänger E-Mail-Adresse für den Report über Gmail (Tab Versand); SMTP-Konten tragen je einen eigenen
Dein Name Eigener vollständiger Name (erscheint im PDF-Bericht und beim Teilen)
Standard-Pause Standardmäßige Pausendauer in Minuten
Pausenpflicht-Warnung Warnen, wenn die Pause die Mindestpause nach § 4 ArbZG unterschreitet (Standard: an)
Nur Werktage Wochenende (Sa/So) überall ausblenden — Kalender, Standardzeiten und Bericht. Überstimmt „Wochenende (Sa/So) im Kalender anzeigen"; Daten bleiben erhalten
Vorabversionen anbieten Auch Pre-Releases als Update anbieten und melden (Standard: aus, gerätelokal)
Betreff E-Mail-Betreff mit Platzhaltern
Begrüßung Anrede im E-Mail-Text
Inhalt E-Mail-Body mit Platzhaltern
Grußformel Abschluss der E-Mail (Zeilenumbrüche mit \n)
Autostart App minimiert bei Systemanmeldung starten (Windows/macOS/Linux)
Lokale API (ab --VERSION--) Tab „API“: lokale HTTP-API ein-/ausschalten, Port, Status, Token kopieren oder neu erzeugen (Standard: aus, gerätelokal)
Mobil (ab --VERSION--) Tab „Mobil“: Handy-Erfassung ein-/ausschalten, Port, Adresse, Obergrenze des Verlaufs („Verlauf bis zu“, höchstens 365 Tage), Status, gekoppelte Geräte, Koppeln und Widerrufen (Standard: aus, gerätelokal)
Synchronisation Multi-Device-Sync via Google Drive aktivieren (siehe Abschnitt oben)
Berechtigungen Zeigt, welche Google-Berechtigungen (OAuth-Scopes) das Konto der App gewährt hat — inkl. solcher, die noch gewährt, aber zurzeit ungenutzt sind. Daneben steht auf einen Blick „n von m Berechtigungen": ✓ alles da, ○ eine zuschaltbare Funktion wartet noch auf ihre Freigabe, ✗ eine Grundberechtigung fehlt (dann klappt auch der Mail-Versand nicht)
Anmeldung (ab 1.23.1) Ob die Google-Anmeldung noch trägt: ✓ gültig, „nicht angemeldet", ⚠ abgelaufen (dann „Google neu verbinden") oder „nicht prüfbar (offline)". Ergänzt die Zeile darüber: die sagt, welche Freigaben erteilt sind, diese, ob die Anmeldung noch funktioniert — beides kann auseinanderfallen. Wird beim Öffnen geprüft, ohne Browser; lässt sich die Anmeldung still erneuern, passiert das dabei

Platzhalter in E-Mail-Vorlagen

Platzhalter Wird ersetzt durch
{zeitraum} Datumsbereich, z.B. "01.03.2026 – 31.03.2026"
{gesamt} Gesamtstunden, z.B. "168.5h"

Plattform-Kompatibilität

Die App läuft auf Windows, macOS und Linux. Plattformspezifische Features werden automatisch erkannt:

Feature Windows macOS Linux
Kalender & Zeiterfassung ✓ ✓ ✓
PDF-Report & E-Mail-Versand ✓ ✓ ✓
Einstellungen & Vorlagen ✓ ✓ ✓
Taskbar-Icon (AppUserModelID) ✓ — (nicht nötig) — (nicht nötig)
Window-Icon ✓ (.ico) ✓ (.png Fallback) ✓ (.png Fallback)
Autostart bei Anmeldung ✓ (Registry HKCU Run) ✓ (LaunchAgent plist) ✓ (.desktop-Datei)
Eintrag im Anwendungsmenü ✓ (Startmenü, vom Setup) ✓ (Zeiterfassung.app in /Applications) ✓ (.desktop in ~/.local/share/applications/, von der App beim Start geschrieben)
Infobereich-Icon (Tray) ✓ (pystray) ○ (NSStatusItem, Opt-in ZEIT_MACOS_TRAY=1) ✓ (StatusNotifierItem) (ab 1.23.2)
Standalone-Binary (PyInstaller) ✓ (.exe) ✓ (.app Bundle) ✓ (AppImage)
Update aus der App ✓ (lädt, prüft, installiert; per Knopf mit Neustart, beim Beenden ohne) — (Download im Browser) ✓ (lädt, prüft, ersetzt die AppImage; per Knopf mit Neustart, beim Beenden ohne)
Handy-Erfassung (Server in der App) (ab --VERSION--) ✓ (Firewall fragt beim ersten Einschalten) ✓ ✓

○ = implementiert, aber bis zum manuellen Plattform-Test dormant. Das Linux-Tray spricht StatusNotifierItem über D-Bus (KDE Plasma, XFCE, GNOME mit AppIndicator-Extension) und schaltet sich ein, sobald die Sitzung einen Infobereich dafür hat; ohne ihn bleibt es aus, und die App sagt beim Einschalten, woran es liegt. Unter Wayland holt ein Klick das Fenster zurück, das Anheben in den Vordergrund darf der Compositor aber verweigern.

Datenspeicherung

Alle Daten liegen lokal in einem Ordner (Pfad je nach Plattform, siehe unten). Das meiste sind JSON-Dateien — instance-secret, api-token und das Protokoll sind es nicht.

Deine Daten:

  • zeiterfassung.json — Zeiteinträge (Schlüssel: ISO-Datum YYYY-MM-DD)
  • reservations.json — Reservierungen, also zukünftige Soll-Zeiten (eigenes Konzept neben den Ist-Zeiten)
  • vacations.json — Urlaubszeiträume. Gerätelokal: reist bewusst nicht über den Drive-Sync mit
  • settings.json — Benutzereinstellungen
  • conflicts.json — Lokaler Spiegel der Sync-Konflikte (nur vorhanden bei mindestens einem registrierten Konflikt — aus dem Drive-Sync oder der Handy-Erfassung)
  • sync_history.json — Marker „wurde auf diesem Gerät je synchronisiert/abgeglichen"; schützt gelöschte Tage davor, nach einer beschädigten settings.json zurückzukehren
  • sync-apply.journal — Write-Ahead-Journal des Sync-Abgleichs; existiert nur währenddessen. Stürzt die App mittendrin ab, holt der nächste Start den Abgleich darüber nach
  • logs/zeiterfassung.log — Protokoll

Zugangsdaten (siehe Sicherheitshinweis unten):

  • credentials.json — dein OAuth-Client aus der Google Cloud Console; legst du selbst dort ab (siehe Gmail-Einrichtung oben)
  • token.json — OAuth-Token für Gmail, Drive und ggf. Kalender (wird automatisch erneuert)
  • webhooks.json — Webhook-Konfiguration inklusive Zugangstoken bzw. HMAC-Schlüssel. Gerätelokal: reist bewusst nicht über den Drive-Sync mit
  • smtp.json — SMTP-Kontokonfiguration; das Passwort liegt darin nur, wenn kein Schlüsselbund verfügbar war (Datei-Fallback, dann im Klartext). Gerätelokal: reist bewusst nicht über den Drive-Sync mit
  • instance-secret — schützt den lokalen Kanal, über den eine zweite Instanz das vorhandene Fenster nach vorn holt
  • api-token (ab --VERSION--) — Zugriffstoken der lokalen HTTP-API; nur vorhanden, wenn die API einmal eingeschaltet war. Gerätelokal: reist bewusst nicht über den Drive-Sync mit
  • mobile_devices.json (ab --VERSION--) — die gekoppelten Handys der Handy-Erfassung: Name, Zeitpunkte und nur ein Hash des Gerätetokens, nie das Token selbst. Gerätelokal: reist bewusst nicht über den Drive-Sync mit
  • mobile_keys.json (ab --VERSION--) — wo der Schlüssel eines gekoppelten Handys liegt: im Schlüsselbund des Betriebssystems oder, wenn keiner verfügbar ist, in dieser Datei selbst (dann nur für dein Benutzerkonto lesbar). Gerätelokal: reist bewusst nicht über den Drive-Sync mit

Bei aktivem Sync liegt zusätzlich in deinem Google Drive eine versteckte Datei zeiterfassung-sync.json im appDataFolder — nicht über die Drive-Web-Oberfläche sichtbar, nur die App kommt dran.

Speicherort je nach Plattform:

Plattform Pfad
Windows (installiert) %LOCALAPPDATA%\Programs\Zeiterfassung\
macOS (installiert) ~/Library/Application Support/Zeiterfassung/
Linux (AppImage) $XDG_DATA_HOME/Zeiterfassung/ (Fallback ~/.local/share/Zeiterfassung/)
Entwicklung (Source) Projekt-Root

Sicherheitshinweis: Vier Dateien im Datenordner sind Geheimnisse, keine Nutzerdaten. token.json enthält einen langlebigen OAuth-Refresh-Token, der laufenden Zugriff auf dein Google-Konto (Gmail-Versand, Drive-Sync, ggf. Kalender) gewährt. webhooks.json enthält die Zugangstoken bzw. HMAC-Schlüssel deiner Webhook-Ziele. smtp.json enthält die SMTP-Kontodaten und, nur im Datei-Fallback, das Mail-Passwort. instance-secret schützt den lokalen Single-Instance-Kanal.

Mit verfügbarem Schlüsselbund (ab 1.23.2) (Windows Anmeldeinformationsverwaltung / macOS-Schlüsselbund / Linux Secret Service) liegen Refresh-Token, Webhook-Secrets und SMTP-Passwort dort statt im Klartext in diesen Dateien — sie tragen dann nur noch einen Verweis auf den Schlüsselbund-Eintrag. Ist kein Schlüsselbund verfügbar, bleibt es wie oben beschrieben: das jeweilige Secret steht im Klartext in der Datei.

Alle vier Dateien werden gleich gehärtet: unter macOS/Linux per chmod 0600 nur für deinen Benutzer lesbar; unter Windows setzt die App per icacls eine eigene ACL — geerbte Rechte (SYSTEM, lokale Administratoren) entfallen, Zugriff hat nur dein Benutzerkonto. Beides ist Zugriffsschutz auf Dateiebene, keine Verschlüsselung, und gilt unverändert für alle Werte, die (im Datei-Fallback oder im Alt-Format) dort stehen. Wer den Daten-/Installationsordner kopiert, sichert oder in die Cloud synchronisiert, nimmt die dort verbliebenen Klartext-Secrets mit — behandle den Ordner entsprechend vertraulich und gib ihn nicht weiter. Für credentials.json gilt dasselbe Weitergabe-Verbot; sie gewährt für sich genommen aber keinen Zugriff auf dein Konto und wird deshalb nicht gehärtet.

Bei Verdacht auf Kompromittierung: den Zugriff in den Google-Kontoeinstellungen entziehen und token.json löschen (die App startet beim nächsten Versand einen neuen Anmelde-Flow); Webhook-Secrets beim jeweiligen Zielsystem neu vergeben.

Die Windows-Deinstallation entfernt diese Dateien automatisch und räumt zuvor auch die zugehörigen Schlüsselbund-Einträge ab. Das beendet den Zugriff auf diesem Rechner — die erteilte Freigabe im Google-Konto bleibt bestehen, bis du sie dort zurückziehst.

Vollständig entfernen

Windows: Unter „Apps & Features" deinstallieren. Der Uninstaller räumt Zugangsdaten, Schlüsselbund-Einträge und den Autostart-Eintrag ab und fragt, ob auch deine Daten (Zeiten, Einstellungen, Protokoll) weg sollen.

macOS und Linux haben keinen Uninstaller — das Löschen der App räumt weder den Datenordner noch den Autostart noch den Schlüsselbund auf. Ab --VERSION-- erledigt das der Button „Zeiterfassung entfernen“ (Einstellungen → App → Daten) in einem Schritt, bis auf die Programmdatei selbst. Von Hand, in dieser Reihenfolge (der Schlüsselbund-Schritt muss vor dem Löschen des Datenordners laufen, denn die Schlüssel stehen in den Dateien dort):

  1. Autostart ausschalten: Einstellungen → App → „Autostart", dann App beenden.

  2. Schlüsselbund abräumen, einmal von Hand:

    • macOS: /Applications/Zeiterfassung.app/Contents/MacOS/Zeiterfassung --forget-secrets
    • Linux: ./Zeiterfassung-*.AppImage --forget-secrets

    Der Aufruf öffnet kein Fenster und beendet sich sofort. Er entfernt Refresh-Token, Webhook-Secrets und SMTP-Passwörter.

  3. App löschen: macOS /Applications/Zeiterfassung.app in den Papierkorb, Linux die AppImage-Datei.

  4. Datenordner löschen (enthält Zeiten, Einstellungen, token.json, webhooks.json, smtp.json, Protokoll und auf Linux das Menü-Icon):

    • macOS: ~/Library/Application Support/Zeiterfassung/
    • Linux: $XDG_DATA_HOME/Zeiterfassung/ (Fallback ~/.local/share/Zeiterfassung/)
  5. Reste prüfen, falls Schritt 1 ausgelassen wurde:

    • macOS: ~/Library/LaunchAgents/com.margenheld.zeiterfassung.plist — vorher launchctl unload darauf ausführen, sonst bleibt der Dienst bis zur nächsten Abmeldung geladen.
    • Linux: ~/.config/autostart/Zeiterfassung.desktop (Autostart) und $XDG_DATA_HOME/applications/Zeiterfassung.desktop (Fallback ~/.local/share/applications/, Anwendungsmenü). Wer appimaged oder AppImageLauncher nutzt, findet dort zusätzlich einen Eintrag appimagekit_<hash>-Zeiterfassung.desktop.

Die Einträge im Schlüsselbund, falls --forget-secrets nicht lief, stehen in der „Schlüsselbundverwaltung" (macOS) bzw. „Passwörter und Schlüssel" (Linux) unter dem Namen Zeiterfassung (SMTP-Passwörter) und Zeiterfassung:<Schlüssel> (Refresh-Token, Webhook-Secrets).

Auf allen Plattformen: Die Freigabe im Google-Konto bleibt bestehen, bis du sie unter myaccount.google.com/permissions zurückziehst. Mit aktivem Drive-Sync liegt dort außerdem die versteckte Sync-Datei im appDataFolder. Der Entzug der Freigabe löscht sie nicht zwingend; in Google Drive unter Einstellungen → „Apps verwalten" lassen sich die ausgeblendeten App-Daten gezielt löschen.

Mitentwickeln

Die App aus dem Quellcode starten, Tests laufen lassen, selbst bauen oder einen Pull Request aufmachen: Das steht in CONTRIBUTING.md — Voraussetzungen und Setup, Projektstruktur, Build je Plattform, Tests und die Konventionen des Projekts.

Lizenz

Veröffentlicht unter der MIT-Lizenz — frei nutz-, änder- und weiterverbreitbar bei Erhalt des Copyright-Hinweises.

Die App bündelt im Installer Drittanbieter-Bibliotheken mit eigenen Lizenzen (u.a. pystray unter LGPL-3.0 sowie die Google-API- und xhtml2pdf-Pakete unter Apache-2.0). Deren Lizenztexte gelten unverändert fort. scripts/build.py erzeugt dafür beim Bauen automatisch eine THIRD-PARTY-NOTICES.txt (via pip-licenses) und liefert sie in jedem Artefakt mit — im Windows-Setup und der AppImage neben der Anwendung, unter macOS in Zeiterfassung.app/Contents/Resources/.

About

Zeiterfassung — Desktop-App zur Arbeitszeiterfassung mit Kalenderansicht, PDF-Reports, SMTP-, Webhook- und Gmail-Versand

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Used by

Contributors

Languages