Fix TimestampProperty
- improved timestamp formatting - python-stix2 will only include subsecond values if they don't equal 0 - in Python 3.6, datetime.astimezone doesn't throw an error on naive timestamps as in previous versionsstix2.1
parent
35981025c5
commit
b4f116a33f
|
@ -1,7 +1,8 @@
|
|||
"""STIX 2.0 Marking Objects"""
|
||||
|
||||
from .base import _STIXBase
|
||||
from .properties import IDProperty, TypeProperty, ListProperty, ReferenceProperty, Property, SelectorProperty
|
||||
from .properties import (IDProperty, TypeProperty, ListProperty, TimestampProperty,
|
||||
ReferenceProperty, Property, SelectorProperty)
|
||||
from .utils import NOW
|
||||
|
||||
|
||||
|
@ -15,7 +16,7 @@ class GranularMarking(_STIXBase):
|
|||
class MarkingDefinition(_STIXBase):
|
||||
_type = 'marking-definition'
|
||||
_properties = {
|
||||
'created': Property(default=lambda: NOW),
|
||||
'created': TimestampProperty(default=lambda: NOW),
|
||||
'external_references': Property(),
|
||||
'created_by_ref': ReferenceProperty(type="identity"),
|
||||
'object_marking_refs': ListProperty(ReferenceProperty(type="marking-definition")),
|
||||
|
|
|
@ -206,25 +206,26 @@ class BooleanProperty(Property):
|
|||
class TimestampProperty(Property):
|
||||
|
||||
def validate(self, value):
|
||||
if isinstance(value, dt.datetime):
|
||||
return value
|
||||
elif isinstance(value, dt.date):
|
||||
return dt.datetime.combine(value, dt.time())
|
||||
if isinstance(value, dt.date):
|
||||
if hasattr(value, 'hour'):
|
||||
return value
|
||||
else:
|
||||
# Add a time component
|
||||
return dt.datetime.combine(value, dt.time(), tzinfo=pytz.timezone('US/Eastern'))
|
||||
|
||||
# value isn't a date or datetime object so assume it's a string
|
||||
try:
|
||||
return parser.parse(value).astimezone(pytz.utc)
|
||||
except ValueError:
|
||||
# Doesn't have timezone info in the string
|
||||
try:
|
||||
return pytz.utc.localize(parser.parse(value))
|
||||
except TypeError:
|
||||
# Unknown format
|
||||
raise ValueError("must be a datetime object, date object, or "
|
||||
"timestamp string in a recognizable format.")
|
||||
parsed = parser.parse(value)
|
||||
except TypeError:
|
||||
# Isn't a string
|
||||
# Unknown format
|
||||
raise ValueError("must be a datetime object, date object, or "
|
||||
"timestamp string.")
|
||||
"timestamp string in a recognizable format.")
|
||||
if parsed.tzinfo:
|
||||
return parsed.astimezone(pytz.utc)
|
||||
else:
|
||||
# Doesn't have timezone info in the string; assume UTC
|
||||
# TODO Should we default to system local timezone instead?
|
||||
return pytz.utc.localize(parsed)
|
||||
|
||||
|
||||
REF_REGEX = re.compile("^[a-z][a-z-]+[a-z]--[0-9a-fA-F]{8}-[0-9a-fA-F]{4}"
|
||||
|
|
|
@ -3,7 +3,7 @@ from stix2.markings import TLP_WHITE
|
|||
import pytest
|
||||
|
||||
EXPECTED_TLP_MARKING_DEFINITION = """{
|
||||
"created": "2017-01-20T00:00:00.000Z",
|
||||
"created": "2017-01-20T00:00:00Z",
|
||||
"definition": {
|
||||
"tlp": "white"
|
||||
},
|
||||
|
@ -13,7 +13,7 @@ EXPECTED_TLP_MARKING_DEFINITION = """{
|
|||
}"""
|
||||
|
||||
EXPECTED_STATEMENT_MARKING_DEFINITION = """{
|
||||
"created": "2017-01-20T00:00:00.000Z",
|
||||
"created": "2017-01-20T00:00:00Z",
|
||||
"definition": {
|
||||
"statement": "Copyright 2016, Example Corp"
|
||||
},
|
||||
|
@ -33,7 +33,7 @@ EXPECTED_GRANULAR_MARKING = """{
|
|||
}"""
|
||||
|
||||
EXPECTED_CAMPAIGN_WITH_GRANULAR_MARKINGS = """{
|
||||
"created": "2016-04-06T20:03:00.000Z",
|
||||
"created": "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.",
|
||||
"granular_markings": [
|
||||
|
@ -45,7 +45,7 @@ EXPECTED_CAMPAIGN_WITH_GRANULAR_MARKINGS = """{
|
|||
}
|
||||
],
|
||||
"id": "campaign--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f",
|
||||
"modified": "2016-04-06T20:03:00.000Z",
|
||||
"modified": "2016-04-06T20:03:00Z",
|
||||
"name": "Green Group Attacks Against Finance",
|
||||
"type": "campaign"
|
||||
}"""
|
||||
|
@ -100,8 +100,8 @@ def test_campaign_with_granular_markings_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.",
|
||||
granular_markings=[
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
"""Utility functions and classes for the stix2 library."""
|
||||
|
||||
import datetime as dt
|
||||
|
||||
import time
|
||||
import pytz
|
||||
|
||||
# Sentinel value for fields that should be set to the current time.
|
||||
|
@ -15,12 +15,20 @@ def get_timestamp():
|
|||
|
||||
|
||||
def format_datetime(dttm):
|
||||
# TODO: how to handle naive datetime
|
||||
# 1. Convert to timezone-aware
|
||||
# 2. Convert to UTC
|
||||
# 3. Format in ISO format
|
||||
# 4. Add subsecond value if non-zero
|
||||
# 5. Add "Z"
|
||||
|
||||
# 1. Convert to UTC
|
||||
# 2. Format in ISO format
|
||||
# 3. Strip off "+00:00"
|
||||
# 4. Add "Z"
|
||||
|
||||
# TODO: how to handle timestamps with subsecond 0's
|
||||
return dttm.astimezone(pytz.utc).isoformat()[:-6] + "Z"
|
||||
try:
|
||||
zoned = dttm.astimezone(pytz.utc)
|
||||
except ValueError:
|
||||
# dttm is timezone-naive
|
||||
tz_name = time.tzname[time.localtime().tm_isdst]
|
||||
zoned = pytz.timezone(tz_name).localize(dttm).astimezone(pytz.utc)
|
||||
ts = zoned.strftime("%Y-%m-%dT%H:%M:%S")
|
||||
if zoned.microsecond > 0:
|
||||
ms = zoned.strftime("%f")
|
||||
ts = ts + '.' + ms.rstrip("0")
|
||||
return ts + "Z"
|
||||
|
|
Loading…
Reference in New Issue