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)
.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
.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
.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
.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
.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
.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
.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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.