diff --git a/Work Hours/Extensions/Date.swift b/Work Hours/Extensions/Date.swift index 108c6ff..c3468a5 100644 --- a/Work Hours/Extensions/Date.swift +++ b/Work Hours/Extensions/Date.swift @@ -50,6 +50,14 @@ extension Date { YearMonthFormatter.timeZone = TimeZone(secondsFromGMT: 0) return YearMonthFormatter }() + + static let HourMinutesFormatter: DateFormatter = { + let YearMonthFormatter = DateFormatter() + YearMonthFormatter.locale = Locale(identifier: "en_US_POSIX") + YearMonthFormatter.dateFormat = "HH:mm" + YearMonthFormatter.timeZone = TimeZone(secondsFromGMT: 0) + return YearMonthFormatter + }() } extension TimeInterval { diff --git a/Work Hours/ReportsGenerator.swift b/Work Hours/ReportsGenerator.swift index 1de2bb2..f599b42 100644 --- a/Work Hours/ReportsGenerator.swift +++ b/Work Hours/ReportsGenerator.swift @@ -21,14 +21,23 @@ struct Report { static func fromData(_ data: [String: Int]) -> [Report] { var reports = [Report]() - for (ts, duration) in data { + for (timestamp, duration) in data { // skip if less than a minute if duration < 60 { continue } - reports.append(Report(timestamp: ts, amount: TimeInterval.hoursAndMinutes(duration))) + reports.append(Report(timestamp: timestamp, amount: TimeInterval.hoursAndMinutes(duration))) } - return reports + return reports.sorted(by: { $0.timestamp < $1.timestamp }) + } +} + +struct Event { + let startTimestamp: Date + let endTimestamp: Date + + var elapsedSeconds: Int { + Int(endTimestamp.timeIntervalSince(startTimestamp)) } } @@ -88,21 +97,38 @@ enum Events { return nil } - static func generateReport(formatter: DateFormatter) -> [Report]? { + static func generateReport(events: [Event], formatter: DateFormatter) -> [Report]? { os_log("Start, generateReport") var data: [String: Int] = [:] + for event in events { + let key = formatter.string(from: event.startTimestamp) + os_log("Elapsed %s, %s > %d", event.startTimestamp.debugDescription, event.endTimestamp.debugDescription, event.elapsedSeconds) + if let val = data[key] { + data[key] = event.elapsedSeconds + val + } else { + data[key] = event.elapsedSeconds + } + } + return Report.fromData(data) + } + + static func getEvents() -> [Event]? { + os_log("Start, getEvents") + + var events = [Event]() + guard let logFile = logFile else { return nil } if FileManager.default.fileExists(atPath: logFile.path) { - guard let csvFile: CSV = try? CSV(url: logFile) else { + guard let CSVFile: CSV = try? CSV(url: logFile) else { return nil } var startTimestamp: Date? var endTimestamp: Date? var prevEvent: String? - for line in csvFile.enumeratedRows { + for line in CSVFile.enumeratedRows { // XOR for event filtering, when there is data corruption if prevEvent == nil { prevEvent = line[0] @@ -130,26 +156,14 @@ enum Events { return nil } - // corrupted data + // Got both actions if startTimestamp != nil, endTimestamp != nil { - // same day, TODO: remove once we are ok with current state of parsing - // if formatter.string(from: startTimestamp!) == formatter.string(from: endTimestamp!) { - let elapsed = Int(endTimestamp!.timeIntervalSince(startTimestamp!)) - let key = formatter.string(from: startTimestamp!) - os_log("Elapsed %s, %s > %d", startTimestamp!.debugDescription, endTimestamp!.debugDescription, elapsed) - if let val = data[key] { - data[key] = elapsed + val - } else { - data[key] = elapsed - } + events.append(Event(startTimestamp: startTimestamp!, endTimestamp: endTimestamp!)) startTimestamp = nil endTimestamp = nil - // }else { - // return nil - // } } } - return Report.fromData(data) + return events } return nil } diff --git a/Work Hours/StatusBar.swift b/Work Hours/StatusBar.swift index 6bc1df5..b21f94e 100644 --- a/Work Hours/StatusBar.swift +++ b/Work Hours/StatusBar.swift @@ -24,12 +24,20 @@ class StatusBarController: NSObject, NSMenuDelegate { func fromReports(_ reports: [Report]) -> NSMenu { let submenu = NSMenu() - for report in reports { + for report in reports.sorted(by: { $0.timestamp > $1.timestamp }) { submenu.addItem(addSubmenu(withTitle: "\(report.timestamp) worked \(report.amount)", action: #selector(copyToPasteboard))) } return submenu } + func fromReportsToday(_ reports: [Report]) -> NSMenu { + let submenu = NSMenu() + for report in reports.sorted(by: { $0.timestamp > $1.timestamp }) { + submenu.addItem(addSubmenu(withTitle: "Started \(report.timestamp) worked \(report.amount)", action: #selector(copyToPasteboard))) + } + return submenu + } + @objc func copyToPasteboard() {} override @@ -76,10 +84,13 @@ class StatusBarController: NSObject, NSMenuDelegate { addApplicationItems() } - func menuDidClose(_: NSMenu) {} + func menuDidClose(_: NSMenu) { + timerModel.update() + } func menuWillOpen(_: NSMenu) { updateMenu() + timerModel.update() } @objc func toggle() { @@ -107,20 +118,28 @@ class StatusBarController: NSObject, NSMenuDelegate { } statusItemMenu.addItem(NSMenuItem.separator()) + if let events = Events.getEvents(), events.count > 0 { + let todayItem = NSMenuItem(title: "Today", action: nil, keyEquivalent: "") + if let todayReports = Events.generateReport(events: events.filter { + Calendar.current.isDateInToday($0.startTimestamp) && Calendar.current.isDateInToday($0.endTimestamp) + }, formatter: Date.HourMinutesFormatter) { + todayItem.submenu = fromReportsToday(todayReports) + } + statusItemMenu.addItem(todayItem) - let dailyItem = NSMenuItem(title: "Daily Reports", action: nil, keyEquivalent: "") - if let dailyReports = Events.generateReport(formatter: Date.YearMonthDayFormatter) { - dailyItem.submenu = fromReports(dailyReports.sorted(by: { $0.timestamp < $1.timestamp }).suffix(7)) - } - statusItemMenu.addItem(dailyItem) + let dailyItem = NSMenuItem(title: "Daily Reports", action: nil, keyEquivalent: "") + if let dailyReports = Events.generateReport(events: events, formatter: Date.YearMonthDayFormatter) { + dailyItem.submenu = fromReports(dailyReports.suffix(7)) + } + statusItemMenu.addItem(dailyItem) - let monthlyItem = NSMenuItem(title: "Monthly Reports", action: nil, keyEquivalent: "") - if let monthlyReports = Events.generateReport(formatter: Date.YearMonthFormatter) { - monthlyItem.submenu = fromReports(monthlyReports.sorted(by: { $0.timestamp < $1.timestamp })) + let monthlyItem = NSMenuItem(title: "Monthly Reports", action: nil, keyEquivalent: "") + if let monthlyReports = Events.generateReport(events: events, formatter: Date.YearMonthFormatter) { + monthlyItem.submenu = fromReports(monthlyReports.suffix(12)) + } + statusItemMenu.addItem(monthlyItem) + statusItemMenu.addItem(NSMenuItem.separator()) } - statusItemMenu.addItem(monthlyItem) - - statusItemMenu.addItem(NSMenuItem.separator()) let preferencesItem = NSMenuItem(title: "Preferences", action: #selector(AppDelegate.showPrefs), keyEquivalent: ",") preferencesItem.target = NSApp.delegate diff --git a/Work Hours/TimerModel.swift b/Work Hours/TimerModel.swift index f091b5c..7bb0d94 100644 --- a/Work Hours/TimerModel.swift +++ b/Work Hours/TimerModel.swift @@ -44,7 +44,7 @@ class TimerModel: ObservableObject { } func update() { - if isRunning, startTime != nil { + if startTime != nil { let diff = Date() - startTime display = diff.hoursAndMinutes() }