Tuo tietosi TimeRetainiin
TimeRetain tuo aikatiedot JSON-tiedostosta. Nopein tapa saada sellainen on pyytää tekoälyavustajaa muuntamaan nykyiset tietosi.
- Vie tiedot nykyisestä työkalustasi CSV- tai laskentataulukkotiedostona.
- Lähetä tiedosto ja alla oleva ohje tekoälyavustajalle TimeRetainin JSON-tiedoston tekemistä varten.
- Avaa Tiedot ja synkronointi, valitse Tuo ja lähetä JSON-tiedosto.
Uudelleentuonti on turvallista: nykyiset merkinnät päivitetään, eivät monistu.
Ohje avustajalle
Muunna liitetty tiedosto TimeRetain v0.12 JSON -muotoon alla olevan määrityksen mukaan.
Käytä deterministisiä tunnisteita eli samalla syötteellä aina samoja tunnisteita.
Johda jokainen tunniste lähteen vakaasta rivitunnisteesta.
Tällöin toistuva tuonti päivittää nykyiset merkinnät eikä luo niistä kopioita.
Tuo valmis tiedosto kohdasta Tiedot ja synkronointi → Tuo.
## TimeRetain v0.12 JSON -tuonti
Muunna ulkoiset ajanseurantatiedot TimeRetain JSON -tiedostoksi tämän määrityksen mukaan.
## Muunnoksen vaiheet
Toimi näin:
1. Valitse lähteestä vakaa rivitunniste, esimerkiksi alkuperäinen rivin ID. Jos sellaista ei ole, kokoa tunniste pysyvistä lähdesarakkeista. Niitä voivat olla päivämäärä, aloitusaika, lopetusaika, asiakas, projekti, tehtävä ja kuvaus.
2. Johda jokainen TimeRetain-tunniste arvosta `first 16 bytes of SHA-256(utf8(externalId))`. Koodaa se URL-turvallisena Base64-merkkijonona ilman täytemerkkejä. Käytä eri siemeniä toisiinsa liittyville riveille. Esimerkkejä ovat `stopwatch:<externalId>`, `interval:<externalId>`, `timeline:<client>/<project>`, `tag:<tagName>` ja `tagStopwatch:<externalId>:<tagName>`.
3. Aseta ulomman objektin `version`-arvoksi `"0.12"`. Ulompi objekti ympäröi `data`-objektin. Aseta sen `timestamp`-arvoksi ISO-8601-merkkijono.
4. Sisällytä `data`-objektiin kaikki seitsemän taulukkoa tässä järjestyksessä: `interval`, `preferences`, `stopwatch`, `tag`, `timeline`, `tagStopwatch`, `exportOptions`. Muut taulukot voivat olla tyhjiä, mutta `preferences` ja `timeline` eivät. `exportOptions` on yleensä `[]`.
5. Esitä asiakkaat, projektit, työtilat, tilit sekä muut laskutus- tai raportointiryhmät `timeline`-riveinä. Sijoita toisiinsa liittyvät aikajanat laajempien aikajanojen alle. Aseta kunkin ajastimen `timelineId` sopivimpaan yksittäiseen aikajanaan.
6. Esitä toimintojen nimet, kuten kokoukset, ohjelmointi, kirjoittaminen, hallinto, tutkimus ja tuki, erillisinä `tag`-riveinä. Tunnisteita ei voi ryhmitellä versiossa v0.12.
7. Käytä `interval.start`- ja `interval.end`-kentissä Unix-aikaa millisekunteina. Jos lähteessä on paikallisia päivämääriä tai kellonaikoja, huomioi niiden aikavyöhyke ennen muunnosta. Käytä `interval.end`-kentässä arvoa `null` vain yhä käynnissä oleville merkinnöille.
8. Käytä jokaisessa `Bool`-kentässä arvoa `0` tai `1`. Älä käytä JSONin totuusarvoja.
9. Laske `checksum` pienaakkosisena heksadesimaalisena SHA-256-tiivisteenä arvosta `JSON.stringify(data)`. Tässä `data` on sisempi objekti, jonka avaimet ovat yllä annetussa järjestyksessä. Älä sisällytä ulomman objektin kenttiä tarkistussummaan.
Tuonti lisää uudet rivit tai päivittää nykyiset rivit niiden `id`-arvon perusteella.
Deterministiset tunnisteet estävät kopiot toistuvissa tuonneissa.
## Aikajanojen säännöt
Noudata näitä sääntöjä:
- Jos lähteessä ei ole ryhmiteltyjä asiakas- tai projektiaikajanoja, lisää oletusarvoinen Henkilökohtainen-aikajana.
- Ryhmän ulkopuolinen aikajana on jo oma laskutusrajansa eli ryhmä, jota voi laskuttaa erikseen. Aseta `isBillingBoundary: 1` ryhmitellylle aikajanalle vain, jos sitä voi laskuttaa erikseen.
- Aseta `parentTimelineId`-arvoksi ylemmän ryhmän `timeline.id` tai `null`, jos ylempää ryhmää ei ole. Älä luo ryhmittelyyn syklejä tai viittauksia puuttuviin aikajanoihin.
- Arkistoidut aikajanat säilyvät historiassa, mutta niitä ei voi valita uusiin merkintöihin. Käytä tavallisessa tuonnissa arvoa `archivedAt: null`.
- Arkistoidut tunnisteet säilyvät historiassa, mutta niitä ei voi valita uusiin merkintöihin. Käytä tavallisessa tuonnissa arvoa `archivedAt: null`.
- Käytä projektin tai asiakkaan oletustuntihintana kenttää `timeline.hourlyRate`. Tallenna kunkin merkinnän lopullinen tuntihinta kenttään `stopwatch.hourlyRate`.
- Kun `stopwatch.hourlyRate` ei ole `null`, aseta `rateCurrencyCodeAtWrite`-arvoksi tuonnissa käytettävän työtilan valuutta. Jos merkinnälle ei ole tuntihintaa, aseta molempien kenttien arvoksi `null`.
- Käytä arvoa `timelineId: null` vain, jos merkinnän kuuluu tarkoituksella olla ilman aikajanaa.
- Aseta `preferences.defaultTimelineId` ja `preferences.lastUsedTimelineId` olemassa olevaan aikajanaan, jota ei ole arkistoitu. Se on yleensä Henkilökohtainen.
## Tyypit
Käytä näitä arvotyyppejä:
- `Id` on 22 merkkiä pitkä URL-turvallinen Base64-merkkijono ilman täytemerkkejä. Se käyttää merkkejä `A-Z`, `a-z`, `0-9`, `-` ja `_` ja koodaa 16 tavua.
- `Bool` on `0` tai `1`, ei koskaan `false` tai `true`.
- Rivien aikaleimat (`interval.start`, `interval.end`) ovat Unix-aikaa millisekunteina. Ulomman objektin `timestamp` on ISO-8601-merkkijono. Kaikkien lukujen on oltava äärellisiä.
## Ulompi objekti
```json
{
"version": "0.12",
"timestamp": "<ISO-8601 string>",
"checksum": "<sha256 hex of JSON.stringify(data)>",
"data": {
"interval": [],
"preferences": [],
"stopwatch": [],
"tag": [],
"timeline": [],
"tagStopwatch": [],
"exportOptions": []
}
}
```
Sisällytä `data`-objektiin kaikki seitsemän avainta täsmälleen tässä järjestyksessä: `interval`, `preferences`, `stopwatch`, `tag`, `timeline`, `tagStopwatch`, `exportOptions`.
## Rakenne
```ts
interval: {
id: Id;
stopwatchId: Id; // -> stopwatch.id
start: number; // Unix-aika millisekunteina
end: number | null; // Unix-aika millisekunteina; null, jos käynnissä
}
preferences: {
// täsmälleen yksi rivi
id: Id;
useMilitaryTime: Bool;
mergeOverlappingStopwatches: Bool; // vanha kenttä; yleensä 0
useAdjust: Bool;
adjustDefault: number; // minuutteja; voi olla 0 tai negatiivinen
backupReminderIntervalInDays: number;
currencyCode: string; // ISO 4217, esimerkiksi "USD"
earningsTrackingEnabled: Bool;
defaultHourlyRate: number | null;
weekStartsOn: number; // 0 = sunnuntai ... 6 = lauantai
alternativeColor: string | null; // Open Color -tunnus; null tarkoittaa harmaata
defaultTimelineId: Id | null; // -> timeline.id
lastUsedTimelineId: Id | null; // -> timeline.id
forgottenRemindersMode: "both" | "start" | "stop" | "off";
}
stopwatch: {
id: Id;
description: string;
adjustMinutes: number; // kokonaisluku; voi olla negatiivinen
isActive: Bool;
isExpanded: Bool;
hourlyRate: number | null; // tälle sekuntikellolle tallennettu tuntihinta
rateCurrencyCodeAtWrite: string | null; // työtilan valuutta hinnan tallennushetkellä
isHourlyRateManual: Bool | null;
targetMinutes: number | null;
timelineId: Id | null; // -> timeline.id; null tarkoittaa Ei aikajanaa
}
tag: {
id: Id;
name: string;
hourlyRate: number | null;
color: string | null; // Open Color -tunnus, kuten "blue.6", tai null
isPinned: Bool | null; // kiinnitetty tunnisteluetteloon; null tulkitaan kiinnitetyksi
archivedAt: number | null; // arkistointihetki Unix-aikana millisekunteina; null pitää tunnisteen aktiivisena
}
timeline: {
id: Id;
name: string;
parentTimelineId: Id | null; // -> ylemmän aikajanan timeline.id; null, jos ei ryhmitelty
hourlyRate: number | null;
isBillingBoundary: Bool;
archivedAt: number | null; // arkistointihetki Unix-aikana millisekunteina; null pitää aikajanan aktiivisena
}
tagStopwatch: {
id: Id;
tagId: Id; // -> tag.id
stopwatchId: Id; // -> stopwatch.id
}
exportOptions: {
// enintään yksi rivi; useimmissa tuonneissa käytetään tyhjää taulukkoa
// Käytä mieluiten id-arvoa "4WnR17vMjH50XmwttC218Q" (sovelluksen ainoa asetusrivi).
// Muut tunnisteet korvataan tällä arvolla tuonnin yhteydessä.
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";
}
```
## Tarkistussumma
`envelope.checksum` on arvon `JSON.stringify(data)` pienaakkosinen heksadesimaalinen SHA-256-tiiviste.
Tässä `data` on sisempi objekti, ei koko ulompi objekti.
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()
```
## Esimerkin vähimmäisrakenne
```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": []
}
}
```
## Yleiset virheet
Vältä näitä virheitä:
- Älä käytä `Bool`-kentissä arvoja `true` tai `false`. Käytä arvoja `0` tai `1`.
- Älä käytä `interval.start`- tai `interval.end`-kentissä sekunteja. Käytä millisekunteja.
- Älä käytä rivien aikaleimoina ISO-merkkijonoja. Vain ulomman objektin `timestamp` on ISO-merkkijono.
- Älä lisää ulompaan objektiin `timezone`-kenttää. Muunnoksessa saatetaan tarvita lähteen aikavyöhyke, mutta TimeRetain tallentaa rivien ajat Unix-aikana millisekunteina.
- Älä jätä tarkistussummaa pois. Laske se sisemmästä `data`-objektista, älä koko ulommasta objektista.
- Älä muotoile `data`-objektia sisennyksillä tai lajittele sen avaimia aakkosjärjestykseen ennen tarkistussumman laskentaa. Käytä yllä annettua avainjärjestystä.
- Älä jätä yhtään seitsemästä `data`-taulukosta pois, vaikka se olisi tyhjä.
- Älä ryhmittele asiakkaita tai projekteja tunnisteilla. Ryhmittele ne toisiinsa liittyvien `timeline`-rivien avulla.
- Älä lisää kenttää `tag.parentTagId`. Tunnisteita ei voi ryhmitellä.
- Älä luo aikajanoja, joiden ylempi ryhmä puuttuu, tai ryhmittelyyn syklejä.
- Jos lähteessä on tiedossa oleva asiakas, projekti, tili tai työtila, aseta `stopwatch.timelineId`.
- Luo tunnisteita käyttäville ajastimille vastaavat `tagStopwatch`-rivit.