Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions stix2/test/v21/test_timestamp_precision.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@ def test_stix_datetime():

@pytest.mark.parametrize(
"us, precision, precision_constraint, expected_truncated_us", [
(123456789, Precision.ANY, PrecisionConstraint.EXACT, 123456),
(123456789, Precision.SECOND, PrecisionConstraint.EXACT, 0),
(123456789, Precision.SECOND, PrecisionConstraint.MIN, 123456),
(123456789, Precision.MILLISECOND, PrecisionConstraint.EXACT, 123000),
(123456789, Precision.MILLISECOND, PrecisionConstraint.MIN, 123456),
(123456, Precision.ANY, PrecisionConstraint.EXACT, 123456),
(123456, Precision.SECOND, PrecisionConstraint.EXACT, 0),
(123456, Precision.SECOND, PrecisionConstraint.MIN, 123456),
Expand Down
28 changes: 28 additions & 0 deletions stix2/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,32 @@ def format_datetime(dttm):
return ts


def adjust_nanoseconds(value):
"""
This function checks if the timestamp has exactly 9 digits after the decimal
and ends with 'Z'. If it does, it adjusts to microseconds precision (6 digits).
Otherwise, it returns the value unchanged.
Comment on lines +216 to +219
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The adjust_nanoseconds function lacks input validation for the value parameter. While the caller (parse_into_datetime) assumes value is a string at this point, the function should be defensive and either check that value is a string or document this assumption in the docstring. Consider adding a type check or updating the docstring to explicitly state: "Args: value (str): A timestamp string to check and adjust".

Suggested change
"""
This function checks if the timestamp has exactly 9 digits after the decimal
and ends with 'Z'. If it does, it adjusts to microseconds precision (6 digits).
Otherwise, it returns the value unchanged.
"""
Adjust a timestamp string from nanoseconds to microseconds precision when applicable.
This function checks if the timestamp has exactly 9 digits after the decimal
and ends with 'Z'. If it does, it adjusts to microseconds precision (6 digits).
Otherwise, it returns the value unchanged.
Args:
value (str): A timestamp string to check and adjust.
Returns:
str: The adjusted timestamp string if nanosecond precision is detected,
otherwise the original value.

Copilot uses AI. Check for mistakes.
"""

# Return the value if there is no decimal or
# if the frac_seconds_part length is not 10
if '.' not in value:
return value

seconds_part, frac_seconds_part = value.split('.', 1)

if len(frac_seconds_part) != 10:
return value

# Ensure the fractional part has exactly 10 characters (9 digits + 1 'Z')
if frac_seconds_part[:9].isdigit() and frac_seconds_part[9] == 'Z':
# Adjust precision to microseconds (6 digits)
microseconds_part = frac_seconds_part[:6]
value = f'{seconds_part}.{microseconds_part}Z'

return value
Comment on lines +215 to +238
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new adjust_nanoseconds function lacks dedicated unit tests. While it is tested indirectly through test_parse_datetime with the new test cases, adding direct unit tests would improve maintainability and make it easier to verify edge cases. Consider adding tests for: timestamps without decimals, timestamps with various fractional digit counts (1-10+), timestamps with non-digit characters in the fractional part, and timestamps not ending with 'Z'.

Copilot uses AI. Check for mistakes.


def parse_into_datetime(
value, precision=Precision.ANY,
precision_constraint=PrecisionConstraint.EXACT,
Expand Down Expand Up @@ -245,6 +271,8 @@ def parse_into_datetime(
else:
# value isn't a date or datetime object so assume it's a string
fmt = _TIMESTAMP_FORMAT_FRAC if "." in value else _TIMESTAMP_FORMAT
# adjust value precision from nanoseconds to microseconds if applicable
value = adjust_nanoseconds(value)
try:
parsed = dt.datetime.strptime(value, fmt)
except (TypeError, ValueError):
Expand Down
Loading