Skip to content

Commit 11e8462

Browse files
committed
Add commands for timestamp conversion and subtraction, as well as delta subtraction and addition
1 parent 28f131c commit 11e8462

7 files changed

Lines changed: 300 additions & 126 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,4 @@ Repository = "https://codeberg.org/DFIR/time-tools.git"
4545
#Changelog = "https://github.com/sweigmann/offset-tools/blob/main/CHANGELOG.md"
4646

4747
[project.scripts]
48-
ts_to_isoutc = "time_tools.ts_to_isoutc:main"
48+
ts_to_iso_utc = "time_tools.ts_to_iso-utc:main"

src/time_tools/common/tsparser.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# flake8: noqa: E501
2+
#
3+
# (C) Sebastian Weigmann, 2025
4+
# This software is released under:
5+
# GNU GENERAL PUBLIC LICENSE, Version 3
6+
# Please find the full text in LICENSE.
7+
#
8+
# generic imports
9+
import re
10+
11+
def tsparse(ts: str) -> str:
12+
# TODO: Make this much much better!
13+
myts = ts
14+
# deal with some common non-parsable time formats
15+
# TODO: Make this a dict to include transformation sytax
16+
list_ts_formats: list[re.Pattern] = []
17+
# Apache logs: 18/Sep/2011:19:18:28 -0400
18+
regex_apache_logs = r"(\d{2})/([a-zA-Z]{3})/(\d{4}):(\d{2}):(\d{2}):(\d{2}) ([+-]\d{4})"
19+
rec = re.compile(regex_apache_logs)
20+
recm = rec.match(myts)
21+
if recm:
22+
newts = f"{recm.group(1)}/{recm.group(2)}/{recm.group(3)} {recm.group(4)}:{recm.group(5)}:{recm.group(6)} {recm.group(7)}"
23+
else:
24+
newts = myts
25+
return newts

src/time_tools/common/tzmapping.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
# flake8: noqa: E501
2-
# This is ts_to_isoutc for converting many kinds of timestamps
3-
# to UTC in ISO-8601.
2+
#
43
# (C) Sebastian Weigmann, 2025
54
# This software is released under:
65
# GNU GENERAL PUBLIC LICENSE, Version 3

src/time_tools/common/version.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
# flake8: noqa: E501
2+
#
3+
# (C) Sebastian Weigmann, 2025
4+
# This software is released under:
5+
# GNU GENERAL PUBLIC LICENSE, Version 3
6+
# Please find the full text in LICENSE.
7+
#
18
# global variables
29
progname = "time-tools"
3-
progver = "0.1.0"
10+
progver = "0.2"

src/time_tools/ts_to_iso_utc.py

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
# flake8: noqa: E501
2+
# This is iso-ts_to_isoutc for converting many kinds of timestamps
3+
# to UTC in ISO-8601.
4+
# (C) Sebastian Weigmann, 2025
5+
# This software is released under:
6+
# GNU GENERAL PUBLIC LICENSE, Version 3
7+
# Please find the full text in LICENSE.
8+
#
9+
# generic imports
10+
import datetime
11+
import sys
12+
import argparse
13+
14+
# specific imports
15+
from dateutil import tz
16+
from dateutil import parser
17+
18+
try:
19+
from common import tzmapping as TZM
20+
from common import tsparser as TSP
21+
from common import version
22+
from common import errors as ERR
23+
except ModuleNotFoundError:
24+
from time_tools.common import tzmapping as TZM
25+
from time_tools.common import tsparser as TSP
26+
from time_tools.common import errors as ERR
27+
from time_tools.common import version
28+
29+
30+
# global variables
31+
my_progname = "ts_to_iso_utc"
32+
my_progver = "3"
33+
progname = my_progname + " (" + version.progname + ")"
34+
progver = version.progver + "-" + my_progver
35+
ERR.verbosity = 0
36+
37+
38+
def parse_args() -> argparse.Namespace:
39+
global progname
40+
global progver
41+
epilog = "Examples: " + my_progname + " 2021-11-01T13:26:06+01:00 --> 2021-11-01T12:26:06Z\n" + \
42+
" " + my_progname + " \"Nov 26 09:12:30\" --> 2021-11-26T09:12:30Z (assumes timestamp TZ is system TZ)\n" + \
43+
" " + my_progname + " \"2021-11-18 09:20:37\" --> 2021-11-18T09:20:37Z (assumes timestamp TZ is system TZ)\n" + \
44+
" " + my_progname + " \"Fri 26 Nov 2021 04:58:00 AM CST\" --> NOT Chinese Standard Time! For Taipei time use CSTTW"
45+
# create the top-level parser
46+
parser = argparse.ArgumentParser(
47+
prog=my_progname,
48+
formatter_class=argparse.RawTextHelpFormatter,
49+
description="Convert, add and subtract timestamps",
50+
epilog=epilog
51+
)
52+
parser.add_argument("-z", "--use-zulu", dest="z", action='store_true', help='replace "+00:00" by "Z" for Zulu time')
53+
parser.add_argument("--version", action="version", version="%(prog)s v" + progver)
54+
# init subparsers
55+
subparsers = parser.add_subparsers(title="commands", dest="command", required=True)
56+
# create the parser for "convert"
57+
parser_convert = subparsers.add_parser('convert', help='convert timestamps to UTC in ISO-8601 format')
58+
parser_convert.add_argument('ts', type=str, help='timestamp to convert')
59+
# create the parser for "subts"
60+
parser_subts = subparsers.add_parser('subts', help='subtract: timestamp - timestamp -> seconds')
61+
parser_subts.add_argument('ts_younger', type=str, help='timestamp to subtract from')
62+
parser_subts.add_argument('ts_older', type=str, help='subtractor')
63+
# create the parser for "subsecs"
64+
parser_subsecs = subparsers.add_parser('subsecs', help='subtract: timestamp - seconds -> timestamp')
65+
parser_subsecs.add_argument('ts', type=str, help='timestamp to subtract from')
66+
parser_subsecs.add_argument('secs', type=float, help='seconds to subtract')
67+
# create the parser for "addsecs"
68+
parser_addsecs = subparsers.add_parser('addsecs', help='add: timestamp + seconds -> timestamp')
69+
parser_addsecs.add_argument('ts', type=str, help='timestamp to add to')
70+
parser_addsecs.add_argument('secs', type=float, help='seconds to add')
71+
# parse arguments
72+
try:
73+
args = parser.parse_args()
74+
# make sure we didn't mess up the code.
75+
return args
76+
except Exception as excpt:
77+
# we get here when anything is wrong with provided arguments. fail and exit.
78+
ERR.printmsg("%s: %s" % (type(excpt).__name__, excpt), ERR.ERRLVL.CRIT)
79+
sys.exit(ERR.EXIT.ARGPARSE)
80+
81+
82+
def ts_convert(ts: str) -> str:
83+
datetimeobj = parser.parse(ts, tzinfos=TZM.tzmapping)
84+
utcdatetimeobj = datetimeobj.astimezone(datetime.timezone.utc)
85+
isoutctimestamp = utcdatetimeobj.isoformat(timespec="seconds")
86+
return isoutctimestamp
87+
88+
89+
def ts_sub_ts(ts1: str, ts2: str) -> str:
90+
datetimeobj1 = parser.parse(ts1, tzinfos=TZM.tzmapping)
91+
datetimeobj2 = parser.parse(ts2, tzinfos=TZM.tzmapping)
92+
if datetimeobj2 > datetimeobj1:
93+
raise ValueError("first timestamp must be older than second timestamp")
94+
utcdatetimeobj1 = datetimeobj1.astimezone(datetime.timezone.utc)
95+
utcdatetimeobj2 = datetimeobj2.astimezone(datetime.timezone.utc)
96+
ts_difference = utcdatetimeobj1 - utcdatetimeobj2
97+
ERR.printmsg(f"Difference: {ts_difference}", ERR.ERRLVL.DEBUG)
98+
return str(ts_difference.total_seconds())
99+
100+
101+
def ts_calc_new_ts(ts: str, secs: float, op: str) -> str:
102+
datetimeobj = parser.parse(ts, tzinfos=TZM.tzmapping)
103+
utcdatetimeobj = datetimeobj.astimezone(datetime.timezone.utc)
104+
if op == "add":
105+
ts_new = utcdatetimeobj + datetime.timedelta(seconds=secs)
106+
elif op == "sub":
107+
ts_new = utcdatetimeobj - datetime.timedelta(seconds=secs)
108+
else:
109+
raise ValueError("unknown operation")
110+
ERR.printmsg(f"New TS: {ts_new}", ERR.ERRLVL.DEBUG)
111+
isoutctimestamp = ts_new.isoformat(timespec="seconds")
112+
return(isoutctimestamp)
113+
114+
115+
def ts_sub_secs(ts: str, secs: float) -> str:
116+
return ts_calc_new_ts(ts, secs, "sub")
117+
118+
119+
def ts_add_secs(ts: str, secs: float) -> str:
120+
return ts_calc_new_ts(ts, secs, "add")
121+
122+
123+
def __main() -> None:
124+
global progname
125+
global progver
126+
ERR.verbosity = ERR.ERRLVL.INFO
127+
args = parse_args()
128+
ERR.printmsg(str(args), ERR.ERRLVL.DEBUG)
129+
result: str = ""
130+
if args.command == 'convert':
131+
mytime = TSP.tsparse(args.ts)
132+
result = ts_convert(mytime)
133+
if args.z:
134+
result = result.replace("+00:00", "Z")
135+
elif args.command == 'subts':
136+
mytime_o = TSP.tsparse(args.ts_older)
137+
mytime_y = TSP.tsparse(args.ts_younger)
138+
result = ts_sub_ts(mytime_o, mytime_y)
139+
elif args.command == 'subsecs':
140+
mytime = TSP.tsparse(args.ts)
141+
result = ts_sub_secs(mytime, args.secs)
142+
if args.z:
143+
result = result.replace("+00:00", "Z")
144+
elif args.command == 'addsecs':
145+
mytime = TSP.tsparse(args.ts)
146+
result = ts_add_secs(mytime, args.secs)
147+
if args.z:
148+
result = result.replace("+00:00", "Z")
149+
else:
150+
# we never should end up here...
151+
raise ValueError("unknown command")
152+
# Print plain result to console --> allows command chaining
153+
print(result)
154+
155+
156+
def main() -> None:
157+
try:
158+
__main()
159+
except Exception as main_excpt:
160+
# yeah, this is truly unexpected. ever got CTRL+C'ed?? bail the heck out!
161+
ERR.printmsg(f"{type(main_excpt).__name__}: {main_excpt}", ERR.ERRLVL.CRIT)
162+
sys.stderr.flush()
163+
sys.stdout.flush()
164+
sys.exit(ERR.EXIT.GENERIC)
165+
sys.exit(ERR.EXIT.OK)
166+
167+
168+
# void main() { do stuff }
169+
if __name__ == "__main__":
170+
main()

src/time_tools/ts_to_isoutc.py

Lines changed: 0 additions & 108 deletions
This file was deleted.

0 commit comments

Comments
 (0)