diff --git a/package.json b/package.json index a6ca95e..a000689 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "zeroclock", - "version": "1.0.1", + "version": "1.0.2", "description": "Time tracking desktop application", "type": "module", "scripts": { diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index c9844fe..2c1d64d 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -5743,7 +5743,7 @@ dependencies = [ [[package]] name = "zeroclock" -version = "1.0.0" +version = "1.0.1" dependencies = [ "chrono", "env_logger", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 142d590..5c19806 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "zeroclock" -version = "1.0.1" +version = "1.0.2" description = "A local time tracking app with invoicing" authors = ["you"] edition = "2021" diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 2301948..e8fd636 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "ZeroClock", - "version": "1.0.1", + "version": "1.0.2", "identifier": "com.localtimetracker.app", "build": { "beforeDevCommand": "npm run dev", diff --git a/src/utils/import.ts b/src/utils/import.ts index e094994..c85ba97 100644 --- a/src/utils/import.ts +++ b/src/utils/import.ts @@ -9,6 +9,23 @@ export interface ImportEntry { tags?: string[] } +export type DateFormat = 'DMY' | 'MDY' +export type DateDetectResult = DateFormat | 'ambiguous' + +export function detectDateFormat(rows: string[][], dateColIndex: number): DateDetectResult { + if (dateColIndex < 0) return 'ambiguous' + for (let i = 1; i < rows.length; i++) { + const val = (rows[i]?.[dateColIndex] || '').trim() + const match = val.match(/^(\d{1,2})[\/.](\d{1,2})[\/.](\d{2,4})$/) + if (!match) continue + const a = parseInt(match[1]) + const b = parseInt(match[2]) + if (a > 12) return 'DMY' + if (b > 12) return 'MDY' + } + return 'ambiguous' +} + export function parseCSV(text: string): string[][] { const lines = text.split('\n').filter(l => l.trim()) return lines.map(line => { @@ -44,7 +61,7 @@ export function parseDurationString(dur: string): number { } // Parse locale-variable date strings into YYYY-MM-DD -function parseFlexDate(dateStr: string): string { +function parseFlexDate(dateStr: string, formatHint?: DateFormat): string { if (!dateStr) return new Date().toISOString().split('T')[0] const s = dateStr.trim() @@ -63,7 +80,9 @@ function parseFlexDate(dateStr: string): string { if (parseInt(a) > 12) return `${year}-${b.padStart(2, '0')}-${a.padStart(2, '0')}` // If second part > 12, must be month-first (MM/DD/YYYY) if (parseInt(b) > 12) return `${year}-${a.padStart(2, '0')}-${b.padStart(2, '0')}` - // Ambiguous - assume MM/DD/YYYY (US default, most common Clockify setting) + // Use format hint if provided + if (formatHint === 'DMY') return `${year}-${b.padStart(2, '0')}-${a.padStart(2, '0')}` + // Default to MDY return `${year}-${a.padStart(2, '0')}-${b.padStart(2, '0')}` } @@ -99,13 +118,13 @@ function parseFlexTime(timeStr: string): string { return '00:00:00' } -function safeDateTime(date: string, time?: string): string { - const d = parseFlexDate(date) +function safeDateTime(date: string, time?: string, dateFormat?: DateFormat): string { + const d = parseFlexDate(date, dateFormat) const t = time ? parseFlexTime(time) : '00:00:00' return `${d}T${t}` } -function findCol(header: string[], ...terms: string[]): number { +export function findCol(header: string[], ...terms: string[]): number { for (const term of terms) { const idx = header.findIndex(h => h === term) if (idx >= 0) return idx @@ -121,7 +140,7 @@ function col(row: string[], idx: number): string { return idx >= 0 ? (row[idx] || '') : '' } -export function mapTogglCSV(rows: string[][]): ImportEntry[] { +export function mapTogglCSV(rows: string[][], dateFormat?: DateFormat): ImportEntry[] { const header = rows[0].map(h => h.toLowerCase().trim()) const descIdx = findCol(header, 'description') const projIdx = findCol(header, 'project') @@ -137,13 +156,13 @@ export function mapTogglCSV(rows: string[][]): ImportEntry[] { client_name: col(row, clientIdx) || undefined, task_name: col(row, taskIdx) || undefined, description: col(row, descIdx) || undefined, - start_time: safeDateTime(col(row, startDateIdx), col(row, startTimeIdx) || undefined), + start_time: safeDateTime(col(row, startDateIdx), col(row, startTimeIdx) || undefined, dateFormat), duration: parseDurationString(col(row, durationIdx)), tags: tagIdx >= 0 && row[tagIdx] ? row[tagIdx].split(',').map(t => t.trim()).filter(Boolean) : undefined, })) } -export function mapClockifyCSV(rows: string[][]): ImportEntry[] { +export function mapClockifyCSV(rows: string[][], dateFormat?: DateFormat): ImportEntry[] { const header = rows[0].map(h => h.toLowerCase().trim()) const descIdx = findCol(header, 'description') const projIdx = findCol(header, 'project') @@ -172,14 +191,14 @@ export function mapClockifyCSV(rows: string[][]): ImportEntry[] { client_name: col(row, clientIdx) || undefined, task_name: col(row, taskIdx) || undefined, description: col(row, descIdx) || undefined, - start_time: safeDateTime(col(row, startDateIdx), col(row, startTimeIdx) || undefined), + start_time: safeDateTime(col(row, startDateIdx), col(row, startTimeIdx) || undefined, dateFormat), duration, tags: tagIdx >= 0 && row[tagIdx] ? row[tagIdx].split(',').map(t => t.trim()).filter(Boolean) : undefined, } }) } -export function mapHarvestCSV(rows: string[][]): ImportEntry[] { +export function mapHarvestCSV(rows: string[][], dateFormat?: DateFormat): ImportEntry[] { const header = rows[0].map(h => h.toLowerCase().trim()) const dateIdx = findCol(header, 'date') const projIdx = header.findIndex(h => h === 'project') @@ -193,7 +212,7 @@ export function mapHarvestCSV(rows: string[][]): ImportEntry[] { client_name: col(row, clientIdx) || undefined, task_name: col(row, taskIdx) || undefined, description: col(row, notesIdx) || undefined, - start_time: safeDateTime(col(row, dateIdx)), + start_time: safeDateTime(col(row, dateIdx), undefined, dateFormat), duration: hoursIdx >= 0 && row[hoursIdx] ? Math.round(parseFloat(row[hoursIdx]) * 3600) : 0, })) } diff --git a/src/views/Settings.vue b/src/views/Settings.vue index 4900788..2c9945e 100644 --- a/src/views/Settings.vue +++ b/src/views/Settings.vue @@ -1445,6 +1445,32 @@ + +