Skip to content

Commit c77cb0a

Browse files
Merge pull request #62 from dvklopfenstein/dev
New release
2 parents 2c76047 + dc67552 commit c77cb0a

File tree

4 files changed

+82
-8
lines changed

4 files changed

+82
-8
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
# Summary
44
* [**Unreleased**](#unreleased)
5+
* **Release 2025-06-30 v0.8.1** Software release
56
* [**Release 2025-06-28 v0.8a1**](#release-2025-06-28-v08a1) Added the command `trk running`
67
* [**Release 2025-06-28 v0.8a0**](#release-2025-06-28-v08a0) Documentation; minor speed; simplify stop defaults
78
* [**Release 2025-06-21 v0.7a0**](#release-2025-06-21-v07a0) Much added functionality for invoicing

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
77
[project]
88
name = "timetracker-csv"
99
description = "Pandas-friendly time tracking from the CLI"
10-
version = "0.8a1"
10+
version = "0.8.1"
1111
license = "AGPL-3.0-or-later"
1212
authors = [
1313
{name = 'DV Klopfenstein, PhD', email = 'dvklopfenstein@protonmail.com'},

tests/test_tt_getdt.py

Lines changed: 79 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
from collections import namedtuple
55
from datetime import timedelta
6+
from datetime import datetime
67
from csv import writer
78
from timeit import default_timer
89
from re import compile as re_compile
@@ -26,22 +27,20 @@ def _run(nto):
2627
# pylint: disable=too-many-locals
2728
timedata = []
2829
#cmp_time = re_compile(r'((\d{1,2}):){0,2}(\d{1,2})\s*(?P<AM_PM>[aApP][mM])')
29-
# pylint: disable=line-too-long
30-
cmp_time = re_compile(r'((?P<hour>\d{1,2})(:(?P<minute>\d{1,2}))?(:(?P<second>\d{1,2}))?\s*(?P<AM_PM>[aApP][mM])?)')
31-
cmp_date = re_compile(r'((?P<year>\d{4})[-/_]?)?(?P<month>\d{1,2})[-/_](?P<day>\d{1,2})')
30+
reobj = _ReDatetime()
3231
print(f'NOW: {NOW}')
3332
for timestr, expdct in TIMESTRS.items():
3433
# pylint: disable=too-many-locals
3534
print(f'\nTIMESTR({timestr})')
3635
tic = default_timer()
37-
print("SEARCH FOR TIME:", (m := cmp_time.search(timestr)), m.groupdict() if m else '')
38-
print("SEARCH FOR DATE:", cmp_date.search(timestr))
36+
dtre = reobj.search(timestr)
3937
tt0 = timedelta(seconds=default_timer()-tic)
40-
print(f'{tt0} re ({timestr})') # {dta}')
38+
print(f'{tt0} re ({timestr}) {dtre}')
4139

4240
tic = default_timer()
4341
dta = _get_dt_ampm(timestr, NOW)
4442
assert dta == expdct['dt'], f'ACT != EXP\nTXT({timestr})\nACT({dta})\nEXP({expdct["dt"]})'
43+
#assert dta == dtre, f'RE != ME\nTXT({timestr})\nRE({dtre})\nME({dta})'
4544
tta = timedelta(seconds=default_timer()-tic)
4645
print(f'{tta} _get_dt_ampm({timestr}) {dta}')
4746

@@ -82,5 +81,79 @@ def _wr_timedata(fcsv, timedata, nto):
8281
wrobj.writerow(ntd)
8382
print(f' WROTE: {fcsv}')
8483

84+
85+
class _ReDatetime:
86+
"""Compare custom solution to Python re solution"""
87+
# pylint: disable=too-few-public-methods
88+
89+
# pylint: disable=line-too-long
90+
cmp_time = re_compile(r'((?<![-\d/])(?P<hour>\d{1,2})(?![-\d/])(:(?P<minute>\d{1,2}))?(:(?P<second>\d{1,2}))?\s*(?P<AM_PM>[aApP][mM])?)')
91+
cmp_date = re_compile(r'((?P<year>\d{4})[-/_]?)?(?P<month>\d{1,2})[-/_](?P<day>\d{1,2})')
92+
93+
def search(self, timestr):
94+
"""Use Python `re` to search for date and time"""
95+
mtch_time = self._search_time(timestr)
96+
mtch_date = self._search_date(timestr)
97+
print("SEARCH FOR TIME:", mtch_time, mtch_time.groupdict() if mtch_time else '')
98+
print("SEARCH FOR DATE:", mtch_date, mtch_date.groupdict() if mtch_date else '')
99+
if mtch_time is None or mtch_time.group('hour') is None:
100+
return None
101+
dct_hms = self._get_hms(mtch_time)
102+
dct_ymd = self._get_ymd(mtch_date)
103+
dct = {**dct_ymd, **dct_hms}
104+
if (m := mtch_time.group('AM_PM')) is not None:
105+
if not self._ampm(dct, m):
106+
return None
107+
print("SEARCH DATETIME:", dct)
108+
if dct:
109+
try:
110+
ret = datetime(year=dct['year'], month=dct['month'], day=dct['day'],
111+
hour=dct['hour'],
112+
minute=dct['minute'],
113+
second=dct['second'])
114+
except ValueError:
115+
print(f'BAD datetime INPUT: {dct}')
116+
else:
117+
return ret
118+
return None
119+
120+
def _get_hms(self, mtch):
121+
return {'hour': int(mtch.group('hour')),
122+
'minute': int(m) if (m := mtch.group('minute')) else 0,
123+
'second': int(m) if (m := mtch.group('second')) else 0}
124+
125+
126+
def _ampm(self, dct, ampm):
127+
if ampm is None:
128+
return True
129+
ampm = ampm.upper()
130+
if ampm == 'PM':
131+
if 0 <= (hour := dct['hour']) < 12:
132+
dct['hour'] = hour + 12
133+
elif hour != 12:
134+
return False
135+
else:
136+
assert ampm == 'AM', dct
137+
if dct['hour'] == 12:
138+
dct['hour'] = 0
139+
return True
140+
141+
def _get_ymd(self, mtch):
142+
if mtch:
143+
return {'year': int(m) if (m := mtch.group('year')) else NOW.year,
144+
'month': int(m) if (m := mtch.group('month')) else NOW.month,
145+
'day': int(m) if (m := mtch.group('day')) else NOW.day}
146+
return {'year': NOW.year,
147+
'month': NOW.month,
148+
'day': NOW.day}
149+
150+
def _search_time(self, timestr):
151+
"""Use Python re to search for time"""
152+
return self.cmp_time.search(timestr)
153+
154+
def _search_date(self, timestr):
155+
"""Use Python re to search for time"""
156+
return self.cmp_date.search(timestr)
157+
85158
if __name__ == '__main__':
86159
test_tt_getdt()

timetracker/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22

33
__copyright__ = 'Copyright (C) 2025-present, DV Klopfenstein, PhD. All rights reserved'
44
__author__ = 'DV Klopfenstein, PhD'
5-
__version__ = '0.8a1'
5+
__version__ = '0.8.1'
66

77
# Copyright (C) 2025-present, DV Klopfenstein, PhD. All rights reserved

0 commit comments

Comments
 (0)