2017-02-10 22:35:02 +01:00
|
|
|
"""Utility functions and classes for the stix2 library."""
|
|
|
|
|
|
|
|
import datetime as dt
|
2017-05-05 16:53:28 +02:00
|
|
|
import json
|
|
|
|
|
2017-05-09 21:10:53 +02:00
|
|
|
from dateutil import parser
|
2017-05-10 00:03:46 +02:00
|
|
|
import pytz
|
2017-04-25 00:29:56 +02:00
|
|
|
|
2017-05-19 19:51:59 +02:00
|
|
|
# Sentinel value for properties that should be set to the current time.
|
2017-02-10 22:35:02 +01:00
|
|
|
# We can't use the standard 'default' approach, since if there are multiple
|
|
|
|
# timestamps in a single object, the timestamps will vary by a few microseconds.
|
|
|
|
NOW = object()
|
|
|
|
|
|
|
|
|
|
|
|
def get_timestamp():
|
|
|
|
return dt.datetime.now(tz=pytz.UTC)
|
|
|
|
|
|
|
|
|
|
|
|
def format_datetime(dttm):
|
2017-04-17 16:48:13 +02:00
|
|
|
# 1. Convert to timezone-aware
|
|
|
|
# 2. Convert to UTC
|
|
|
|
# 3. Format in ISO format
|
|
|
|
# 4. Add subsecond value if non-zero
|
|
|
|
# 5. Add "Z"
|
|
|
|
|
2017-05-22 17:11:42 +02:00
|
|
|
if dttm.tzinfo is None or dttm.tzinfo.utcoffset(dttm) is None:
|
2017-04-17 19:16:14 +02:00
|
|
|
# dttm is timezone-naive; assume UTC
|
2017-05-22 17:11:42 +02:00
|
|
|
zoned = pytz.utc.localize(dttm)
|
|
|
|
else:
|
|
|
|
zoned = dttm.astimezone(pytz.utc)
|
2017-04-17 16:48:13 +02:00
|
|
|
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"
|
2017-04-19 20:32:56 +02:00
|
|
|
|
|
|
|
|
2017-05-04 22:34:08 +02:00
|
|
|
def parse_into_datetime(value):
|
|
|
|
if isinstance(value, dt.date):
|
|
|
|
if hasattr(value, 'hour'):
|
|
|
|
return value
|
|
|
|
else:
|
|
|
|
# Add a time component
|
2017-05-22 17:11:42 +02:00
|
|
|
return dt.datetime.combine(value, dt.time(0, 0, tzinfo=pytz.utc))
|
2017-05-04 22:34:08 +02:00
|
|
|
|
|
|
|
# value isn't a date or datetime object so assume it's a string
|
|
|
|
try:
|
|
|
|
parsed = parser.parse(value)
|
2017-05-22 17:11:42 +02:00
|
|
|
except (TypeError, ValueError):
|
2017-05-04 22:34:08 +02:00
|
|
|
# Unknown format
|
|
|
|
raise ValueError("must be a datetime object, date object, or "
|
|
|
|
"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
|
|
|
|
return pytz.utc.localize(parsed)
|
|
|
|
|
|
|
|
|
2017-04-19 20:32:56 +02:00
|
|
|
def get_dict(data):
|
|
|
|
"""Return data as a dictionary.
|
|
|
|
Input can be a dictionary, string, or file-like object.
|
|
|
|
"""
|
|
|
|
|
|
|
|
if type(data) is dict:
|
2017-05-16 15:25:08 +02:00
|
|
|
return data
|
2017-04-19 20:32:56 +02:00
|
|
|
else:
|
|
|
|
try:
|
2017-05-16 15:25:08 +02:00
|
|
|
return json.loads(data)
|
2017-04-19 20:32:56 +02:00
|
|
|
except TypeError:
|
2017-05-16 15:25:08 +02:00
|
|
|
pass
|
|
|
|
try:
|
|
|
|
return json.load(data)
|
|
|
|
except AttributeError:
|
|
|
|
pass
|
|
|
|
try:
|
|
|
|
return dict(data)
|
|
|
|
except (ValueError, TypeError):
|
|
|
|
raise ValueError("Cannot convert '%s' to dictionary." % str(data))
|