From 29d9467ce07f6b9571352ffae3a2b4ffd9d82f59 Mon Sep 17 00:00:00 2001 From: clenk Date: Wed, 28 Jun 2017 15:55:23 -0400 Subject: [PATCH] Add more timestamp test cases, address suggestions --- stix2/observables.py | 2 +- stix2/properties.py | 5 +---- stix2/test/test_campaign.py | 8 ++++---- stix2/test/test_utils.py | 13 +++++++++++++ stix2/utils.py | 27 ++++++++++++++------------- 5 files changed, 33 insertions(+), 22 deletions(-) diff --git a/stix2/observables.py b/stix2/observables.py index 086dc45..a8f3b67 100644 --- a/stix2/observables.py +++ b/stix2/observables.py @@ -215,7 +215,7 @@ class WindowsPEBinaryExt(_Extension): 'imphash': StringProperty(), 'machine_hex': HexProperty(), 'number_of_sections': IntegerProperty(), - 'time_date_stamp': TimestampProperty(), + 'time_date_stamp': TimestampProperty(precision='second'), 'pointer_to_symbol_table_hex': HexProperty(), 'number_of_symbols': IntegerProperty(), 'size_of_optional_header': IntegerProperty(), diff --git a/stix2/properties.py b/stix2/properties.py index fe4c4a6..80e5345 100644 --- a/stix2/properties.py +++ b/stix2/properties.py @@ -217,10 +217,7 @@ class TimestampProperty(Property): super(TimestampProperty, self).__init__(**kwargs) def clean(self, value): - try: - return parse_into_datetime(value, self.precision) - except ValueError: - raise + return parse_into_datetime(value, self.precision) class ObservableProperty(Property): diff --git a/stix2/test/test_campaign.py b/stix2/test/test_campaign.py index 5edcdf3..30b9444 100644 --- a/stix2/test/test_campaign.py +++ b/stix2/test/test_campaign.py @@ -23,8 +23,8 @@ def test_campaign_example(): campaign = stix2.Campaign( id="campaign--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f", created_by_ref="identity--f431f809-377b-45e0-aa1c-6a4751cae5ff", - created="2016-04-06T20:03:00.000Z", - modified="2016-04-06T20:03:00.000Z", + created="2016-04-06T20:03:00Z", + modified="2016-04-06T20:03:00Z", name="Green Group Attacks Against Finance", description="Campaign by Green Group against a series of targets in the financial services sector." ) @@ -37,8 +37,8 @@ def test_campaign_example(): { "type": "campaign", "id": "campaign--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f", - "created": "2016-04-06T20:03:00.000Z", - "modified": "2016-04-06T20:03:00.000Z", + "created": "2016-04-06T20:03:00Z", + "modified": "2016-04-06T20:03:00Z", "created_by_ref": "identity--f431f809-377b-45e0-aa1c-6a4751cae5ff", "description": "Campaign by Green Group against a series of targets in the financial services sector.", "name": "Green Group Attacks Against Finance", diff --git a/stix2/test/test_utils.py b/stix2/test/test_utils.py index dbc0ed5..c73bcd2 100644 --- a/stix2/test/test_utils.py +++ b/stix2/test/test_utils.py @@ -17,6 +17,8 @@ eastern = pytz.timezone('US/Eastern') (eastern.localize(dt.datetime(2017, 7, 1)), '2017-07-01T04:00:00Z'), (dt.datetime(2017, 7, 1), '2017-07-01T00:00:00Z'), (dt.datetime(2017, 7, 1, 0, 0, 0, 1), '2017-07-01T00:00:00.000001Z'), + (stix2.utils.STIXdatetime(2017, 7, 1, 0, 0, 0, 1, precision='millisecond'), '2017-07-01T00:00:00.000Z'), + (stix2.utils.STIXdatetime(2017, 7, 1, 0, 0, 0, 1, precision='second'), '2017-07-01T00:00:00Z'), ]) def test_timestamp_formatting(dttm, timestamp): assert stix2.utils.format_datetime(dttm) == timestamp @@ -33,6 +35,17 @@ def test_parse_datetime(timestamp, dttm): assert stix2.utils.parse_into_datetime(timestamp) == dttm +@pytest.mark.parametrize('timestamp, dttm, precision', [ + ('2017-01-01T01:02:03.000001', dt.datetime(2017, 1, 1, 1, 2, 3, 0, tzinfo=pytz.utc), 'millisecond'), + ('2017-01-01T01:02:03.001', dt.datetime(2017, 1, 1, 1, 2, 3, 1000, tzinfo=pytz.utc), 'millisecond'), + ('2017-01-01T01:02:03.1', dt.datetime(2017, 1, 1, 1, 2, 3, 100000, tzinfo=pytz.utc), 'millisecond'), + ('2017-01-01T01:02:03.45', dt.datetime(2017, 1, 1, 1, 2, 3, 450000, tzinfo=pytz.utc), 'millisecond'), + ('2017-01-01T01:02:03.45', dt.datetime(2017, 1, 1, 1, 2, 3, tzinfo=pytz.utc), 'second'), +]) +def test_parse_datetime_precision(timestamp, dttm, precision): + assert stix2.utils.parse_into_datetime(timestamp, precision) == dttm + + @pytest.mark.parametrize('ts', [ 'foobar', 1, diff --git a/stix2/utils.py b/stix2/utils.py index 2848a72..12b889c 100644 --- a/stix2/utils.py +++ b/stix2/utils.py @@ -17,14 +17,9 @@ class STIXdatetime(dt.datetime): precision = kwargs.pop('precision', None) if isinstance(args[0], dt.datetime): # Allow passing in a datetime object dttm = args[0] - args = (dttm.year,) - kwargs['month'] = dttm.month - kwargs['day'] = dttm.day - kwargs['hour'] = dttm.hour - kwargs['minute'] = dttm.minute - kwargs['second'] = dttm.second - kwargs['microsecond'] = dttm.microsecond - kwargs['tzinfo'] = dttm.tzinfo + args = (dttm.year, dttm.month, dttm.day, dttm.hour, dttm.minute, + dttm.second, dttm.microsecond, dttm.tzinfo) + # self will be an instance of STIXdatetime, not dt.datetime self = dt.datetime.__new__(cls, *args, **kwargs) self.precision = precision return self @@ -50,9 +45,10 @@ def format_datetime(dttm): ts = zoned.strftime("%Y-%m-%dT%H:%M:%S") ms = zoned.strftime("%f") precision = getattr(dttm, "precision", None) - if precision: - if precision == "millisecond": - ts = ts + '.' + ms[:3] + if precision == 'second': + pass # Alredy precise to the second + elif precision == "millisecond": + ts = ts + '.' + ms[:3] elif zoned.microsecond > 0: ts = ts + '.' + ms.rstrip("0") return ts + "Z" @@ -83,11 +79,16 @@ def parse_into_datetime(value, precision=None): if not precision: return ts ms = ts.microsecond - if precision == 'millisecond': + if precision == 'second': + ts = ts.replace(microsecond=0) + elif precision == 'millisecond': ms_len = len(str(ms)) if ms_len > 3: # Truncate to millisecond precision - return ts.replace(microsecond=(ts.microsecond // (10 ** (ms_len - 3)))) + factor = 10 ** (ms_len - 3) + ts = ts.replace(microsecond=(ts.microsecond // factor) * factor) + else: + ts = ts.replace(microsecond=0) return STIXdatetime(ts, precision=precision)