diff --git a/geomagio/TimeseriesUtility.py b/geomagio/TimeseriesUtility.py index c40ba8609576b60bf0bcc68d6af85abec6192a56..418bbe9c17a04b903d83a8906b133eefb37f48ce 100644 --- a/geomagio/TimeseriesUtility.py +++ b/geomagio/TimeseriesUtility.py @@ -535,3 +535,28 @@ def pad_and_trim_trace(trace, starttime, endtime): trace.data = numpy.concatenate( [trace.data, numpy.full(cnt, numpy.nan, dtype=numpy.float64)] ) + + +def round_usecs(time): + """Rounds residual microseconds to milliseconds. + + Parameters + ---------- + time: UTCDateTime + time containing microsecond values + + Returns + ---------- + time: UTCDateTime + time containing rounded(or non-rounded) microsecond values + """ + usecs = time.microsecond + # round microseconds to nearest millisecond + rounded_usecs = int(round(usecs / 1000, 0)) * 1000 + # reset microseconds to 0 at top of second, add second to input time + if rounded_usecs > 999000: + rounded_usecs = 0 + time += 1 + if rounded_usecs != usecs: + time = time.replace(microsecond=rounded_usecs) + return time diff --git a/geomagio/edge/RawInputClient.py b/geomagio/edge/RawInputClient.py index f50a93cd24ab445216232828e6cbacb740235c0b..e6483c7fa5859990f23cbbf84811a937b172ef76 100644 --- a/geomagio/edge/RawInputClient.py +++ b/geomagio/edge/RawInputClient.py @@ -6,6 +6,8 @@ import struct import sys from datetime import datetime from ..TimeseriesFactoryException import TimeseriesFactoryException +from ..TimeseriesUtility import round_usecs +import logging from obspy.core import UTCDateTime from time import sleep @@ -414,7 +416,7 @@ class RawInputClient: """ PARAMETERS ---------- - time: UTCDateTime + input_time: UTCDateTime time of the first sample RETURNS @@ -425,11 +427,20 @@ class RawInputClient: secs: int usecs: int """ + + # check for presence of residual microseconds + if (time.microsecond / 1000).is_integer() == False: + # create warning message when rounding is necessary + logging.warning( + "residual microsecond values encountered, rounding to nearest millisecond" + ) + # round residual microsecond values + time = round_usecs(time) + yr = time.year doy = time.datetime.timetuple().tm_yday secs = time.hour * 3600 + time.minute * 60 + time.second usecs = time.microsecond - return (yr, doy, secs, usecs) def _open_socket(self): diff --git a/test/TimeseriesUtility_test.py b/test/TimeseriesUtility_test.py index 827b0afeced6bc9e7a80c0cb75f48bc3cf98ee34..a664b2e28a718e42e57553ca9c57d29aa02bae15 100644 --- a/test/TimeseriesUtility_test.py +++ b/test/TimeseriesUtility_test.py @@ -449,6 +449,22 @@ def test_pad_and_trim_trace_fixfloat(): ) +def test_round_usecs(): + """TimeseriesUtility_test.test_round_usecs() + This tests whether microsecond values are rounded or + not depending on residual microsecond values + """ + # test case with no residual microseconds + time = TimeseriesUtility.round_usecs(UTCDateTime("2020-10-07T00:00:00Z")) + assert_equal(time, UTCDateTime("2020-10-07T00:00:00Z")) + # test case with residual microseconds + time = TimeseriesUtility.round_usecs(UTCDateTime("2020-10-07T00:00:00.995600Z")) + assert_equal(time, UTCDateTime("2020-10-07T00:00:00.996000Z")) + # test case with rounding to next second + time = TimeseriesUtility.round_usecs(UTCDateTime("2020-10-07T00:00:00.9995Z")) + assert_equal(time, UTCDateTime("2020-10-07T00:00:01.000Z")) + + def _create_trace(data, channel, starttime, delta=60.0): stats = Stats() stats.channel = channel diff --git a/test/edge_test/RawInputClient_test.py b/test/edge_test/RawInputClient_test.py index 850f1a7dfff04cc18d0dc45ffc860d334ba36bd8..ef21290aca7b15bb3784628999673fe8a30431bb 100644 --- a/test/edge_test/RawInputClient_test.py +++ b/test/edge_test/RawInputClient_test.py @@ -1,6 +1,8 @@ """Tests for RawInputClient.py""" import numpy +from datetime import datetime +import logging from obspy.core import Stats, Trace, UTCDateTime from geomagio.edge import EdgeFactory, RawInputClient from numpy.testing import assert_equal @@ -56,7 +58,7 @@ def test_raw_input_client(): def test__get_tag(): - """edge_test.RawInputClient_test.test_raw_input_client()""" + """edge_test.RawInputClient_test.test__get_tag()""" network = "NT" station = "BOU" channel = "MVH" @@ -72,3 +74,63 @@ def test__get_tag(): ) tag_send = client._get_tag() assert_equal(tag_send is not None, True) + + +def test__get_time_values(caplog): + """edge_test.RawInputClient_test.test__get_time_values()""" + network = "NT" + station = "BOU" + channel = "MVH" + location = "R0" + client = MockRawInputClient( + tag="tag", + host="host", + port="port", + station=station, + channel=channel, + location=location, + network=network, + ) + + # test rounding up of microsecond value + time = UTCDateTime("2020-10-07T00:00:15.196855Z") + yr, doy, secs, usecs = client._get_time_values(time) + assert_equal(yr, 2020) + assert_equal(doy, 281) + assert_equal(secs, 15) + assert_equal(usecs, 197000) + # test rounding down of microsecond value + time = UTCDateTime("2020-10-07T00:00:15.196455Z") + yr, doy, secs, usecs = client._get_time_values(time) + assert_equal(yr, 2020) + assert_equal(doy, 281) + assert_equal(secs, 15) + assert_equal(usecs, 196000) + # test top of second adjustment + time = UTCDateTime("2020-10-07T00:00:00.999999Z") + yr, doy, secs, usecs = client._get_time_values(time) + assert_equal(yr, 2020) + assert_equal(doy, 281) + assert_equal(secs, 1) + assert_equal(usecs, 0) + # test top of day adjustment + time = UTCDateTime("2020-10-07T23:59:59.999999Z") + yr, doy, secs, usecs = client._get_time_values(time) + assert_equal(yr, 2020) + assert_equal(doy, 282) + assert_equal(secs, 0) + assert_equal(usecs, 0) + # assert if previous 4 tests generate 4 warning messages + assert_equal(len(caplog.messages), 4) + + # clear warnings from log + caplog.clear() + # test ideal case + time = UTCDateTime("2020-10-07T00:00:00.232000Z") + yr, doy, secs, usecs = client._get_time_values(time) + assert_equal(yr, 2020) + assert_equal(doy, 281) + assert_equal(secs, 0) + assert_equal(usecs, 232000) + # assert if previous test does not generate a warning message + assert_equal(len(caplog.messages), 0)