Importér dine data til TimeRetain
TimeRetain importerer tidsdata fra en JSON-fil. En AI-assistent kan hurtigt lave filen ud fra dine eksisterende data.
- Eksportér dine data fra dit nuværende værktøj som CSV eller regneark.
- Send filen og vejledningen nedenfor til en AI-assistent, så den laver en TimeRetain-JSON-fil.
- Åbn Data og synkronisering, vælg Importér, og upload JSON-filen.
Du kan roligt importere igen: eksisterende poster opdateres uden dubletter.
Vejledning til assistenten
Konvertér den vedhæftede fil til TimeRetain v0.12 JSON efter specifikationen nedenfor.
Brug deterministiske id'er, som er ens for det samme input.
Dan hvert id ud fra en stabil rækkenøgle i kilden.
Så opdaterer gentagne importer eksisterende poster i stedet for at oprette dubletter.
Importér den færdige fil via Data og synkronisering → Importér.
## TimeRetain v0.12 JSON-import
Brug denne specifikation til at konvertere eksterne tidsregistreringer til en TimeRetain-JSON-fil.
## Fremgangsmåde for konvertering
Følg disse trin:
1. Vælg en stabil ekstern rækkenøgle, f.eks. det oprindelige række-id. Hvis der ikke findes et, så sammensæt en nøgle af stabile kildekolonner. Det kan være dato, starttid, sluttid, kunde, projekt, opgave og beskrivelse.
2. Dan hvert TimeRetain-id ud fra `first 16 bytes of SHA-256(utf8(externalId))`. Kod det som URL-sikker Base64 uden udfyldningstegn. Brug forskellige udgangsstrenge til beslægtede rækker. Eksempler er `stopwatch:<externalId>`, `interval:<externalId>`, `timeline:<client>/<project>`, `tag:<tagName>` og `tagStopwatch:<externalId>:<tagName>`.
3. Sæt `version` i den ydre struktur til `"0.12"`. Den ydre struktur omslutter `data`. Sæt dens `timestamp` til en ISO-8601-streng.
4. Medtag alle syv arrays i `data` i denne rækkefølge: `interval`, `preferences`, `stopwatch`, `tag`, `timeline`, `tagStopwatch`, `exportOptions`. Arrays må være tomme, bortset fra `preferences` og `timeline`. `exportOptions` er normalt `[]`.
5. Repræsentér kunder, projekter, arbejdsområder, konti og andre grupper til fakturering eller rapportering som `timeline`-rækker. Saml beslægtede tidslinjer under overordnede tidslinjer. Sæt hvert stopurs `timelineId` til den bedst matchende, specifikke tidslinje.
6. Repræsentér aktivitetsnavne som møder, kodning, skrivning, administration, research og support som flade `tag`-rækker. Tags kan ikke grupperes i v0.12.
7. Brug Unix-tid i millisekunder til `interval.start` og `interval.end`. Hvis kilden bruger lokale datoer eller klokkeslæt, skal du anvende dens tidszone før konvertering. Brug kun `null` til `interval.end` for poster, der stadig er i gang.
8. Brug `0` eller `1` i alle `Bool`-felter. Brug ikke JSON-boolske værdier.
9. Beregn `checksum` som SHA-256 af `JSON.stringify(data)` skrevet med små hexadecimale bogstaver. Her er `data` det indre objekt med nøglerne i rækkefølgen ovenfor. Medtag ikke felter fra den ydre struktur i kontrolsummen.
Importen indsætter nye rækker eller opdaterer eksisterende rækker efter `id`.
Deterministiske id'er forhindrer dubletter ved gentagen import.
## Regler for tidslinjer
Brug disse regler:
- Hvis kilden ikke har grupperede tidslinjer for kunder eller projekter, skal du medtage en standardtidslinje med navnet Personligt.
- En tidslinje uden for en gruppe er allerede en faktureringsgrænse, altså en gruppe, der kan faktureres særskilt. Sæt kun `isBillingBoundary: 1` for en grupperet tidslinje, hvis den kan faktureres særskilt.
- Sæt `parentTimelineId` til den overordnede gruppes `timeline.id`, eller til `null`, hvis der ikke er en overordnet gruppe. Opret ikke cykler i grupperingen eller henvisninger til tidslinjer, der mangler.
- Arkiverede tidslinjer er fortsat gyldige i historikken, men kan ikke vælges til nye poster. Brug `archivedAt: null` ved normale importer.
- Arkiverede tags er fortsat gyldige i historikken, men kan ikke vælges til nye poster. Brug `archivedAt: null` ved normale importer.
- Brug `timeline.hourlyRate` som standardtimesats for et projekt eller en kunde. Gem den endelige sats for hver post i `stopwatch.hourlyRate`.
- Når `stopwatch.hourlyRate` ikke er `null`, skal `rateCurrencyCodeAtWrite` sættes til arbejdsområdets valuta ved importen. Når posten ikke har en beregnet sats, skal begge felter sættes til `null`.
- Brug kun `timelineId: null`, når en post med vilje hører til Ingen tidslinje.
- Sæt `preferences.defaultTimelineId` og `preferences.lastUsedTimelineId` til en eksisterende tidslinje, der ikke er arkiveret, normalt Personligt.
## Typer
Brug disse værdityper:
- `Id` er en URL-sikker Base64-streng på 22 tegn uden udfyldningstegn. Den bruger `A-Z`, `a-z`, `0-9`, `-` og `_` og koder 16 byte.
- `Bool` er `0` eller `1`, aldrig `false` eller `true`.
- Tidsstempler i rækker (`interval.start`, `interval.end`) bruger Unix-tid i millisekunder. Den ydre strukturs `timestamp` er en ISO-8601-streng. Alle tal skal være endelige.
## Ydre struktur
```json
{
"version": "0.12",
"timestamp": "<ISO-8601 string>",
"checksum": "<sha256 hex of JSON.stringify(data)>",
"data": {
"interval": [],
"preferences": [],
"stopwatch": [],
"tag": [],
"timeline": [],
"tagStopwatch": [],
"exportOptions": []
}
}
```
Medtag alle syv nøgler i `data` i præcis denne rækkefølge: `interval`, `preferences`, `stopwatch`, `tag`, `timeline`, `tagStopwatch`, `exportOptions`.
## Skema
```ts
interval: {
id: Id;
stopwatchId: Id; // -> stopwatch.id
start: number; // Unix-millisekunder
end: number | null; // Unix-millisekunder, eller null hvis registreringen kører
}
preferences: {
// præcis én række
id: Id;
useMilitaryTime: Bool;
mergeOverlappingStopwatches: Bool; // ældre felt; normalt 0
useAdjust: Bool;
adjustDefault: number; // minutter; kan være 0 eller negativt
backupReminderIntervalInDays: number;
currencyCode: string; // ISO 4217, for eksempel "USD"
earningsTrackingEnabled: Bool;
defaultHourlyRate: number | null;
weekStartsOn: number; // 0 = søndag ... 6 = lørdag
alternativeColor: string | null; // Open Color-værdi, eller null for grå
defaultTimelineId: Id | null; // -> timeline.id
lastUsedTimelineId: Id | null; // -> timeline.id
forgottenRemindersMode: "both" | "start" | "stop" | "off";
}
stopwatch: {
id: Id;
description: string;
adjustMinutes: number; // heltal; kan være negativt
isActive: Bool;
isExpanded: Bool;
hourlyRate: number | null; // gemt sats for dette stopur
rateCurrencyCodeAtWrite: string | null; // arbejdsområdets valuta, da satsen blev gemt
isHourlyRateManual: Bool | null;
targetMinutes: number | null;
timelineId: Id | null; // -> timeline.id; null betyder Ingen tidslinje
}
tag: {
id: Id;
name: string;
hourlyRate: number | null;
color: string | null; // Open Color-værdi som "blue.6", eller null
isPinned: Bool | null; // fastgjort i taglisten; null behandles som fastgjort
archivedAt: number | null; // Unix-millisekunder ved arkivering; null holder tagget aktivt
}
timeline: {
id: Id;
name: string;
parentTimelineId: Id | null; // -> overordnet timeline.id, eller null uden overordnet gruppe
hourlyRate: number | null;
isBillingBoundary: Bool;
archivedAt: number | null; // Unix-millisekunder ved arkivering; null holder tidslinjen aktiv
}
tagStopwatch: {
id: Id;
tagId: Id; // -> tag.id
stopwatchId: Id; // -> stopwatch.id
}
exportOptions: {
// højst én række; de fleste importer bruger i stedet et tomt array
// Brug helst id "4WnR17vMjH50XmwttC218Q" (appens ene indstillingsrække).
// Andre id'er ændres til denne værdi ved import.
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";
}
```
## Kontrolsum
`envelope.checksum` er SHA-256-digesten af `JSON.stringify(data)` skrevet med små hexadecimale bogstaver.
Her er `data` det indre objekt, ikke hele den ydre struktur.
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()
```
## Mindste eksempelstruktur
```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": []
}
}
```
## Almindelige fejl
Undgå disse fejl:
- Brug ikke `true` eller `false` i `Bool`-felter. Brug `0` eller `1`.
- Brug ikke sekunder til `interval.start` eller `interval.end`. Brug millisekunder.
- Brug ikke ISO-strenge som tidsstempler i rækker. Kun `timestamp` i den ydre struktur er en ISO-streng.
- Tilføj ikke et `timezone`-felt til den ydre struktur. Konverteringen kan kræve kildens tidszone, men TimeRetain gemmer rækketider som Unix-tid i millisekunder.
- Udelad ikke kontrolsummen. Beregn den ud fra det indre `data`-objekt, ikke hele den ydre struktur.
- Formatér ikke `data` med ekstra indrykninger, og sorter ikke nøglerne alfabetisk før beregning af kontrolsummen. Brug den præcise nøglerækkefølge ovenfor.
- Udelad ikke nogen af de syv arrays i `data`, heller ikke hvis de er tomme.
- Gruppér ikke kunder eller projekter med tags. Brug `timeline`-rækker til at samle dem under beslægtede tidslinjer.
- Tilføj ikke `tag.parentTagId`. Tags kan ikke grupperes.
- Opret ikke tidslinjer med manglende overordnede grupper eller cykler i grupperingen.
- Hvis kilden har en kendt kunde, et projekt, en konto eller et arbejdsområde, skal du sætte `stopwatch.timelineId`.
- Opret tilsvarende `tagStopwatch`-rækker for stopure med tags.