tiktak

package module
v0.4.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 10, 2024 License: AGPL-3.0 Imports: 12 Imported by: 0

README

logo

A personal time tracker for terminal users


tiktak will help you keep track of time you spent on different activities—probably projects. It keeps its time records in ridiculous simple text files. This should help if it comes to use your time records for some more fancy application or keep them in a VCS repo, etc.

Disclaimer

The data generated by this program is collected and computed with best effort for correctness. There is no guarantee for the fitness of that data for any specific purpose! Don't blame any author of the software if numbers you get from the program are incorrect in any way.

Getting Started

  • Get you the tiktak executable by
  • Put the tiktak executable in your path. (You pretend to be a terminal user, i.e. I won't have to explain…)
  • tiktak will store its files in $HOME/.local/share/fqb/tiktak on UN!X like systems if $HOME/.local/share exists. On Windows, files go into %HOME%\AppData\Roaming\fqb\tiktak. Otherwise tiktak stores files in '.'.
  • If you decide to keep files somwhere else, set the TIKTAK_DATA environment variable to the directory of your liking.
  • Start working on something cool, i.e. type tiktak /something/cool in your terminal. This will start the subtaks cool withing its parent task something.
  • Do some miscellaneous work you want to account on the global level and type tiktak /
  • After a while go back to work on something by typing tiktak something. Note that you don't need to type a slash '/' because tiktak will find the already known task /something.
  • 32 minutes later, check the time collected, i.e. type tiktak
SUMS: Sat, 01 Apr 2023; Week 13:                                   
-------------------------------------------------------------
Task             Today.  Today/  Week.  Week/  Month.  Month/
-------------------------------------------------------------
/something/cool   00:19       -  00:19      -   00:19       -
/something        00:31   00:50  00:31  00:50   00:31   00:50
/                 00:29   01:20  00:29  01:20   00:29   01:20
  • This is enough work for today, say tiktak -zzz
  • Check the recorded time spans in detail: titkat -r spans
Wed, 05 Apr 2023; Week 14                   
  10:40:09  10:59:12  00:19  /something/cool
  10:59:12  11:28:36  00:29  /              
  11:28:36  12:00:01  00:31  /something
  • You can also get a time sheet for the current month showing accumulated time for something, including subtasks and the rest by typing tiktak -r sheet /something /
SHEET: Sat, 01 Apr 2023 – Sat, 01 Apr 2023                                 
---------------------------------------------------------------------------
Day          Start  Stop   Break  Work   /something  /something/cool      /
---------------------------------------------------------------------------
Wed, 05 Apr  10:40  12:00  00:00  01:20       00:31            00:19  00:29
---------------------------------------------------------------------------
   Average:  10:40  12:00  00:00  01:20       00:31            00:19  00:29
     Count:      1   Sum:  00:00  01:20       00:31            00:19  00:29
  • Now comes the point where you think "Nice! But what can I do with this?". I'd suggest you write your time sheet into a CSV file to import it with some spreadsheet program: tiktak -r sheet -layout csv -formats c /something /
SHEET: Wed, 05 Apr 2023 – Wed, 05 Apr 2023;;;;;;;
Day;Start;Stop;Break;Work;/something;/something/cool;/
Wed, 05 Apr;10:40;12:00;00:00;01:20;00:31;00:19;00:29
Average:;10:40;12:00;00:00;01:20;00:31;00:19;00:29
Count:;1;Sum:;00:00;01:20;00:31;00:19;00:29

Detailed Docs

Build from Source

  • Be sure to have Go SDK installed. The most recent one should work. In case of doubt, the minimum required version is in go.mod.

  • Clone the repo: git clone https://github.com/fractalqb/tiktak.git

  • Enter the project dir: cd tiktak

  • Run go run mk/mk.go and find the executables in ./cmd/tik*

  • To use Go's standard install, run go run mk/mk.go -install

Locating time record files

tiktak keeps its files in a single directory. If you don't do nothing, tiktak has OS-specific strategies to locate these files. If you use tiktak -h you will find the query option that can show you the tiktak data directory: tiktak -q dir

To control the directory yourself, set the TIKTAK_DATA environment variable to the directory of your choice. Inside that directory you will find:

  • Files named yyyy-mm.tiktak that store your tasks of month mm in year yyyy. tiktak creates them depending on the current time.

  • An optional file tiktak.yaml (not .yml) with your tiktak configuration – if you created one.

  • An optional file template.yaml. If tiktak has to create a new monthly file it will first copy the contents of template.yaml unchanged into the new file. One might use it to have some preconfigured tasks that are needed every month.

Setting now

Filters

Migrating old files with tikmig

Documentation

Index

Examples

Constants

View Source
const (
	FileVersion = "1.0.0"
	IOTimeFmt   = time.RFC3339
)

Variables

This section is empty.

Functions

func AddDay

func AddDay(t time.Time, add int, loc *time.Location) time.Time
Example
t := time.Date(1999, time.June, 25, 12, 33, 9, 0, time.UTC)
fmt.Println(AddDay(t, 1, nil))
fmt.Println(AddDay(t, 10, nil))
fmt.Println(AddDay(t, -25, nil))
Output:

1999-06-26 12:33:09 +0000 UTC
1999-07-05 12:33:09 +0000 UTC
1999-05-31 12:33:09 +0000 UTC

func AllSwitch added in v0.3.1

func AllSwitch(int, *Switch) bool

func AnyTask added in v0.3.0

func AnyTask(sw *Switch) bool

func HMSF

func HMSF(d time.Duration) (h, m, s int, f float64)

func Infinite

func Infinite(d time.Duration) bool

func IsATask added in v0.3.0

func IsATask(t *Task) func(*Switch) bool

func LastDay

func LastDay(wd time.Weekday, from time.Time, loc *time.Location) time.Time

LastDay returns Time from shifted to the last Weekday wd not after from.

Example
t := time.Date(1999, time.June, 25, 12, 33, 9, 0, time.UTC)
fmt.Println(LastDay(time.Friday, t, nil))
fmt.Println(LastDay(time.Monday, t, nil))
fmt.Println(LastDay(time.Sunday, t, nil))
fmt.Println(LastDay(time.Saturday, t, nil))
Output:

1999-06-25 12:33:09 +0000 UTC
1999-06-21 12:33:09 +0000 UTC
1999-06-20 12:33:09 +0000 UTC
1999-06-19 12:33:09 +0000 UTC

func NextDay

func NextDay(wd time.Weekday, from time.Time, loc *time.Location) time.Time

NextDay returns Time from shifted to the next Weekday wd after from.

Example
t := time.Date(1999, time.June, 25, 12, 33, 9, 0, time.UTC)
fmt.Println(NextDay(time.Sunday, t, nil))
fmt.Println(NextDay(time.Monday, t, nil))
fmt.Println(NextDay(time.Friday, t, nil))
Output:

1999-06-27 12:33:09 +0000 UTC
1999-06-28 12:33:09 +0000 UTC
1999-07-02 12:33:09 +0000 UTC

func NonNilTask added in v0.3.1

func NonNilTask(_ int, sw *Switch) bool

func SameTask added in v0.3.0

func SameTask(t *Task) func(*Switch) bool

func StartDay

func StartDay(t time.Time, add int, loc *time.Location) time.Time
Example
t := time.Date(1999, time.June, 25, 12, 33, 9, 345876, time.UTC)
fmt.Println(StartDay(t, 0, nil))
fmt.Println(StartDay(t, -1, nil))
fmt.Println(StartDay(t, 1, nil))
Output:

1999-06-25 00:00:00 +0000 UTC
1999-06-24 00:00:00 +0000 UTC
1999-06-26 00:00:00 +0000 UTC

func StartMonth

func StartMonth(t time.Time, add int, loc *time.Location) time.Time
Example
t := time.Date(1999, time.June, 25, 12, 33, 9, 4711, time.UTC)
fmt.Println(StartMonth(t, 0, nil))
fmt.Println(StartMonth(t, -1, nil))
fmt.Println(StartMonth(t, 1, nil))
Output:

1999-06-01 00:00:00 +0000 UTC
1999-05-01 00:00:00 +0000 UTC
1999-07-01 00:00:00 +0000 UTC

func Warning added in v0.3.0

func Warning(n Note) bool

func Write

func Write(w io.Writer, tl TimeLine) error

Types

type Clock

type Clock struct {
	Dur      time.Duration
	Location *time.Location
}

func ClockOf

func ClockOf(t time.Time) Clock

func (Clock) HMSF

func (c Clock) HMSF() (h, m, s int, f float64)

func (Clock) On

func (c Clock) On(day time.Time) time.Time

type Date

type Date struct {
	Year     int
	Month    time.Month
	Day      int
	Location *time.Location
}

func DateOf

func DateOf(t time.Time) (res Date)

func (*Date) Compare

func (l *Date) Compare(r *Date) (d int)

func (*Date) Start

func (d *Date) Start() time.Time

type Note

type Note struct {
	Sym  rune
	Text string
}

type SelectSwitch added in v0.3.1

type SelectSwitch = func(int, *Switch) bool

type Switch

type Switch struct {
	// contains filtered or unexported fields
}

func (*Switch) AddNote added in v0.3.0

func (s *Switch) AddNote(text string) (i int)

func (*Switch) AddWarning added in v0.3.0

func (s *Switch) AddWarning(sym rune, text string) (i int)

func (*Switch) DelNote added in v0.3.0

func (s *Switch) DelNote(i int)

func (*Switch) Duration

func (s *Switch) Duration() time.Duration

func (*Switch) FilterNotes added in v0.3.0

func (s *Switch) FilterNotes(f func(Note) bool)

func (*Switch) Next

func (s *Switch) Next() *Switch

func (*Switch) Notes

func (s *Switch) Notes() []Note

func (*Switch) SelectNotes added in v0.3.0

func (s *Switch) SelectNotes(idxs []int, f func(Note) bool) []int

func (*Switch) Task

func (s *Switch) Task() *Task

func (*Switch) When

func (s *Switch) When() time.Time

type Task

type Task struct {
	// contains filtered or unexported fields
}

func (*Task) Find

func (t *Task) Find(tip bool, path ...string) *Task

func (*Task) FindString

func (t *Task) FindString(p string) *Task

func (*Task) Get

func (t *Task) Get(path ...string) (*Task, error)

func (*Task) GetString

func (t *Task) GetString(p string) (*Task, error)

func (*Task) Is

func (t *Task) Is(in *Task) bool

func (*Task) Match

func (t *Task) Match(tip bool, pattern ...string) (matches []*Task, err error)

func (*Task) MatchString

func (t *Task) MatchString(p string) ([]*Task, error)

func (*Task) Name

func (t *Task) Name() string

func (*Task) Path

func (t *Task) Path() []string

func (*Task) Root

func (t *Task) Root() *Task

func (*Task) SetTitle

func (t *Task) SetTitle(s string)

func (*Task) String

func (t *Task) String() string

func (*Task) Subtasks

func (t *Task) Subtasks() []*Task

func (*Task) Title

func (t *Task) Title() string

func (*Task) Visit

func (t *Task) Visit(pre bool, do func(*Task) error) error

type TimeLine

type TimeLine []*Switch
Example
root := new(Task)
var tl TimeLine
show := func(i int) {
	sw := tl[i]
	fmt.Printf("%d %s %s", i, sw.When(), sw.Task())
	if n := sw.Next(); n != nil {
		fmt.Printf(" > %s", n.Task())
	}
	fmt.Println()
}
t := time.Date(2023, time.April, 1, 12, 0, 0, 0, time.UTC)
show(tl.Switch(t, catch.MustRet(root.Get("1"))))
show(tl.Switch(t.Add(time.Hour), catch.MustRet(root.Get("2"))))
show(tl.Switch(t.Add(-time.Hour), catch.MustRet(root.Get("3"))))
show(tl.Switch(t.Add(-30*time.Minute), catch.MustRet(root.Get("4"))))
show(tl.Switch(t.Add(30*time.Minute), catch.MustRet(root.Get("5"))))
Output:

0 2023-04-01 12:00:00 +0000 UTC /1
1 2023-04-01 13:00:00 +0000 UTC /2
0 2023-04-01 11:00:00 +0000 UTC /3 > /1
1 2023-04-01 11:30:00 +0000 UTC /4 > /1
3 2023-04-01 12:30:00 +0000 UTC /5 > /2

func Read

func Read(r io.Reader, root *Task) (tl TimeLine, err error)
Example
ts, err := Read(strings.NewReader(`/3 Just to test titles
# Sat, 01 Apr 2023
2023-04-01T12:00:00Z /1
2023-04-01T13:00:00Z /2
	. A note
2023-04-01T11:00:00Z /3
	!? A warning with the question symbol '?'
2023-04-01T11:30:00Z /4
2023-04-01T12:30:00Z /5`), nil)
if err != nil {
	fmt.Println(err)
	return
}
Write(os.Stdout, ts)
Output:

v1.0.0	tiktak time tracker
/1
/2
/3 Just to test titles
/4
/5
# Sat, 01 Apr 2023
2023-04-01T11:00:00Z /3
	!? A warning with the question symbol '?'
2023-04-01T11:30:00Z /4
2023-04-01T12:00:00Z /1
2023-04-01T12:30:00Z /5
2023-04-01T13:00:00Z /2
	. A note

func (*TimeLine) Clip

func (tl *TimeLine) Clip(start, end time.Time)

func (*TimeLine) ClipAfter

func (tl *TimeLine) ClipAfter(t time.Time)

func (*TimeLine) ClipBefore

func (tl *TimeLine) ClipBefore(t time.Time)

func (*TimeLine) Del

func (tl *TimeLine) Del(at time.Time, pre, post SelectSwitch)

If at == start & pre != nil && post == nil => shift the past

func (*TimeLine) DelSwitch

func (tl *TimeLine) DelSwitch(i int) error

func (TimeLine) Duration

func (tl TimeLine) Duration(from, to, now time.Time, f func(*Switch) bool) (d time.Duration, s, e time.Time)

Duration computes the sum of durations of all time line switches filtered by f. If now switch was considered, s will be zero. Otherwise it is the time of the first switch.

Example
root := new(Task)
var tl TimeLine
t := time.Date(2023, time.April, 1, 12, 0, 0, 0, time.UTC)
tl.Switch(t, root)
var d time.Duration
var s, e time.Time
print := func(name string) {
	fmt.Print(name, ": ", d)
	if s.IsZero() {
		fmt.Print(" -")
	} else {
		fmt.Print(" ", s.Format(time.TimeOnly))
	}
	if e.IsZero() {
		fmt.Print(" -")
	} else {
		fmt.Print(" ", e.Format(time.TimeOnly))
	}
	fmt.Println()
}

now := t.Add(20 * time.Minute)
d, s, e = tl.Duration(t.Add(-time.Hour), t.Add(-30*time.Minute), now, AnyTask)
print("open/before")
d, s, e = tl.Duration(t.Add(-30*time.Minute), t, now, AnyTask)
print("open/touch")
d, s, e = tl.Duration(t.Add(-15*time.Minute), t.Add(15*time.Minute), now, AnyTask)
print("open/x-start")
d, s, e = tl.Duration(t, t.Add(30*time.Minute), now, AnyTask)
print("open/at-start")
d, s, e = tl.Duration(t, t.Add(30*time.Minute), t.Add(-10*time.Minute), AnyTask)
print("open/future")
d, s, e = tl.Duration(t.Add(15*time.Minute), t.Add(45*time.Minute), now, AnyTask)
print("open/after")

tl.Switch(t.Add(30*time.Minute), nil)
d, s, e = tl.Duration(t.Add(-time.Hour), t.Add(-30*time.Minute), now, AnyTask)
print("within/before")
d, s, e = tl.Duration(t.Add(-30*time.Minute), t, now, AnyTask)
print("within/touch")
d, s, e = tl.Duration(t.Add(-15*time.Minute), t.Add(15*time.Minute), now, AnyTask)
print("within/x-start")
d, s, e = tl.Duration(t, t.Add(30*time.Minute), now, AnyTask)
print("within/at-start")
d, s, e = tl.Duration(t, t.Add(30*time.Minute), t.Add(-10*time.Minute), AnyTask)
print("within/future")
d, s, e = tl.Duration(t.Add(15*time.Minute), t.Add(45*time.Minute), now, AnyTask)
print("within/after")

now = t.Add(40 * time.Minute)
d, s, e = tl.Duration(t.Add(-time.Hour), t.Add(-30*time.Minute), now, AnyTask)
print("after/before")
d, s, e = tl.Duration(t.Add(-30*time.Minute), t, now, AnyTask)
print("after/touch")
d, s, e = tl.Duration(t.Add(-15*time.Minute), t.Add(15*time.Minute), now, AnyTask)
print("after/x-start")
d, s, e = tl.Duration(t, t.Add(30*time.Minute), now, AnyTask)
print("after/at-start")
d, s, e = tl.Duration(t, t.Add(30*time.Minute), t.Add(-10*time.Minute), AnyTask)
print("after/future")
d, s, e = tl.Duration(t.Add(15*time.Minute), t.Add(45*time.Minute), now, AnyTask)
print("after/after")
Output:

open/before: 0s - -
open/touch: 0s - -
open/x-start: 15m0s 12:00:00 -
open/at-start: 20m0s 12:00:00 -
open/future: -10m0s 12:00:00 -
open/after: 5m0s 12:15:00 -
within/before: 0s - -
within/touch: 0s - -
within/x-start: 15m0s 12:00:00 12:15:00
within/at-start: 30m0s 12:00:00 12:30:00
within/future: 30m0s 12:00:00 12:30:00
within/after: 15m0s 12:15:00 12:30:00
after/before: 0s - -
after/touch: 0s - -
after/x-start: 15m0s 12:00:00 12:15:00
after/at-start: 30m0s 12:00:00 12:30:00
after/future: 30m0s 12:00:00 12:30:00
after/after: 15m0s 12:15:00 12:30:00

func (TimeLine) FirstTask

func (tl TimeLine) FirstTask() *Task

func (*TimeLine) Insert added in v0.3.1

func (tl *TimeLine) Insert(
	at time.Time, to *Task,
	dtPast time.Duration, swPast SelectSwitch,
	dtFutr time.Duration, swFutr SelectSwitch,
) int

func (TimeLine) Pick

func (ts TimeLine) Pick(t time.Time) (int, *Switch)

Pick returns the latest task switch that happens at or before t. If t is before all task switches Pick retunrns -1, nil. Otherwies it retuns the index of the switch in the TimeLine and the switch itself.

func (*TimeLine) Reschedule

func (tl *TimeLine) Reschedule(i int, to time.Time) error

func (TimeLine) RootTask added in v0.3.1

func (tl TimeLine) RootTask() *Task

func (*TimeLine) Switch

func (tl *TimeLine) Switch(at time.Time, to *Task) int

Directories

Path Synopsis
cmd
internal

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL