Module astrapy.data_types
Expand source code
# Copyright DataStax, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
from astrapy.data_types.data_api_date import DataAPIDate
from astrapy.data_types.data_api_duration import DataAPIDuration
from astrapy.data_types.data_api_map import DataAPIMap
from astrapy.data_types.data_api_set import DataAPISet
from astrapy.data_types.data_api_time import DataAPITime
from astrapy.data_types.data_api_timestamp import DataAPITimestamp
from astrapy.data_types.data_api_vector import DataAPIVector
__all__ = [
"DataAPITimestamp",
"DataAPIVector",
"DataAPIDate",
"DataAPIDuration",
"DataAPIMap",
"DataAPISet",
"DataAPITime",
]
Sub-modules
astrapy.data_types.data_api_date
astrapy.data_types.data_api_duration
astrapy.data_types.data_api_map
astrapy.data_types.data_api_set
astrapy.data_types.data_api_time
astrapy.data_types.data_api_timestamp
astrapy.data_types.data_api_vector
Classes
class DataAPIDate (year: int, month: int, day: int)
-
A value expressing a date, composed of a year, a month and a day, suitable for working with the "date" table column type.
This class is designed to losslessly express the full date range the Data API supports, overcoming the year range limitation of Python's standard-library date (i.e. 1AD to 9999AD).
DataAPIDate objects are meant to easily work in the context of the Data API, hence its conversion methods from/to a string assumed the particular format employed by the API.
The class also offers conversion methods from/to the regular Python
datetime.date
; however these may fail if the year falls outside of the range supported by the latter.Args
year
- the year for the date. Any integer is accepted.
month
- an integer number in the 1-12 range.
day
- an integer number in a range between 1 and the number of days in the chosen month (whose value depends on the month and, in the case of February, on whether the year is a leap year).
Example
>>> from astrapy.data_types import DataAPIDate >>> date1 = DataAPIDate(2024, 12, 31) >>> date2 = DataAPIDate(-44, 3, 15) >>> date2 DataAPIDate(-44, 3, 15) >>> date1.year 2024
Expand source code
@dataclass class DataAPIDate: """ A value expressing a date, composed of a year, a month and a day, suitable for working with the "date" table column type. This class is designed to losslessly express the full date range the Data API supports, overcoming the year range limitation of Python's standard-library date (i.e. 1AD to 9999AD). DataAPIDate objects are meant to easily work in the context of the Data API, hence its conversion methods from/to a string assumed the particular format employed by the API. The class also offers conversion methods from/to the regular Python `datetime.date`; however these may fail if the year falls outside of the range supported by the latter. Args: year: the year for the date. Any integer is accepted. month: an integer number in the 1-12 range. day: an integer number in a range between 1 and the number of days in the chosen month (whose value depends on the month and, in the case of February, on whether the year is a leap year). Example: >>> from astrapy.data_types import DataAPIDate >>> date1 = DataAPIDate(2024, 12, 31) >>> date2 = DataAPIDate(-44, 3, 15) >>> date2 DataAPIDate(-44, 3, 15) >>> date1.year 2024 """ year: int month: int day: int def __init__(self, year: int, month: int, day: int): _fail_reason = _validate_date( year=year, month=month, day=day, ) if _fail_reason: raise ValueError(f"Invalid date arguments: {_fail_reason}.") self.year = year self.month = month self.day = day def __hash__(self) -> int: return hash((self.year, self.month, self.day)) def __repr__(self) -> str: return f"{self.__class__.__name__}({self.year}, {self.month}, {self.day})" def __str__(self) -> str: return self.to_string() def __reduce__(self) -> tuple[type, tuple[int, int, int]]: return self.__class__, (self.year, self.month, self.day) def __le__(self, other: Any) -> bool: if isinstance(other, DataAPIDate): return self._to_tuple() <= other._to_tuple() elif isinstance(other, datetime.date): return self.__le__(DataAPIDate.from_date(other)) else: return NotImplemented def __lt__(self, other: Any) -> bool: if isinstance(other, DataAPIDate): return self._to_tuple() < other._to_tuple() elif isinstance(other, datetime.date): return self.__lt__(DataAPIDate.from_date(other)) else: return NotImplemented def __ge__(self, other: Any) -> bool: if isinstance(other, DataAPIDate): return self._to_tuple() >= other._to_tuple() elif isinstance(other, datetime.date): return self.__ge__(DataAPIDate.from_date(other)) else: return NotImplemented def __gt__(self, other: Any) -> bool: if isinstance(other, DataAPIDate): return self._to_tuple() > other._to_tuple() elif isinstance(other, datetime.date): return self.__gt__(DataAPIDate.from_date(other)) else: return NotImplemented def _to_tuple(self) -> tuple[int, int, int]: return (self.year, self.month, self.day) def to_string(self) -> str: """ Express the date as a string according to the Data API convention, including the presence of a sign, and the number of digits, for the year. Returns: a string, such as "2024-12-31", formatted in a way suitable to be in a Data API payload. Example: >>> from astrapy.data_types import DataAPIDate >>> date1 = DataAPIDate(2024, 12, 31) >>> date2 = DataAPIDate(-44, 3, 15) >>> date1.to_string() '2024-12-31' >>> date2.to_string() '-0044-03-15' """ # the year part requires care around the sign and number of digits y = self.year year_str: str if y > 9999: year_str = f"{y:+}" elif y >= 0: year_str = f"{y:04}" else: year_str = f"{y:+05}" return f"{year_str}-{self.month:02}-{self.day:02}" def to_date(self) -> datetime.date: """ Attempt to convert the date into a Python standard-library `datetime.date`. This operation may fail with a ValueError if the DataAPIDate's year falls outside of the range supported by the standard library. Returns: a `datetime.date` object if the conversion is successful. Example: >>> from astrapy.data_types import DataAPIDate >>> date1 = DataAPIDate(2024, 12, 31) >>> date2 = DataAPIDate(-44, 3, 15) >>> date1.to_date() datetime.date(2024, 12, 31) >>> date2.to_date() Traceback (most recent call last): [...] ValueError: year -44 is out of range """ return datetime.date(*self._to_tuple()) @staticmethod def from_date(dt: datetime.date) -> DataAPIDate: """ Convert a Python standard-library date into a DataAPIDate. Args: dt: a `datetime.date` object. Returns: a DataAPIDate, corresponding to the provided input. Example: >>> from datetime import date >>> >>> from astrapy.data_types import DataAPIDate >>> >>> std_date = date(2024, 12, 31) >>> DataAPIDate.from_date(std_date) DataAPIDate(2024, 12, 31) """ return DataAPIDate( year=dt.year, month=dt.month, day=dt.day, ) @staticmethod def from_string(date_string: str) -> DataAPIDate: """ Convert a string into a DataAPIDate, provided the string represents one according to the Data API format conventions. If the format is unrecognized, a ValueError is raised. Args: date_string: a valid string expressing a date as per Data API conventions. Returns: a DataAPIDate corresponding to the provided input. Example: >>> from astrapy.data_types import DataAPIDate >>> >>> DataAPIDate.from_string("2024-12-31") DataAPIDate(2024, 12, 31) >>> DataAPIDate.from_string("-0044-03-15") DataAPIDate(-44, 3, 15) >>> DataAPIDate.from_string("1905-13-15") Traceback (most recent call last): [...] ValueError: Cannot parse '1905-13-15' into a valid date: illegal month [...] """ match = DATE_PARSE_PATTERN.match(date_string) if match: # the year string has additional constraints besides the regexp: year_str = match[1] if year_str and year_str[0] == "+": if len(year_str[1:]) <= 4: raise ValueError( f"Cannot parse '{date_string}' into a valid timestamp: " "four-digit positive year should bear no plus sign. " f"{DATE_FORMAT_DESC}" ) if len(year_str) > 4 and year_str[0] not in {"+", "-"}: raise ValueError( f"Cannot parse '{date_string}' into a valid timestamp: " "years with more than four digits should bear a leading sign. " f"{DATE_FORMAT_DESC}" ) year = int(year_str) if year == 0 and year_str[0] == "-": raise ValueError( f"Cannot parse '{date_string}' into a valid timestamp: " "year zero should be provided as '0000' without leading sign. " f"{DATE_FORMAT_DESC}" ) month = int(match[2]) day = int(match[3]) _fail_reason = _validate_date(year=year, month=month, day=day) if _fail_reason: raise ValueError( f"Cannot parse '{date_string}' into a valid date: " f"{_fail_reason}. {DATE_FORMAT_DESC}" ) return DataAPIDate(year=year, month=month, day=day) else: raise ValueError( f"Cannot parse '{date_string}' into a valid date " f"(unrecognized format). {DATE_FORMAT_DESC}" )
Class variables
var day : int
var month : int
var year : int
Static methods
def from_date(dt: datetime.date) ‑> DataAPIDate
-
Convert a Python standard-library date into a DataAPIDate.
Args
dt
- a
datetime.date
object.
Returns
a DataAPIDate, corresponding to the provided input.
Example
>>> from datetime import date >>> >>> from astrapy.data_types import DataAPIDate >>> >>> std_date = date(2024, 12, 31) >>> DataAPIDate.from_date(std_date) DataAPIDate(2024, 12, 31)
Expand source code
@staticmethod def from_date(dt: datetime.date) -> DataAPIDate: """ Convert a Python standard-library date into a DataAPIDate. Args: dt: a `datetime.date` object. Returns: a DataAPIDate, corresponding to the provided input. Example: >>> from datetime import date >>> >>> from astrapy.data_types import DataAPIDate >>> >>> std_date = date(2024, 12, 31) >>> DataAPIDate.from_date(std_date) DataAPIDate(2024, 12, 31) """ return DataAPIDate( year=dt.year, month=dt.month, day=dt.day, )
def from_string(date_string: str) ‑> DataAPIDate
-
Convert a string into a DataAPIDate, provided the string represents one according to the Data API format conventions. If the format is unrecognized, a ValueError is raised.
Args
date_string
- a valid string expressing a date as per Data API conventions.
Returns
a DataAPIDate corresponding to the provided input.
Example
>>> from astrapy.data_types import DataAPIDate >>> >>> DataAPIDate.from_string("2024-12-31") DataAPIDate(2024, 12, 31) >>> DataAPIDate.from_string("-0044-03-15") DataAPIDate(-44, 3, 15) >>> DataAPIDate.from_string("1905-13-15") Traceback (most recent call last): [...] ValueError: Cannot parse '1905-13-15' into a valid date: illegal month [...]
Expand source code
@staticmethod def from_string(date_string: str) -> DataAPIDate: """ Convert a string into a DataAPIDate, provided the string represents one according to the Data API format conventions. If the format is unrecognized, a ValueError is raised. Args: date_string: a valid string expressing a date as per Data API conventions. Returns: a DataAPIDate corresponding to the provided input. Example: >>> from astrapy.data_types import DataAPIDate >>> >>> DataAPIDate.from_string("2024-12-31") DataAPIDate(2024, 12, 31) >>> DataAPIDate.from_string("-0044-03-15") DataAPIDate(-44, 3, 15) >>> DataAPIDate.from_string("1905-13-15") Traceback (most recent call last): [...] ValueError: Cannot parse '1905-13-15' into a valid date: illegal month [...] """ match = DATE_PARSE_PATTERN.match(date_string) if match: # the year string has additional constraints besides the regexp: year_str = match[1] if year_str and year_str[0] == "+": if len(year_str[1:]) <= 4: raise ValueError( f"Cannot parse '{date_string}' into a valid timestamp: " "four-digit positive year should bear no plus sign. " f"{DATE_FORMAT_DESC}" ) if len(year_str) > 4 and year_str[0] not in {"+", "-"}: raise ValueError( f"Cannot parse '{date_string}' into a valid timestamp: " "years with more than four digits should bear a leading sign. " f"{DATE_FORMAT_DESC}" ) year = int(year_str) if year == 0 and year_str[0] == "-": raise ValueError( f"Cannot parse '{date_string}' into a valid timestamp: " "year zero should be provided as '0000' without leading sign. " f"{DATE_FORMAT_DESC}" ) month = int(match[2]) day = int(match[3]) _fail_reason = _validate_date(year=year, month=month, day=day) if _fail_reason: raise ValueError( f"Cannot parse '{date_string}' into a valid date: " f"{_fail_reason}. {DATE_FORMAT_DESC}" ) return DataAPIDate(year=year, month=month, day=day) else: raise ValueError( f"Cannot parse '{date_string}' into a valid date " f"(unrecognized format). {DATE_FORMAT_DESC}" )
Methods
def to_date(self) ‑> datetime.date
-
Attempt to convert the date into a Python standard-library
datetime.date
. This operation may fail with a ValueError if the DataAPIDate's year falls outside of the range supported by the standard library.Returns
a
datetime.date
object if the conversion is successful.Example
>>> from astrapy.data_types import DataAPIDate >>> date1 = DataAPIDate(2024, 12, 31) >>> date2 = DataAPIDate(-44, 3, 15) >>> date1.to_date() datetime.date(2024, 12, 31) >>> date2.to_date() Traceback (most recent call last): [...] ValueError: year -44 is out of range
Expand source code
def to_date(self) -> datetime.date: """ Attempt to convert the date into a Python standard-library `datetime.date`. This operation may fail with a ValueError if the DataAPIDate's year falls outside of the range supported by the standard library. Returns: a `datetime.date` object if the conversion is successful. Example: >>> from astrapy.data_types import DataAPIDate >>> date1 = DataAPIDate(2024, 12, 31) >>> date2 = DataAPIDate(-44, 3, 15) >>> date1.to_date() datetime.date(2024, 12, 31) >>> date2.to_date() Traceback (most recent call last): [...] ValueError: year -44 is out of range """ return datetime.date(*self._to_tuple())
def to_string(self) ‑> str
-
Express the date as a string according to the Data API convention, including the presence of a sign, and the number of digits, for the year.
Returns
a string, such as "2024-12-31", formatted in a way suitable to be in a Data API payload.
Example
>>> from astrapy.data_types import DataAPIDate >>> date1 = DataAPIDate(2024, 12, 31) >>> date2 = DataAPIDate(-44, 3, 15) >>> date1.to_string() '2024-12-31' >>> date2.to_string() '-0044-03-15'
Expand source code
def to_string(self) -> str: """ Express the date as a string according to the Data API convention, including the presence of a sign, and the number of digits, for the year. Returns: a string, such as "2024-12-31", formatted in a way suitable to be in a Data API payload. Example: >>> from astrapy.data_types import DataAPIDate >>> date1 = DataAPIDate(2024, 12, 31) >>> date2 = DataAPIDate(-44, 3, 15) >>> date1.to_string() '2024-12-31' >>> date2.to_string() '-0044-03-15' """ # the year part requires care around the sign and number of digits y = self.year year_str: str if y > 9999: year_str = f"{y:+}" elif y >= 0: year_str = f"{y:04}" else: year_str = f"{y:+05}" return f"{year_str}-{self.month:02}-{self.day:02}"
class DataAPIDuration (signum: int, months: int, days: int, nanoseconds: int)
-
A "duration" value, suitable for working with the "duration" table column type.
Durations are an abstract notion: as such, they cannot be mapped into a precise, well-defined time span. In other words, their components cannot be referred one to another. Indeed, a 'month' is not a fixed amount of days, and likewise a 'day' is not a fixed amount of hours (due to daylight saving changes, there are days made of 23 or 25 hours).
For this reason, the DataAPIDuration class cannot really be mapped to a Python standard-library
datetime.timedelta
, unless in a restricted set of cases and/or with some approximations involved.Args
months
- an integer non-negative amount of months.
days
- an integer non-negative amount of days.
nanoseconds
- an integer non-negative amount of nanoseconds. This quantity encodes the whole sub-day component of the duration: for instance, a duration of 36 hours is expressed as 36 * 3600 * 1000000000 = 129600000000000 nanoseconds.
signum
- an overall plus or minus sign, represented as either +1 or -1. This allows the encoding of negative durations.
Expand source code
@dataclass class DataAPIDuration: """ A "duration" value, suitable for working with the "duration" table column type. Durations are an abstract notion: as such, they cannot be mapped into a precise, well-defined time span. In other words, their components cannot be referred one to another. Indeed, a 'month' is not a fixed amount of days, and likewise a 'day' is not a fixed amount of hours (due to daylight saving changes, there are days made of 23 or 25 hours). For this reason, the DataAPIDuration class cannot really be mapped to a Python standard-library `datetime.timedelta`, unless in a restricted set of cases and/or with some approximations involved. Args: months: an integer non-negative amount of months. days: an integer non-negative amount of days. nanoseconds: an integer non-negative amount of nanoseconds. This quantity encodes the whole sub-day component of the duration: for instance, a duration of 36 hours is expressed as 36 * 3600 * 1000000000 = 129600000000000 nanoseconds. signum: an overall plus or minus sign, represented as either +1 or -1. This allows the encoding of negative durations. """ months: int days: int nanoseconds: int signum: int def __init__( self, signum: int, months: int, days: int, nanoseconds: int, ) -> None: if months < 0 or days < 0 or nanoseconds < 0: raise ValueError( "months, days, nanoseconds cannot be negative. Use overall 'signum'." ) if signum not in {+1, -1}: raise ValueError("signum must be either +1 or -1.") self.months = months self.days = days self.nanoseconds = nanoseconds self.signum = signum def __repr__(self) -> str: def irepr(val: int) -> str: if val != 0 and self.signum < 0: return f"-{val}" else: return f"{val}" inner_desc = ( f"months={irepr(self.months)}, days={irepr(self.days)}, " f"nanoseconds={irepr(self.nanoseconds)}" ) return f"{self.__class__.__name__}({inner_desc})" def __str__(self) -> str: return self.to_string() def __reduce__(self) -> tuple[type, tuple[int, int, int, int]]: return self.__class__, (self.signum, self.months, self.days, self.nanoseconds) def __hash__(self) -> int: return hash((self.signum, self.months, self.days, self.nanoseconds)) @staticmethod def from_string(duration_string: str) -> DataAPIDuration: """ Parse a string, expressed according to the Data API ISO-8601 format, into a DataAPIDuration. If the format is not recognized, a ValueError is raised. Args: duration_string: a string compliant to the Data API ISO-8601 specification for durations. Returns: a DataAPIDuration corresponding to the provided input. Example: >>> from astrapy.data_types import DataAPIDuration >>> >>> DataAPIDuration.from_string("P3Y6M4DT12H30M5S") DataAPIDuration(months=42, days=4, nanoseconds=45005000000000) >>> DataAPIDuration.from_string("PT12H") DataAPIDuration(months=0, days=0, nanoseconds=43200000000000) >>> DataAPIDuration.from_string("PT12H11X") Traceback (most recent call last): [...] ValueError: Invalid fraction-of-day component for a duration string ... """ si, mo, da, ns = _parse_std_duration_string(duration_string) return DataAPIDuration( signum=si, months=mo, days=da, nanoseconds=ns, ) def to_string(self) -> str: """ Convert the DataAPIDuration to a string according to the Data API ISO-8601 standard. Returns: a string expressing the time value in a way compatible with the Data API conventions. Example: >>> from astrapy.data_types import DataAPIDuration >>> >>> dd1 = DataAPIDuration(1, months=42, days=4, nanoseconds=45005000000000) >>> dd1.to_string() 'P3Y6M4DT12H30M5S' >>> dd2 = DataAPIDuration(1, months=0, days=0, nanoseconds=43200000000000) >>> dd2.to_string() 'PT12H' """ return _build_std_duration_string( signum=self.signum, months=self.months, days=self.days, nanoseconds=self.nanoseconds, ) @staticmethod def from_c_string(duration_string: str) -> DataAPIDuration: """ Parse a string, expressed according to the Data API "Apache Cassandra(R) notation", into a DataAPIDuration. If the format is not recognized, a ValueError is raised. Args: duration_string: a string compliant to the Data API "Apache Cassandra(R) notation" for durations. Returns: a DataAPIDuration corresponding to the provided input. Example: >>> from astrapy.data_types import DataAPIDuration >>> >>> DataAPIDuration.from_c_string("12y3mo1d") DataAPIDuration(months=147, days=1, nanoseconds=0) >>> DataAPIDuration.from_c_string("12y3mo1d12h30m5s") DataAPIDuration(months=147, days=1, nanoseconds=45005000000000) >>> DataAPIDuration.from_c_string("-4h1978us") DataAPIDuration(months=0, days=0, nanoseconds=-14400001978000) >>> DataAPIDuration.from_c_string("0h0m") DataAPIDuration(months=0, days=0, nanoseconds=0) >>> DataAPIDuration.from_c_string("1h1y") Traceback (most recent call last): [...] ValueError: Unit 'y' cannot follow smaller units in literal for ... """ si, mo, da, ns = _parse_c_duration_string(duration_string) return DataAPIDuration( signum=si, months=mo, days=da, nanoseconds=ns, ) def to_c_string(self) -> str: """ Convert the DataAPIDuration to a string according to the Data API "Apache Cassandra(R) notation". Returns: a string expressing the time value in a way compatible with the Data API conventions. Example: >>> from astrapy.data_types import DataAPIDuration >>> >>> dd1 = DataAPIDuration(1, months=147, days=1, nanoseconds=0) >>> dd1.to_c_string() '12y3mo1d' >>> dd2 = DataAPIDuration(1, months=147, days=1, nanoseconds=45005000000000) >>> dd2.to_c_string() '12y3mo1d12h30m5s' >>> dd3 = DataAPIDuration(-1, months=0, days=0, nanoseconds=14400001978000) >>> dd3.to_c_string() '-4h1ms978us' >>> dd4 = DataAPIDuration(1, months=0, days=0, nanoseconds=0) >>> dd4.to_c_string() '0s' """ return _build_c_duration_string( signum=self.signum, months=self.months, days=self.days, nanoseconds=self.nanoseconds, ) @staticmethod def from_timedelta(td: datetime.timedelta) -> DataAPIDuration: """ Construct a DataAPIDuration from a Python standard-library `datetime.timedelta`. Due to the intrinsic difference between the notions of a DataAPIDuration and the timedelta, the latter - a definite time span - is only ever resulting in a duration with a nonzero "nanoseconds" component. Args: dt: a `datetime.timedelta` value. Returns: A DataAPIDuration corresponding value, with null month and day components by construction. Example: >>> from datetime import timedelta >>> >>> from astrapy.data_types import DataAPIDuration >>> >>> DataAPIDuration.from_timedelta( ... timedelta(days=1, hours=2, seconds=10.987) ... ) DataAPIDuration(months=0, days=0, nanoseconds=93610987000000) >>> DataAPIDuration.from_timedelta(timedelta(hours=-4)) DataAPIDuration(months=0, days=0, nanoseconds=-14400000000000) """ # this conversion expresses a duration with sub-days component only, # since a 'timedelta' is a precise time span (as opposed to durations). total_nanoseconds = int(td.total_seconds() * 1000000000) if total_nanoseconds >= 0: return DataAPIDuration( signum=+1, months=0, days=0, nanoseconds=total_nanoseconds, ) else: return DataAPIDuration( signum=-1, months=0, days=0, nanoseconds=-total_nanoseconds, ) def to_timedelta(self) -> datetime.timedelta: """ Convert a DataAPIDuration into a Python standard library `datetime.timedelta`. Due to the intrinsic difference between the notions of a DataAPIDuration and the timedelta, the conversion attempt raises an error if the starting DataAPIDuration has a nonzero number of months. For the same reason, a somewhat lossy conversion occurs for a nonzero number of days since the formal notion of a day is lost in favor of that of "a span of exactly 24 hours". Returns: a `datetime.timedelta` value corresponding to the origin DataAPIDuration in the best possible way. Example: >>> from astrapy.data_types import DataAPIDuration >>> >>> dd1 = DataAPIDuration( ... signum=-1, months=0, days=0, nanoseconds=93610987000000 ... ) >>> dd1.to_timedelta() datetime.timedelta(days=-2, seconds=79189, microseconds=13000) >>> dd2 = DataAPIDuration( ... signum=1, months=0, days=0, nanoseconds=14400000000000 ... ) >>> dd2.to_timedelta() datetime.timedelta(seconds=14400) >>> dd3 = DataAPIDuration(signum=1, months=19, days=0, nanoseconds=0) >>> dd3.to_timedelta() Traceback (most recent call last): [...] ValueError: Cannot convert a DataAPIDuration with nonzero months into ... """ if self.months != 0: raise ValueError( "Cannot convert a DataAPIDuration with nonzero months into a timedelta." ) return datetime.timedelta( days=self.signum * self.days, microseconds=self.signum * self.nanoseconds // 1000, )
Class variables
var days : int
var months : int
var nanoseconds : int
var signum : int
Static methods
def from_c_string(duration_string: str) ‑> DataAPIDuration
-
Parse a string, expressed according to the Data API "Apache Cassandra(R) notation", into a DataAPIDuration. If the format is not recognized, a ValueError is raised.
Args
duration_string
- a string compliant to the Data API "Apache Cassandra(R)
notation" for durations.
Returns
a DataAPIDuration corresponding to the provided input.
Example
>>> from astrapy.data_types import DataAPIDuration >>> >>> DataAPIDuration.from_c_string("12y3mo1d") DataAPIDuration(months=147, days=1, nanoseconds=0) >>> DataAPIDuration.from_c_string("12y3mo1d12h30m5s") DataAPIDuration(months=147, days=1, nanoseconds=45005000000000) >>> DataAPIDuration.from_c_string("-4h1978us") DataAPIDuration(months=0, days=0, nanoseconds=-14400001978000) >>> DataAPIDuration.from_c_string("0h0m") DataAPIDuration(months=0, days=0, nanoseconds=0) >>> DataAPIDuration.from_c_string("1h1y") Traceback (most recent call last): [...] ValueError: Unit 'y' cannot follow smaller units in literal for ...
Expand source code
@staticmethod def from_c_string(duration_string: str) -> DataAPIDuration: """ Parse a string, expressed according to the Data API "Apache Cassandra(R) notation", into a DataAPIDuration. If the format is not recognized, a ValueError is raised. Args: duration_string: a string compliant to the Data API "Apache Cassandra(R) notation" for durations. Returns: a DataAPIDuration corresponding to the provided input. Example: >>> from astrapy.data_types import DataAPIDuration >>> >>> DataAPIDuration.from_c_string("12y3mo1d") DataAPIDuration(months=147, days=1, nanoseconds=0) >>> DataAPIDuration.from_c_string("12y3mo1d12h30m5s") DataAPIDuration(months=147, days=1, nanoseconds=45005000000000) >>> DataAPIDuration.from_c_string("-4h1978us") DataAPIDuration(months=0, days=0, nanoseconds=-14400001978000) >>> DataAPIDuration.from_c_string("0h0m") DataAPIDuration(months=0, days=0, nanoseconds=0) >>> DataAPIDuration.from_c_string("1h1y") Traceback (most recent call last): [...] ValueError: Unit 'y' cannot follow smaller units in literal for ... """ si, mo, da, ns = _parse_c_duration_string(duration_string) return DataAPIDuration( signum=si, months=mo, days=da, nanoseconds=ns, )
def from_string(duration_string: str) ‑> DataAPIDuration
-
Parse a string, expressed according to the Data API ISO-8601 format, into a DataAPIDuration. If the format is not recognized, a ValueError is raised.
Args
duration_string
- a string compliant to the Data API ISO-8601 specification
for durations.
Returns
a DataAPIDuration corresponding to the provided input.
Example
>>> from astrapy.data_types import DataAPIDuration >>> >>> DataAPIDuration.from_string("P3Y6M4DT12H30M5S") DataAPIDuration(months=42, days=4, nanoseconds=45005000000000) >>> DataAPIDuration.from_string("PT12H") DataAPIDuration(months=0, days=0, nanoseconds=43200000000000) >>> DataAPIDuration.from_string("PT12H11X") Traceback (most recent call last): [...] ValueError: Invalid fraction-of-day component for a duration string ...
Expand source code
@staticmethod def from_string(duration_string: str) -> DataAPIDuration: """ Parse a string, expressed according to the Data API ISO-8601 format, into a DataAPIDuration. If the format is not recognized, a ValueError is raised. Args: duration_string: a string compliant to the Data API ISO-8601 specification for durations. Returns: a DataAPIDuration corresponding to the provided input. Example: >>> from astrapy.data_types import DataAPIDuration >>> >>> DataAPIDuration.from_string("P3Y6M4DT12H30M5S") DataAPIDuration(months=42, days=4, nanoseconds=45005000000000) >>> DataAPIDuration.from_string("PT12H") DataAPIDuration(months=0, days=0, nanoseconds=43200000000000) >>> DataAPIDuration.from_string("PT12H11X") Traceback (most recent call last): [...] ValueError: Invalid fraction-of-day component for a duration string ... """ si, mo, da, ns = _parse_std_duration_string(duration_string) return DataAPIDuration( signum=si, months=mo, days=da, nanoseconds=ns, )
def from_timedelta(td: datetime.timedelta) ‑> DataAPIDuration
-
Construct a DataAPIDuration from a Python standard-library
datetime.timedelta
.Due to the intrinsic difference between the notions of a DataAPIDuration and the timedelta, the latter - a definite time span - is only ever resulting in a duration with a nonzero "nanoseconds" component.
Args
dt
- a
datetime.timedelta
value.
Returns
A DataAPIDuration corresponding value, with null month and day components by construction.
Example
>>> from datetime import timedelta >>> >>> from astrapy.data_types import DataAPIDuration >>> >>> DataAPIDuration.from_timedelta( ... timedelta(days=1, hours=2, seconds=10.987) ... ) DataAPIDuration(months=0, days=0, nanoseconds=93610987000000) >>> DataAPIDuration.from_timedelta(timedelta(hours=-4)) DataAPIDuration(months=0, days=0, nanoseconds=-14400000000000)
Expand source code
@staticmethod def from_timedelta(td: datetime.timedelta) -> DataAPIDuration: """ Construct a DataAPIDuration from a Python standard-library `datetime.timedelta`. Due to the intrinsic difference between the notions of a DataAPIDuration and the timedelta, the latter - a definite time span - is only ever resulting in a duration with a nonzero "nanoseconds" component. Args: dt: a `datetime.timedelta` value. Returns: A DataAPIDuration corresponding value, with null month and day components by construction. Example: >>> from datetime import timedelta >>> >>> from astrapy.data_types import DataAPIDuration >>> >>> DataAPIDuration.from_timedelta( ... timedelta(days=1, hours=2, seconds=10.987) ... ) DataAPIDuration(months=0, days=0, nanoseconds=93610987000000) >>> DataAPIDuration.from_timedelta(timedelta(hours=-4)) DataAPIDuration(months=0, days=0, nanoseconds=-14400000000000) """ # this conversion expresses a duration with sub-days component only, # since a 'timedelta' is a precise time span (as opposed to durations). total_nanoseconds = int(td.total_seconds() * 1000000000) if total_nanoseconds >= 0: return DataAPIDuration( signum=+1, months=0, days=0, nanoseconds=total_nanoseconds, ) else: return DataAPIDuration( signum=-1, months=0, days=0, nanoseconds=-total_nanoseconds, )
Methods
def to_c_string(self) ‑> str
-
Convert the DataAPIDuration to a string according to the Data API "Apache Cassandra(R) notation".
Returns
a string expressing the time value in a way compatible with the Data API conventions.
Example
>>> from astrapy.data_types import DataAPIDuration >>> >>> dd1 = DataAPIDuration(1, months=147, days=1, nanoseconds=0) >>> dd1.to_c_string() '12y3mo1d' >>> dd2 = DataAPIDuration(1, months=147, days=1, nanoseconds=45005000000000) >>> dd2.to_c_string() '12y3mo1d12h30m5s' >>> dd3 = DataAPIDuration(-1, months=0, days=0, nanoseconds=14400001978000) >>> dd3.to_c_string() '-4h1ms978us' >>> dd4 = DataAPIDuration(1, months=0, days=0, nanoseconds=0) >>> dd4.to_c_string() '0s'
Expand source code
def to_c_string(self) -> str: """ Convert the DataAPIDuration to a string according to the Data API "Apache Cassandra(R) notation". Returns: a string expressing the time value in a way compatible with the Data API conventions. Example: >>> from astrapy.data_types import DataAPIDuration >>> >>> dd1 = DataAPIDuration(1, months=147, days=1, nanoseconds=0) >>> dd1.to_c_string() '12y3mo1d' >>> dd2 = DataAPIDuration(1, months=147, days=1, nanoseconds=45005000000000) >>> dd2.to_c_string() '12y3mo1d12h30m5s' >>> dd3 = DataAPIDuration(-1, months=0, days=0, nanoseconds=14400001978000) >>> dd3.to_c_string() '-4h1ms978us' >>> dd4 = DataAPIDuration(1, months=0, days=0, nanoseconds=0) >>> dd4.to_c_string() '0s' """ return _build_c_duration_string( signum=self.signum, months=self.months, days=self.days, nanoseconds=self.nanoseconds, )
def to_string(self) ‑> str
-
Convert the DataAPIDuration to a string according to the Data API ISO-8601 standard.
Returns
a string expressing the time value in a way compatible with the Data API conventions.
Example
>>> from astrapy.data_types import DataAPIDuration >>> >>> dd1 = DataAPIDuration(1, months=42, days=4, nanoseconds=45005000000000) >>> dd1.to_string() 'P3Y6M4DT12H30M5S' >>> dd2 = DataAPIDuration(1, months=0, days=0, nanoseconds=43200000000000) >>> dd2.to_string() 'PT12H'
Expand source code
def to_string(self) -> str: """ Convert the DataAPIDuration to a string according to the Data API ISO-8601 standard. Returns: a string expressing the time value in a way compatible with the Data API conventions. Example: >>> from astrapy.data_types import DataAPIDuration >>> >>> dd1 = DataAPIDuration(1, months=42, days=4, nanoseconds=45005000000000) >>> dd1.to_string() 'P3Y6M4DT12H30M5S' >>> dd2 = DataAPIDuration(1, months=0, days=0, nanoseconds=43200000000000) >>> dd2.to_string() 'PT12H' """ return _build_std_duration_string( signum=self.signum, months=self.months, days=self.days, nanoseconds=self.nanoseconds, )
def to_timedelta(self) ‑> datetime.timedelta
-
Convert a DataAPIDuration into a Python standard library
datetime.timedelta
.Due to the intrinsic difference between the notions of a DataAPIDuration and the timedelta, the conversion attempt raises an error if the starting DataAPIDuration has a nonzero number of months. For the same reason, a somewhat lossy conversion occurs for a nonzero number of days since the formal notion of a day is lost in favor of that of "a span of exactly 24 hours".
Returns
a
datetime.timedelta
value corresponding to the origin DataAPIDuration in the best possible way.Example
>>> from astrapy.data_types import DataAPIDuration >>> >>> dd1 = DataAPIDuration( ... signum=-1, months=0, days=0, nanoseconds=93610987000000 ... ) >>> dd1.to_timedelta() datetime.timedelta(days=-2, seconds=79189, microseconds=13000) >>> dd2 = DataAPIDuration( ... signum=1, months=0, days=0, nanoseconds=14400000000000 ... ) >>> dd2.to_timedelta() datetime.timedelta(seconds=14400) >>> dd3 = DataAPIDuration(signum=1, months=19, days=0, nanoseconds=0) >>> dd3.to_timedelta() Traceback (most recent call last): [...] ValueError: Cannot convert a DataAPIDuration with nonzero months into ...
Expand source code
def to_timedelta(self) -> datetime.timedelta: """ Convert a DataAPIDuration into a Python standard library `datetime.timedelta`. Due to the intrinsic difference between the notions of a DataAPIDuration and the timedelta, the conversion attempt raises an error if the starting DataAPIDuration has a nonzero number of months. For the same reason, a somewhat lossy conversion occurs for a nonzero number of days since the formal notion of a day is lost in favor of that of "a span of exactly 24 hours". Returns: a `datetime.timedelta` value corresponding to the origin DataAPIDuration in the best possible way. Example: >>> from astrapy.data_types import DataAPIDuration >>> >>> dd1 = DataAPIDuration( ... signum=-1, months=0, days=0, nanoseconds=93610987000000 ... ) >>> dd1.to_timedelta() datetime.timedelta(days=-2, seconds=79189, microseconds=13000) >>> dd2 = DataAPIDuration( ... signum=1, months=0, days=0, nanoseconds=14400000000000 ... ) >>> dd2.to_timedelta() datetime.timedelta(seconds=14400) >>> dd3 = DataAPIDuration(signum=1, months=19, days=0, nanoseconds=0) >>> dd3.to_timedelta() Traceback (most recent call last): [...] ValueError: Cannot convert a DataAPIDuration with nonzero months into ... """ if self.months != 0: raise ValueError( "Cannot convert a DataAPIDuration with nonzero months into a timedelta." ) return datetime.timedelta( days=self.signum * self.days, microseconds=self.signum * self.nanoseconds // 1000, )
class DataAPIMap (source: Iterable[tuple[T, U]] | dict[T, U] = [])
-
An immutable 'map-like' class that preserves the order and can employ non-hashable keys (which must support eq). Not designed for performance.
Despite internally preserving the order, equality between DataAPIMap instances (and with regular dicts) is independent of the order.
Expand source code
class DataAPIMap(Generic[T, U], Mapping[T, U]): """ An immutable 'map-like' class that preserves the order and can employ non-hashable keys (which must support __eq__). Not designed for performance. Despite internally preserving the order, equality between DataAPIMap instances (and with regular dicts) is independent of the order. """ _keys: list[T] _values: list[U] def __init__(self, source: Iterable[tuple[T, U]] | dict[T, U] = []) -> None: if isinstance(source, dict): self._keys, self._values = _accumulate_pairs( ([], []), source.items(), ) else: self._keys, self._values = _accumulate_pairs( ([], []), source, ) def __getitem__(self, key: T) -> U: if isinstance(key, float) and math.isnan(key): for idx, k in enumerate(self._keys): if isinstance(k, float) and math.isnan(k): return self._values[idx] raise KeyError(str(key)) else: for idx, k in enumerate(self._keys): if k == key: return self._values[idx] raise KeyError(str(key) + "//" + str(self._keys) + "//" + str(self._values)) def __iter__(self) -> Iterator[T]: return iter(self._keys) def __len__(self) -> int: return len(self._keys) def __eq__(self, other: object) -> bool: if isinstance(other, DataAPIMap): if len(self) == len(other): if all(o_k in self for o_k in other): return all(other[k] == self[k] for k in self) return False try: dother = dict(other) # type: ignore[call-overload] return all( [ len(dother) == len(self), all(o_k in self for o_k in dother), all(dother[k] == self[k] for k in self), ] ) except KeyError: return False except TypeError: pass return NotImplemented def __repr__(self) -> str: _map_repr = ", ".join( f"({repr(k)}, {repr(v)})" for k, v in zip(self._keys, self._values) ) return f"{self.__class__.__name__}([{_map_repr}])" def __str__(self) -> str: _map_repr = ", ".join(f"({k}, {v})" for k, v in zip(self._keys, self._values)) return f"{_map_repr}" def __reduce__(self) -> tuple[type, tuple[Iterable[tuple[T, U]]]]: return self.__class__, (list(zip(self._keys, self._values)),)
Ancestors
- collections.abc.Mapping
- collections.abc.Collection
- collections.abc.Sized
- collections.abc.Iterable
- collections.abc.Container
- typing.Generic
class DataAPISet (items: Iterable[T] = [])
-
An immutable 'set-like' class that preserves the order and can store non-hashable entries (entries must support eq). Not designed for performance.
Despite internally preserving the order, equality between DataAPISet instances (and with regular sets) is independent of the order.
Expand source code
class DataAPISet(Generic[T], AbstractSet[T]): """ An immutable 'set-like' class that preserves the order and can store non-hashable entries (entries must support __eq__). Not designed for performance. Despite internally preserving the order, equality between DataAPISet instances (and with regular sets) is independent of the order. """ _items: list[T] def __init__(self, items: Iterable[T] = []) -> None: self._items = _accumulate([], items) def __len__(self) -> int: return len(self._items) def __getitem__(self, i: int) -> T: return self._items[i] def __iter__(self) -> Iterator[T]: return iter(self._items) def __reversed__(self) -> Iterable[T]: return reversed(self._items) def __repr__(self) -> str: return f"{self.__class__.__name__}({self._items})" def __reduce__(self) -> tuple[type, tuple[Iterable[T]]]: return self.__class__, (self._items,) def __eq__(self, other: Any) -> bool: if isinstance(other, (set, DataAPISet)): return len(other) == len(self._items) and all( item in self for item in other ) else: return NotImplemented def __ne__(self, other: Any) -> bool: if isinstance(other, self.__class__): return self._items != other._items else: try: return len(other) != len(self._items) or any( item not in self for item in other ) except TypeError: return NotImplemented def __le__(self, other: Any) -> bool: return self.issubset(other) def __lt__(self, other: Any) -> bool: return len(other) > len(self._items) and self.issubset(other) def __ge__(self, other: Any) -> bool: return self.issuperset(other) def __gt__(self, other: Any) -> bool: return len(self._items) > len(other) and self.issuperset(other) def __and__(self, other: Any) -> DataAPISet[T]: return self._intersect(other) __rand__ = __and__ def __or__(self, other: Any) -> DataAPISet[T]: return self.union(other) __ror__ = __or__ def __sub__(self, other: Any) -> DataAPISet[T]: return self._diff(other) def __rsub__(self, other: Any) -> DataAPISet[T]: return DataAPISet(other) - self def __xor__(self, other: Any) -> DataAPISet[T]: return self.symmetric_difference(other) __rxor__ = __xor__ def __contains__(self, item: Any) -> bool: return item in self._items def isdisjoint(self, other: Any) -> bool: return len(self._intersect(other)) == 0 def issubset(self, other: Any) -> bool: return len(self._intersect(other)) == len(self._items) def issuperset(self, other: Any) -> bool: return len(self._intersect(other)) == len(other) def union(self, *others: Any) -> DataAPISet[T]: return DataAPISet( _accumulate(list(iter(self)), (item for other in others for item in other)), ) def intersection(self, *others: Any) -> DataAPISet[T]: isect = DataAPISet(iter(self)) for other in others: isect = isect._intersect(other) if not isect: break return isect def difference(self, *others: Any) -> DataAPISet[T]: diff = DataAPISet(iter(self)) for other in others: diff = diff._diff(other) if not diff: break return diff def symmetric_difference(self, other: Any) -> DataAPISet[T]: diff_self_other = self._diff(other) diff_other_self = other.difference(self) return diff_self_other.union(diff_other_self) def _diff(self, other: Any) -> DataAPISet[T]: return DataAPISet( _accumulate( [], (item for item in self._items if item not in other), ) ) def _intersect(self, other: Any) -> DataAPISet[T]: return DataAPISet( _accumulate( [], (item for item in self._items if item in other), ) )
Ancestors
- collections.abc.Set
- collections.abc.Collection
- collections.abc.Sized
- collections.abc.Iterable
- collections.abc.Container
- typing.Generic
Methods
def difference(self, *others: Any) ‑> DataAPISet[~T]
-
Expand source code
def difference(self, *others: Any) -> DataAPISet[T]: diff = DataAPISet(iter(self)) for other in others: diff = diff._diff(other) if not diff: break return diff
def intersection(self, *others: Any) ‑> DataAPISet[~T]
-
Expand source code
def intersection(self, *others: Any) -> DataAPISet[T]: isect = DataAPISet(iter(self)) for other in others: isect = isect._intersect(other) if not isect: break return isect
def isdisjoint(self, other: Any) ‑> bool
-
Return True if two sets have a null intersection.
Expand source code
def isdisjoint(self, other: Any) -> bool: return len(self._intersect(other)) == 0
def issubset(self, other: Any) ‑> bool
-
Expand source code
def issubset(self, other: Any) -> bool: return len(self._intersect(other)) == len(self._items)
def issuperset(self, other: Any) ‑> bool
-
Expand source code
def issuperset(self, other: Any) -> bool: return len(self._intersect(other)) == len(other)
def symmetric_difference(self, other: Any) ‑> DataAPISet[~T]
-
Expand source code
def symmetric_difference(self, other: Any) -> DataAPISet[T]: diff_self_other = self._diff(other) diff_other_self = other.difference(self) return diff_self_other.union(diff_other_self)
def union(self, *others: Any) ‑> DataAPISet[~T]
-
Expand source code
def union(self, *others: Any) -> DataAPISet[T]: return DataAPISet( _accumulate(list(iter(self)), (item for other in others for item in other)), )
class DataAPITime (hour: int, minute: int = 0, second: int = 0, nanosecond: int = 0)
-
A value expressing a time, composed of hours, minutes, seconds and nanoseconds, suitable for working with the "time" table column type.
This class is designed to losslessly express the time values the Data API supports, overcoming the precision limitation of Python's standard-library time (whose sub-second quantity only has microsecond precision).
DataAPITime objects are meant to easily work in the context of the Data API, hence its conversion methods from/to a string assumed the particular format employed by the API.
The class also offers conversion methods from/to the regular Python
datetime.time
; however these can entail a lossy conversion because of the missing support for nanoseconds by the latter.Args
hour
- an integer in 0-23, the hour value of the time.
minute
- an integer in 0-59, the minute value of the time.
second
- an integer in 0-59, the second value of the time.
nanosecond
- an integer in 0-999999999, the nanosecond value of the time.
Example
>>> from astrapy.data_types import DataAPITime >>> >>> DataAPITime(12, 34, 56) DataAPITime(12, 34, 56, 0) >>> t1 = DataAPITime(21, 43, 56, 789012345) >>> t1 DataAPITime(21, 43, 56, 789012345) >>> t1.second 56
Expand source code
@dataclass class DataAPITime: """ A value expressing a time, composed of hours, minutes, seconds and nanoseconds, suitable for working with the "time" table column type. This class is designed to losslessly express the time values the Data API supports, overcoming the precision limitation of Python's standard-library time (whose sub-second quantity only has microsecond precision). DataAPITime objects are meant to easily work in the context of the Data API, hence its conversion methods from/to a string assumed the particular format employed by the API. The class also offers conversion methods from/to the regular Python `datetime.time`; however these can entail a lossy conversion because of the missing support for nanoseconds by the latter. Args: hour: an integer in 0-23, the hour value of the time. minute: an integer in 0-59, the minute value of the time. second: an integer in 0-59, the second value of the time. nanosecond: an integer in 0-999999999, the nanosecond value of the time. Example: >>> from astrapy.data_types import DataAPITime >>> >>> DataAPITime(12, 34, 56) DataAPITime(12, 34, 56, 0) >>> t1 = DataAPITime(21, 43, 56, 789012345) >>> t1 DataAPITime(21, 43, 56, 789012345) >>> t1.second 56 """ hour: int minute: int second: int nanosecond: int def __init__( self, hour: int, minute: int = 0, second: int = 0, nanosecond: int = 0 ): _fail_reason = _validate_time( hour=hour, minute=minute, second=second, nanosecond=nanosecond, ) if _fail_reason: raise ValueError(f"Invalid time arguments: {_fail_reason}.") self.hour = hour self.minute = minute self.second = second self.nanosecond = nanosecond def __repr__(self) -> str: return ( f"{self.__class__.__name__}({self.hour}, {self.minute}, " f"{self.second}, {self.nanosecond})" ) def __hash__(self) -> int: return hash((self.hour, self.minute, self.second, self.nanosecond)) def __str__(self) -> str: return self.to_string() def __reduce__(self) -> tuple[type, tuple[int, int, int, int]]: return self.__class__, (self.hour, self.minute, self.second, self.nanosecond) def __le__(self, other: Any) -> bool: if isinstance(other, DataAPITime): return self._to_tuple() <= other._to_tuple() elif isinstance(other, datetime.time): return self.__le__(DataAPITime.from_time(other)) else: return NotImplemented def __lt__(self, other: Any) -> bool: if isinstance(other, DataAPITime): return self._to_tuple() < other._to_tuple() elif isinstance(other, datetime.time): return self.__lt__(DataAPITime.from_time(other)) else: return NotImplemented def __ge__(self, other: Any) -> bool: if isinstance(other, DataAPITime): return self._to_tuple() >= other._to_tuple() elif isinstance(other, datetime.time): return self.__ge__(DataAPITime.from_time(other)) else: return NotImplemented def __gt__(self, other: Any) -> bool: if isinstance(other, DataAPITime): return self._to_tuple() > other._to_tuple() elif isinstance(other, datetime.time): return self.__gt__(DataAPITime.from_time(other)) else: return NotImplemented def _to_tuple(self) -> tuple[int, int, int, int]: return (self.hour, self.minute, self.second, self.nanosecond) def to_string(self) -> str: """ Convert the DataAPITime to a string according to the Data API specification. Returns: a string expressing the time value in a way compatible with the Data API conventions. Example: >>> from astrapy.data_types import DataAPITime >>> >>> DataAPITime(12, 34, 56).to_string() '12:34:56' >>> DataAPITime(21, 43, 56, 789012345).to_string() '21:43:56.789012345' """ hm_part = f"{self.hour:02}:{self.minute:02}" s_part: str if self.nanosecond: nano_div: int nano_digits: int if self.nanosecond % 1000000 == 0: nano_div = 1000000 nano_digits = 3 elif self.nanosecond % 1000 == 0: nano_div = 1000 nano_digits = 6 else: nano_div = 1 nano_digits = 9 ns_format_string = f"%0{nano_digits}i" s_part = ( f"{self.second:02}.{ns_format_string % (self.nanosecond // nano_div)}" ) else: s_part = f"{self.second:02}" return f"{hm_part}:{s_part}" def to_time(self) -> datetime.time: """ Convert the DataAPITime into a Python standard-library `datetime.time` object. This may involve a loss of precision since the latter cannot express nanoseconds, only microseconds. Returns: a `datetime.time` object, corresponding (possibly in a lossy way) to the original DataAPITime. Example: >>> from astrapy.data_types import DataAPITime >>> >>> DataAPITime(12, 34, 56).to_time() datetime.time(12, 34, 56) >>> DataAPITime(21, 43, 56, 789012345).to_time() datetime.time(21, 43, 56, 789012) """ return datetime.time( hour=self.hour, minute=self.minute, second=self.second, microsecond=int(self.nanosecond / 1000.0), ) @staticmethod def from_time(dt: datetime.time) -> DataAPITime: """ Create a DataAPITime from a Python standard-library `datetime.time` object. Args: dt: a `datetime.time` value Returns: a DataAPITime object, corresponding exactly to the provided input. Example: >>> from datetime import time >>> >>> from astrapy.data_types import DataAPITime >>> >>> DataAPITime.from_time(time(12, 34, 56)) DataAPITime(12, 34, 56, 0) >>> DataAPITime.from_time(time(12, 34, 56, 789012)) DataAPITime(12, 34, 56, 789012000) """ return DataAPITime( hour=dt.hour, minute=dt.minute, second=dt.second, nanosecond=dt.microsecond * 1000, ) @staticmethod def from_string(time_string: str) -> DataAPITime: """ Parse a string, expressed according to the Data API format, into a DataAPITime. If the format is not recognized, a ValueError is raised. Args: time_string: a string compliant to the Data API specification for times. Returns: a DataAPITime corresponding to the provided input. Example: >>> from astrapy.data_types import DataAPITime >>> >>> DataAPITime.from_string("12:34:56") DataAPITime(12, 34, 56, 0) >>> DataAPITime.from_string("21:43:56.789012345") DataAPITime(21, 43, 56, 789012345) >>> DataAPITime.from_string("34:11:22.123") Traceback (most recent call last): [...] ValueError: Cannot parse '34:11:22.123' into a valid time: illegal hour. ... """ match = TIME_PARSE_PATTERN.match(time_string) if match: hour = int(match[1]) minute = int(match[2]) second = int(match[3]) nanosecond: int if match[4]: nanosecond = int(float(match[4]) * 1000000000) else: nanosecond = 0 _fail_reason = _validate_time( hour=hour, minute=minute, second=second, nanosecond=nanosecond, ) if _fail_reason: raise ValueError( f"Cannot parse '{time_string}' into a valid time: " f"{_fail_reason}. {TIME_FORMAT_DESC}" ) return DataAPITime( hour=hour, minute=minute, second=second, nanosecond=nanosecond, ) else: raise ValueError( f"Cannot parse '{time_string}' into a valid time " f"(unrecognized format). {TIME_FORMAT_DESC}" )
Class variables
var hour : int
var minute : int
var nanosecond : int
var second : int
Static methods
def from_string(time_string: str) ‑> DataAPITime
-
Parse a string, expressed according to the Data API format, into a DataAPITime. If the format is not recognized, a ValueError is raised.
Args
time_string
- a string compliant to the Data API specification for times.
Returns
a DataAPITime corresponding to the provided input.
Example
>>> from astrapy.data_types import DataAPITime >>> >>> DataAPITime.from_string("12:34:56") DataAPITime(12, 34, 56, 0) >>> DataAPITime.from_string("21:43:56.789012345") DataAPITime(21, 43, 56, 789012345) >>> DataAPITime.from_string("34:11:22.123") Traceback (most recent call last): [...] ValueError: Cannot parse '34:11:22.123' into a valid time: illegal hour. ...
Expand source code
@staticmethod def from_string(time_string: str) -> DataAPITime: """ Parse a string, expressed according to the Data API format, into a DataAPITime. If the format is not recognized, a ValueError is raised. Args: time_string: a string compliant to the Data API specification for times. Returns: a DataAPITime corresponding to the provided input. Example: >>> from astrapy.data_types import DataAPITime >>> >>> DataAPITime.from_string("12:34:56") DataAPITime(12, 34, 56, 0) >>> DataAPITime.from_string("21:43:56.789012345") DataAPITime(21, 43, 56, 789012345) >>> DataAPITime.from_string("34:11:22.123") Traceback (most recent call last): [...] ValueError: Cannot parse '34:11:22.123' into a valid time: illegal hour. ... """ match = TIME_PARSE_PATTERN.match(time_string) if match: hour = int(match[1]) minute = int(match[2]) second = int(match[3]) nanosecond: int if match[4]: nanosecond = int(float(match[4]) * 1000000000) else: nanosecond = 0 _fail_reason = _validate_time( hour=hour, minute=minute, second=second, nanosecond=nanosecond, ) if _fail_reason: raise ValueError( f"Cannot parse '{time_string}' into a valid time: " f"{_fail_reason}. {TIME_FORMAT_DESC}" ) return DataAPITime( hour=hour, minute=minute, second=second, nanosecond=nanosecond, ) else: raise ValueError( f"Cannot parse '{time_string}' into a valid time " f"(unrecognized format). {TIME_FORMAT_DESC}" )
def from_time(dt: datetime.time) ‑> DataAPITime
-
Create a DataAPITime from a Python standard-library
datetime.time
object.Args
dt
- a
datetime.time
value
Returns
a DataAPITime object, corresponding exactly to the provided input.
Example
>>> from datetime import time >>> >>> from astrapy.data_types import DataAPITime >>> >>> DataAPITime.from_time(time(12, 34, 56)) DataAPITime(12, 34, 56, 0) >>> DataAPITime.from_time(time(12, 34, 56, 789012)) DataAPITime(12, 34, 56, 789012000)
Expand source code
@staticmethod def from_time(dt: datetime.time) -> DataAPITime: """ Create a DataAPITime from a Python standard-library `datetime.time` object. Args: dt: a `datetime.time` value Returns: a DataAPITime object, corresponding exactly to the provided input. Example: >>> from datetime import time >>> >>> from astrapy.data_types import DataAPITime >>> >>> DataAPITime.from_time(time(12, 34, 56)) DataAPITime(12, 34, 56, 0) >>> DataAPITime.from_time(time(12, 34, 56, 789012)) DataAPITime(12, 34, 56, 789012000) """ return DataAPITime( hour=dt.hour, minute=dt.minute, second=dt.second, nanosecond=dt.microsecond * 1000, )
Methods
def to_string(self) ‑> str
-
Convert the DataAPITime to a string according to the Data API specification.
Returns
a string expressing the time value in a way compatible with the Data API conventions.
Example
>>> from astrapy.data_types import DataAPITime >>> >>> DataAPITime(12, 34, 56).to_string() '12:34:56' >>> DataAPITime(21, 43, 56, 789012345).to_string() '21:43:56.789012345'
Expand source code
def to_string(self) -> str: """ Convert the DataAPITime to a string according to the Data API specification. Returns: a string expressing the time value in a way compatible with the Data API conventions. Example: >>> from astrapy.data_types import DataAPITime >>> >>> DataAPITime(12, 34, 56).to_string() '12:34:56' >>> DataAPITime(21, 43, 56, 789012345).to_string() '21:43:56.789012345' """ hm_part = f"{self.hour:02}:{self.minute:02}" s_part: str if self.nanosecond: nano_div: int nano_digits: int if self.nanosecond % 1000000 == 0: nano_div = 1000000 nano_digits = 3 elif self.nanosecond % 1000 == 0: nano_div = 1000 nano_digits = 6 else: nano_div = 1 nano_digits = 9 ns_format_string = f"%0{nano_digits}i" s_part = ( f"{self.second:02}.{ns_format_string % (self.nanosecond // nano_div)}" ) else: s_part = f"{self.second:02}" return f"{hm_part}:{s_part}"
def to_time(self) ‑> datetime.time
-
Convert the DataAPITime into a Python standard-library
datetime.time
object. This may involve a loss of precision since the latter cannot express nanoseconds, only microseconds.Returns
a
datetime.time
object, corresponding (possibly in a lossy way) to the original DataAPITime.Example
>>> from astrapy.data_types import DataAPITime >>> >>> DataAPITime(12, 34, 56).to_time() datetime.time(12, 34, 56) >>> DataAPITime(21, 43, 56, 789012345).to_time() datetime.time(21, 43, 56, 789012)
Expand source code
def to_time(self) -> datetime.time: """ Convert the DataAPITime into a Python standard-library `datetime.time` object. This may involve a loss of precision since the latter cannot express nanoseconds, only microseconds. Returns: a `datetime.time` object, corresponding (possibly in a lossy way) to the original DataAPITime. Example: >>> from astrapy.data_types import DataAPITime >>> >>> DataAPITime(12, 34, 56).to_time() datetime.time(12, 34, 56) >>> DataAPITime(21, 43, 56, 789012345).to_time() datetime.time(21, 43, 56, 789012) """ return datetime.time( hour=self.hour, minute=self.minute, second=self.second, microsecond=int(self.nanosecond / 1000.0), )
class DataAPITimestamp (timestamp_ms: int)
-
A value expressing a unambiguous timestamp, accurate to down millisecond precision, suitable for working with the "timestamp" table column type.
A DataAPITimestamp can be thought of as an integer signed number of milliseconds elapsed since (or before) the epoch (that is, 1970-01-01 00:00:00 GMT+0). An alternative representation is that of a "date + time + offset", such as the (year, month, day; hour, minute, second, millisecond; offset), where offset is a quantity of type "hours:minutes" essentially canceling the timezone ambiguity of the remaining terms. This latter representation thus is not a bijection to the former, as different representation map to one and the same timestamp integer value.
The fact that DataAPITimestamp values are only identified by their timestamp value is one of the key differences between this class and the Python standard-library
datetime.datetime
; another important practical difference is the available year range, which for the latter spans the 1AD-9999AD time period while for the DataAPITimestamp is unlimited for most practical purposes. In particular, this class is designed to losslessly express the full date range the Data API supports.DataAPITimestamp objects are meant to easily work in the context of the Data API, hence its conversion methods from/to a string assumed the particular format employed by the API.
The class also offers conversion methods from/to the regular Python
datetime.datetime
; however these may fail if the year falls outside of the range supported by the latter.Args
timestamp_ms
- an integer number of milliseconds elapsed since the epoch. Negative numbers signify timestamp occurring before the epoch.
Example
>>> from astrapy.data_types import DataAPITimestamp >>> >>> ds1 = DataAPITimestamp(2000000000321) >>> ds1 DataAPITimestamp(timestamp_ms=2000000000321 [2033-05-18T03:33:20.321Z]) >>> ds1.timestamp_ms 2000000000321 >>> DataAPITimestamp(-1000000000321) DataAPITimestamp(timestamp_ms=-1000000000321 [1938-04-24T22:13:19.679Z])
Expand source code
@dataclass class DataAPITimestamp: """ A value expressing a unambiguous timestamp, accurate to down millisecond precision, suitable for working with the "timestamp" table column type. A DataAPITimestamp can be thought of as an integer signed number of milliseconds elapsed since (or before) the epoch (that is, 1970-01-01 00:00:00 GMT+0). An alternative representation is that of a "date + time + offset", such as the (year, month, day; hour, minute, second, millisecond; offset), where offset is a quantity of type "hours:minutes" essentially canceling the timezone ambiguity of the remaining terms. This latter representation thus is not a bijection to the former, as different representation map to one and the same timestamp integer value. The fact that DataAPITimestamp values are only identified by their timestamp value is one of the key differences between this class and the Python standard-library `datetime.datetime`; another important practical difference is the available year range, which for the latter spans the 1AD-9999AD time period while for the DataAPITimestamp is unlimited for most practical purposes. In particular, this class is designed to losslessly express the full date range the Data API supports. DataAPITimestamp objects are meant to easily work in the context of the Data API, hence its conversion methods from/to a string assumed the particular format employed by the API. The class also offers conversion methods from/to the regular Python `datetime.datetime`; however these may fail if the year falls outside of the range supported by the latter. Args: timestamp_ms: an integer number of milliseconds elapsed since the epoch. Negative numbers signify timestamp occurring before the epoch. Example: >>> from astrapy.data_types import DataAPITimestamp >>> >>> ds1 = DataAPITimestamp(2000000000321) >>> ds1 DataAPITimestamp(timestamp_ms=2000000000321 [2033-05-18T03:33:20.321Z]) >>> ds1.timestamp_ms 2000000000321 >>> DataAPITimestamp(-1000000000321) DataAPITimestamp(timestamp_ms=-1000000000321 [1938-04-24T22:13:19.679Z]) """ timestamp_ms: int def __repr__(self) -> str: return ( f"{self.__class__.__name__}(timestamp_ms={self.timestamp_ms}" f" [{self.to_string()}])" ) def __str__(self) -> str: return self.to_string() def __hash__(self) -> int: return self.timestamp_ms def __reduce__(self) -> tuple[type, tuple[int]]: return self.__class__, (self.timestamp_ms,) def __le__(self, other: Any) -> bool: if isinstance(other, DataAPITimestamp): return self.timestamp_ms <= other.timestamp_ms else: return NotImplemented def __lt__(self, other: Any) -> bool: if isinstance(other, DataAPITimestamp): return self.timestamp_ms < other.timestamp_ms else: return NotImplemented def __ge__(self, other: Any) -> bool: if isinstance(other, DataAPITimestamp): return self.timestamp_ms >= other.timestamp_ms else: return NotImplemented def __gt__(self, other: Any) -> bool: if isinstance(other, DataAPITimestamp): return self.timestamp_ms > other.timestamp_ms else: return NotImplemented def __sub__(self, other: Any) -> int: if isinstance(other, DataAPITimestamp): return self.timestamp_ms - other.timestamp_ms else: return NotImplemented def to_datetime(self, *, tz: datetime.timezone | None) -> datetime.datetime: """ Convert the DataAPITimestamp into a standard-library `datetime.datetime` value. The conversion may fail if the target year range is outside of the capabilities of `datetime.datetime`, in which case a ValueError will be raised. Args: tz: a `datetime.timezone` setting for providing offset information to the result, thus making it an "aware" datetime. If a "naive" datetime is desired (which however may lead to inconsistent timestamp handling throughout the application), it is possible to pass `tz=None`. Returns: A `datetime.datetime` value, set to the desired timezone - or naive, in case a null timezone is explicitly provided. Example: >>> import datetime >>> >>> from astrapy.data_types import DataAPITimestamp >>> >>> ds1 = DataAPITimestamp(2000000000321) >>> ds1.to_datetime(tz=datetime.timezone.utc) datetime.datetime(2033, 5, 18, 3, 33, 20, 321000, tzinfo=datetime.timezone.utc) >>> ds1.to_datetime(tz=None) datetime.datetime(2033, 5, 18, 5, 33, 20, 321000) >>> >>> ds2 = DataAPITimestamp(300000000000000) >>> ds2.to_datetime(tz=datetime.timezone.utc) Traceback (most recent call last): [...] ValueError: year 11476 is out of range """ return datetime.datetime.fromtimestamp(self.timestamp_ms / 1000.0, tz=tz) def to_naive_datetime(self) -> datetime.datetime: """ Convert the DataAPITimestamp into a standard-library naive `datetime.datetime` value, i.e. one without attached timezone/offset info. The conversion may fail if the target year range is outside of the capabilities of `datetime.datetime`, in which case a ValueError will be raised. Returns: A naive `datetime.datetime` value. The ambiguity stemming from the lack of timezone information is handed off to the standard-library `.fromtimestamp` method, which works in the timezone set by the system locale. Example: >>> import datetime >>> >>> from astrapy.data_types import DataAPITimestamp >>> >>> ds1 = DataAPITimestamp(300000000321) >>> ds1 DataAPITimestamp(timestamp_ms=300000000321 [1979-07-05T05:20:00.321Z]) >>> # running in wintertime, in the Paris/Berlin timezone: >>> ds1.to_naive_datetime() datetime.datetime(1979, 7, 5, 7, 20, 0, 321000) """ return datetime.datetime.fromtimestamp(self.timestamp_ms / 1000.0, tz=None) @staticmethod def from_datetime(dt: datetime.datetime) -> DataAPITimestamp: """ Convert a standard-library `datetime.datetime` into a DataAPITimestamp. The conversion correctly takes timezone information into account, if provided. If it is absent (naive datetime), the ambiguity is resolved by the stdlib `.timestamp()` method, which assumes the timezone set by the system locale. Args: dt: the `datetime.datetime` to convert into a DataAPITimestamp. Returns: A DataAPITimestamp, corresponding to the provided datetime. Example (running in the Paris/Berlin timezone): >>> import datetime >>> >>> from astrapy.data_types import DataAPITimestamp >>> >>> ds1 = DataAPITimestamp(300000000321) >>> ds1 DataAPITimestamp(timestamp_ms=300000000321 [1979-07-05T05:20:00.321Z]) >>> ds1.to_naive_datetime() datetime.datetime(1979, 7, 5, 7, 20, 0, 321000) """ return DataAPITimestamp(timestamp_ms=int(dt.timestamp() * 1000.0)) @staticmethod def from_string(datetime_string: str) -> DataAPITimestamp: """ Convert a string into a DataAPITimestamp, provided the string represents one according to the Data API RFC3339 format conventions. If the format is unrecognized, a ValueError is raised. Args: date_string: a string expressing a timestamp as per Data API conventions. Returns: a DataAPITimestamp corresponding to the provided input. Example: >>> from astrapy.data_types import DataAPITimestamp >>> >>> DataAPITimestamp.from_string("2021-07-18T14:56:23.987Z") DataAPITimestamp(timestamp_ms=1626620183987 [2021-07-18T14:56:23.987Z]) >>> DataAPITimestamp.from_string("-0044-03-15T11:22:33+01:00") DataAPITimestamp(timestamp_ms=-63549322647000 [-0044-03-15T10:22:33.000Z]) >>> # missing trailing offset information: >>> DataAPITimestamp.from_string("1991-11-22T01:23:45.678") Traceback (most recent call last): [...] ValueError: Cannot parse '1991-11-22T01:23:45.678' into a valid timestamp... """ _datetime_string = datetime_string.upper().replace("Z", "+00:00") match = TIMESTAMP_PARSE_PATTERN.match(_datetime_string) if match: # the year string has additional constraints besides the regexp: year_str = match[1] if year_str and year_str[0] == "+": if len(year_str[1:]) <= 4: raise ValueError( f"Cannot parse '{datetime_string}' into a valid timestamp: " "four-digit positive year should bear no plus sign. " f"{TIMESTAMP_FORMAT_DESC}" ) if len(year_str) > 4 and year_str[0] not in {"+", "-"}: raise ValueError( f"Cannot parse '{datetime_string}' into a valid timestamp: " "years with more than four digits should bear a leading sign. " f"{TIMESTAMP_FORMAT_DESC}" ) year = int(year_str) if year == 0 and year_str[0] == "-": raise ValueError( f"Cannot parse '{datetime_string}' into a valid timestamp: " "year zero should be provided as '0000' without leading sign. " f"{TIMESTAMP_FORMAT_DESC}" ) month = int(match[2]) day = int(match[3]) hour = int(match[4]) minute = int(match[5]) second = int(match[6]) millisecond: int if match[7]: millisecond = int(float(match[7]) * 1000) else: millisecond = 0 offset_hour = int(match[8]) offset_minute = int(match[9]) # validations _d_f_reason = _validate_date( year=year, month=month, day=day, ) if _d_f_reason: raise ValueError( f"Cannot parse '{datetime_string}' into a valid timestamp: " f"{_d_f_reason}. {TIMESTAMP_FORMAT_DESC}" ) _t_f_reason = _validate_time( hour=hour, minute=minute, second=second, nanosecond=millisecond * 1000000, ) if _t_f_reason: raise ValueError( f"Cannot parse '{datetime_string}' into a valid timestamp: " f"{_t_f_reason}. {TIMESTAMP_FORMAT_DESC}" ) # validate offset if offset_hour < -23 or offset_hour > 23: raise ValueError( f"Cannot parse '{datetime_string}' into a valid timestamp: " f"illegal offset hours. {TIMESTAMP_FORMAT_DESC}" ) if offset_minute < 0 or offset_hour > 59: raise ValueError( f"Cannot parse '{datetime_string}' into a valid timestamp: " f"illegal offset minutes. {TIMESTAMP_FORMAT_DESC}" ) # convert into a timestamp, part 1: year year_timestamp_ms = _year_to_unix_timestamp_ms(year) # convert into a timestamp, part 2: the rest (taking care of offset as well) ref_year = 1972 if _is_leap_year(year) else 1971 year_start_date = datetime.datetime(ref_year, 1, 1, 0, 0, 0, 0).replace( tzinfo=datetime.timezone.utc ) offset_delta = datetime.timedelta(hours=offset_hour, minutes=offset_minute) year_reset_date = datetime.datetime( ref_year, month, day, hour, minute, second, millisecond * 1000 ).replace(tzinfo=datetime.timezone.utc) in_year_timestamp_ms = int( (year_reset_date - offset_delta - year_start_date).total_seconds() * 1000 ) return DataAPITimestamp( timestamp_ms=year_timestamp_ms + in_year_timestamp_ms ) else: raise ValueError( f"Cannot parse '{datetime_string}' into a valid timestamp " f"(unrecognized format). {TIMESTAMP_FORMAT_DESC}" ) def timetuple(self) -> tuple[int, int, int, int, int, int, int]: """ Convert the DataAPITimestamp into a 7-item tuple expressing the corresponding datetime in UTC, i.e. with implied "+00:00" offset. Returns: a (year, month, day, hour, minute, second, millisecond) of integers. Note the last entry is millisecond. Example: >>> from astrapy.data_types import DataAPITimestamp >>> >>> dt1 = DataAPITimestamp(300000000321) >>> dt1 DataAPITimestamp(timestamp_ms=300000000321 [1979-07-05T05:20:00.321Z]) >>> dt1.timetuple() (1979, 7, 5, 5, 20, 0, 321) """ return _unix_timestamp_ms_to_timetuple(self.timestamp_ms) def to_string(self) -> str: """ Express the timestamp as a string according to the Data API RFC3339 syntax, including the presence of a sign, and the number of digits, for the year. The string returned from this method can be directly used in the appropriate parts of a payload to the Data API. Note that there is no parameter to control formatting (as there would be for `datetime.strftime`). To customize the formatting, one should invoke `DataAPITimestamp.timetuple` and use its output subsequently. Returns: a string, such as "2024-12-31T12:34:56.543Z", formatted in a way suitable to be used in a Data API payload. Example: >>> from astrapy.data_types import DataAPITimestamp >>> >>> dt1 = DataAPITimestamp(300000000321) >>> dt1 DataAPITimestamp(timestamp_ms=300000000321 [1979-07-05T05:20:00.321Z]) >>> dt1.to_string() '1979-07-05T05:20:00.321Z' """ y, mo, d, h, m, s, ms = self.timetuple() # the year part requires care around the sign and number of digits year_str: str if y > 9999: year_str = f"{y:+}" elif y >= 0: year_str = f"{y:04}" else: year_str = f"{y:+05}" return f"{year_str}-{mo:02}-{d:02}T{h:02}:{m:02}:{s:02}.{ms:03}Z"
Class variables
var timestamp_ms : int
Static methods
def from_datetime(dt: datetime.datetime) ‑> DataAPITimestamp
-
Convert a standard-library
datetime.datetime
into a DataAPITimestamp.The conversion correctly takes timezone information into account, if provided. If it is absent (naive datetime), the ambiguity is resolved by the stdlib
.timestamp()
method, which assumes the timezone set by the system locale.Args
dt
- the
datetime.datetime
to convert into a DataAPITimestamp.
Returns
A DataAPITimestamp, corresponding to the provided datetime. Example (running in the Paris/Berlin timezone): >>> import datetime >>> >>> from astrapy.data_types import DataAPITimestamp >>> >>> ds1 = DataAPITimestamp(300000000321) >>> ds1 DataAPITimestamp(timestamp_ms=300000000321 [1979-07-05T05:20:00.321Z]) >>> ds1.to_naive_datetime() datetime.datetime(1979, 7, 5, 7, 20, 0, 321000)
Expand source code
@staticmethod def from_datetime(dt: datetime.datetime) -> DataAPITimestamp: """ Convert a standard-library `datetime.datetime` into a DataAPITimestamp. The conversion correctly takes timezone information into account, if provided. If it is absent (naive datetime), the ambiguity is resolved by the stdlib `.timestamp()` method, which assumes the timezone set by the system locale. Args: dt: the `datetime.datetime` to convert into a DataAPITimestamp. Returns: A DataAPITimestamp, corresponding to the provided datetime. Example (running in the Paris/Berlin timezone): >>> import datetime >>> >>> from astrapy.data_types import DataAPITimestamp >>> >>> ds1 = DataAPITimestamp(300000000321) >>> ds1 DataAPITimestamp(timestamp_ms=300000000321 [1979-07-05T05:20:00.321Z]) >>> ds1.to_naive_datetime() datetime.datetime(1979, 7, 5, 7, 20, 0, 321000) """ return DataAPITimestamp(timestamp_ms=int(dt.timestamp() * 1000.0))
def from_string(datetime_string: str) ‑> DataAPITimestamp
-
Convert a string into a DataAPITimestamp, provided the string represents one according to the Data API RFC3339 format conventions. If the format is unrecognized, a ValueError is raised.
Args
date_string
- a string expressing a timestamp as per Data API conventions.
Returns
a DataAPITimestamp corresponding to the provided input.
Example
>>> from astrapy.data_types import DataAPITimestamp >>> >>> DataAPITimestamp.from_string("2021-07-18T14:56:23.987Z") DataAPITimestamp(timestamp_ms=1626620183987 [2021-07-18T14:56:23.987Z]) >>> DataAPITimestamp.from_string("-0044-03-15T11:22:33+01:00") DataAPITimestamp(timestamp_ms=-63549322647000 [-0044-03-15T10:22:33.000Z]) >>> # missing trailing offset information: >>> DataAPITimestamp.from_string("1991-11-22T01:23:45.678") Traceback (most recent call last): [...] ValueError: Cannot parse '1991-11-22T01:23:45.678' into a valid timestamp...
Expand source code
@staticmethod def from_string(datetime_string: str) -> DataAPITimestamp: """ Convert a string into a DataAPITimestamp, provided the string represents one according to the Data API RFC3339 format conventions. If the format is unrecognized, a ValueError is raised. Args: date_string: a string expressing a timestamp as per Data API conventions. Returns: a DataAPITimestamp corresponding to the provided input. Example: >>> from astrapy.data_types import DataAPITimestamp >>> >>> DataAPITimestamp.from_string("2021-07-18T14:56:23.987Z") DataAPITimestamp(timestamp_ms=1626620183987 [2021-07-18T14:56:23.987Z]) >>> DataAPITimestamp.from_string("-0044-03-15T11:22:33+01:00") DataAPITimestamp(timestamp_ms=-63549322647000 [-0044-03-15T10:22:33.000Z]) >>> # missing trailing offset information: >>> DataAPITimestamp.from_string("1991-11-22T01:23:45.678") Traceback (most recent call last): [...] ValueError: Cannot parse '1991-11-22T01:23:45.678' into a valid timestamp... """ _datetime_string = datetime_string.upper().replace("Z", "+00:00") match = TIMESTAMP_PARSE_PATTERN.match(_datetime_string) if match: # the year string has additional constraints besides the regexp: year_str = match[1] if year_str and year_str[0] == "+": if len(year_str[1:]) <= 4: raise ValueError( f"Cannot parse '{datetime_string}' into a valid timestamp: " "four-digit positive year should bear no plus sign. " f"{TIMESTAMP_FORMAT_DESC}" ) if len(year_str) > 4 and year_str[0] not in {"+", "-"}: raise ValueError( f"Cannot parse '{datetime_string}' into a valid timestamp: " "years with more than four digits should bear a leading sign. " f"{TIMESTAMP_FORMAT_DESC}" ) year = int(year_str) if year == 0 and year_str[0] == "-": raise ValueError( f"Cannot parse '{datetime_string}' into a valid timestamp: " "year zero should be provided as '0000' without leading sign. " f"{TIMESTAMP_FORMAT_DESC}" ) month = int(match[2]) day = int(match[3]) hour = int(match[4]) minute = int(match[5]) second = int(match[6]) millisecond: int if match[7]: millisecond = int(float(match[7]) * 1000) else: millisecond = 0 offset_hour = int(match[8]) offset_minute = int(match[9]) # validations _d_f_reason = _validate_date( year=year, month=month, day=day, ) if _d_f_reason: raise ValueError( f"Cannot parse '{datetime_string}' into a valid timestamp: " f"{_d_f_reason}. {TIMESTAMP_FORMAT_DESC}" ) _t_f_reason = _validate_time( hour=hour, minute=minute, second=second, nanosecond=millisecond * 1000000, ) if _t_f_reason: raise ValueError( f"Cannot parse '{datetime_string}' into a valid timestamp: " f"{_t_f_reason}. {TIMESTAMP_FORMAT_DESC}" ) # validate offset if offset_hour < -23 or offset_hour > 23: raise ValueError( f"Cannot parse '{datetime_string}' into a valid timestamp: " f"illegal offset hours. {TIMESTAMP_FORMAT_DESC}" ) if offset_minute < 0 or offset_hour > 59: raise ValueError( f"Cannot parse '{datetime_string}' into a valid timestamp: " f"illegal offset minutes. {TIMESTAMP_FORMAT_DESC}" ) # convert into a timestamp, part 1: year year_timestamp_ms = _year_to_unix_timestamp_ms(year) # convert into a timestamp, part 2: the rest (taking care of offset as well) ref_year = 1972 if _is_leap_year(year) else 1971 year_start_date = datetime.datetime(ref_year, 1, 1, 0, 0, 0, 0).replace( tzinfo=datetime.timezone.utc ) offset_delta = datetime.timedelta(hours=offset_hour, minutes=offset_minute) year_reset_date = datetime.datetime( ref_year, month, day, hour, minute, second, millisecond * 1000 ).replace(tzinfo=datetime.timezone.utc) in_year_timestamp_ms = int( (year_reset_date - offset_delta - year_start_date).total_seconds() * 1000 ) return DataAPITimestamp( timestamp_ms=year_timestamp_ms + in_year_timestamp_ms ) else: raise ValueError( f"Cannot parse '{datetime_string}' into a valid timestamp " f"(unrecognized format). {TIMESTAMP_FORMAT_DESC}" )
Methods
def timetuple(self) ‑> tuple[int, int, int, int, int, int, int]
-
Convert the DataAPITimestamp into a 7-item tuple expressing the corresponding datetime in UTC, i.e. with implied "+00:00" offset.
Returns
a (year, month, day, hour, minute, second, millisecond) of integers. Note the last entry is millisecond.
Example
>>> from astrapy.data_types import DataAPITimestamp >>> >>> dt1 = DataAPITimestamp(300000000321) >>> dt1 DataAPITimestamp(timestamp_ms=300000000321 [1979-07-05T05:20:00.321Z]) >>> dt1.timetuple() (1979, 7, 5, 5, 20, 0, 321)
Expand source code
def timetuple(self) -> tuple[int, int, int, int, int, int, int]: """ Convert the DataAPITimestamp into a 7-item tuple expressing the corresponding datetime in UTC, i.e. with implied "+00:00" offset. Returns: a (year, month, day, hour, minute, second, millisecond) of integers. Note the last entry is millisecond. Example: >>> from astrapy.data_types import DataAPITimestamp >>> >>> dt1 = DataAPITimestamp(300000000321) >>> dt1 DataAPITimestamp(timestamp_ms=300000000321 [1979-07-05T05:20:00.321Z]) >>> dt1.timetuple() (1979, 7, 5, 5, 20, 0, 321) """ return _unix_timestamp_ms_to_timetuple(self.timestamp_ms)
def to_datetime(self, *, tz: datetime.timezone | None) ‑> datetime.datetime
-
Convert the DataAPITimestamp into a standard-library
datetime.datetime
value.The conversion may fail if the target year range is outside of the capabilities of
datetime.datetime
, in which case a ValueError will be raised.Args
tz
- a
datetime.timezone
setting for providing offset information to the result, thus making it an "aware" datetime. If a "naive" datetime is desired (which however may lead to inconsistent timestamp handling throughout the application), it is possible to passtz=None
.
Returns
A
datetime.datetime
value, set to the desired timezone - or naive, in case a null timezone is explicitly provided.Example
>>> import datetime >>> >>> from astrapy.data_types import DataAPITimestamp >>> >>> ds1 = DataAPITimestamp(2000000000321) >>> ds1.to_datetime(tz=datetime.timezone.utc) datetime.datetime(2033, 5, 18, 3, 33, 20, 321000, tzinfo=datetime.timezone.utc) >>> ds1.to_datetime(tz=None) datetime.datetime(2033, 5, 18, 5, 33, 20, 321000) >>> >>> ds2 = DataAPITimestamp(300000000000000) >>> ds2.to_datetime(tz=datetime.timezone.utc) Traceback (most recent call last): [...] ValueError: year 11476 is out of range
Expand source code
def to_datetime(self, *, tz: datetime.timezone | None) -> datetime.datetime: """ Convert the DataAPITimestamp into a standard-library `datetime.datetime` value. The conversion may fail if the target year range is outside of the capabilities of `datetime.datetime`, in which case a ValueError will be raised. Args: tz: a `datetime.timezone` setting for providing offset information to the result, thus making it an "aware" datetime. If a "naive" datetime is desired (which however may lead to inconsistent timestamp handling throughout the application), it is possible to pass `tz=None`. Returns: A `datetime.datetime` value, set to the desired timezone - or naive, in case a null timezone is explicitly provided. Example: >>> import datetime >>> >>> from astrapy.data_types import DataAPITimestamp >>> >>> ds1 = DataAPITimestamp(2000000000321) >>> ds1.to_datetime(tz=datetime.timezone.utc) datetime.datetime(2033, 5, 18, 3, 33, 20, 321000, tzinfo=datetime.timezone.utc) >>> ds1.to_datetime(tz=None) datetime.datetime(2033, 5, 18, 5, 33, 20, 321000) >>> >>> ds2 = DataAPITimestamp(300000000000000) >>> ds2.to_datetime(tz=datetime.timezone.utc) Traceback (most recent call last): [...] ValueError: year 11476 is out of range """ return datetime.datetime.fromtimestamp(self.timestamp_ms / 1000.0, tz=tz)
def to_naive_datetime(self) ‑> datetime.datetime
-
Convert the DataAPITimestamp into a standard-library naive
datetime.datetime
value, i.e. one without attached timezone/offset info.The conversion may fail if the target year range is outside of the capabilities of
datetime.datetime
, in which case a ValueError will be raised.Returns
A naive
datetime.datetime
value. The ambiguity stemming from the lack of timezone information is handed off to the standard-library.fromtimestamp
method, which works in the timezone set by the system locale.Example
>>> import datetime >>> >>> from astrapy.data_types import DataAPITimestamp >>> >>> ds1 = DataAPITimestamp(300000000321) >>> ds1 DataAPITimestamp(timestamp_ms=300000000321 [1979-07-05T05:20:00.321Z]) >>> # running in wintertime, in the Paris/Berlin timezone: >>> ds1.to_naive_datetime() datetime.datetime(1979, 7, 5, 7, 20, 0, 321000)
Expand source code
def to_naive_datetime(self) -> datetime.datetime: """ Convert the DataAPITimestamp into a standard-library naive `datetime.datetime` value, i.e. one without attached timezone/offset info. The conversion may fail if the target year range is outside of the capabilities of `datetime.datetime`, in which case a ValueError will be raised. Returns: A naive `datetime.datetime` value. The ambiguity stemming from the lack of timezone information is handed off to the standard-library `.fromtimestamp` method, which works in the timezone set by the system locale. Example: >>> import datetime >>> >>> from astrapy.data_types import DataAPITimestamp >>> >>> ds1 = DataAPITimestamp(300000000321) >>> ds1 DataAPITimestamp(timestamp_ms=300000000321 [1979-07-05T05:20:00.321Z]) >>> # running in wintertime, in the Paris/Berlin timezone: >>> ds1.to_naive_datetime() datetime.datetime(1979, 7, 5, 7, 20, 0, 321000) """ return datetime.datetime.fromtimestamp(self.timestamp_ms / 1000.0, tz=None)
def to_string(self) ‑> str
-
Express the timestamp as a string according to the Data API RFC3339 syntax, including the presence of a sign, and the number of digits, for the year.
The string returned from this method can be directly used in the appropriate parts of a payload to the Data API.
Note that there is no parameter to control formatting (as there would be for
datetime.strftime
). To customize the formatting, one should invokeDataAPITimestamp.timetuple()
and use its output subsequently.Returns
a string, such as "2024-12-31T12:34:56.543Z", formatted in a way suitable to be used in a Data API payload.
Example
>>> from astrapy.data_types import DataAPITimestamp >>> >>> dt1 = DataAPITimestamp(300000000321) >>> dt1 DataAPITimestamp(timestamp_ms=300000000321 [1979-07-05T05:20:00.321Z]) >>> dt1.to_string() '1979-07-05T05:20:00.321Z'
Expand source code
def to_string(self) -> str: """ Express the timestamp as a string according to the Data API RFC3339 syntax, including the presence of a sign, and the number of digits, for the year. The string returned from this method can be directly used in the appropriate parts of a payload to the Data API. Note that there is no parameter to control formatting (as there would be for `datetime.strftime`). To customize the formatting, one should invoke `DataAPITimestamp.timetuple` and use its output subsequently. Returns: a string, such as "2024-12-31T12:34:56.543Z", formatted in a way suitable to be used in a Data API payload. Example: >>> from astrapy.data_types import DataAPITimestamp >>> >>> dt1 = DataAPITimestamp(300000000321) >>> dt1 DataAPITimestamp(timestamp_ms=300000000321 [1979-07-05T05:20:00.321Z]) >>> dt1.to_string() '1979-07-05T05:20:00.321Z' """ y, mo, d, h, m, s, ms = self.timetuple() # the year part requires care around the sign and number of digits year_str: str if y > 9999: year_str = f"{y:+}" elif y >= 0: year_str = f"{y:04}" else: year_str = f"{y:+05}" return f"{year_str}-{mo:02}-{d:02}T{h:02}:{m:02}:{s:02}.{ms:03}Z"
class DataAPIVector (vector: list[float] = [])
-
A class wrapping a list of float numbers to be treated as a "vector" within the Data API. This class has the same functionalities as the underlying
list[float]
, plus it can be used to signal the Data API that a certain list of numbers can be encoded as a binary object (which improves on the performance and bandwidth of the write operations to the Data API).Attributes
data
- a list of float numbers, the underlying content of the vector
n
- the number of components, i.e. the length of the list.
Example
>>> from astrapy.data_types import DataAPIVector >>> >>> v1 = DataAPIVector([0.1, -0.2, 0.3]) >>> print(v1.to_bytes()) b'=\xcc\xcc\xcd\xbeL\xcc\xcd>\x99\x99\x9a' >>> DataAPIVector.from_bytes(b"=\xcc\xcc\xcd\xbeL\xcc\xcd>\x99\x99\x9a") DataAPIVector([0.10000000149011612, -0.20000000298023224, 0.30000001192092896]) >>> for i, x in enumerate(v1): ... print(f"component {i} => {x}") ... component 0 => 0.1 component 1 => -0.2 component 2 => 0.3
Expand source code
@dataclass class DataAPIVector(FloatList): r""" A class wrapping a list of float numbers to be treated as a "vector" within the Data API. This class has the same functionalities as the underlying `list[float]`, plus it can be used to signal the Data API that a certain list of numbers can be encoded as a binary object (which improves on the performance and bandwidth of the write operations to the Data API). Attributes: data: a list of float numbers, the underlying content of the vector n: the number of components, i.e. the length of the list. Example: >>> from astrapy.data_types import DataAPIVector >>> >>> v1 = DataAPIVector([0.1, -0.2, 0.3]) >>> print(v1.to_bytes()) b'=\xcc\xcc\xcd\xbeL\xcc\xcd>\x99\x99\x9a' >>> DataAPIVector.from_bytes(b"=\xcc\xcc\xcd\xbeL\xcc\xcd>\x99\x99\x9a") DataAPIVector([0.10000000149011612, -0.20000000298023224, 0.30000001192092896]) >>> for i, x in enumerate(v1): ... print(f"component {i} => {x}") ... component 0 => 0.1 component 1 => -0.2 component 2 => 0.3 """ data: list[float] n: int def __init__(self, vector: list[float] = []) -> None: self.data = vector self.n = len(self.data) def __iter__(self) -> Iterator[float]: return iter(self.data) def __hash__(self) -> int: return hash(tuple(self.data)) def __repr__(self) -> str: if self.n < 5: return f"{self.__class__.__name__}({self.data})" else: data_start = f"[{', '.join(str(x) for x in self.data[:3])} ...]" return f"{self.__class__.__name__}({data_start}, n={self.n})" def __str__(self) -> str: if self.n < 5: return str(self.data) else: return f"[{', '.join(str(x) for x in self.data[:3])} ...]" def to_bytes(self) -> bytes: """ Convert the vector into its binary blob (`bytes`) representation, according to the Data API convention (including endianness). Returns: a `bytes` object, expressing the vector values in a lossless way. """ return floats_to_bytes(self.data, self.n) @staticmethod def from_bytes(byte_blob: bytes) -> DataAPIVector: """ Create a DataAPIVector from a binary blob, decoding its contents according to the Data API convention (including endianness). Args: byte_blob: a binary sequence, encoding a vector of floats as specified by the Data API convention. Returns: a DataAPIVector corresponding to the provided blob. """ return DataAPIVector(bytes_to_floats(byte_blob))
Ancestors
- collections.UserList
- collections.abc.MutableSequence
- collections.abc.Sequence
- collections.abc.Reversible
- collections.abc.Collection
- collections.abc.Sized
- collections.abc.Iterable
- collections.abc.Container
Class variables
var data : list[float]
var n : int
Static methods
def from_bytes(byte_blob: bytes) ‑> DataAPIVector
-
Create a DataAPIVector from a binary blob, decoding its contents according to the Data API convention (including endianness).
Args
byte_blob
- a binary sequence, encoding a vector of floats as specified
by the Data API convention.
Returns
a DataAPIVector corresponding to the provided blob.
Expand source code
@staticmethod def from_bytes(byte_blob: bytes) -> DataAPIVector: """ Create a DataAPIVector from a binary blob, decoding its contents according to the Data API convention (including endianness). Args: byte_blob: a binary sequence, encoding a vector of floats as specified by the Data API convention. Returns: a DataAPIVector corresponding to the provided blob. """ return DataAPIVector(bytes_to_floats(byte_blob))
Methods
def to_bytes(self) ‑> bytes
-
Convert the vector into its binary blob (
bytes
) representation, according to the Data API convention (including endianness).Returns
a
bytes
object, expressing the vector values in a lossless way.Expand source code
def to_bytes(self) -> bytes: """ Convert the vector into its binary blob (`bytes`) representation, according to the Data API convention (including endianness). Returns: a `bytes` object, expressing the vector values in a lossless way. """ return floats_to_bytes(self.data, self.n)