Skip to content

Commit 2274172

Browse files
Kyle A LogueTeque5
authored andcommitted
datetime parsing for python 3.7 thru 3.12; fix deprecation warning
1 parent f358e30 commit 2274172

File tree

4 files changed

+59
-44
lines changed

4 files changed

+59
-44
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ import datetime as dt
100100
import numpy as np
101101
import sigmf
102102
from sigmf import SigMFFile
103-
from sigmf.utils import get_data_type_str
103+
from sigmf.utils import get_data_type_str, get_sigmf_iso8601_datetime_now
104104

105105
# suppose we have an complex timeseries signal
106106
data = np.zeros(1024, dtype=np.complex64)
@@ -122,7 +122,7 @@ meta = SigMFFile(
122122
# create a capture key at time index 0
123123
meta.add_capture(0, metadata={
124124
SigMFFile.FREQUENCY_KEY: 915000000,
125-
SigMFFile.DATETIME_KEY: dt.datetime.utcnow().isoformat()+'Z',
125+
SigMFFile.DATETIME_KEY: get_sigmf_iso8601_datetime_now(),
126126
})
127127

128128
# add an annotation at sample 100 with length 200 & 10 KHz width

sigmf/apps/convert_wav.py

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,29 @@
77
"""converter for wav containers"""
88

99
import argparse
10-
import datetime
1110
import getpass
1211
import logging
1312
import os
1413
import pathlib
1514
import tempfile
15+
from typing import Optional
1616

1717
from scipy.io import wavfile
1818

1919
from .. import SigMFFile, __specification__
2020
from .. import __version__ as toolversion
2121
from .. import archive
22-
from ..utils import get_data_type_str
22+
from ..utils import get_data_type_str, get_sigmf_iso8601_datetime_now
2323

2424
log = logging.getLogger()
2525

2626

27-
def convert_wav(input_wav_filename, archive_filename=None, start_datetime=None, author=None):
27+
def convert_wav(
28+
input_wav_filename: str,
29+
archive_filename: Optional[str],
30+
start_datetime: Optional[str] = None,
31+
author: Optional[str] = None,
32+
):
2833
"""
2934
read a .wav and write a .sigmf archive
3035
"""
@@ -43,12 +48,12 @@ def convert_wav(input_wav_filename, archive_filename=None, start_datetime=None,
4348
}
4449

4550
if start_datetime is None:
46-
mtime = datetime.datetime.fromtimestamp(input_path.stat().st_mtime)
47-
start_datetime = mtime.isoformat() + "Z"
51+
start_datetime = get_sigmf_iso8601_datetime_now()
4852

49-
capture_info = {SigMFFile.START_INDEX_KEY: 0}
50-
if start_datetime is not None:
51-
capture_info[SigMFFile.DATETIME_KEY] = start_datetime
53+
capture_info = {
54+
SigMFFile.START_INDEX_KEY: 0,
55+
SigMFFile.DATETIME_KEY: start_datetime,
56+
}
5257

5358
tmpdir = tempfile.mkdtemp()
5459
sigmf_data_filename = input_stem + archive.SIGMF_DATASET_EXT
@@ -71,8 +76,8 @@ def main():
7176
parser = argparse.ArgumentParser(description="Convert .wav to .sigmf container.")
7277
parser.add_argument("input", type=str, help="Wavfile path")
7378
parser.add_argument("--author", type=str, default=None, help=f"set {SigMFFile.AUTHOR_KEY} metadata")
74-
parser.add_argument('-v', '--verbose', action='count', default=0)
75-
parser.add_argument('--version', action='version', version=f'%(prog)s v{toolversion}')
79+
parser.add_argument("-v", "--verbose", action="count", default=0)
80+
parser.add_argument("--version", action="version", version=f"%(prog)s v{toolversion}")
7681
args = parser.parse_args()
7782

7883
level_lut = {

sigmf/utils.py

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import re
1010
import sys
1111
from copy import deepcopy
12-
from datetime import datetime
12+
from datetime import datetime, timezone
1313

1414
import numpy as np
1515

@@ -19,31 +19,34 @@
1919

2020

2121
def get_sigmf_iso8601_datetime_now() -> str:
22-
"""Get current UTC time as iso8601 string"""
23-
return datetime.isoformat(datetime.utcnow()) + "Z"
22+
"""Get current UTC time as iso8601 string."""
23+
return datetime.now(timezone.utc).strftime(SIGMF_DATETIME_ISO8601_FMT)
2424

2525

26-
def parse_iso8601_datetime(datestr: str) -> datetime:
26+
def parse_iso8601_datetime(string: str) -> datetime:
2727
"""
28-
Parse an iso8601 string as a datetime
28+
Parse an iso8601 string as a datetime struct.
29+
Input string (indicated by final Z) is in UTC tz.
2930
3031
Example
3132
-------
3233
>>> parse_iso8601_datetime("1955-11-05T06:15:00Z")
33-
datetime.datetime(1955, 11, 5, 6, 15)
34+
datetime.datetime(1955, 11, 5, 6, 15, tzinfo=datetime.timezone.utc)
3435
"""
35-
# provided string exceeds max precision -> truncate to µs
36-
match = re.match(r"^(?P<dt>.*)(?P<frac>\.[0-9]{7,})Z$", datestr)
36+
match = re.match(r"^(?P<dt>.*)(?P<frac>\.[0-9]{7,})Z$", string)
3737
if match:
38-
md = match.groupdict()
39-
length = min(7, len(md["frac"]))
40-
datestr = ''.join([md["dt"], md["frac"][:length], "Z"])
41-
42-
try:
43-
timestamp = datetime.strptime(datestr, '%Y-%m-%dT%H:%M:%S.%fZ')
44-
except ValueError:
45-
timestamp = datetime.strptime(datestr, '%Y-%m-%dT%H:%M:%SZ')
46-
return timestamp
38+
# string exceeds max precision allowed by strptime -> truncate to µs
39+
groups = match.groupdict()
40+
length = min(7, len(groups["frac"]))
41+
string = "".join([groups["dt"], groups["frac"][:length], "Z"])
42+
43+
if "." in string:
44+
# parse float seconds
45+
format_str = SIGMF_DATETIME_ISO8601_FMT
46+
else:
47+
# parse whole seconds
48+
format_str = SIGMF_DATETIME_ISO8601_FMT.replace(".%f", "")
49+
return datetime.strptime(string, format_str).replace(tzinfo=timezone.utc)
4750

4851

4952
def dict_merge(a_dict: dict, b_dict: dict) -> dict:
@@ -83,11 +86,10 @@ def get_endian_str(ray: np.ndarray) -> str:
8386

8487
if atype.byteorder == "<":
8588
return "_le"
86-
elif atype.byteorder == ">":
89+
if atype.byteorder == ">":
8790
return "_be"
88-
else:
89-
# endianness is then either '=' (native) or '|' (doesn't matter)
90-
return "_le" if sys.byteorder == "little" else "_be"
91+
# endianness is then either '=' (native) or '|' (doesn't matter)
92+
return "_le" if sys.byteorder == "little" else "_be"
9193

9294

9395
def get_data_type_str(ray: np.ndarray) -> str:

tests/test_utils.py

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,30 @@
66

77
"""Tests for Utilities"""
88

9-
from datetime import datetime
9+
from datetime import datetime, timezone
1010

1111
import pytest
1212

1313
from sigmf import utils
1414

1515

16-
@pytest.mark.parametrize("ts, expected", [
17-
("1955-07-04T05:15:00Z", datetime(year=1955, month=7, day=4, hour=5, minute=15, second=00, microsecond=0)),
18-
("2956-08-05T06:15:12Z", datetime(year=2956, month=8, day=5, hour=6, minute=15, second=12, microsecond=0)),
19-
("3957-09-06T07:15:12.345Z", datetime(year=3957, month=9, day=6, hour=7, minute=15, second=12, microsecond=345000)),
20-
("4958-10-07T08:15:12.0345Z", datetime(year=4958, month=10, day=7, hour=8, minute=15, second=12, microsecond=34500)),
21-
("5959-11-08T09:15:12.000000Z", datetime(year=5959, month=11, day=8, hour=9, minute=15, second=12, microsecond=0)),
22-
("6960-12-09T10:15:12.123456789123Z", datetime(year=6960, month=12, day=9, hour=10, minute=15, second=12, microsecond=123456)),
16+
@pytest.mark.parametrize("time_str, expected", [
17+
("1955-07-04T05:15:00Z", datetime(year=1955, month=7, day=4, hour=5, minute=15, second=00, microsecond=0, tzinfo=timezone.utc)),
18+
("2956-08-05T06:15:12Z", datetime(year=2956, month=8, day=5, hour=6, minute=15, second=12, microsecond=0, tzinfo=timezone.utc)),
19+
("3957-09-06T07:15:12.345Z", datetime(year=3957, month=9, day=6, hour=7, minute=15, second=12, microsecond=345000, tzinfo=timezone.utc)),
20+
("4958-10-07T08:15:12.0345Z", datetime(year=4958, month=10, day=7, hour=8, minute=15, second=12, microsecond=34500, tzinfo=timezone.utc)),
21+
("5959-11-08T09:15:12.000000Z", datetime(year=5959, month=11, day=8, hour=9, minute=15, second=12, microsecond=0, tzinfo=timezone.utc)),
22+
("6960-12-09T10:15:12.123456789123Z", datetime(year=6960, month=12, day=9, hour=10, minute=15, second=12, microsecond=123456, tzinfo=timezone.utc)),
2323
2424
])
25-
def test_parse_simple_iso8601(ts, expected):
26-
dt = utils.parse_iso8601_datetime(ts)
27-
assert dt == expected
25+
def test_parse_simple_iso8601(time_str: str, expected: datetime) -> None:
26+
"""Ensure various times are represented as expected"""
27+
date_struct = utils.parse_iso8601_datetime(time_str)
28+
assert date_struct == expected
29+
30+
31+
def test_roundtrip_datetime() -> None:
32+
"""New string -> struct -> string is ok"""
33+
now_str = utils.get_sigmf_iso8601_datetime_now()
34+
now_struct = utils.parse_iso8601_datetime(now_str)
35+
assert now_str == now_struct.strftime(utils.SIGMF_DATETIME_ISO8601_FMT)

0 commit comments

Comments
 (0)