|
| 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() |
0 commit comments