#!/usr/bin/env python3
# Software Name: ngsildclient
# SPDX-FileCopyrightText: Copyright (c) 2021 Orange
# SPDX-License-Identifier: Apache 2.0
#
# This software is distributed under the Apache 2.0;
# see the NOTICE file for more details.
#
# Author: Fabien BATTELLO <fabien.battello@orange.com> et al.
"""
This module contains helper functions to generate iso8601-formatted strings from native Python date types.
Notes
-----
NGSI-LD dates are expressed in UTC, using the ISO8601 format.
References
----------
.. [1] ETSI, 2021. "Supported data types for Values" in Context Information Management (CIM); NGSI-LD API
ETSI GS CIM 009 V1.4.2, pp. 41-42, 2021-04.
"""
import re
from typing import Union, Optional
from datetime import datetime, date, time, timezone
from contextlib import suppress
from ngsildclient.model.constants import TemporalType
ISO8601_PATTERN = re.compile(r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z")
[docs]def from_datetime(value: datetime) -> str:
"""Convert from datetime to ISO8601-formatted string.
Parameters
----------
value : datetime
The datetime to be converted. Expected in UTC.
Returns
-------
str
ISO8601-formatted string
Example
-------
>>> from datetime import datetime
>>> from ngsildclient import iso8601
>>> d = datetime(2021, 10, 13, 9, 29)
>>> print(iso8601.from_datetime(d))
2021-10-13T09:29:00Z
"""
# convert to UTC if naive-datetime or not-UTC aware-datetime
if value.tzinfo is None or value.tzinfo != timezone.utc:
value = value.astimezone(timezone.utc)
return value.strftime("%Y-%m-%dT%H:%M:%SZ")
[docs]def utcnow() -> str:
"""Converts the current UTC datetime to ISO8601-formatted string.
Returns
-------
str
The current UTC datetime, ISO8601-formatted
"""
return from_datetime(datetime.utcnow())
[docs]def from_date(value: date) -> str:
"""Convert from date to ISO8601-formatted string.
Parameters
----------
value : date
The date to be converted.
Returns
-------
str
ISO8601-formatted string
Example
-------
>>> from datetime import date
>>> from ngsildclient import iso8601
>>> d = date(2021, 10, 13)
>>> print(iso8601.from_date(d))
2021-10-13
"""
return value.strftime("%Y-%m-%d")
[docs]def from_time(value: time) -> str:
"""Convert from time to ISO8601-formatted string.
Parameters
----------
value : time
The time to be converted. Expected in UTC.
Returns
-------
str
ISO8601-formatted string
Example
-------
>>> from datetime import time
>>> from ngsildclient import iso8601
>>> d = time(9, 29)
>>> print(iso8601.from_time(d))
09:29:00Z
"""
return value.strftime("%H:%M:%SZ")
def _from_string(value: str) -> tuple[str, TemporalType, datetime]:
"""Guess the temporal date type from a given string.
This function should not be called by the end user. It is used internally by the `parse()` function.
Parameters
----------
value : str
A string representation of either a datetime, date or time
Returns
-------
tuple[str, TemporalType]
A tuple composed of the `value` argument and the TemporalType that has been identified
Raises
------
ValueError
The date format does not match datetime, date or time
Example
-------
>>> from ngsildclient import iso8601
>>> print(iso8601._from_string("2021-10-13"))
('2021-10-13', <TemporalType.DATE: 'Date'>)
"""
with suppress(ValueError):
if len(value) == 20:
dt = datetime.strptime(value, "%Y-%m-%dT%H:%M:%SZ")
return value, TemporalType.DATETIME, dt
elif len(value) == 10:
datetime.strptime(value, "%Y-%m-%d")
return value, TemporalType.DATE, None
elif len(value) == 9:
datetime.strptime(value, "%H:%M:%SZ")
return value, TemporalType.TIME, None
raise ValueError(f"Bad date format : {value}")
[docs]def parse(value: Union[datetime, date, time, str]) -> tuple[str, TemporalType, datetime]:
"""Guess the temporal date type from a given argument carrying a temporal information.
This function is typically used to build a NGSI-LD Temporal Property or temporal metadata such as `observedAt`.
Parameters
----------
value : Union[datetime, date, time, str]
A value carrying temporal information, could be either either a datetime, date or time,
or a string representation of these former types.
Returns
-------
tuple[str, TemporalType, datetime]
A tuple composed of a ISO8601 string representation of the date and the TemporalType that has been identified
Raises
------
ValueError
The date format does not match datetime, date or time or a valid string representation of a date
See Also
--------
ngsildclient.attribute
Example
-------
>>> from ngsildclient import iso8601
>>> print(iso8601.parse(date(2021,9,13)))
('2021-10-13', <TemporalType.DATE: 'Date'>)
"""
if isinstance(value, datetime):
return from_datetime(value), TemporalType.DATETIME, value
if isinstance(value, date):
return from_date(value), TemporalType.DATE, None
if isinstance(value, time):
return from_time(value), TemporalType.TIME, None
if isinstance(value, str):
return _from_string(value)
raise ValueError(f"Bad date format : {value}")