-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
242fd86
commit 59dae1d
Showing
18 changed files
with
913 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
{ | ||
"version": 1, | ||
"isRoot": true, | ||
"tools": { | ||
"fantomas-tool": { | ||
"version": "4.7.9", | ||
"commands": [ | ||
"fantomas" | ||
] | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
**/bin/** | ||
**/obj/** | ||
.fake | ||
.ionide |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
{ | ||
"FSharp.inlayHints.parameterNames": false, | ||
"FSharp.inlayHints.typeAnnotations": false | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,84 @@ | ||
# time-track | ||
A CLI time tracking tool | ||
# Time Tracking (`tt`) | ||
|
||
The main operation of our Time Tracker is to keep track of *Activities*. An Activity represents a continuous stretch of time that we spent doing something. | ||
When we add an Activity to the Time Tracker we say that we *log* (verb) the Activity. We then refer to the *Log* (noun) as the set of Activities we have logged. | ||
|
||
We want to mine the Log to extract insights on how we spend our time. To do so, we want to generate *Reports*. A Report is a summary of the time we spent on various activities during a *Period* (e.g., day, week, work-week). To make our Reports more useful, we use *Keywords* to annotate activities and leverage Keywords to filter and aggregate Activities.[^1] | ||
|
||
[^1]: Reporting is limited to total tracked time in the current week for release 1. | ||
|
||
### CLI Example | ||
|
||
To input activities in the Time Tracking, you can specify the start and end of an activity. | ||
The activity is made up of keywords that will be used for reporting and filtering. | ||
|
||
``` | ||
$ tt start yoga | ||
Starting yoga at 9:00 | ||
$ tt end | ||
Ending yoga at 9:20 (elapsed 00:20) | ||
$ tt start work emails | ||
Starting work emails at 9:30 | ||
$ tt end | ||
Ending work emails at 10:00 (elapsed 00:30) | ||
``` | ||
|
||
You can also omit the start of the activity for contiguous activities: | ||
|
||
``` | ||
$ tt start coffee-break | ||
Ending work emails at 10:05 (elapsed 00:05) | ||
Starting coffee-break | ||
``` | ||
|
||
To display all entries for the day we use `show`: | ||
|
||
``` | ||
$ tt show | ||
09:00 - 09:20 : Yoga | ||
09:30 - 10:00 : Work Emails | ||
10:00 - 10:05 : Coffee Break | ||
10:05 - 11:55 : Pair with John Issue-37 | ||
11:55 - 12:10 : Pairing follow-up | ||
12:15 - 12:35 : Work Emails | ||
12:35 - >>>>> : Lunch | ||
Total: 03:20 | ||
``` | ||
|
||
The last entry is on-going, so the end-time is not included. | ||
|
||
At the end of the week (day or month), we can extract a report. In the current version the report only shows the total for the current week (Mon-Sun): | ||
|
||
``` | ||
$ tt report | ||
Weekly Total: 00:35 | ||
``` | ||
|
||
Logs are stored under `~/.tt/` (configurable via `TIMETRACKER_DIR` env) one file per day using the following CSV structure: | ||
``` | ||
start, end, description | ||
``` | ||
|
||
where: | ||
* `start` and `end` are timestamps in ISO-8601 format | ||
* `end` might be empty | ||
* `description` is a quoted string | ||
|
||
Our examples would look like (omitting milliseconds and tzinfo): | ||
|
||
```csv | ||
start , end , description | ||
2022-01-22T09:00:00,2022-01-22T09:20:00,"yoga" | ||
2022-01-22T09:30:00,2022-01-22T10:00:00,"work emails" | ||
2022-01-22T10:00:00,2022-01-22T10:05:00,"coffee break" | ||
2022-01-22T10:05:00,2022-01-22T11:55:00,"pair with John Issue-37" | ||
2022-01-22T11:55:00,2022-01-22T12:10:00,"pairing follow-up" | ||
2022-01-22T12:15:00,2022-01-22T12:35:00,"work emails" | ||
2022-01-22T12:35:00,,"lunch" | ||
``` | ||
|
||
NOTE: There are a few ambiguities around CSV and delimiters that tend to be implementation dependent (how to represent line breaks, quotes etc.) As our descriptions are meant to be rather simple, we are not going to worry about them for now. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
open System | ||
open System.IO | ||
open TimeTracker.Storage | ||
open TimeTracker.Core | ||
|
||
module Cli = | ||
|
||
let DEFAULT_STORE_DIR = | ||
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), ".tt") | ||
|
||
let STORE_DIR = | ||
match Environment.GetEnvironmentVariable("TIMETRACKER_DIR") with | ||
| null -> DEFAULT_STORE_DIR | ||
| v -> v | ||
|
||
let logPath (date: DateTime) = | ||
let dateStr = date.ToString "yyyyMMdd" | ||
Path.Combine(STORE_DIR, dateStr + ".csv") | ||
|
||
let todayLog = logPath DateTime.Today | ||
|
||
// Returns the current time truncated to the minute | ||
let timeNow = | ||
let truncate = fun (d: DateTime) -> d.AddTicks(-(d.Ticks % TimeSpan.TicksPerMinute)) | ||
truncate DateTime.Now | ||
|
||
let endCurrentActivityIfAny log : ClosedLog = | ||
log | ||
|> Log.fold | ||
(fun l -> | ||
let newLog = l |> Log.endCurrentActivity timeNow | ||
let a = Log.Last(Closed newLog) | ||
printf "End: %s" a.Description | ||
printf " (elapsed %s)\n" ((Activity.Duration a).ToString("hh\\:mm")) | ||
newLog) | ||
id | ||
|
||
let start desc log = | ||
Active( | ||
log | ||
|> endCurrentActivityIfAny | ||
|> Log.startActivity | ||
{ Start = timeNow | ||
Description = desc | ||
End = None } | ||
|> fun log -> | ||
printf "Start: %s\n" desc | ||
log | ||
) | ||
|
||
let editLogErrorToString (msg, e) = msg + "\n" + e.ToString() | ||
|
||
let withTodaysLogDo f = | ||
f | ||
|> editLog todayLog | ||
|> Result.mapError editLogErrorToString | ||
|
||
let startCmd desc = | ||
withTodaysLogDo (fun log -> Some(start desc log)) | ||
|
||
let endCmd () = | ||
withTodaysLogDo (fun log -> Some(Closed(endCurrentActivityIfAny log))) | ||
|
||
let showCmd () = | ||
withTodaysLogDo (fun log -> | ||
printf "%s\n" (Log.ToString log) | ||
printf "Total: %s\n" ((Log.TotalTime log).ToString("hh\\:mm")) | ||
None) | ||
|
||
let reportCmd () = | ||
let printTotal logs = | ||
let report = Report.weekReport timeNow logs | ||
let total = report.TotalTime | ||
printf "Weekly Total: %s\n" (total.ToString("hh\\:mm")) | ||
|
||
match loadAllLogs STORE_DIR with | ||
| Ok logs -> | ||
Ok( | ||
printTotal logs | ||
Log.Empty | ||
) | ||
| Error e -> Error e | ||
|
||
|
||
|
||
let errorCheck = | ||
function | ||
| Ok _ -> 0 | ||
| Error e -> | ||
printfn "Error: %s" (e) | ||
-1 | ||
|
||
[<EntryPoint>] | ||
let main args = | ||
if args.Length > 0 then | ||
match args.[0].ToLower() with | ||
// TODO: The return type of the Cmd should be Result<unit, string> | ||
| "start" -> Cli.startCmd (args |> Seq.skip 1 |> String.concat " ") | ||
| "end" -> Cli.endCmd () | ||
| "show" -> Cli.showCmd () | ||
| "report" -> Cli.reportCmd () | ||
| _ -> Error "Must provide one of start|end|show|report" | ||
else | ||
Cli.showCmd () | ||
|> errorCheck |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<OutputType>Exe</OutputType> | ||
<TargetFramework>net6.0</TargetFramework> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<Compile Include="Program.fs" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="..\TimeTracker\TimeTracker.fsproj" /> | ||
</ItemGroup> | ||
|
||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
module Program = let [<EntryPoint>] main _ = 0 |
Oops, something went wrong.