This is a simple command line interface program that just keeps track of duration spent on multiple tasks.
Any areas of improvements are welcomed to be pointed out.
import json
import datetime
dt = datetime.datetime
td = datetime.timedelta
def encode_time(t):
if isinstance(t, dt):
return {
'_type': 'datetime',
'year': t.year,
'month': t.month,
'day': t.day,
'hour': t.hour,
'minute': t.minute,
'second': t.second
}
elif isinstance(t, td):
return {
'_type': 'timedelta',
'days': t.days,
'seconds': t.seconds,
'microseconds': t.microseconds
}
return t
def decode_time(t):
if '_type' in t:
data_type = t['_type']
del t['_type']
if data_type == 'datetime':
return dt(**t)
elif data_type == 'timedelta':
return td(**t)
return t
def display_pending():
"""
pretty prints the pending dictionary.
:return: None
"""
print('\n')
if pending:
max_len = max([len(key) for key in pending.keys()])
print('Pending Activities:')
print('-'*40)
else:
max_len = 0
print('No pending data.')
counter = 0
for key, value in pending.items():
duration_so_far = (dt.now() - value)
seconds = duration_so_far.seconds
days = duration_so_far.days
hours = seconds // 3600
minutes = (seconds // 60) % 60
s = 's' if days > 1 else ''
days = f'{days} Day{s} ' if days > 0 else ''
s = 's' if hours > 1 else ''
hours = f'{hours} Hour{s} ' if hours > 0 else ''
s = 's' if minutes > 1 else ''
minutes = f'{minutes} Min{s} ' if minutes > 0 else ''
seconds = f'{seconds} Seconds' if seconds < 60 else ''
print(f'[{counter}] {key.capitalize():{max_len}} | {value}') # Key and start time.
max_len += 4 # Adding 4 to max_len to make sure this line is aligned with the one above it.
print(f'{"." * max_len:{max_len}} | {days}{hours}{minutes}{seconds}\n') # Duration so far.
print('-' * 40)
max_len -= 4 # Avoiding mutating max_len.
counter += 1
def display_durations():
"""
Pretty prints the durations dictionary.
:return: None
"""
print('\n')
counter = 0
if durations:
max_len = max([len(key) for key in durations.keys()])
print('Durations: ')
print('_' * 40)
else:
max_len = 0
print('No durations data')
for key, value in durations.items():
print(f'[{counter}] {key.capitalize():{max_len}} | {value}')
print('-' * 40)
counter += 1
pending = {}
durations = {}
# Reading data from pending.json
try:
with open('pending.json', 'r') as pending_json:
pending = json.load(pending_json, object_hook=decode_time)
s = 's' if len(pending) > 1 else ''
print(f'{len(pending)} pending item{s} loaded from disk')
except FileNotFoundError:
print(' "pending.json" was not found, creating.')
open('pending.json', 'x').close()
except json.decoder.JSONDecodeError:
print('pending.json is empty...')
# Reading data from durations.json
try:
with open('durations.json', 'r') as durations_json:
durations = json.load(durations_json, object_hook=decode_time)
s = 's' if len(durations) > 1 else ''
print(f'{len(durations)} duration item{s} loaded from disk')
except FileNotFoundError:
print(' "durations.json" was not found, creating.')
open('durations.json', 'x').close()
except json.decoder.JSONDecodeError:
print('durations.json is empty...')
if pending:
display_pending()
if durations:
display_durations()
# Acquiring user input.
while True:
activity = input('\n>>> ').lower()
now = dt.now()
start_time = pending.get(activity, None)
if activity == 'quit':
print('Exiting.')
break
elif activity == '':
continue
if activity in pending:
duration = now - start_time
print(f'"{activity}" ended. Duration: {duration}')
durations[activity] = durations.get(activity, td(0)) + duration # Record duration of activity.
del pending[activity] # Delete activity from pending list to confirm that it's completed.
continue
elif activity == 'man':
activity = input('Activity Name: ')
activity_duration = {}
# Get num of days, hours, etc.. of manually eneterd activity.
for parameter in ['days', 'hours', 'minutes', 'seconds', 'microseconds']:
while True:
i = input(f'{parameter.capitalize()}: ')
if i.isnumeric():
activity_duration[parameter] = int(i)
break
elif i == '':
activity_duration[parameter] = 0
break
add_minus = input('Add / Minus: ').lower()
if add_minus == 'add':
durations[activity] = durations.get(activity, td(0)) + td(**activity_duration)
elif add_minus == 'minus':
durations[activity] = durations.get(activity, td(0)) - td(**activity_duration)
display_durations()
continue
elif activity == 'del':
activity = input('Delete: ')
if activity == 'all':
confirmed = input('Delete Everything? y/n ')
if confirmed == 'y':
pending.clear()
durations.clear()
print('Data Cleared.')
continue
key_index = [[None, None]] # A list of index, key pairs for each key in pending or durations dictionaries.
# If the activity the user wants to delete is a number, treat it as an index,
# Unless the activity is an entry in either pending/durations lists:
if activity.isnumeric() and activity not in set(list(pending.keys()) + list(durations.keys())):
is_numeric = True
activity = int(activity)
wanted_list = input('Delete From Pending/Durations? p/d: ')
if wanted_list == 'd':
key_index = [(index, key) for index, key in enumerate(durations.keys())]
elif wanted_list == 'p':
key_index = [(index, key) for index, key in enumerate(pending.keys())]
# If no list specified then delete from durations or pending according to index given.
else:
if activity <= len(durations) - 1:
key_index = [(index, key) for index, key in enumerate(durations.keys())]
elif activity <= len(pending) - 1:
key_index = [(index, key) for index, key in enumerate(pending.keys())]
for index, key in key_index:
if index == activity:
break
activity = key
else:
is_numeric = False
if activity in pending:
not_in_pending = False
del pending[activity]
print(f'"{activity.capitalize()}" deleted from pending')
else:
not_in_pending = True
if activity in durations:
not_in_durations = False
del durations[activity]
print(f'{activity.capitalize()} deleted from durations')
else:
not_in_durations = True
if not_in_pending and not_in_durations:
if is_numeric:
print('No data')
else:
print('Key Not Found')
continue
elif activity == 'data':
display_pending()
display_durations()
continue
elif activity == 'help':
print('''
Enter activity name for it to be started.
Enter the same name again for it to be ended and record it's duration.
Commands:
man: manually edit a duration
del: delete an entry from pending activities and/or duration
data: show currently pending activities and duration records
quit: exit program and save edits.
''')
continue
pending[activity] = now
print(f'"{activity.capitalize()}" started on: {now}')
print(' Writing updated data to disk.')
if len(pending) > 0:
with open('pending.json', 'w') as pending_json:
json.dump(pending, pending_json, default=encode_time, indent=2)
else:
# So that json doesn't dump an empty dictionary symbol: {} to the file.
open('pending.json', 'w').close()
if len(durations) > 0:
with open('durations.json', 'w') as durations_json:
json.dump(durations, durations_json, default=encode_time, indent=2)
else:
open('durations.json', 'w').close()
print(' Data updated.')
exit_confirm = input('Press Enter To Exit...')
```