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.
Aus welcher Version ein Screenshot stammt, steht in seinem Dateinamen (…-v<version>.png), siehe docs/screenshots/.
Features · Installation · Gmail API · SMTP · Multi-Device-Sync · Google-Kalender · Lokale API · Handy-Erfassung · Einstellungen · Plattform-Kompatibilität · Datenspeicherung · Entfernen · Mitentwickeln · Lizenz
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.
- 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
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).
- 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)
Der Sende-Dialog: Zeitraum, Kategorie-Filter und die Gesamtstunden vor dem Absenden.
Der erzeugte PDF-Bericht — pro ISO-Kalenderwoche gruppiert, mit Tages- und Wochensummen.
- 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
Die Einstellungen, hier der Tab „Arbeitszeit" mit Standardzeiten, Pausenpflicht und Werkstudenten-Limit.
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.appDer 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.AppImageVoraussetzung: 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.
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.
- Google Cloud Console öffnen
- Projekt-Dropdown → Neues Projekt → Name: "Zeiterfassung" → Erstellen
- APIs & Dienste → Bibliothek
- Nach "Gmail API" suchen → Aktivieren
Google hat die OAuth-Konfiguration 2025 unter Google Auth Platform zusammengezogen (früher „OAuth-Zustimmungsbildschirm" unter APIs & Dienste).
- Menü ☰ → Google Auth Platform öffnen (oder direkt console.cloud.google.com/auth/overview)
- Falls noch nicht konfiguriert: Jetzt starten / Get Started klicken
- Branding: App-Name "Zeiterfassung" + Support-E-Mail (deine Gmail-Adresse)
- Zielgruppe / Audience: Nutzertyp Extern wählen
- Kontaktinformationen: deine E-Mail-Adresse für Benachrichtigungen
- Datenschutzbedingungen akzeptieren → Erstellen / Finish
- 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.emailwird benötigt, damit die App die Absender-E-Mail-Adresse automatisch aus dem Google-Konto übernehmen kann (non-sensitive, keine Verifizierung nötig)
- Menü ☰ → Google Auth Platform → Clients → Client erstellen (alternativ weiterhin unter APIs & Dienste → Anmeldedaten → Anmeldedaten erstellen → OAuth-Client-ID)
- Anwendungstyp: Desktop-App → Name: "Zeiterfassung" → Erstellen
- JSON herunterladen → als
credentials.jsonspeichern:- 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/)
- App starten
- Unter Einstellungen (⚙) → Versand → Gmail den Empfänger eintragen (der Absender ist das Google-Konto)
- Arbeitszeiten senden klicken
- Browser öffnet sich → mit Google anmelden → Zugriff erlauben (bei unverifizierter App: Erweitert → „Zu Zeiterfassung (unsicher)")
token.jsonwird automatisch erstellt
- Test-Modus läuft alle 7 Tage ab: Solange das Cloud-Projekt den Veröffentlichungsstatus „Testing" hat und Scopes über
userinfo.email/profilehinaus 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.jsonundtoken.jsongehören nicht ins Repository
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.
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.
- Google Cloud Console öffnen, dein bestehendes Zeiterfassungs-Projekt wählen
- APIs & Dienste → Bibliothek
- Nach "Google Drive API" suchen → Aktivieren
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.
- Bereiche hinzufügen oder entfernen klicken
- Im Filter
drive.appdataeintippen - Häkchen bei
.../auth/drive.appdata(Google Drive API) setzen — Beschreibung: „Eigene Konfigurationsdaten in Google Drive abrufen, erstellen und löschen" - Aktualisieren klicken → der Scope landet unter „Nicht vertrauliche Bereiche" (keine Verifizierung nötig —
drive.appdataist Non-Sensitive)
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.jsonim Projekt-Root
- App starten → Einstellungen (⚙) öffnen
- Sektion Synchronisation ganz unten → Checkbox „Mit Google Drive synchronisieren" anhaken
- 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)
- 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.
- 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
- 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 insettings.jsongespeicherten 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
appDataFolderdeines Google Drives — nicht überdrive.google.comeinsehbar, 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.
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).
- Google Cloud Console öffnen, dein bestehendes Zeiterfassungs-Projekt wählen
- APIs & Dienste → Bibliothek
- Nach "Google Calendar API" suchen → Aktivieren
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.
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.
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 (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.
- Einstellungen → API → „Lokale API aktivieren“, speichern. Der Standard-Port ist 17653; die Statuszeile zeigt, ob die API läuft.
- Token kopieren — jeder Zugriff braucht es als
Authorization: Bearer <Token>. „Neu erzeugen …“ sperrt alle Programme aus, die das alte Token benutzen. - 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 (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.
- 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.
- „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.
- 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.
- 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.
Ü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 | Wird ersetzt durch |
|---|---|
{zeitraum} |
Datumsbereich, z.B. "01.03.2026 – 31.03.2026" |
{gesamt} |
Gesamtstunden, z.B. "168.5h" |
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.
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.jsonzurü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.jsonenthält einen langlebigen OAuth-Refresh-Token, der laufenden Zugriff auf dein Google-Konto (Gmail-Versand, Drive-Sync, ggf. Kalender) gewährt.webhooks.jsonenthält die Zugangstoken bzw. HMAC-Schlüssel deiner Webhook-Ziele.smtp.jsonenthält die SMTP-Kontodaten und, nur im Datei-Fallback, das Mail-Passwort.instance-secretschü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 0600nur für deinen Benutzer lesbar; unter Windows setzt die App pericaclseine 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ürcredentials.jsongilt 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.jsonlö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.
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):
-
Autostart ausschalten: Einstellungen → App → „Autostart", dann App beenden.
-
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.
- macOS:
-
App löschen: macOS
/Applications/Zeiterfassung.appin den Papierkorb, Linux die AppImage-Datei. -
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/)
- macOS:
-
Reste prüfen, falls Schritt 1 ausgelassen wurde:
- macOS:
~/Library/LaunchAgents/com.margenheld.zeiterfassung.plist— vorherlaunchctl unloaddarauf 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ü). Werappimagedoder AppImageLauncher nutzt, findet dort zusätzlich einen Eintragappimagekit_<hash>-Zeiterfassung.desktop.
- macOS:
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.
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.
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/.




