Importera dina data till TimeRetain
TimeRetain importerar tidsdata från en JSON-fil. Det snabbaste sättet att få en sådan är att låta en AI-assistent omvandla dina befintliga data.
- Exportera data från ditt nuvarande verktyg som CSV eller kalkylblad.
- Skicka filen och instruktionen nedan till en AI-assistent som skapar en TimeRetain-JSON-fil.
- Öppna Data och synkronisering, välj Importera och ladda upp JSON-filen.
Du kan importera igen: befintliga poster uppdateras utan att dupliceras.
Instruktion till assistenten
Konvertera den bifogade filen till TimeRetain JSON v0.12 enligt specifikationen nedan.
Använd deterministiska ID:n som blir likadana för samma indata.
Härled varje ID från en stabil nyckel för källraden.
Då uppdaterar upprepade importer befintliga poster i stället för att skapa dubbletter.
Importera den färdiga filen via Data och synkronisering → Importera.
## Import av TimeRetain JSON v0.12
Använd specifikationen för att konvertera extern tidsregistrering till en JSON-fil för TimeRetain.
## Steg för konvertering
Följ dessa steg:
1. Välj en stabil extern radnyckel, till exempel radens ursprungliga ID. Om ingen finns skapar du en av stabila källkolumner. De kan omfatta datum, starttid, sluttid, kund, projekt, uppgift och beskrivning.
2. Härled varje TimeRetain-ID från `first 16 bytes of SHA-256(utf8(externalId))`. Koda det som URL-säker Base64 utan utfyllnad. Använd olika utgångssträngar för relaterade rader. Exempel: `stopwatch:<externalId>`, `interval:<externalId>`, `timeline:<client>/<project>`, `tag:<tagName>` och `tagStopwatch:<externalId>:<tagName>`.
3. Sätt omslagets `version` till `"0.12"`. Omslaget är det yttre objektet runt `data`. Sätt dess `timestamp` till en ISO-8601-sträng.
4. Ta med alla sju arrayer i `data` i denna ordning: `interval`, `preferences`, `stopwatch`, `tag`, `timeline`, `tagStopwatch`, `exportOptions`. Arrayerna får vara tomma utom `preferences` och `timeline`. `exportOptions` är vanligtvis `[]`.
5. Representera kunder, projekt, arbetsytor, konton och andra grupper för fakturering eller rapportering som `timeline`-rader. Lägg relaterade tidslinjer under bredare tidslinjer. Sätt varje stoppurs `timelineId` till den specifika tidslinje som passar bäst.
6. Representera aktivitetsnamn som möten, kodning, skrivande, administration, research och support som fristående `tag`-rader. Taggar kan inte grupperas i v0.12.
7. Använd Unix-tid i millisekunder för `interval.start` och `interval.end`. Om källan använder lokala datum eller tider tillämpar du dess tidszon före konvertering. Använd `null` för `interval.end` endast för poster som fortfarande pågår.
8. Använd `0` eller `1` för varje `Bool`-fält. Använd inte booleska JSON-värden.
9. Beräkna `checksum` som SHA-256 av `JSON.stringify(data)`, i hexadecimalt format med små bokstäver. Här är `data` det inre objektet med nyckelordningen ovan. Ta inte med fält från omslaget i kontrollsumman.
Importen lägger till nya rader eller uppdaterar befintliga rader efter `id`.
Deterministiska ID:n gör att upprepade importer inte skapar dubbletter.
## Regler för tidslinjer
Använd dessa regler:
- Om källan inte har grupperade kund- eller projekttidslinjer, ta med en standardtidslinje med namnet Personal.
- En tidslinje utanför en grupp är redan en faktureringsgräns, alltså en grupp som kan faktureras självständigt. Sätt `isBillingBoundary: 1` endast när en grupperad tidslinje kan faktureras självständigt.
- Sätt `parentTimelineId` till den överordnade gruppens `timeline.id`, eller `null` om det inte finns någon överordnad grupp. Skapa inte gruppcykler eller referenser till tidslinjer som saknas.
- Arkiverade tidslinjer är fortfarande giltiga för historik men kan inte väljas för nya poster. Använd `archivedAt: null` för vanliga importer.
- Arkiverade taggar är fortfarande giltiga för historik men kan inte väljas för nya poster. Använd `archivedAt: null` för vanliga importer.
- Använd `timeline.hourlyRate` för standardpriser per projekt eller kund. Sätt det beräknade priset för varje post i `stopwatch.hourlyRate`.
- När `stopwatch.hourlyRate` inte är `null` sätter du `rateCurrencyCodeAtWrite` till arbetsytans valuta för importen. När posten saknar ett beräknat pris sätter du båda fälten till `null`.
- Använd `timelineId: null` endast när en post avsiktligt hör till Ingen tidslinje.
- Sätt `preferences.defaultTimelineId` och `preferences.lastUsedTimelineId` till en befintlig tidslinje som inte är arkiverad, vanligtvis Personal.
## Typer
Använd dessa värdetyper:
- `Id` är en 22 tecken lång URL-säker Base64-sträng utan utfyllnad. Den använder `A-Z`, `a-z`, `0-9`, `-` och `_` och kodar 16 byte.
- `Bool` är `0` eller `1`, aldrig `false` eller `true`.
- Tidsstämplar för rader (`interval.start`, `interval.end`) använder Unix-tid i millisekunder. Omslagets `timestamp` är en ISO-8601-sträng. Alla tal måste vara ändliga.
## Omslag
```json
{
"version": "0.12",
"timestamp": "<ISO-8601 string>",
"checksum": "<sha256 hex of JSON.stringify(data)>",
"data": {
"interval": [],
"preferences": [],
"stopwatch": [],
"tag": [],
"timeline": [],
"tagStopwatch": [],
"exportOptions": []
}
}
```
Ta med alla sju nycklar i `data` i exakt denna ordning: `interval`, `preferences`, `stopwatch`, `tag`, `timeline`, `tagStopwatch`, `exportOptions`.
## Schema
```ts
interval: {
id: Id;
stopwatchId: Id; // -> stopwatch.id
start: number; // Unix-tid i millisekunder
end: number | null; // Unix-tid i millisekunder, eller null om posten pågår
}
preferences: {
// exakt en rad
id: Id;
useMilitaryTime: Bool;
mergeOverlappingStopwatches: Bool; // äldre fält; vanligtvis 0
useAdjust: Bool;
adjustDefault: number; // minuter; kan vara 0 eller negativt
backupReminderIntervalInDays: number;
currencyCode: string; // ISO 4217, till exempel "USD"
earningsTrackingEnabled: Bool;
defaultHourlyRate: number | null;
weekStartsOn: number; // 0 = söndag ... 6 = lördag
alternativeColor: string | null; // färgkod från Open Color, eller null för grått
defaultTimelineId: Id | null; // -> timeline.id
lastUsedTimelineId: Id | null; // -> timeline.id
forgottenRemindersMode: "both" | "start" | "stop" | "off";
}
stopwatch: {
id: Id;
description: string;
adjustMinutes: number; // heltal; kan vara negativt
isActive: Bool;
isExpanded: Bool;
hourlyRate: number | null; // sparat timpris för detta stoppur
rateCurrencyCodeAtWrite: string | null; // arbetsytans valuta när timpriset sparades
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; // färgkod från Open Color, som "blue.6", eller null
isPinned: Bool | null; // fäst i tagglistan; null räknas som fäst
archivedAt: number | null; // Unix-tid i millisekunder vid arkivering; null behåller taggen som aktiv
}
timeline: {
id: Id;
name: string;
parentTimelineId: Id | null; // -> timeline.id för överordnad tidslinje, eller null om den inte är grupperad
hourlyRate: number | null;
isBillingBoundary: Bool;
archivedAt: number | null; // Unix-tid i millisekunder vid arkivering; null behåller tidslinjen som aktiv
}
tagStopwatch: {
id: Id;
tagId: Id; // -> tag.id
stopwatchId: Id; // -> stopwatch.id
}
exportOptions: {
// högst en rad; de flesta importer använder en tom array i stället
// Använd helst id "4WnR17vMjH50XmwttC218Q" för appens enda inställningsrad. Andra ID:n
// ändras till detta värde vid 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";
}
```
## Kontrollsumma
`envelope.checksum` är det gemena hexadecimala SHA-256-värdet av `JSON.stringify(data)`.
Här är `data` det inre objektet, inte hela omslaget.
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()
```
## Minimalt exempel
```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": []
}
}
```
## Vanliga fel
Undvik dessa fel:
- Använd inte `true` eller `false` för `Bool`-fält. Använd `0` eller `1`.
- Använd inte sekunder för `interval.start` eller `interval.end`. Använd millisekunder.
- Använd inte ISO-strängar som tidsstämplar för rader. Endast omslagets `timestamp` är en ISO-sträng.
- Lägg inte till ett `timezone`-fält i omslaget. Konverteringen kan kräva källans tidszon, men TimeRetain lagrar radtider som Unix-tid i millisekunder.
- Utelämna inte kontrollsumman. Beräkna den från det inre objektet `data`, inte från hela omslaget.
- Formatera inte `data` med indrag och sortera inte nycklarna alfabetiskt före beräkning av kontrollsumman. Använd exakt nyckelordningen ovan.
- Utelämna inte någon av de sju arrayerna i `data`, även om den är tom.
- Gruppera inte kunder eller projekt med taggar. Använd `timeline`-rader för att lägga dem under relaterade tidslinjer.
- Lägg inte till `tag.parentTagId`. Taggar kan inte grupperas.
- Skapa inte tidslinjer som hänvisar till saknade överordnade grupper eller bildar gruppcykler.
- Om källan har en känd kund, ett projekt, konto eller en arbetsyta, sätt `stopwatch.timelineId`.
- Skapa motsvarande `tagStopwatch`-rader för stoppur med taggar.