Importeer je gegevens in TimeRetain
TimeRetain importeert tijdgegevens uit een JSON-bestand. Een AI-assistent kan zo'n bestand snel voor je maken van bestaande gegevens.
- Exporteer je gegevens uit je huidige hulpmiddel als CSV of spreadsheet.
- Stuur dat bestand met de onderstaande opdracht naar een AI-assistent om een TimeRetain-JSON-bestand te maken.
- Open Gegevens en synchronisatie, kies Importeren en upload het JSON-bestand.
Opnieuw importeren is veilig: bestaande regels worden bijgewerkt, niet gedupliceerd.
Opdracht voor de assistent
Zet het bijgevoegde bestand om naar TimeRetain v0.12 JSON volgens de onderstaande specificatie.
Gebruik deterministische ID’s die voor dezelfde invoer gelijk blijven.
Leid elke ID af van een stabiele sleutel voor de bronrij.
Bij een herhaalde import worden bestaande vermeldingen dan bijgewerkt in plaats van gedupliceerd.
Importeer het resultaat via Gegevens en synchronisatie → Importeren.
## TimeRetain v0.12 JSON importeren
Gebruik deze specificatie om externe tijdregistratie om te zetten naar een TimeRetain-JSON-bestand.
## Stappen voor de omzetting
Volg deze stappen:
1. Kies een stabiele externe rijsleutel, zoals de oorspronkelijke rij-ID. Ontbreekt die, maak er dan een uit stabiele bronkolommen. Dat kunnen datum, begintijd, eindtijd, klant, project, taak en beschrijving zijn.
2. Leid elke TimeRetain-ID af van `first 16 bytes of SHA-256(utf8(externalId))`. Codeer deze als URL-veilige Base64 zonder opvulling. Gebruik verschillende uitgangswaarden voor verwante rijen. Voorbeelden zijn `stopwatch:<externalId>`, `interval:<externalId>`, `timeline:<client>/<project>`, `tag:<tagName>` en `tagStopwatch:<externalId>:<tagName>`.
3. Zet `version` in de envelop op `"0.12"`. De envelop is het buitenste object rond `data`. Zet de `timestamp` ervan op een ISO-8601-tekenreeks.
4. Neem alle zeven arrays in deze volgorde op in `data`: `interval`, `preferences`, `stopwatch`, `tag`, `timeline`, `tagStopwatch`, `exportOptions`. Arrays mogen leeg zijn, behalve `preferences` en `timeline`. `exportOptions` is meestal `[]`.
5. Geef klanten, projecten, werkruimten, accounts en andere groepen voor facturatie of rapportage weer als `timeline`-rijen. Plaats verwante tijdlijnen onder bredere tijdlijnen. Zet de `timelineId` van elke stopwatch op de best passende specifieke tijdlijn.
6. Geef activiteiten zoals vergaderingen, programmeren, schrijven, administratie, onderzoek en ondersteuning weer als platte `tag`-rijen. Tags kunnen in v0.12 niet worden gegroepeerd.
7. Gebruik Unix-tijd in milliseconden voor `interval.start` en `interval.end`. Heeft de bron lokale datums of tijden, pas dan eerst de tijdzone toe. Gebruik `null` voor `interval.end` alleen bij vermeldingen die nog lopen.
8. Gebruik `0` of `1` voor elk `Bool`-veld. Gebruik geen JSON-booleans.
9. Bereken `checksum` als SHA-256 van `JSON.stringify(data)`, in hexadecimale kleine letters. `data` is hierbij het binnenste object met de bovenstaande sleutelvolgorde. Neem envelopvelden niet op in de controlesom.
De import voegt nieuwe rijen toe of werkt bestaande rijen bij op basis van `id`.
Deterministische ID’s voorkomen dubbele vermeldingen bij herhaalde imports.
## Regels voor tijdlijnen
Pas deze regels toe:
- Heeft de bron geen gegroepeerde tijdlijnen voor klanten of projecten, neem dan een standaardrij voor de tijdlijn Personal op.
- Een tijdlijn buiten een groep is al een facturatiegrens: een groep die zelfstandig kan worden gefactureerd. Zet `isBillingBoundary: 1` alleen als een gegroepeerde tijdlijn zelfstandig kan worden gefactureerd.
- Zet `parentTimelineId` op de `timeline.id` van de bovenliggende groep, of op `null` als die er niet is. Maak geen groepslussen of verwijzingen naar ontbrekende tijdlijnen.
- Gearchiveerde tijdlijnen blijven geldig voor historische gegevens, maar kunnen niet voor nieuwe vermeldingen worden gekozen. Gebruik bij normale imports `archivedAt: null`.
- Gearchiveerde tags blijven geldig voor historische gegevens, maar kunnen niet voor nieuwe vermeldingen worden gekozen. Gebruik bij normale imports `archivedAt: null`.
- Gebruik `timeline.hourlyRate` voor standaardtarieven per project of klant. Zet het bepaalde tarief voor elke vermelding in `stopwatch.hourlyRate`.
- Als `stopwatch.hourlyRate` niet `null` is, zet `rateCurrencyCodeAtWrite` dan op de valuta van de werkruimte voor deze import. Heeft de vermelding geen bepaald tarief, zet beide velden dan op `null`.
- Gebruik `timelineId: null` alleen als een vermelding bewust bij Geen tijdlijn hoort.
- Zet `preferences.defaultTimelineId` en `preferences.lastUsedTimelineId` op een bestaande tijdlijn die niet is gearchiveerd, meestal Personal.
## Typen
Gebruik deze waardetypen:
- `Id` is een URL-veilige Base64-tekenreeks van 22 tekens zonder opvulling. Deze gebruikt `A-Z`, `a-z`, `0-9`, `-` en `_`, en codeert 16 bytes.
- `Bool` is `0` of `1`, nooit `false` of `true`.
- Tijdstempels van rijen (`interval.start`, `interval.end`) gebruiken Unix-tijd in milliseconden. De envelop-`timestamp` is een ISO-8601-tekenreeks. Alle getallen moeten eindig zijn.
## Envelop
```json
{
"version": "0.12",
"timestamp": "<ISO-8601 string>",
"checksum": "<sha256 hex of JSON.stringify(data)>",
"data": {
"interval": [],
"preferences": [],
"stopwatch": [],
"tag": [],
"timeline": [],
"tagStopwatch": [],
"exportOptions": []
}
}
```
Neem alle zeven sleutels in precies deze volgorde op in `data`: `interval`, `preferences`, `stopwatch`, `tag`, `timeline`, `tagStopwatch`, `exportOptions`.
## Schema
```ts
interval: {
id: Id;
stopwatchId: Id; // -> stopwatch.id
start: number; // Unix-milliseconden
end: number | null; // Unix-milliseconden, of null als de sessie loopt
}
preferences: {
// precies één rij
id: Id;
useMilitaryTime: Bool;
mergeOverlappingStopwatches: Bool; // verouderd veld; meestal 0
useAdjust: Bool;
adjustDefault: number; // minuten; mag 0 of negatief zijn
backupReminderIntervalInDays: number;
currencyCode: string; // ISO 4217, bijvoorbeeld "USD"
earningsTrackingEnabled: Bool;
defaultHourlyRate: number | null;
weekStartsOn: number; // 0 = zondag ... 6 = zaterdag
alternativeColor: string | null; // Open Color-token, of null voor grijs
defaultTimelineId: Id | null; // -> timeline.id
lastUsedTimelineId: Id | null; // -> timeline.id
forgottenRemindersMode: "both" | "start" | "stop" | "off";
}
stopwatch: {
id: Id;
description: string;
adjustMinutes: number; // geheel getal; mag negatief zijn
isActive: Bool;
isExpanded: Bool;
hourlyRate: number | null; // opgeslagen tarief voor deze stopwatch
rateCurrencyCodeAtWrite: string | null; // valuta van de werkruimte toen het tarief werd opgeslagen
isHourlyRateManual: Bool | null;
targetMinutes: number | null;
timelineId: Id | null; // -> timeline.id; null betekent Geen tijdlijn
}
tag: {
id: Id;
name: string;
hourlyRate: number | null;
color: string | null; // Open Color-token zoals "blue.6", of null
isPinned: Bool | null; // vastgezet in de taglijst; null geldt als vastgezet
archivedAt: number | null; // Unix-milliseconden bij archivering; null houdt de tag actief
}
timeline: {
id: Id;
name: string;
parentTimelineId: Id | null; // -> timeline.id van de bovenliggende tijdlijn, of null zonder groep
hourlyRate: number | null;
isBillingBoundary: Bool;
archivedAt: number | null; // Unix-milliseconden bij archivering; null houdt de tijdlijn actief
}
tagStopwatch: {
id: Id;
tagId: Id; // -> tag.id
stopwatchId: Id; // -> stopwatch.id
}
exportOptions: {
// hoogstens één rij; de meeste imports gebruiken een lege array
// Gebruik bij voorkeur id "4WnR17vMjH50XmwttC218Q" (de vaste rij van de app).
// Andere ID’s worden bij het importeren door die waarde vervangen.
id: Id;
format: "csv";
explodeIntervals: 0 | 1;
explodeTags: 0 | 1;
durationBreakdown: 0 | 1;
roundTarget: "off" | "interval" | "total";
roundIncrement: "5" | "6" | "10" | "15" | "30" | "60";
roundDirection: "nearest" | "up" | "down";
timeColumns: "utc" | "split";
}
```
## Controlesom
`envelope.checksum` is de SHA-256-hash van `JSON.stringify(data)` in hexadecimale kleine letters.
`data` is hier het binnenste object, niet de hele envelop.
JavaScript:
```js
import { createHash } from "node:crypto";
const checksum = createHash("sha256")
.update(JSON.stringify(data))
.digest("hex");
```
Python:
```python
import hashlib
import json
payload = json.dumps(data, separators=(",", ":"), ensure_ascii=False)
checksum = hashlib.sha256(payload.encode("utf-8")).hexdigest()
```
## Minimale voorbeeldvorm
```json
{
"version": "0.12",
"timestamp": "2026-04-17T12:00:00.000Z",
"checksum": "3149c7d5240911d6fe1112839e7511b43d4b8e93500a3173c309771769c83f3a",
"data": {
"interval": [
{
"id": "YpC0F378hvhs9cwPrPZqjA",
"stopwatchId": "uHDqdpI_ehZ1fWTWH7I1dw",
"start": 1763884800000,
"end": 1763890200000
}
],
"preferences": [
{
"id": "NymWULO4G6GjGMp6_e5MOA",
"useMilitaryTime": 0,
"mergeOverlappingStopwatches": 0,
"useAdjust": 0,
"adjustDefault": 0,
"backupReminderIntervalInDays": 7,
"currencyCode": "USD",
"earningsTrackingEnabled": 1,
"defaultHourlyRate": null,
"weekStartsOn": 1,
"alternativeColor": null,
"defaultTimelineId": "nM-FuMhKJ-Qc34ihF_5GAQ",
"lastUsedTimelineId": "nM-FuMhKJ-Qc34ihF_5GAQ",
"forgottenRemindersMode": "both"
}
],
"stopwatch": [
{
"id": "uHDqdpI_ehZ1fWTWH7I1dw",
"description": "Client work",
"adjustMinutes": 0,
"isActive": 0,
"isExpanded": 0,
"hourlyRate": 125,
"rateCurrencyCodeAtWrite": "USD",
"isHourlyRateManual": 0,
"targetMinutes": null,
"timelineId": "C_MCKuU1uvIkXqlM2cuvvA"
}
],
"tag": [
{
"id": "-PpIeZugec_a_57XR7os3A",
"name": "writing",
"hourlyRate": null,
"color": null,
"isPinned": null,
"archivedAt": null
}
],
"timeline": [
{
"id": "nM-FuMhKJ-Qc34ihF_5GAQ",
"name": "Personal",
"parentTimelineId": null,
"hourlyRate": null,
"isBillingBoundary": 0,
"archivedAt": null
},
{
"id": "C_MCKuU1uvIkXqlM2cuvvA",
"name": "Client A",
"parentTimelineId": null,
"hourlyRate": 125,
"isBillingBoundary": 0,
"archivedAt": null
}
],
"tagStopwatch": [
{
"id": "Z5XfUVYDOcXMuFH02iHMxQ",
"tagId": "-PpIeZugec_a_57XR7os3A",
"stopwatchId": "uHDqdpI_ehZ1fWTWH7I1dw"
}
],
"exportOptions": []
}
}
```
## Veelgemaakte fouten
Vermijd deze fouten:
- Gebruik voor `Bool`-velden geen `true` of `false`. Gebruik `0` of `1`.
- Gebruik voor `interval.start` en `interval.end` geen seconden. Gebruik milliseconden.
- Gebruik geen ISO-tekenreeksen voor tijdstempels van rijen. Alleen de envelop-`timestamp` is een ISO-tekenreeks.
- Voeg geen veld `timezone` aan de envelop toe. Bij de omzetting kan de brontijdzone nodig zijn, maar TimeRetain bewaart rijtijden als Unix-milliseconden.
- Laat de controlesom niet weg. Bereken deze uit het binnenste object `data`, niet uit de hele envelop.
- Voeg geen extra inspringing of regeleinden aan `data` toe en sorteer de sleutels niet alfabetisch voordat je de controlesom berekent. Gebruik precies de bovenstaande objectsleutelvolgorde.
- Laat geen van de zeven `data`-arrays weg, ook niet als ze leeg zijn.
- Groepeer klanten of projecten niet met tags. Gebruik `timeline`-rijen om ze onder verwante tijdlijnen te plaatsen.
- Voeg geen `tag.parentTagId` toe. Tags kunnen niet worden gegroepeerd.
- Maak geen tijdlijnen met ontbrekende bovenliggende groepen of groepslussen.
- Als de bron een bekende klant, een project, een account of een werkruimte heeft, zet dan `stopwatch.timelineId`.
- Maak bij stopwatches met tags bijbehorende `tagStopwatch`-rijen.