From 3da9195697176b4a72da3cf92e87f0ee5dfc20a0 Mon Sep 17 00:00:00 2001
From: anima
Date: Tue, 8 Jun 2021 00:38:26 +0200
Subject: [PATCH] release version 2.1.0
---
timeTrack.py | 454 +++++++++++++++++++++++++++++++++------------------
1 file changed, 296 insertions(+), 158 deletions(-)
diff --git a/timeTrack.py b/timeTrack.py
index ed43b6d..8b1425d 100644
--- a/timeTrack.py
+++ b/timeTrack.py
@@ -3,7 +3,7 @@
#
# timeTrack.py
# by 4nima
-# v.2.0.2
+# v.2.1.0
#
#########################
# simple time tracking with database
@@ -24,6 +24,7 @@ class TimeTrack:
self.USERNAME = ''
self.OLDEVENT = 2
self.LOGFILE = 'timetrack.log'
+ self.DBCON = sqlite3.connect(self.DATABASE, detect_types=sqlite3.PARSE_DECLTYPES | sqlite3.PARSE_COLNAMES)
logging.basicConfig(
filename=self.LOGFILE,
level=logging.DEBUG,
@@ -33,6 +34,9 @@ class TimeTrack:
self.db_setup()
self.load_config()
+ def __del__(self):
+ self.DBCON.close()
+
## Prepartation
### Check OS and clear screen
def clear_screen(self):
@@ -42,6 +46,38 @@ class TimeTrack:
else:
logging.debug('Winwos System detected')
_ = os.system('cls')
+
+ ### Loads or creates a config file
+ def load_config(self):
+ if os.path.isfile(self.CONFIG):
+ logging.info('Config file was found')
+ try:
+ with open(self.CONFIG) as config_data:
+ data = json.load(config_data)
+ except ValueError:
+ logging.error('Config file has no valid JSON syntax')
+ print('TimeTrack wird beendet: Fehler in der JSON-Konfig ({})'.format(self.CONFIG))
+ quit()
+ else:
+ logging.info('Config file was loaded successfully')
+ self.USERID = data['user']
+ self.OLDEVENT = data['oldevent']
+ logging.debug('UserID {} was used'.format(data['user']))
+ self.set_user()
+
+ else:
+ logging.warning('Config file not found')
+ config = {
+ 'default' : 'interactive',
+ 'user' : 1,
+ 'oldevent' : 2
+ }
+ with open(self.CONFIG, "w") as outfile:
+ json.dump(config, outfile, indent=4, sort_keys=True)
+ logging.info('Config file created successfully')
+
+ #> wenn man keine datei erstellen darf/kann, fällt das skript in einen loop ?
+ self.load_config()
### Creates a database if none is found
def db_setup(self):
@@ -58,7 +94,8 @@ class TimeTrack:
endtime TIMESTAMP NOT NULL,
user_id INTEGER NOT NULL,
activity TEXT,
- catrgory_id INTEGER,
+ reference TEXT,
+ category_id INTEGER,
client_id INTEGER,
lock BOOLEAN
)
@@ -95,54 +132,133 @@ class TimeTrack:
)
""")
- connect = sqlite3.connect(self.DATABASE)
- cursor = connect.cursor()
logging.debug('Create initial database tables')
- for SQL in sql:
- try:
- cursor.execute(SQL)
- except:
- logging.error('Table could not be created')
- logging.debug(SQL)
- print('TimeTrack wird beendet: Fehler bei Datanbank Setup')
- quit()
- else:
- logging.debug('Table was created successfully or already exists')
-
- connect.commit()
- connect.close()
-
- ### Loads or creates a config file
- def load_config(self):
- if os.path.isfile(self.CONFIG):
- logging.info('Config file was found')
- try:
- with open(self.CONFIG) as config_data:
- data = json.load(config_data)
- except ValueError:
- logging.error('Config file has no valid JSON syntax')
- print('TimeTrack wird beendet: Fehler in der JSON-Konfig ({})'.format(self.CONFIG))
- quit()
- else:
- logging.info('Config file was loaded successfully')
- self.USERID = data['user']
- self.OLDEVENT = data['oldevent']
- logging.debug('UserID {} was used'.format(data['user']))
- self.set_user()
-
+ try:
+ with self.DBCON as con:
+ for SQL in sql:
+ con.execute(SQL)
+ except sqlite3.Error as err:
+ logging.error('Table could not be created')
+ logging.debug(sql)
+ logging.error('SQLError: {}'.format(err))
+ print('TimeTrack wird beendet: Fehler bei Datanbank Setup')
+ quit()
else:
- logging.warning('Config file not found')
- config = {
- 'default' : 'interactive',
- 'user' : 1,
- 'oldevent' : 2
- }
- with open(self.CONFIG, "w") as outfile:
- json.dump(config, outfile, indent=4, sort_keys=True)
- logging.info('Config file created successfully')
+ logging.debug('Table was created successfully or already exists')
- #> wenn man keine datei erstellen darf/kann, fällt das skript in einen loop ?
- self.load_config()
+ ### Get an active event based on a user or event ID
+ def get_active_event(self, USERID='', EVENTID=''):
+ if USERID:
+ logging.debug('Search events based on userid: {}'.format(USERID))
+ sql = "SELECT * FROM active_events WHERE user_id = ?"
+ searchdata = [USERID]
+ elif EVENTID:
+ logging.debug('Search event based on eventid: {}'.format(EVENTID))
+ sql = "SELECT * FROM active_events WHERE id = ?"
+ searchdata = [EVENTID]
+ else:
+ sql = "SELECT * FROM active_events"
+ searchdata = False
+
+ try:
+ with sqlite3.connect(self.DATABASE, detect_types=sqlite3.PARSE_DECLTYPES | sqlite3.PARSE_COLNAMES) as con:
+ cur = con.cursor()
+ if searchdata:
+ logging.debug('Search event')
+ cur.execute(sql, searchdata)
+ else:
+ logging.debug('Get all Events')
+ cur.execute(sql)
+ eventdata = cur.fetchall()
+ except sqlite3.Error as err:
+ logging.debug(sql)
+ logging.error('SQLError: {}'.format(err))
+ print('Fehler beim auslesen der aktiven Events')
+ else:
+ logging.debug('Events could be read out successfully')
+
+ con.close()
+ if eventdata == []:
+ logging.debug('No active events found')
+ return 0
+ else:
+ logging.debug('{} events found'.format(len(eventdata)))
+ return eventdata[0]
+
+ ### Get time entries based on various criteria
+ def get_time_entry(self, USERID='', TIMEID='', CATEGORYID='', CLIENTID='', REFERENCE='', STARTTIME='', ENDTIME='', DAY=''):
+ if USERID:
+ logging.debug('Search time entries based on userid: {}'.format(USERID))
+ sql = "SELECT * FROM time_entries WHERE user_id = ?"
+ searchdata = [USERID]
+ elif TIMEID:
+ logging.debug('Search time entrys based on timeid: {}'.format(TIMEID))
+ sql = "SELECT * FROM time_entries WHERE id = ?"
+ searchdata = [TIMEID]
+ elif CATEGORYID:
+ logging.debug('Search time entrys based on categoryid: {}'.format(CATEGORYID))
+ sql = "SELECT * FROM time_entries WHERE category_id = ?"
+ searchdata = [CATEGORYID]
+ elif CLIENTID:
+ logging.debug('Search time entrys based on clientid: {}'.format(CLIENTID))
+ sql = "SELECT * FROM time_entries WHERE client_id = ?"
+ searchdata = [CLIENTID]
+ elif REFERENCE:
+ logging.debug('Search time entrys based on reference: {}'.format(REFERENCE))
+ sql = "SELECT * FROM time_entries WHERE reference = ?"
+ searchdata = [REFERENCE]
+ else:
+ sql = "SELECT * FROM time_entries"
+ searchdata = False
+
+ try:
+ with sqlite3.connect(self.DATABASE, detect_types=sqlite3.PARSE_DECLTYPES | sqlite3.PARSE_COLNAMES) as con:
+ cur = con.cursor()
+ if searchdata:
+ logging.debug('Search time entry')
+ cur.execute(sql, searchdata)
+ else:
+ logging.debug('Get all time entries')
+ cur.execute(sql)
+ timedata = cur.fetchall()
+ except sqlite3.Error as err:
+ logging.debug(sql)
+ logging.error('SQLError: {}'.format(err))
+ print('Fehler beim auslesen der Zeiteinträge')
+ else:
+ logging.debug('Time entries could be read out successfully')
+
+ con.close()
+
+ if DAY:
+ logging.debug('Search time entries by date: {}'.format(DAY))
+ tmp = []
+ for entry in timedata:
+ if entry[1].date() == DAY:
+ tmp.append(entry)
+ timedata = tmp[:]
+ else:
+ tmp = []
+ if STARTTIME:
+ logging.debug('Search time entries by starttime: {}'.format(STARTTIME))
+ for entry in timedata:
+ if entry[1] > STARTTIME:
+ tmp.append(entry)
+ timedata = tmp[:]
+ pass
+ tmp = []
+ if ENDTIME:
+ logging.debug('Search time entrie by endtime: {}'.format(ENDTIME))
+ for entry in timedata:
+ if entry[2] < ENDTIME:
+ tmp.append(entry)
+ timedata = tmp[:]
+ if timedata == []:
+ logging.debug('No time entries found')
+ return 0
+ else:
+ logging.debug('{} time entries found'.format(len(timedata)))
+ return timedata
## user handling
### Creates a user who does not yet exist
@@ -160,21 +276,16 @@ class TimeTrack:
logging.debug('Accepted username: {}'.format(username))
- connect = sqlite3.connect(self.DATABASE)
- cursor = connect.cursor()
-
sql = "INSERT INTO users ( name ) values ( ? )"
-
try:
- cursor.execute(sql, [username])
- except:
+ with self.DBCON as con:
+ con.execute(sql, [username])
+ except sqlite3.Error as err:
logging.error('User could not be saved in database')
logging.debug(sql)
+ logging.error('SQLError: {}'.format(err))
else:
logging.info('User was saved successfully')
- connect.commit()
-
- connect.close()
### Outputs existing users from the DB
def get_users(self, UID=0, NAME=''):
@@ -191,25 +302,23 @@ class TimeTrack:
data = ''
sql = "SELECT * FROM users"
- connect = sqlite3.connect(self.DATABASE)
- cursor = connect.cursor()
-
try:
- if data:
- cursor.execute(sql, [data])
- else:
- cursor.execute(sql)
- except:
+ with self.DBCON as con:
+ cur = con.cursor()
+ if data:
+ cur.execute(sql, [data])
+ else:
+ cur.execute(sql)
+ data = cur.fetchall()
+ except sqlite3.Error as err:
logging.error('Could not get user')
logging.debug(sql)
+ logging.error('SQLError: {}'.format(err))
print('Fehler beim Zugriff auf die Benutzer Datenbank')
return 1
else:
logging.debug('User database read out successfully')
- connect.commit()
- data = cursor.fetchall()
- connect.close()
return data
### Defines a user for the session
@@ -229,25 +338,24 @@ class TimeTrack:
## Time handling
### Creates an active event if none exists for the user
def save_event(self, TIME=datetime.datetime.now()):
- if not self.get_event(USERID=self.USERID):
+ if not self.get_active_event(USERID=self.USERID):
logging.debug('No active events found for the user: {}'.format(self.USERID))
- connect = sqlite3.connect(self.DATABASE)
- cursor = connect.cursor()
- sql = "INSERT INTO active_events ( starttime, user_id ) VALUES ( ?, ? )"
+ sql = "INSERT INTO active_events ( starttime, user_id ) VALUES ( ?, ? )"
try:
- cursor.execute(sql, [TIME, self.USERID])
- except:
+ with self.DBCON as con:
+ con.execute(sql, [TIME, self.USERID])
+ except sqlite3.Error as err:
logging.error('Event could not be created')
logging.debug(sql)
+ logging.error('SQLError: {}'.format(err))
print('Event konnte nicht gespeichert werden.')
return False
else:
logging.info('Event was created successfully')
- connect.commit()
-
- connect.close()
+
return True
+
else:
logging.warning('Active events found for the user, new event could not be created')
return False
@@ -267,59 +375,18 @@ class TimeTrack:
print('Keine angabe was gelöscht werden soll')
return 0
- connect = sqlite3.connect(self.DATABASE, detect_types=sqlite3.PARSE_DECLTYPES | sqlite3.PARSE_COLNAMES)
- cursor = connect.cursor()
-
try:
- cursor.execute(sql, [data])
- except:
+ with self.DBCON as con:
+ con.execute(sql, [data])
+ except sqlite3.Error as err:
logging.error('Event could not be deleted')
logging.debug(sql)
+ logging.error('SQLError: {}'.format(err))
print('Fehler beim löschen des Events.')
else:
logging.debug('Event was successfully deleted')
- connect.commit()
-
- connect.close()
-
- ### Get an active event based on a user or event ID
- def get_event(self, ENTRYID='', USERID=''):
- if not ENTRYID == '':
- logging.debug('Search event based on eventid: {}'.format(ENTRYID))
- sql = "SELECT * FROM active_events WHERE id = ?"
- data = ENTRYID
- elif not USERID == '':
- logging.debug('Search events based on userid: {}'.format(USERID))
- sql = "SELECT * FROM active_events WHERE user_id = ?"
- data = USERID
- else:
- sql = "SELECT * FROM active_events"
- data = ''
-
- connect = sqlite3.connect(self.DATABASE, detect_types=sqlite3.PARSE_DECLTYPES | sqlite3.PARSE_COLNAMES)
- cursor = connect.cursor()
- try:
- if data:
- logging.debug('Search event')
- cursor.execute(sql, [data])
- else:
- logging.debug('Get all Events')
- cursor.execute(sql)
- except:
- logging.error('Events could not be read')
- logging.debug(sql)
- print('Fehler beim auslesen der aktiven Events')
- else:
- logging.debug('Events could be read out successfully')
- data = cursor.fetchall()
- connect.close()
- if data == []:
- logging.debug('No active events found')
- return 0
- else:
- logging.debug('{} events found'.format(len(data)))
- return data[0]
+ # Checks for existing time entries and creates a new one if none is available.
def time_start(self, AUTOFORWARD=True):
self.clear_screen()
starttime = datetime.datetime.now()
@@ -329,7 +396,7 @@ class TimeTrack:
if AUTOFORWARD:
self.time_stop()
else:
- data = self.get_event(USERID=self.USERID)
+ data = self.get_active_event(USERID=self.USERID)
print('Es existiert bereits ein aktives Event.')
if (data[1] + datetime.timedelta(hours=self.OLDEVENT)) <= datetime.datetime.now():
logging.info('Event exceeds allowed duration')
@@ -378,8 +445,9 @@ class TimeTrack:
print('Event von {} Uhr geladen'.format(data[1].strftime("%H:%M")))
self.time_stop()
+ # Stops existing time entries
def time_stop(self):
- data = self.get_event(USERID=self.USERID)
+ data = self.get_active_event(USERID=self.USERID)
logging.debug('Event stop progess is started')
if data:
self.clear_screen()
@@ -421,44 +489,40 @@ class TimeTrack:
endtime = datetime.datetime.now()
logging.debug('Event end process start at {}'.format(endtime))
- connect = sqlite3.connect(self.DATABASE)
- cursor = connect.cursor()
- sql = "INSERT INTO time_entries ( starttime, endtime, user_id, activity ) VALUES ( ?, ?, ?, ? )"
+ sql = "INSERT INTO time_entries ( starttime, endtime, user_id, activity ) VALUES ( ?, ?, ?, ? )"
try:
- cursor.execute(sql, [data[1], endtime, self.USERID, action])
- except:
+ with self.DBCON as con:
+ con.execute(sql, [data[1], endtime, self.USERID, action])
+ except sqlite3.Error as err:
logging.error('Time entry could not be created')
logging.debug(sql)
+ logging.error('SQLError: {}'.format(err))
print('Zeiteintrag konnte nicht gespeichert werden.')
return False
else:
logging.info('Time entry was created successfully')
- connect.commit()
- self.delete_event(data[0])
- self.print_time_entry(STARTTIME=data[1], ENDTIME=endtime, ACTIVITY=action)
- print('Zeiteintrag wurde gespeichert.')
- userinput = 0
- while not 0 < int(userinput) < 3:
- print('Nächsten Zeiteintrag beginnen ?')
- print('[1] Ja')
- print('[2] Nein')
- userinput = input('Aktion: ')
- logging.debug('User input: {}'.format(userinput))
- try:
- int(userinput)
- except ValueError:
- userinput = 0
- self.clear_screen()
-
- if userinput == "1":
- self.time_start()
- else:
- logging.debug('Terminated by the user')
- exit()
-
- connect.close()
+ self.delete_event(data[0])
+ self.print_time_entry(STARTTIME=data[1], ENDTIME=endtime, ACTIVITY=action)
+ print('Zeiteintrag wurde gespeichert.')
+ userinput = 0
+ while not 0 < int(userinput) < 3:
+ print('Nächsten Zeiteintrag beginnen ?')
+ print('[1] Ja')
+ print('[2] Nein')
+ userinput = input('Aktion: ')
+ logging.debug('User input: {}'.format(userinput))
+ try:
+ int(userinput)
+ except ValueError:
+ userinput = 0
+ self.clear_screen()
+
+ if userinput == "1":
+ self.time_start()
+ else:
+ self.start_interactive_mode()
elif userinput == "2":
logging.info('Event should be deleted (eventid: {})'.format(data[0]))
@@ -467,10 +531,16 @@ class TimeTrack:
logging.debug('Terminated by the user')
exit()
- def get_time(self):
- pass
-
+ def timedela_to_string(self, TIME):
+ s = TIME.seconds
+ hours, remainder = divmod(s, 3600)
+ minutes, seconds = divmod(remainder, 60)
+ output = '{:02}:{:02}:{:02}'.format(int(hours), int(minutes), int(seconds))
+ return output
+
+ # Shows a time entry
def print_time_entry(self, STARTTIME='', ENDTIME='', ACTIVITY=''):
+ self.clear_screen()
s = (ENDTIME - STARTTIME).seconds
hours, remainder = divmod(s, 3600)
minutes, seconds = divmod(remainder, 60)
@@ -484,5 +554,73 @@ class TimeTrack:
print(ACTIVITY)
print(50*"-")
-test = TimeTrack()
-test.time_start()
\ No newline at end of file
+ def report_by_day(self, DATE=datetime.date.today(), USER=''):
+ if not USER:
+ USER = self.USERID
+ timedata = self.get_time_entry(DAY=DATE, USERID=USER)
+
+ STARTTIMES = []
+ ENDTIMES = []
+ DURATIONS = []
+
+ for entry in timedata:
+ STARTTIMES.append(entry[1])
+ ENDTIMES.append(entry[2])
+ DURATIONS.append(entry[2] - entry[1])
+
+ if timedata:
+ FIRSTSTARTTIME = min(STARTTIMES)
+ LASTENDTIME = max(ENDTIMES)
+ TRACKEDTIMESPAN = (LASTENDTIME - FIRSTSTARTTIME)
+ TRACKEDTIME = sum(DURATIONS, datetime.timedelta())
+ NONTRACKEDTIME = (TRACKEDTIMESPAN - TRACKEDTIME)
+ ENTRIECOUNT = len(timedata)
+
+ self.clear_screen()
+ print('{:40} {}'.format("Beginn des ersten Zeiteintrags:", FIRSTSTARTTIME.strftime('%d.%m.%Y %H:%M')))
+ print('{:40} {}'.format("Ende des letzen Zeiteintrags:", LASTENDTIME.strftime('%d.%m.%Y %H:%M')))
+ print('{:40} {}'.format("Maximal erfassbare Zeit:", self.timedela_to_string(TRACKEDTIMESPAN)))
+ print('{:40} {}'.format("Nicht erfasste Zeit:", self.timedela_to_string(NONTRACKEDTIME)))
+ print('{:40} {}'.format("Erfasste Zeit:", self.timedela_to_string(TRACKEDTIME)))
+ print('{:40} {}'.format("Zeiteinträge:", ENTRIECOUNT))
+
+ def start_interactive_mode(self):
+ self.clear_screen()
+ userinput = 0
+ while not 0 < int(userinput) < 4:
+ print('Was willst du tun?')
+ print('[1] für Zeiterfassung starten')
+ print('[2] für heutiger Report')
+ print('[3] für Report für Tag x')
+ print('[9] für Programm verlassen')
+ userinput = input('Aktion: ')
+ logging.debug('User input: {}'.format(userinput))
+ try:
+ int(userinput)
+ except ValueError:
+ userinput = 0
+ else:
+ if int(userinput) == 9:
+ logging.debug('Terminated by the user')
+ exit()
+ self.clear_screen()
+
+ if userinput == "1":
+ logging.debug('Start TimeTrack')
+ self.time_start()
+ elif userinput == "2":
+ logging.info('Print todays report')
+ self.report_by_day()
+ input()
+ self.start_interactive_mode()
+ elif userinput == "3":
+ print('commig soon ...')
+ input()
+ self.start_interactive_mode()
+
+ print(userinput)
+
+if __name__ == "__main__":
+ test = TimeTrack()
+ test.start_interactive_mode()
+ test.DBCON.close()
\ No newline at end of file