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 versions
stix2.1
clenk 2017-04-17 10:48:13 -04:00
parent 35981025c5
commit b4f116a33f
4 changed files with 42 additions and 32 deletions

View File

@ -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")),

View File

@ -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}"

View File

@ -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=[

View File

@ -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"