2 changed files with 265 additions and 71 deletions
@ -1,71 +1,194 @@ |
|||
#!/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 |
|||
#!/usr/bin/env python3 |
|||
# -*- coding: utf-8 -*- |
|||
|
|||
import argparse |
|||
from signal import signal, SIGINT |
|||
import sys |
|||
from time import sleep |
|||
from os.path import expanduser, join |
|||
from datetime import datetime, timedelta |
|||
|
|||
TIME_FILE = join(expanduser("~"), "timekeeping.csv") |
|||
TODAY_FILE = join(expanduser("~"), ".timekeeping") |
|||
|
|||
is_on_break = False |
|||
today_fields = [] |
|||
start_time = datetime.now() |
|||
|
|||
|
|||
def die(*args): |
|||
print("Error: {}".format(*args), file=sys.stderr) |
|||
sys.exit(1) |
|||
|
|||
|
|||
def log(*args): |
|||
print(*args, file=sys.stderr) |
|||
|
|||
|
|||
def signal_handler(signal, frame): |
|||
global today_fields |
|||
now = datetime.now() |
|||
hour = now.strftime("%H:%M") |
|||
if not is_on_break: |
|||
sys.exit(0) |
|||
else: |
|||
break_time = now - start_time |
|||
log("\nBreak ended at {} and lasted {}".format( |
|||
hour, td_format(break_time))) |
|||
today_fields.append(start_time.strftime("%H:%M")) |
|||
today_fields.append(hour) |
|||
with open(TODAY_FILE, "w") as f: |
|||
f.write(",".join(today_fields)) |
|||
sys.exit(0) |
|||
|
|||
|
|||
def td_format(td): |
|||
hours, remainder = divmod(td.total_seconds(), 3600) |
|||
minutes, seconds = divmod(remainder, 60) |
|||
|
|||
return '{:d}:{:02d}'.format(int(hours), int(minutes)) |
|||
|
|||
|
|||
def convert_manual_entry(): |
|||
for line in sys.stdin: |
|||
fields = line.split(",") |
|||
if len(fields) < 6: |
|||
break |
|||
morning = datetime.strptime(fields[1], "%H:%M") |
|||
break_start = datetime.strptime(fields[2], "%H:%M") |
|||
break_end = datetime.strptime(fields[3], "%H:%M") |
|||
evening = datetime.strptime(fields[4], "%H:%M") |
|||
break_time = break_end - break_start |
|||
full_day = evening - morning |
|||
work_day = full_day - break_time |
|||
print('{},{},{},{},{},{}'.format( |
|||
fields[0], |
|||
morning.strftime("%H:%M"), |
|||
evening.strftime("%H:%M"), |
|||
td_format(break_time), |
|||
td_format(work_day), |
|||
"".join(fields[5:]).strip())) |
|||
|
|||
|
|||
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