2 changed files with 265 additions and 71 deletions
@ -1,71 +1,194 @@ |
|||||
#!/bin/sh |
#!/usr/bin/env python3 |
||||
|
# -*- coding: utf-8 -*- |
||||
TIMEFILE="$HOME/timekeeping.csv" |
|
||||
|
import argparse |
||||
usage () { |
from signal import signal, SIGINT |
||||
>&2 printf "usage:\n\t%s <start|pause|end>" "$(basename "$0")" |
import sys |
||||
exit 0 |
from time import sleep |
||||
} |
from os.path import expanduser, join |
||||
|
from datetime import datetime, timedelta |
||||
die () { |
|
||||
>&2 echo "Error: $*" |
TIME_FILE = join(expanduser("~"), "timekeeping.csv") |
||||
exit 1 |
TODAY_FILE = join(expanduser("~"), ".timekeeping") |
||||
} |
|
||||
|
is_on_break = False |
||||
from_ts () { |
today_fields = [] |
||||
if ! date --version >/dev/null 2>&1; then # BSD |
start_time = datetime.now() |
||||
date -r "$1" '+%H:%M' |
|
||||
else # GNU |
|
||||
date -d "@$1" '+%H:%M' |
def die(*args): |
||||
fi |
print("Error: {}".format(*args), file=sys.stderr) |
||||
} |
sys.exit(1) |
||||
|
|
||||
pause_end () { |
|
||||
if [ "$ACTION" = "pause" ]; then |
def log(*args): |
||||
now="$(date '+%s')" |
print(*args, file=sys.stderr) |
||||
printf "%s," "$((now - START))" >> "$TIMEFILE" |
|
||||
>&2 printf "\nYou took a %d minutes pause\n" "$(( (now - START) / 60))" |
|
||||
fi |
def signal_handler(signal, frame): |
||||
} |
global today_fields |
||||
|
now = datetime.now() |
||||
trap pause_end INT |
hour = now.strftime("%H:%M") |
||||
|
if not is_on_break: |
||||
test -z "$1" && usage |
sys.exit(0) |
||||
test -e "$TIMEFILE" || touch "$TIMEFILE" |
else: |
||||
|
break_time = now - start_time |
||||
TODAY="$(date '+%Y-%m-%d')" |
log("\nBreak ended at {} and lasted {}".format( |
||||
START="$(date '+%s')" |
hour, td_format(break_time))) |
||||
ACTION="$1" |
today_fields.append(start_time.strftime("%H:%M")) |
||||
|
today_fields.append(hour) |
||||
|
with open(TODAY_FILE, "w") as f: |
||||
case "$1" in |
f.write(",".join(today_fields)) |
||||
start) |
sys.exit(0) |
||||
grep -qE "^$TODAY" "$TIMEFILE" && die "you already started your day" |
|
||||
printf "%s,%s," "$TODAY" "$(date '+%s')" >> "$TIMEFILE" |
|
||||
>&2 printf "Started work at %s\n" "$(date '+%H:%M')";; |
def td_format(td): |
||||
pause) |
hours, remainder = divmod(td.total_seconds(), 3600) |
||||
grep -qE "^$TODAY" "$TIMEFILE" || die "You haven't even started your day!" |
minutes, seconds = divmod(remainder, 60) |
||||
awk -F, "/^$TODAY/"'{if (NF > 4) exit 1}' "$TIMEFILE" || die "You're already done for the day" |
|
||||
awk -F, "/^$TODAY/"'{if (NF > 2) exit 1}' "$TIMEFILE" || die "You've already expanded your daily break allowance" |
return '{:d}:{:02d}'.format(int(hours), int(minutes)) |
||||
>&2 echo "Taking a break..." |
|
||||
sleep 9999999;; |
|
||||
end) |
def convert_manual_entry(): |
||||
test -z "$2" && die "Tell me what you did today" |
for line in sys.stdin: |
||||
grep -qE "^$TODAY" "$TIMEFILE" || die "You haven't even started your day!" |
fields = line.split(",") |
||||
start_hour="$(from_ts "$(tail -n1 "$TIMEFILE" | cut -f 2 -d ,)")" |
if len(fields) < 6: |
||||
end_hour="$(date '+%H:%M')" |
break |
||||
shift 1 |
morning = datetime.strptime(fields[1], "%H:%M") |
||||
msg="$(printf "%s" "$*" | sed 's/"/""/g')" |
break_start = datetime.strptime(fields[2], "%H:%M") |
||||
line="$(awk -F ',' -v now="$START" -v start="$start_hour" -v end="$end_hour" -v msg="$msg" \ |
break_end = datetime.strptime(fields[3], "%H:%M") |
||||
"/^$TODAY/"'{ |
evening = datetime.strptime(fields[4], "%H:%M") |
||||
total=now-$2-$3; |
break_time = break_end - break_start |
||||
h=int(total/3600); |
full_day = evening - morning |
||||
m=int(total/60%60); |
work_day = full_day - break_time |
||||
ph=int($3/3600); |
print('{},{},{},{},{},{}'.format( |
||||
pm=int($3/60%60); |
fields[0], |
||||
printf "%s,%s,%s,%s:%s,%s:%s,\"%s\"",$1,start,end,ph,pm,h,m,msg; |
morning.strftime("%H:%M"), |
||||
}' \ |
evening.strftime("%H:%M"), |
||||
"$TIMEFILE")" |
td_format(break_time), |
||||
printf "\$d\nw\n\q" | ed "$TIMEFILE" > /dev/null 2>&1 |
td_format(work_day), |
||||
printf "%s\n" "$line" >> "$TIMEFILE";; |
"".join(fields[5:]).strip())) |
||||
esac |
|
||||
|
|
||||
|
def get_today_fields(): |
||||
|
try: |
||||
|
with open(TODAY_FILE, "r") as f: |
||||
|
for line in f: |
||||
|
if line.startswith(start_time.strftime("%Y-%m-%d")): |
||||
|
return line.strip().split(",") |
||||
|
except FileNotFoundError: |
||||
|
return [] |
||||
|
return [] |
||||
|
|
||||
|
|
||||
|
def work(args): |
||||
|
fields = get_today_fields() |
||||
|
if len(fields) < 2: |
||||
|
die("you haven't started working yet today") |
||||
|
print("stats: {}".format(fields)) |
||||
|
|
||||
|
|
||||
|
def work_start(args): |
||||
|
fields = get_today_fields() |
||||
|
hour = start_time.strftime("%H:%M") |
||||
|
if fields: |
||||
|
die("You already started working") |
||||
|
with open(TODAY_FILE, "w") as f: |
||||
|
f.write("{},{}".format( |
||||
|
start_time.strftime("%Y-%m-%d"), hour)) |
||||
|
log("Started working at {}".format(hour)) |
||||
|
|
||||
|
|
||||
|
def work_pause(args): |
||||
|
global is_on_break |
||||
|
global today_fields |
||||
|
is_on_break = True |
||||
|
today_fields = get_today_fields() |
||||
|
hour = start_time.strftime("%H:%M") |
||||
|
if not today_fields: |
||||
|
die("no work to take a break from") |
||||
|
log("Taking a break at {}".format(hour)) |
||||
|
# Wait to be stopped by a Ctrl-C |
||||
|
sleep(999999) |
||||
|
|
||||
|
|
||||
|
def work_end(args): |
||||
|
fields = get_today_fields() |
||||
|
hour = start_time.strftime("%H:%M") |
||||
|
if not fields: |
||||
|
die("Why try to leave when you haven't even started") |
||||
|
fields.append(hour) |
||||
|
# Test for even number of timestamp (but fields[0] is the date) |
||||
|
if len(fields) < 3: |
||||
|
die("not enough fields in {}".format(TODAY_FILE)) |
||||
|
elif len(fields) % 2 == 0: |
||||
|
die("odd number of timestamps in {}".format(TODAY_FILE)) |
||||
|
begin_time = None |
||||
|
worked_time = timedelta() |
||||
|
for field in fields[1:]: |
||||
|
try: |
||||
|
if begin_time is None: |
||||
|
begin_time = datetime.strptime(field, "%H:%M") |
||||
|
else: |
||||
|
end_time = datetime.strptime(field, "%H:%M") |
||||
|
worked_time += end_time - begin_time |
||||
|
begin_time = None |
||||
|
except ValueError: |
||||
|
die("couldn't parse field '{}' in {}".format( |
||||
|
field, TODAY_FILE)) |
||||
|
day_start = datetime.strptime(fields[1], "%H:%M") |
||||
|
day_end = datetime.strptime(fields[-1], "%H:%M") |
||||
|
total_time = day_end - day_start |
||||
|
break_time = total_time - worked_time |
||||
|
with open(TIME_FILE, "a") as f: |
||||
|
f.write("{},{},{},{},{},{}\n".format( |
||||
|
fields[0], |
||||
|
day_start.strftime("%H:%M"), |
||||
|
start_time.strftime("%H:%M"), |
||||
|
td_format(break_time), |
||||
|
td_format(worked_time), |
||||
|
args.description)) |
||||
|
# Erase TODAY_FILE |
||||
|
with open(TODAY_FILE, "w") as f: |
||||
|
f.write("") |
||||
|
f.flush() |
||||
|
log("Finished working at {} after working {}".format( |
||||
|
hour, td_format(worked_time))) |
||||
|
|
||||
|
|
||||
|
def work_export(args): |
||||
|
print("export") |
||||
|
|
||||
|
|
||||
|
def work_parse(args): |
||||
|
print("parse") |
||||
|
|
||||
|
|
||||
|
if __name__ == "__main__": |
||||
|
# Handle Ctrl-C |
||||
|
signal(SIGINT, signal_handler) |
||||
|
|
||||
|
parser = argparse.ArgumentParser() |
||||
|
parser.set_defaults(func=work) |
||||
|
commands = parser.add_subparsers(dest="command") |
||||
|
|
||||
|
start_parser = commands.add_parser("start") |
||||
|
pause_parser = commands.add_parser("pause") |
||||
|
end_parser = commands.add_parser("end") |
||||
|
export_parser = commands.add_parser("export") |
||||
|
parse_parser = commands.add_parser("parse") |
||||
|
|
||||
|
start_parser.set_defaults(func=work_start) |
||||
|
pause_parser.set_defaults(func=work_pause) |
||||
|
end_parser.add_argument("description") |
||||
|
end_parser.set_defaults(func=work_end) |
||||
|
export_parser.set_defaults(func=work_export) |
||||
|
parse_parser.set_defaults(func=work_parse) |
||||
|
|
||||
|
args = parser.parse_args() |
||||
|
args.func(args) |
||||
|
|
||||
|
#now = datetime.now() |
||||
|
#with open(TIME_FILE, "r") as f: |
||||
|
# for line in f: |
||||
|
# print(line.strip()) |
||||
|
|||||
@ -0,0 +1,71 @@ |
|||||
|
#!/bin/sh |
||||
|
|
||||
|
TIMEFILE="$HOME/timekeeping.csv" |
||||
|
|
||||
|
usage () { |
||||
|
>&2 printf "usage:\n\t%s <start|pause|end>" "$(basename "$0")" |
||||
|
exit 0 |
||||
|
} |
||||
|
|
||||
|
die () { |
||||
|
>&2 echo "Error: $*" |
||||
|
exit 1 |
||||
|
} |
||||
|
|
||||
|
from_ts () { |
||||
|
if ! date --version >/dev/null 2>&1; then # BSD |
||||
|
date -r "$1" '+%H:%M' |
||||
|
else # GNU |
||||
|
date -d "@$1" '+%H:%M' |
||||
|
fi |
||||
|
} |
||||
|
|
||||
|
pause_end () { |
||||
|
if [ "$ACTION" = "pause" ]; then |
||||
|
now="$(date '+%s')" |
||||
|
printf "%s," "$((now - START))" >> "$TIMEFILE" |
||||
|
>&2 printf "\nYou took a %d minutes pause\n" "$(( (now - START) / 60))" |
||||
|
fi |
||||
|
} |
||||
|
|
||||
|
trap pause_end INT |
||||
|
|
||||
|
test -z "$1" && usage |
||||
|
test -e "$TIMEFILE" || touch "$TIMEFILE" |
||||
|
|
||||
|
TODAY="$(date '+%Y-%m-%d')" |
||||
|
START="$(date '+%s')" |
||||
|
ACTION="$1" |
||||
|
|
||||
|
|
||||
|
case "$1" in |
||||
|
start) |
||||
|
grep -qE "^$TODAY" "$TIMEFILE" && die "you already started your day" |
||||
|
printf "%s,%s," "$TODAY" "$(date '+%s')" >> "$TIMEFILE" |
||||
|
>&2 printf "Started work at %s\n" "$(date '+%H:%M')";; |
||||
|
pause) |
||||
|
grep -qE "^$TODAY" "$TIMEFILE" || die "You haven't even started your day!" |
||||
|
awk -F, "/^$TODAY/"'{if (NF > 4) exit 1}' "$TIMEFILE" || die "You're already done for the day" |
||||
|
awk -F, "/^$TODAY/"'{if (NF > 2) exit 1}' "$TIMEFILE" || die "You've already expanded your daily break allowance" |
||||
|
>&2 echo "Taking a break..." |
||||
|
sleep 9999999;; |
||||
|
end) |
||||
|
test -z "$2" && die "Tell me what you did today" |
||||
|
grep -qE "^$TODAY" "$TIMEFILE" || die "You haven't even started your day!" |
||||
|
start_hour="$(from_ts "$(tail -n1 "$TIMEFILE" | cut -f 2 -d ,)")" |
||||
|
end_hour="$(date '+%H:%M')" |
||||
|
shift 1 |
||||
|
msg="$(printf "%s" "$*" | sed 's/"/""/g')" |
||||
|
line="$(awk -F ',' -v now="$START" -v start="$start_hour" -v end="$end_hour" -v msg="$msg" \ |
||||
|
"/^$TODAY/"'{ |
||||
|
total=now-$2-$3; |
||||
|
h=int(total/3600); |
||||
|
m=int(total/60%60); |
||||
|
ph=int($3/3600); |
||||
|
pm=int($3/60%60); |
||||
|
printf "%s,%s,%s,%s:%s,%s:%s,\"%s\"",$1,start,end,ph,pm,h,m,msg; |
||||
|
}' \ |
||||
|
"$TIMEFILE")" |
||||
|
printf "\$d\nw\n\q" | ed "$TIMEFILE" > /dev/null 2>&1 |
||||
|
printf "%s\n" "$line" >> "$TIMEFILE";; |
||||
|
esac |
||||
Loading…
Reference in new issue