Skip to main content

A simple hour tracker for git

Project description

%load_ext autoreload

%autoreload 2

Hourly

A simple hour tracker for git projects. hourly parses your commit messages for "clock in/out" keywords and uses their unix timestamps to precisely calculate work hours.

Getting Started

Install

pip install hourly

Requirements

pandas
gitpython

Usage

Hourly will look for key words for clocking in/out.

To clock in:

git commit -m "clock in - starting work on new feature"

do stuff as usual, then clock out

git commit -m "clock out - finished feature"

Tutorial

We can illustrate how to use hourly on the hourly repo itself.

git clone https://github.com/asherp/hourly.git
cd hourly
from hourly.hourly import get_work_commits, get_labor, get_earnings

get_work_commits gathers all commits into a pandas array

work = get_work_commits('.')
work
c:\programdata\miniconda2\lib\site-packages\pandas\core\sorting.py:257: FutureWarning: Converting timezone-aware DatetimeArray to timezone-naive ndarray with 'datetime64[ns]' dtype. In the future, this will return an ndarray with 'object' dtype where each element is a 'pandas.Timestamp' with the correct 'tz'.
	To accept the future behavior, pass 'dtype=object'.
	To keep the old behavior, pass 'dtype="datetime64[ns]"'.
  items = np.asanyarray(items)
<style scoped> .dataframe tbody tr th:only-of-type { vertical-align: middle; }
.dataframe tbody tr th {
    vertical-align: top;
}

.dataframe thead th {
    text-align: right;
}
</style>
message hash
time
2018-10-19 23:40:41-04:00 Initial commit ef5690543bfb354b9325d1fbd1f9abbafbb4d9a4
2018-10-19 23:57:48-04:00 clock in 5c8f05b57b739ec525291c248ea9200651b49997
2018-10-20 00:21:40-04:00 preparing setup.py 254ecdacb52fc70bc358f8d55be58df3b70c7609
2018-10-20 00:39:11-04:00 clock out - work done for the day 0e33fa3d74f663f954b05dd9f30e0128ca7af162
2018-10-20 01:06:08-04:00 clock in - start adding requirements and examp... dc065b17337b14c2f8e0458de61e6880a338d6ae
2018-10-20 01:47:01-04:00 clock out 644ad6ebf4c9015fd512ed47b858602d784d6204
2018-10-20 01:47:45-04:00 clock in - pro bono e6b5f78daa68e3731f82effccb66fd4bd14996bf
2018-10-20 01:51:36-04:00 clock out - pro bono 1aff88af5e9688645966ccd15da8e1530205cfea
2018-10-20 02:03:56-04:00 clock in - finishing tutorial 53bd7316e579d8582c46af09277b40fbba3a390e
2018-10-20 02:11:54-04:00 clock out - converted notebook for README d55b5718a3178ab6161f7e3a148c6561a305cd79
2018-10-20 02:14:21-04:00 had to clock out so notebook examples don't break d9ec537b36475b565df6b28d0cab6edc3a89f2da
2018-10-20 02:18:27-04:00 fixed for rendering e2f8a2ca212fa9e7568618934da18a0ec7164fa3
2018-10-20 02:22:56-04:00 fixing requiremens that raised a security alert e0e71a05c6c5af1fc5515040ec75f33b71bd3b15
2018-10-20 02:51:13-04:00 fixed requirements, tutorial updates d8d87767005022c9a8e83015d40f6fb736f2b73b
2018-10-20 02:57:22-04:00 merging f5585c89612a5fac2d948ee61536fffe276f8949
2018-10-20 03:24:58-04:00 Remove autoreload b2c1d8677ea57aeb959350d663475ba3077f2c96
2018-10-20 11:53:00-04:00 clock in - handling errant messages fa615994ba6b771594d711dea6087cc7ba0348b5
2018-10-20 13:16:13-04:00 clock out - converting to pd.Timestamp ed7aab29e43e7120428816481216198a255de8f4
2018-10-20 13:47:56-04:00 clock in - adding work log 5b398037bf24cd503a7fc88c3b078913fa184f7e
2018-10-20 14:33:35-04:00 clock out - see WorkLog.md 93c2aa04aeba7cfe1573205abec053c7da9e9864
2018-10-20 14:35:58-04:00 merged 6a7f4ac45de70d94fb943d0676265cafd5bf1e41
2018-10-20 17:34:38-04:00 pushing version 2b633644d6ba24b9c1bbff6881a99df6ab935a8d
2018-10-28 13:44:48-04:00 clock in c4e95f59dc0c8ce296a40300760ab68800f7e139
2018-10-28 13:56:35-04:00 clock out f5200e718c062e828d436506286fd05e56b606c5
2018-11-23 13:51:05-05:00 changed formatting of wages earned 9630e123f8748bb8260da27498e3600a3271e4c2
2018-11-23 14:14:38-05:00 changed license to Apache 2.0' b7791826f2df0212ce49b51b621dd7e7cc0d6f14
2019-02-25 10:19:10-05:00 clock in T-1hr d7add63b4d2e3e1ca1423296aaed25d9c28944da
2019-02-25 12:49:51-05:00 clock out T-5m acfb8596317786e38177345aa2531098043a5c49

get_labor calculates hours worked by differencing commit timestamps and raises an error if clock in and clock out are of different lengths.

Getting time card

import pandas as pd
pd.set_option('display.width', 400)
get_labor(work, end_date='2018-10-20 02:11:54-04:00')
pay period: 2018-10-19 23:57:48-04:00 -> 2018-10-20 02:11:54-04:00
<style scoped> .dataframe tbody tr th:only-of-type { vertical-align: middle; }
.dataframe tbody tr th {
    vertical-align: top;
}

.dataframe thead th {
    text-align: right;
}
</style>
TimeIn log in TimeOut log out TimeDelta
0 2018-10-19 23:57:48-04:00 clock in 2018-10-20 00:39:11-04:00 clock out - work done for the day 00:41:23
1 2018-10-20 01:06:08-04:00 clock in - start adding requirements and examp... 2018-10-20 01:47:01-04:00 clock out 00:40:53
2 2018-10-20 01:47:45-04:00 clock in - pro bono 2018-10-20 01:51:36-04:00 clock out - pro bono 00:03:51
3 2018-10-20 02:03:56-04:00 clock in - finishing tutorial 2018-10-20 02:11:54-04:00 clock out - converted notebook for README 00:07:58

Handling errant clock in/out messages

If you mistakenly put "clock out" in a message, hourly will interpret the message as a legitimate end time. This will likely raise an error when computing the labor. For example, there is a problematic commit in the this repo's history:

problematic_commit = work[work.hash == 'd9ec537b36475b565df6b28d0cab6edc3a89f2da']
problematic_commit
<style scoped> .dataframe tbody tr th:only-of-type { vertical-align: middle; }
.dataframe tbody tr th {
    vertical-align: top;
}

.dataframe thead th {
    text-align: right;
}
</style>
message hash
time
2018-10-20 02:14:21-04:00 had to clock out so notebook examples don't break d9ec537b36475b565df6b28d0cab6edc3a89f2da

When we include this in our labor calculation, we get the following error:

try:
    get_labor(work, end_date = '2018-10-20 13:16:13-04:00')
except ValueError as e:
    print(e)
pay period: 2018-10-19 23:57:48-04:00 -> 2018-10-20 13:16:13-04:00
In/Out logs do not match

We can skip this errant commit by setting errant_clocks

get_labor(work, end_date = '2018-10-20 13:16:13-04:00', 
          errant_clocks = ['d9ec537b36475b565df6b28d0cab6edc3a89f2da'])
pay period: 2018-10-19 23:57:48-04:00 -> 2018-10-20 13:16:13-04:00
<style scoped> .dataframe tbody tr th:only-of-type { vertical-align: middle; }
.dataframe tbody tr th {
    vertical-align: top;
}

.dataframe thead th {
    text-align: right;
}
</style>
TimeIn log in TimeOut log out TimeDelta
0 2018-10-19 23:57:48-04:00 clock in 2018-10-20 00:39:11-04:00 clock out - work done for the day 00:41:23
1 2018-10-20 01:06:08-04:00 clock in - start adding requirements and examp... 2018-10-20 01:47:01-04:00 clock out 00:40:53
2 2018-10-20 01:47:45-04:00 clock in - pro bono 2018-10-20 01:51:36-04:00 clock out - pro bono 00:03:51
3 2018-10-20 02:03:56-04:00 clock in - finishing tutorial 2018-10-20 02:11:54-04:00 clock out - converted notebook for README 00:07:58
4 2018-10-20 11:53:00-04:00 clock in - handling errant messages 2018-10-20 13:16:13-04:00 clock out - converting to pd.Timestamp 01:23:13

Filtering work session keywords

Use the "ignore" key word to skip any work you don't want to include in your invoices.

labor = get_labor(work, 
            ignore = 'pro bono', 
            end_date = '2018-10-20 13:16:13-04:00',
            errant_clocks = ['d9ec537b36475b565df6b28d0cab6edc3a89f2da'])
labor
pay period: 2018-10-19 23:57:48-04:00 -> 2018-10-20 13:16:13-04:00
ignoring pro bono
<style scoped> .dataframe tbody tr th:only-of-type { vertical-align: middle; }
.dataframe tbody tr th {
    vertical-align: top;
}

.dataframe thead th {
    text-align: right;
}
</style>
TimeIn log in TimeOut log out TimeDelta
0 2018-10-19 23:57:48-04:00 clock in 2018-10-20 00:39:11-04:00 clock out - work done for the day 00:41:23
1 2018-10-20 01:06:08-04:00 clock in - start adding requirements and examp... 2018-10-20 01:47:01-04:00 clock out 00:40:53
3 2018-10-20 02:03:56-04:00 clock in - finishing tutorial 2018-10-20 02:11:54-04:00 clock out - converted notebook for README 00:07:58
4 2018-10-20 11:53:00-04:00 clock in - handling errant messages 2018-10-20 13:16:13-04:00 clock out - converting to pd.Timestamp 01:23:13

Get total earnings

Total earnings can be found using this function. Currency is just a string for printing, but in the future we can add unit conversion.

get_earnings(labor, wage = 30, currency = 'USD')
0 days 02:53:27, 2.89 hours worked
86.72 USD





86.72

Time adjustment

If you forget to clock in or clock out, you can correct your time sheet by adjusting your clock time. By default, Hourly adjusts time stamps automatically when a commit message contains the keyword "T-". Set correct_times to false to override this behavior.

work = get_work_commits('.', correct_times=False) # reports the actual time of the commit
work = work[work.message.str.contains('T-')] #filter by T-
work
<style scoped> .dataframe tbody tr th:only-of-type { vertical-align: middle; }
.dataframe tbody tr th {
    vertical-align: top;
}

.dataframe thead th {
    text-align: right;
}
</style>
message hash
time
2019-02-25 11:19:10-05:00 clock in T-1hr d7add63b4d2e3e1ca1423296aaed25d9c28944da
2019-02-25 12:54:51-05:00 clock out T-5m acfb8596317786e38177345aa2531098043a5c49
work = get_work_commits('.') # reports actual work time, according to commit message
work = work[work.message.str.contains('T-')] #filter by T-
work
<style scoped> .dataframe tbody tr th:only-of-type { vertical-align: middle; }
.dataframe tbody tr th {
    vertical-align: top;
}

.dataframe thead th {
    text-align: right;
}
</style>
message hash
time
2019-02-25 10:19:10-05:00 clock in T-1hr d7add63b4d2e3e1ca1423296aaed25d9c28944da
2019-02-25 12:49:51-05:00 clock out T-5m acfb8596317786e38177345aa2531098043a5c49

Note: Only time subtractions are supported for now.

Time delta formats

Hourly uses pandas' Timedelta format to modify times. This allows for a lot of flexibility.

pd.Timedelta('1hr45m')
Timedelta('0 days 01:45:00')

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distribution

hourly-0.0.4-py2-none-any.whl (7.3 kB view hashes)

Uploaded Python 2

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page