dey 0.2.0__tar.gz → 0.2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
dey-0.2.2/LICENSE ADDED
@@ -0,0 +1,18 @@
1
+ Copyright 2026 David Woodburn
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining acopy of
4
+ this software and associated documentation files (the"Software"), to deal in the
5
+ Software without restriction, includingwithout limitation the rights to use,
6
+ copy, modify, merge, publish,distribute, sublicense, and/or sell copies of the
7
+ Software, and topermit persons to whom the Software is furnished to do so,
8
+ subject tothe following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be includedin all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESSOR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OFMERCHANTABILITY, FITNESS
15
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR
16
+ COPYRIGHT HOLDERS BE LIABLE FOR ANYCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
17
+ AN ACTION OF CONTRACT,TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
18
+ WITH THESOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
dey-0.2.2/PKG-INFO ADDED
@@ -0,0 +1,409 @@
1
+ Metadata-Version: 2.4
2
+ Name: dey
3
+ Version: 0.2.2
4
+ Summary: Terminal time tracker
5
+ Author-email: David Woodburn <david.woodburn@icloud.com>
6
+ License-Expression: MIT
7
+ Requires-Python: >=3.7
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+ Dynamic: license-file
11
+
12
+ # Introduction
13
+
14
+ `dey` is a terminal-based time tracker for logging work hours against named
15
+ jobs.
16
+
17
+ The name comes from the [Alexander Dey Time Register Clock][1] (1888), an early
18
+ punch-clock that recorded employee hours on a paper roll. Like its namesake,
19
+ `dey` uses a simple punch-in, punch-out model: start a timer against a job, stop
20
+ it later. You can also backfill entries for past times.
21
+
22
+ Using a spreadsheet to track work hours is cumbersome: Excel is slow to open,
23
+ times and dates must be entered manually, multiple jobs create an unwieldy
24
+ number of columns, and generating summaries is complicated. This program offers
25
+ a simple terminal-based alternative. It automatically records the current date
26
+ and time, resolves short job-name fragments by substring matching, and produces
27
+ clear daily and weekly summaries that can be filtered the same way. A single
28
+ command also lets you undo the last entry, adjust its time, or change its job.
29
+
30
+ The design deliberately stays simple. Only one job can be active at a time.
31
+ Features such as billing calculations, notes, hierarchical projects, tags, idle
32
+ detection, goals, configuration files, and shell completions are omitted so the
33
+ tool remains a focused Unix-style utility: easy to understand, quick to use with
34
+ short commands, and simple to combine with other programs through its clean,
35
+ well-formatted output.
36
+
37
+ A simple example shows creating a job for the first time, starting time tracking
38
+ against that job, and then stopping the work over an hour later:
39
+ ```bash
40
+ > dey -j meetings
41
+ New job "meetings" created.
42
+ > dey -t
43
+ 00:00 | 07:42 - meetings
44
+ > dey -t
45
+ 01:05 | 07:42 - 08:47 meetings
46
+ ```
47
+
48
+ # Data Storage
49
+
50
+ The data for the program is saved in text files at
51
+
52
+ | OS | Location |
53
+ | :------ | :------------------------------------------ |
54
+ | Windows | `%LOCALAPPDATA%\dey\` |
55
+ | Linux | `$XDG_CONFIG_HOME/dey/` or `~/.config/dey/` |
56
+ | macOS | `~/.config/dey/` |
57
+
58
+ The `jobs.txt` file stores definitions of jobs:
59
+ ```
60
+ a meetings
61
+ a permissive vacation
62
+ a permissive sickness
63
+ i Project X
64
+ ```
65
+ Each job is defined with an ID (the row number in the file), an active, `a`, or
66
+ inactive, `i`, flag, and a label. The inactive flag hides the job from searches
67
+ when starting work on a job; it does not hide the history of work done for those
68
+ jobs. The jobs are in the order they were created.
69
+
70
+ The `logs.txt` file stores the work done, called "logs":
71
+ ```
72
+ 20260919 07:31 0001 09:22
73
+ 20260919 10:03 0003 13:45
74
+ 20260920 08:10 0001 10:50
75
+ 20260926 08:30 0002
76
+ ```
77
+ Each log contains a date represented as an integer in `YYYYMMDD` format, a start
78
+ time, a job ID, and a stop time. Times are in 24-hour format. Job IDs are
79
+ four-digit integers, which means up to 9999 different jobs can exist. A missing
80
+ stop time indicates that the log is currently active. Times are recorded to the
81
+ nearest minute; finer precision is omitted because minute precision introduces
82
+ only a zero-bias error, and many organizations do not require accuracy beyond
83
+ one-tenth of an hour.
84
+
85
+ The time intervals of different logs cannot overlap, so at any given moment,
86
+ work can be performed on only one job. As a consequence, no more than one log
87
+ may have a missing stop time because it is current. A log cannot span more than
88
+ one day. At the latest, it must end by 23:59. Consequently, no date arithmetic
89
+ is performed. Logs are listed in the order they were created, which may differ
90
+ from their chronological order.
91
+
92
+ Both the `jobs` file and the `logs` file have been designed so that in most
93
+ cases, a full rewrite of the file is not necessary. Instead, a new job or log
94
+ can be appended to the corresponding file. Though negligible anyway, this does
95
+ reduce processing time and wear on solid state discs. It is not intended that
96
+ the user ever directly modify these files; if you do, be very careful not to
97
+ corrupt the format.
98
+
99
+ If full editing ability were supported, a log ID would need to accompany each
100
+ log in order to address the specific log. Start and stop times would need to be
101
+ directly specified. Even an interactive interface for editing would be
102
+ preferable. These are all directions that would take away from the simplicity of
103
+ this program. Therefore, what is not supported is the ability to edit any
104
+ arbitrary log, besides the last. Consequently, it is the responsibility of the
105
+ user to pay attention when entering logs and not create the occasion for editing
106
+ logs several entries prior. The last entry can be modified, but anything prior
107
+ must be modified by undoing entries. That said, the logs file is formatted such
108
+ that it is not too difficult to directly edit, and the line numbers beside the
109
+ jobs and visible with a code editor correspond to the job IDs.
110
+
111
+ # Interface
112
+
113
+ Just the name of the program prints the status:
114
+ ```bash
115
+ > dey
116
+ 07:32 | 14:10 - 15:05 meetings
117
+ > dey
118
+ 07:32 | 14:10 - meetings
119
+ ```
120
+ This shows the total time worked for the present day, the start and stop times
121
+ and the job label of the latest log. If the latest log is current, nothing is
122
+ printed in place of the stop time. If no logs exist for the present day, a
123
+ statement to this effect is output.
124
+
125
+ Additional functionality is available with the following command arguments (flag
126
+ letters preceded by a dash) and their optional tails (text following the
127
+ arguments):
128
+
129
+ | Arguments | Description |
130
+ | :------------------------- | :----------------------------------- |
131
+ | `-l {label}` | List all active jobs matching label |
132
+ | `-L {label}` | List all jobs matching label |
133
+ | `-j {label} {label}` | Create job, rename job |
134
+ | `-i {label}` | Make job active job inactive |
135
+ | `-a {label}` | Make job inactive job active |
136
+ | | |
137
+ | `-t {time} {date} {label}` | Toggle time or switch active job |
138
+ | `-e {time} {label}` | Edit time or job of last entry |
139
+ | `-u {integer}` | Undo last log entries (default to 1) |
140
+ | | |
141
+ | `-d {date} {label}` | Day report (default to recent date) |
142
+ | `-w {weeks} {label}` | Week report (default to recent week) |
143
+ | | |
144
+ | `-h` | Print command help |
145
+
146
+ A `date` is entered in the format `YYYY-MM-DD` or `YY-MM-DD`. Time is entered in
147
+ the format `HH:MM`. Each argument is a standalone instruction that does not
148
+ depend on any other argument. Multiple command arguments can be given in a
149
+ single terminal command and they will be executed in the order they are written.
150
+
151
+ ## Jobs
152
+
153
+ ### List Jobs
154
+
155
+ The list of existing active jobs can be printed by entering the `-l` flag
156
+ without any job labels. This will print all the jobs in alphabetical order:
157
+ ```bash
158
+ > dey -l
159
+ meetings
160
+ permissive sickness
161
+ permissive vacation
162
+ ```
163
+
164
+ The list can be narrowed down by entering part of a job label after the flag:
165
+ ```bash
166
+ > dey -l perm
167
+ permissive sickness
168
+ permissive vacation
169
+ ```
170
+ All string matching to job labels is case-insensitive.
171
+
172
+ All jobs including inactive ones can be listed with the `-L` flag:
173
+ ```bash
174
+ > dey -L
175
+ meetings
176
+ permissive sickness
177
+ permissive vacation
178
+ Project X
179
+ ```
180
+ The inactive jobs are marked in gray.
181
+
182
+ ### Create a Job
183
+
184
+ A new job can be defined with the `-j` argument followed by the job label:
185
+ ```bash
186
+ > dey -j Clean-up
187
+ New job "Clean-up" created.
188
+ ```
189
+ If a job by that exact name (using case-insensitive matching) already exists,
190
+ the command will be rejected.
191
+
192
+ ### Rename a Job
193
+
194
+ An active job can be renamed by adding a second string:
195
+ ```bash
196
+ > dey -j Clean-up Cleanup
197
+ Job "Clean-up" renamed to "Cleanup".
198
+ ```
199
+ When searching for matches on the first string, a partial match will be
200
+ accepted. For example, `vaca` would match `permissive vacation`. If no match or
201
+ more than one match can be found, the command will be rejected. If the second
202
+ string fully matches an existing job label, the command will be rejected.
203
+
204
+ Renaming an inactive job requires first making it active.
205
+
206
+ ### Active and Inactive Jobs
207
+
208
+ An active job can be made inactive so that it no longer is chosen as a valid
209
+ match to new work by using the `-i` flag:
210
+ ```bash
211
+ > dey -i Project\ X
212
+ "Project X" is now inactive.
213
+ ```
214
+ An inactive job can be made active by using the `-a` flag:
215
+ ```bash
216
+ > dey -a Project\ X
217
+ "Project X" is now active.
218
+ ```
219
+
220
+ ### Job Aliases (*not implemented*)
221
+
222
+ A job can have an alias (or description) by appending the extra text to the job
223
+ name separated by a colon:
224
+ ```bash
225
+ > dey -j "Project X: Hyper-engine research"
226
+ ```
227
+ This is useful when printing reports. Reports will list the names of projects.
228
+ Because many projects can lead to very similar names which can make entering
229
+ totals into official time-reporting software error-prone, the print feature will
230
+ emphasize those parts of the names which are unique. However, the alias of the
231
+ name will never be included in the emphasis because that part will never be used
232
+ in reporting. That said, job searches can still match to the alias.
233
+
234
+ ## Toggle Clock
235
+
236
+ ### Without Time
237
+
238
+ Time tracking is toggled using the `-t` argument:
239
+ ```bash
240
+ > dey -t
241
+ 00:18 | 07:42 - Project X
242
+ ```
243
+ This will start a log if none is current or end a log if one is current. The
244
+ status will be automatically printed to give confirmation. The simplicity of not
245
+ specifying whether the time is a start or stop means there can be only one
246
+ active log at a time.
247
+
248
+ ### With Time
249
+
250
+ By default, the current time is used, but any time can be explicitly specified
251
+ after the `-t` argument:
252
+ ```bash
253
+ > dey -t 07:45
254
+ 00:15 | 07:45 - Project X
255
+ ```
256
+ This will mark the start or stop of a log with that time. If a new start or stop
257
+ time overlaps with any other log of the same day, the command will be rejected
258
+ and an explanation will be printed. So, giving a stop time that comes before the
259
+ start time of the same log will be rejected and an explanation will be printed.
260
+
261
+ Only one current log is allowed at a time. This is why the interface does not
262
+ provide separate start and stop commands. So, if a log is current (has no stop
263
+ time) another log cannot be started. Instead, if the time is after the start
264
+ time of the current log, the new time will be used as the stop time of the
265
+ current log.
266
+
267
+ It is possible to forget to stop a log at the end of the day. So, the next day
268
+ when a time is given, it will be used as the stop time of yesterday's log. If
269
+ the stop time is before the log's start time, the command will be rejected.
270
+ Clocking out will only apply to the same day as the start of the current log.
271
+ This means you can clock out by entering something like `dey -t 17:00` and this
272
+ will apply to the day the current log was started. The program will not mark the
273
+ log as continuing into the next day.
274
+
275
+ Because logs can be defined with specific times (other than the present), they
276
+ can be created out of order. The log file is not sorted in chronological order.
277
+ This is a deliberate choice to support undoing actions in the order they were
278
+ commanded.
279
+
280
+ If an active log was started on a prior day, the date must be specified. A log
281
+ cannot span across dates.
282
+
283
+ ### With Date
284
+
285
+ By default, the current day is used, but any date can be specified explicitly
286
+ after the `-t` argument:
287
+ ```bash
288
+ > dey -t 07:45 26-09-17
289
+ ```
290
+ The format for dates is either `26-09-07` or `2026-09-07`.
291
+
292
+ ### With Job Label
293
+
294
+ By default, the job of the last log is the assumed job for a new log. However,
295
+ any job can be specified:
296
+ ```bash
297
+ > dey -t meetings
298
+ 00:18 | 07:42 - meetings
299
+ ```
300
+ If no log is current, the job specified is used as the job of the new log. If a
301
+ log is current, the current time or the time provided is used to stop the
302
+ current log and to create a new log and the specified job is used for the new
303
+ log. This makes it easy to switch jobs without having to explicitly stop the
304
+ current log and start another. If the specified job does not match any
305
+ previously-defined jobs, the command will be rejected.
306
+
307
+ ### Undo Last Entry (*not implemented*)
308
+
309
+ The last entry (start or stop or a log) can be undone with the `-u` argument:
310
+ ```bash
311
+ > dey -u
312
+ No logs for today.
313
+ ```
314
+ Multiple entries can be undone at once by providing an integer.
315
+
316
+ ### Edit Last Entry (*not implemented*)
317
+
318
+ The last log entry (start time, job label, or stop time) can be edited with
319
+ the `-e` argument. So, if a log is current, the `-e` command can be used to
320
+ change the start time:
321
+ ```bash
322
+ > dey -e 07:45
323
+ 00:15 | 07:45 - Project X
324
+ ```
325
+ Providing a job label will change the job of the last entry.
326
+ ```bash
327
+ > dey -e meetings
328
+ 00:18 | 07:42 - meetings
329
+ ```
330
+ Both a time and a job label can be specified together.
331
+
332
+ ## Reports
333
+
334
+ ### Day Reports (*partially implemented*)
335
+
336
+ The day report shows the date of the last log and a color-coded graph of that
337
+ day's work. This is followed by that day's logs, each preceded by the
338
+ corresponding color-coding. Finally, the total hours for the day are printed:
339
+ ```bash
340
+ > dey -d
341
+ 2026-11-09 ================== ===================
342
+ = 01:15 (1.25) permissive sick
343
+ = 04:38 (4.63) Project X
344
+ 05:53 (5.88)
345
+ ```
346
+
347
+ Colors are chosen from a subset of the 8-bit ANSI-escape-code colors based on
348
+ the order the jobs first appeared in the day. (Only 8-bit colors are used
349
+ because most terminal emulators support those but not all terminal emulators
350
+ support millions of colors.) Depending on the start and stop times for the day
351
+ and the column width of the present output stream, each character in the graph
352
+ corresponds to a 5, 10, 15, 20, or 30 minute increment. The start of the bar
353
+ corresponds to the beginning of the first log of the day and the end corresponds
354
+ to the end of the last or present if a log is current.
355
+
356
+ Because the day summary can accept a date, any past day can be reported. If a
357
+ date is provided (in the format `26-09-07` or `2026-09-07`) the summary of that
358
+ day will be printed. If a job label is provided as in `dey -d meetings`, the
359
+ summary of just that job (or any job to which the text substring matches) for
360
+ the current date will be printed. These two options can be combined as in `dey
361
+ -d 26-09-07 meetings` to print a report of just that job for that date. The
362
+ colors chosen will be the same as if all the jobs for the day were printed.
363
+
364
+ ### Week Reports (*not implemented*)
365
+
366
+ The week report (`dey -w`) aligns with the beginning of a week, not the current
367
+ day. For example, if the current day is Wednesday, the current week would be
368
+ from Sunday of that week to Wednesday, not the past seven days. The purpose of
369
+ the week report is to show the total hours worked per day, per job, and for the
370
+ week. The report is printed in a markdown-compatible format:
371
+ ```bash
372
+ > dey -w
373
+ Total | 09-06 | 09-07 | 09-09 | 09-10 | 09-11 | Job
374
+ :---: | :---: | :---: | :---: | :---: | :---: | :---
375
+ 13:15 | 02:45 | 08:00 | 02:00 | 00:00 | 00:30 | Meetings
376
+ 16:30 | 03:30 | 00:00 | 05:00 | 02:00 | 06:00 | Documentation
377
+ 10:15 | 02:00 | 00:00 | 01:30 | 05:30 | 01:15 | Education
378
+ --- | --- | --- | --- | --- | --- |
379
+ 40:00 | 08:15 | 08:00 | 08:30 | 07:30 | 07:45 |
380
+ ```
381
+ More weeks can be specified with a number after the `-w` argument flag. The
382
+ additional weeks will be printed as separate tables in chronological order.
383
+
384
+ If a job label is provided, just like the day report, only logs matching that
385
+ search will be printed. Because of substring search, multiple job labels can be
386
+ matches.
387
+
388
+ # Alternatives
389
+
390
+ There are several alternatives, but I believe none approach this problem with
391
+ the same level of simplicity and succinctness or brevity as `dey`.
392
+
393
+ - [bartib][2]
394
+ - [timetrace][3]
395
+ - [timetrap][4]
396
+ - [timewarrior][5]
397
+ - [tmpo][6]
398
+ - [watson][7]
399
+ - [zeit][8]
400
+
401
+
402
+ [1]:https://americanhistory.si.edu/collections/object/nmah_856741
403
+ [2]:https://github.com/nikolassv/bartib
404
+ [3]:https://github.com/dominikbraun/timetrace
405
+ [4]:https://github.com/samg/timetrap
406
+ [5]:https://github.com/gothenburgbitfactory/timewarrior
407
+ [6]:https://github.com/DylanDevelops/tmpo
408
+ [7]:https://github.com/jazzband/Watson
409
+ [8]:https://github.com/mrusme/zeit
@@ -322,29 +322,25 @@ Both a time and a job label can be specified together.
322
322
 
323
323
  ### Day Reports (*partially implemented*)
324
324
 
325
- The day report shows the date of the last log, a color-coded graph of that day's
326
- work, and the total time worked for that day. This is followed by that day's
327
- logs, each preceded by the corresponding color-coding. If the output stream does
328
- not support ANSI escape codes, letters are used instead:
325
+ The day report shows the date of the last log and a color-coded graph of that
326
+ day's work. This is followed by that day's logs, each preceded by the
327
+ corresponding color-coding. Finally, the total hours for the day are printed:
329
328
  ```bash
330
329
  > dey -d
331
- 2026-09-12 aaaaaabbbbbbbbbbbbbccccccccccccccccccccc 07:15
332
-
333
- a 02:45 | meetings
334
- b 02:30 | Project X
335
- c 03:59 | meetings
330
+ 2026-11-09 ================== ===================
331
+ = 01:15 (1.25) permissive sick
332
+ = 04:38 (4.63) Project X
333
+ 05:53 (5.88)
336
334
  ```
337
335
 
338
336
  Colors are chosen from a subset of the 8-bit ANSI-escape-code colors based on
339
337
  the order the jobs first appeared in the day. (Only 8-bit colors are used
340
338
  because most terminal emulators support those but not all terminal emulators
341
- support millions of colors.) If letters are used instead, they are chosen in
342
- alphabetic order, again based on the jobs sequence in the day. Depending on the
343
- start and stop times for the day and the column width of the present output
344
- stream, each character in the graph corresponds to a tenth, quarter, half, or
345
- whole hour. The start of the bar corresponds to the beginning of the first log
346
- of the day and the end corresponds to the end of the last or present if a log is
347
- current.
339
+ support millions of colors.) Depending on the start and stop times for the day
340
+ and the column width of the present output stream, each character in the graph
341
+ corresponds to a 5, 10, 15, 20, or 30 minute increment. The start of the bar
342
+ corresponds to the beginning of the first log of the day and the end corresponds
343
+ to the end of the last or present if a log is current.
348
344
 
349
345
  Because the day summary can accept a date, any past day can be reported. If a
350
346
  date is provided (in the format `26-09-07` or `2026-09-07`) the summary of that
@@ -25,6 +25,7 @@ RESET = "\x1b[0m"
25
25
 
26
26
  # Constants
27
27
  N_GIVE_UP = 100
28
+ BAR_CHAR = '='
28
29
 
29
30
  # -----------------
30
31
  # File IO functions
@@ -118,8 +119,9 @@ def dump_logs(path: str, logs: list):
118
119
 
119
120
  def print_status(total, start, stop, job, possible_stop):
120
121
  total_hr, total_min = divmod(total, 60)
122
+ total_frac = round(total_hr + total_min/60.0, 2)
121
123
  start_hr, start_min = divmod(start, 60)
122
- print(f" {total_hr:02d}:{total_min:02d} | ", end="")
124
+ print(f" {total_hr:02d}:{total_min:02d} ({total_frac:.2f}) | ", end="")
123
125
  print(f"{start_hr:02d}:{start_min:02d} - ", end="")
124
126
  if stop < 0:
125
127
  stop_hr, stop_min = divmod(possible_stop, 60)
@@ -645,8 +647,13 @@ def report_day(jobs, logs, tails):
645
647
  continue
646
648
  job_query = tail
647
649
 
648
- # Get the logs for the specified date, sorted, and concluded.
650
+ # Get the logs for the specified date.
649
651
  logs_on_date = find_logs_on_date(logs, date)
652
+ if not logs_on_date:
653
+ print(" No work to report for this day.")
654
+ return
655
+
656
+ # Sort the logs and conclude the active log.
650
657
  logs_sorted = sort_logs(logs_on_date)
651
658
  logs_concluded, possible_stop = conclude_active(logs_sorted)
652
659
 
@@ -669,13 +676,53 @@ def report_day(jobs, logs, tails):
669
676
  idx = jobs_unique.index(log[2])
670
677
  totals_unique[idx] += log[3] - log[1]
671
678
 
679
+ # Initialize bar graph.
680
+ N_chars = 60
681
+ increments = [5, 10, 15, 20, 30]
682
+ for inc in increments:
683
+ if span <= inc*N_chars:
684
+ break
685
+ bar = [0]*N_chars
686
+
687
+ # Fill bar graph.
688
+ for log in logs_concluded:
689
+ idx = jobs_unique.index(log[2])
690
+ ia = round((log[1] - logs_concluded[0][1]) / inc)
691
+ ib = round((log[3] - logs_concluded[0][1]) / inc)
692
+ for i in range(ia, ib + 1):
693
+ bar[i] = (idx % 6) + 1
694
+
695
+ # Translate the bar graph.
696
+ s = ""
697
+ b_last = 0
698
+ for b in bar:
699
+ if b != b_last:
700
+ s += RESET
701
+ if b > 0:
702
+ s += f"\x1b[{90 + b}m"
703
+ if b == 0:
704
+ s += " "
705
+ else:
706
+ s += BAR_CHAR
707
+ b_last = b
708
+ s += RESET
709
+
710
+ # Print the bar graph.
711
+ y, res = divmod(date, 10000)
712
+ m, d = divmod(res, 100)
713
+ print(f" {y:04d}-{m:02d}-{d:02d} {s}")
714
+
672
715
  # Print the totals.
673
716
  for j in range(J):
674
717
  hu, mu = divmod(totals_unique[j], 60)
718
+ hu_frac = round(hu + mu/60.0, 2)
675
719
  label = jobs[jobs_unique[j] - 1][1]
676
- print(f" {hu:02d}:{mu:02d} {label}")
720
+ print(f" \x1b[{90 + j + 1}m{BAR_CHAR}{RESET}"
721
+ + f" {hu:02d}:{mu:02d} ({hu_frac:.2f})"
722
+ + f" {label}")
677
723
  hn, mn = divmod(net, 60)
678
- print(f" {hn:02d}:{mn:02d}")
724
+ hn_frac = round(hn + mn/60.0, 2)
725
+ print(f" {hn:02d}:{mn:02d} ({hn_frac:.2f})")
679
726
 
680
727
 
681
728
  def report_week(jobs, logs, tails):
@@ -0,0 +1,409 @@
1
+ Metadata-Version: 2.4
2
+ Name: dey
3
+ Version: 0.2.2
4
+ Summary: Terminal time tracker
5
+ Author-email: David Woodburn <david.woodburn@icloud.com>
6
+ License-Expression: MIT
7
+ Requires-Python: >=3.7
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+ Dynamic: license-file
11
+
12
+ # Introduction
13
+
14
+ `dey` is a terminal-based time tracker for logging work hours against named
15
+ jobs.
16
+
17
+ The name comes from the [Alexander Dey Time Register Clock][1] (1888), an early
18
+ punch-clock that recorded employee hours on a paper roll. Like its namesake,
19
+ `dey` uses a simple punch-in, punch-out model: start a timer against a job, stop
20
+ it later. You can also backfill entries for past times.
21
+
22
+ Using a spreadsheet to track work hours is cumbersome: Excel is slow to open,
23
+ times and dates must be entered manually, multiple jobs create an unwieldy
24
+ number of columns, and generating summaries is complicated. This program offers
25
+ a simple terminal-based alternative. It automatically records the current date
26
+ and time, resolves short job-name fragments by substring matching, and produces
27
+ clear daily and weekly summaries that can be filtered the same way. A single
28
+ command also lets you undo the last entry, adjust its time, or change its job.
29
+
30
+ The design deliberately stays simple. Only one job can be active at a time.
31
+ Features such as billing calculations, notes, hierarchical projects, tags, idle
32
+ detection, goals, configuration files, and shell completions are omitted so the
33
+ tool remains a focused Unix-style utility: easy to understand, quick to use with
34
+ short commands, and simple to combine with other programs through its clean,
35
+ well-formatted output.
36
+
37
+ A simple example shows creating a job for the first time, starting time tracking
38
+ against that job, and then stopping the work over an hour later:
39
+ ```bash
40
+ > dey -j meetings
41
+ New job "meetings" created.
42
+ > dey -t
43
+ 00:00 | 07:42 - meetings
44
+ > dey -t
45
+ 01:05 | 07:42 - 08:47 meetings
46
+ ```
47
+
48
+ # Data Storage
49
+
50
+ The data for the program is saved in text files at
51
+
52
+ | OS | Location |
53
+ | :------ | :------------------------------------------ |
54
+ | Windows | `%LOCALAPPDATA%\dey\` |
55
+ | Linux | `$XDG_CONFIG_HOME/dey/` or `~/.config/dey/` |
56
+ | macOS | `~/.config/dey/` |
57
+
58
+ The `jobs.txt` file stores definitions of jobs:
59
+ ```
60
+ a meetings
61
+ a permissive vacation
62
+ a permissive sickness
63
+ i Project X
64
+ ```
65
+ Each job is defined with an ID (the row number in the file), an active, `a`, or
66
+ inactive, `i`, flag, and a label. The inactive flag hides the job from searches
67
+ when starting work on a job; it does not hide the history of work done for those
68
+ jobs. The jobs are in the order they were created.
69
+
70
+ The `logs.txt` file stores the work done, called "logs":
71
+ ```
72
+ 20260919 07:31 0001 09:22
73
+ 20260919 10:03 0003 13:45
74
+ 20260920 08:10 0001 10:50
75
+ 20260926 08:30 0002
76
+ ```
77
+ Each log contains a date represented as an integer in `YYYYMMDD` format, a start
78
+ time, a job ID, and a stop time. Times are in 24-hour format. Job IDs are
79
+ four-digit integers, which means up to 9999 different jobs can exist. A missing
80
+ stop time indicates that the log is currently active. Times are recorded to the
81
+ nearest minute; finer precision is omitted because minute precision introduces
82
+ only a zero-bias error, and many organizations do not require accuracy beyond
83
+ one-tenth of an hour.
84
+
85
+ The time intervals of different logs cannot overlap, so at any given moment,
86
+ work can be performed on only one job. As a consequence, no more than one log
87
+ may have a missing stop time because it is current. A log cannot span more than
88
+ one day. At the latest, it must end by 23:59. Consequently, no date arithmetic
89
+ is performed. Logs are listed in the order they were created, which may differ
90
+ from their chronological order.
91
+
92
+ Both the `jobs` file and the `logs` file have been designed so that in most
93
+ cases, a full rewrite of the file is not necessary. Instead, a new job or log
94
+ can be appended to the corresponding file. Though negligible anyway, this does
95
+ reduce processing time and wear on solid state discs. It is not intended that
96
+ the user ever directly modify these files; if you do, be very careful not to
97
+ corrupt the format.
98
+
99
+ If full editing ability were supported, a log ID would need to accompany each
100
+ log in order to address the specific log. Start and stop times would need to be
101
+ directly specified. Even an interactive interface for editing would be
102
+ preferable. These are all directions that would take away from the simplicity of
103
+ this program. Therefore, what is not supported is the ability to edit any
104
+ arbitrary log, besides the last. Consequently, it is the responsibility of the
105
+ user to pay attention when entering logs and not create the occasion for editing
106
+ logs several entries prior. The last entry can be modified, but anything prior
107
+ must be modified by undoing entries. That said, the logs file is formatted such
108
+ that it is not too difficult to directly edit, and the line numbers beside the
109
+ jobs and visible with a code editor correspond to the job IDs.
110
+
111
+ # Interface
112
+
113
+ Just the name of the program prints the status:
114
+ ```bash
115
+ > dey
116
+ 07:32 | 14:10 - 15:05 meetings
117
+ > dey
118
+ 07:32 | 14:10 - meetings
119
+ ```
120
+ This shows the total time worked for the present day, the start and stop times
121
+ and the job label of the latest log. If the latest log is current, nothing is
122
+ printed in place of the stop time. If no logs exist for the present day, a
123
+ statement to this effect is output.
124
+
125
+ Additional functionality is available with the following command arguments (flag
126
+ letters preceded by a dash) and their optional tails (text following the
127
+ arguments):
128
+
129
+ | Arguments | Description |
130
+ | :------------------------- | :----------------------------------- |
131
+ | `-l {label}` | List all active jobs matching label |
132
+ | `-L {label}` | List all jobs matching label |
133
+ | `-j {label} {label}` | Create job, rename job |
134
+ | `-i {label}` | Make job active job inactive |
135
+ | `-a {label}` | Make job inactive job active |
136
+ | | |
137
+ | `-t {time} {date} {label}` | Toggle time or switch active job |
138
+ | `-e {time} {label}` | Edit time or job of last entry |
139
+ | `-u {integer}` | Undo last log entries (default to 1) |
140
+ | | |
141
+ | `-d {date} {label}` | Day report (default to recent date) |
142
+ | `-w {weeks} {label}` | Week report (default to recent week) |
143
+ | | |
144
+ | `-h` | Print command help |
145
+
146
+ A `date` is entered in the format `YYYY-MM-DD` or `YY-MM-DD`. Time is entered in
147
+ the format `HH:MM`. Each argument is a standalone instruction that does not
148
+ depend on any other argument. Multiple command arguments can be given in a
149
+ single terminal command and they will be executed in the order they are written.
150
+
151
+ ## Jobs
152
+
153
+ ### List Jobs
154
+
155
+ The list of existing active jobs can be printed by entering the `-l` flag
156
+ without any job labels. This will print all the jobs in alphabetical order:
157
+ ```bash
158
+ > dey -l
159
+ meetings
160
+ permissive sickness
161
+ permissive vacation
162
+ ```
163
+
164
+ The list can be narrowed down by entering part of a job label after the flag:
165
+ ```bash
166
+ > dey -l perm
167
+ permissive sickness
168
+ permissive vacation
169
+ ```
170
+ All string matching to job labels is case-insensitive.
171
+
172
+ All jobs including inactive ones can be listed with the `-L` flag:
173
+ ```bash
174
+ > dey -L
175
+ meetings
176
+ permissive sickness
177
+ permissive vacation
178
+ Project X
179
+ ```
180
+ The inactive jobs are marked in gray.
181
+
182
+ ### Create a Job
183
+
184
+ A new job can be defined with the `-j` argument followed by the job label:
185
+ ```bash
186
+ > dey -j Clean-up
187
+ New job "Clean-up" created.
188
+ ```
189
+ If a job by that exact name (using case-insensitive matching) already exists,
190
+ the command will be rejected.
191
+
192
+ ### Rename a Job
193
+
194
+ An active job can be renamed by adding a second string:
195
+ ```bash
196
+ > dey -j Clean-up Cleanup
197
+ Job "Clean-up" renamed to "Cleanup".
198
+ ```
199
+ When searching for matches on the first string, a partial match will be
200
+ accepted. For example, `vaca` would match `permissive vacation`. If no match or
201
+ more than one match can be found, the command will be rejected. If the second
202
+ string fully matches an existing job label, the command will be rejected.
203
+
204
+ Renaming an inactive job requires first making it active.
205
+
206
+ ### Active and Inactive Jobs
207
+
208
+ An active job can be made inactive so that it no longer is chosen as a valid
209
+ match to new work by using the `-i` flag:
210
+ ```bash
211
+ > dey -i Project\ X
212
+ "Project X" is now inactive.
213
+ ```
214
+ An inactive job can be made active by using the `-a` flag:
215
+ ```bash
216
+ > dey -a Project\ X
217
+ "Project X" is now active.
218
+ ```
219
+
220
+ ### Job Aliases (*not implemented*)
221
+
222
+ A job can have an alias (or description) by appending the extra text to the job
223
+ name separated by a colon:
224
+ ```bash
225
+ > dey -j "Project X: Hyper-engine research"
226
+ ```
227
+ This is useful when printing reports. Reports will list the names of projects.
228
+ Because many projects can lead to very similar names which can make entering
229
+ totals into official time-reporting software error-prone, the print feature will
230
+ emphasize those parts of the names which are unique. However, the alias of the
231
+ name will never be included in the emphasis because that part will never be used
232
+ in reporting. That said, job searches can still match to the alias.
233
+
234
+ ## Toggle Clock
235
+
236
+ ### Without Time
237
+
238
+ Time tracking is toggled using the `-t` argument:
239
+ ```bash
240
+ > dey -t
241
+ 00:18 | 07:42 - Project X
242
+ ```
243
+ This will start a log if none is current or end a log if one is current. The
244
+ status will be automatically printed to give confirmation. The simplicity of not
245
+ specifying whether the time is a start or stop means there can be only one
246
+ active log at a time.
247
+
248
+ ### With Time
249
+
250
+ By default, the current time is used, but any time can be explicitly specified
251
+ after the `-t` argument:
252
+ ```bash
253
+ > dey -t 07:45
254
+ 00:15 | 07:45 - Project X
255
+ ```
256
+ This will mark the start or stop of a log with that time. If a new start or stop
257
+ time overlaps with any other log of the same day, the command will be rejected
258
+ and an explanation will be printed. So, giving a stop time that comes before the
259
+ start time of the same log will be rejected and an explanation will be printed.
260
+
261
+ Only one current log is allowed at a time. This is why the interface does not
262
+ provide separate start and stop commands. So, if a log is current (has no stop
263
+ time) another log cannot be started. Instead, if the time is after the start
264
+ time of the current log, the new time will be used as the stop time of the
265
+ current log.
266
+
267
+ It is possible to forget to stop a log at the end of the day. So, the next day
268
+ when a time is given, it will be used as the stop time of yesterday's log. If
269
+ the stop time is before the log's start time, the command will be rejected.
270
+ Clocking out will only apply to the same day as the start of the current log.
271
+ This means you can clock out by entering something like `dey -t 17:00` and this
272
+ will apply to the day the current log was started. The program will not mark the
273
+ log as continuing into the next day.
274
+
275
+ Because logs can be defined with specific times (other than the present), they
276
+ can be created out of order. The log file is not sorted in chronological order.
277
+ This is a deliberate choice to support undoing actions in the order they were
278
+ commanded.
279
+
280
+ If an active log was started on a prior day, the date must be specified. A log
281
+ cannot span across dates.
282
+
283
+ ### With Date
284
+
285
+ By default, the current day is used, but any date can be specified explicitly
286
+ after the `-t` argument:
287
+ ```bash
288
+ > dey -t 07:45 26-09-17
289
+ ```
290
+ The format for dates is either `26-09-07` or `2026-09-07`.
291
+
292
+ ### With Job Label
293
+
294
+ By default, the job of the last log is the assumed job for a new log. However,
295
+ any job can be specified:
296
+ ```bash
297
+ > dey -t meetings
298
+ 00:18 | 07:42 - meetings
299
+ ```
300
+ If no log is current, the job specified is used as the job of the new log. If a
301
+ log is current, the current time or the time provided is used to stop the
302
+ current log and to create a new log and the specified job is used for the new
303
+ log. This makes it easy to switch jobs without having to explicitly stop the
304
+ current log and start another. If the specified job does not match any
305
+ previously-defined jobs, the command will be rejected.
306
+
307
+ ### Undo Last Entry (*not implemented*)
308
+
309
+ The last entry (start or stop or a log) can be undone with the `-u` argument:
310
+ ```bash
311
+ > dey -u
312
+ No logs for today.
313
+ ```
314
+ Multiple entries can be undone at once by providing an integer.
315
+
316
+ ### Edit Last Entry (*not implemented*)
317
+
318
+ The last log entry (start time, job label, or stop time) can be edited with
319
+ the `-e` argument. So, if a log is current, the `-e` command can be used to
320
+ change the start time:
321
+ ```bash
322
+ > dey -e 07:45
323
+ 00:15 | 07:45 - Project X
324
+ ```
325
+ Providing a job label will change the job of the last entry.
326
+ ```bash
327
+ > dey -e meetings
328
+ 00:18 | 07:42 - meetings
329
+ ```
330
+ Both a time and a job label can be specified together.
331
+
332
+ ## Reports
333
+
334
+ ### Day Reports (*partially implemented*)
335
+
336
+ The day report shows the date of the last log and a color-coded graph of that
337
+ day's work. This is followed by that day's logs, each preceded by the
338
+ corresponding color-coding. Finally, the total hours for the day are printed:
339
+ ```bash
340
+ > dey -d
341
+ 2026-11-09 ================== ===================
342
+ = 01:15 (1.25) permissive sick
343
+ = 04:38 (4.63) Project X
344
+ 05:53 (5.88)
345
+ ```
346
+
347
+ Colors are chosen from a subset of the 8-bit ANSI-escape-code colors based on
348
+ the order the jobs first appeared in the day. (Only 8-bit colors are used
349
+ because most terminal emulators support those but not all terminal emulators
350
+ support millions of colors.) Depending on the start and stop times for the day
351
+ and the column width of the present output stream, each character in the graph
352
+ corresponds to a 5, 10, 15, 20, or 30 minute increment. The start of the bar
353
+ corresponds to the beginning of the first log of the day and the end corresponds
354
+ to the end of the last or present if a log is current.
355
+
356
+ Because the day summary can accept a date, any past day can be reported. If a
357
+ date is provided (in the format `26-09-07` or `2026-09-07`) the summary of that
358
+ day will be printed. If a job label is provided as in `dey -d meetings`, the
359
+ summary of just that job (or any job to which the text substring matches) for
360
+ the current date will be printed. These two options can be combined as in `dey
361
+ -d 26-09-07 meetings` to print a report of just that job for that date. The
362
+ colors chosen will be the same as if all the jobs for the day were printed.
363
+
364
+ ### Week Reports (*not implemented*)
365
+
366
+ The week report (`dey -w`) aligns with the beginning of a week, not the current
367
+ day. For example, if the current day is Wednesday, the current week would be
368
+ from Sunday of that week to Wednesday, not the past seven days. The purpose of
369
+ the week report is to show the total hours worked per day, per job, and for the
370
+ week. The report is printed in a markdown-compatible format:
371
+ ```bash
372
+ > dey -w
373
+ Total | 09-06 | 09-07 | 09-09 | 09-10 | 09-11 | Job
374
+ :---: | :---: | :---: | :---: | :---: | :---: | :---
375
+ 13:15 | 02:45 | 08:00 | 02:00 | 00:00 | 00:30 | Meetings
376
+ 16:30 | 03:30 | 00:00 | 05:00 | 02:00 | 06:00 | Documentation
377
+ 10:15 | 02:00 | 00:00 | 01:30 | 05:30 | 01:15 | Education
378
+ --- | --- | --- | --- | --- | --- |
379
+ 40:00 | 08:15 | 08:00 | 08:30 | 07:30 | 07:45 |
380
+ ```
381
+ More weeks can be specified with a number after the `-w` argument flag. The
382
+ additional weeks will be printed as separate tables in chronological order.
383
+
384
+ If a job label is provided, just like the day report, only logs matching that
385
+ search will be printed. Because of substring search, multiple job labels can be
386
+ matches.
387
+
388
+ # Alternatives
389
+
390
+ There are several alternatives, but I believe none approach this problem with
391
+ the same level of simplicity and succinctness or brevity as `dey`.
392
+
393
+ - [bartib][2]
394
+ - [timetrace][3]
395
+ - [timetrap][4]
396
+ - [timewarrior][5]
397
+ - [tmpo][6]
398
+ - [watson][7]
399
+ - [zeit][8]
400
+
401
+
402
+ [1]:https://americanhistory.si.edu/collections/object/nmah_856741
403
+ [2]:https://github.com/nikolassv/bartib
404
+ [3]:https://github.com/dominikbraun/timetrace
405
+ [4]:https://github.com/samg/timetrap
406
+ [5]:https://github.com/gothenburgbitfactory/timewarrior
407
+ [6]:https://github.com/DylanDevelops/tmpo
408
+ [7]:https://github.com/jazzband/Watson
409
+ [8]:https://github.com/mrusme/zeit
@@ -1,3 +1,4 @@
1
+ LICENSE
1
2
  README.md
2
3
  pyproject.toml
3
4
  dey/__init__.py
@@ -4,9 +4,16 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "dey"
7
- version = "0.2.0"
7
+ version = "0.2.2"
8
8
  description = "Terminal time tracker"
9
+ readme = "README.md"
10
+ authors = [
11
+ { name = "David Woodburn", email = "david.woodburn@icloud.com" },
12
+ ]
13
+ license = "MIT"
9
14
  requires-python = ">=3.7"
15
+ dependencies = [
16
+ ]
10
17
 
11
18
  [project.scripts]
12
19
  dey = "dey.cli:main"
dey-0.2.0/PKG-INFO DELETED
@@ -1,5 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: dey
3
- Version: 0.2.0
4
- Summary: Terminal time tracker
5
- Requires-Python: >=3.7
@@ -1,5 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: dey
3
- Version: 0.2.0
4
- Summary: Terminal time tracker
5
- Requires-Python: >=3.7
File without changes
File without changes
File without changes
File without changes
File without changes