edq.util.time

  1import datetime
  2import re
  3import time
  4import typing
  5
  6import edq.util.serial
  7
  8PRETTY_SHORT_FORMAT: str = '%Y-%m-%d %H:%M'
  9"""
 10The format string for a pretty timestamp.
 11See: https://docs.python.org/3/library/datetime.html#strftime-strptime-behavior
 12"""
 13
 14DEFAULT_EMBEDDED_PATTERN: str = r'<timestamp:(-?\d+|nil)>'
 15""" A regex for matching an embedded timestamp. """
 16
 17UTC: datetime.timezone = datetime.timezone.utc
 18""" A shortcut for the UTC timezone. """
 19
 20UNIXTIME_THRESHOLD_SECS: int = int(1e10)
 21""" Epoch time guessing threshold for seconds. """
 22
 23UNIXTIME_THRESHOLD_MSECS: int = int(1e13)
 24""" Epoch time guessing threshold for milliseconds. """
 25
 26UNIXTIME_THRESHOLD_USECS: int = int(1e16)
 27""" Epoch time guessing threshold for nanoseconds. """
 28
 29_testing_timezone: typing.Union[datetime.tzinfo, None] = None  # pylint: disable=invalid-name
 30""" A timezone to use for testing. """
 31
 32def set_testing_local_timezone(timezone: typing.Union[datetime.tzinfo, None] = UTC) -> None:
 33    """
 34    Force the local timezone to be a specific value (UTC by default).
 35    This will only affect this package (e.g., the stdlib will not be affected).
 36    """
 37
 38    global _testing_timezone  # pylint: disable=global-statement
 39    _testing_timezone = timezone
 40
 41class Duration(int):
 42    """
 43    A Duration represents some length in time in milliseconds.
 44    """
 45
 46    def to_secs(self) -> float:
 47        """ Convert the duration to float seconds. """
 48
 49        return self / 1000.0
 50
 51    def to_msecs(self) -> int:
 52        """ Convert the duration to integer milliseconds. """
 53
 54        return self
 55
 56class Timestamp(int, edq.util.serial.PODConverter):  # type: ignore[misc]
 57    """
 58    A Timestamp represent a moment in time (sometimes called "datetimes").
 59    Timestamps are internally represented by the number of milliseconds since the
 60    (Unix Epoch)[https://en.wikipedia.org/wiki/Unix_time].
 61    This is sometimes referred to as "Unix Time".
 62    Since Unix Time is in UTC, timestamps do not need to carry timestamp information with them.
 63
 64    Note that timestamps are just integers with some decoration,
 65    so they respond to all normal int functionality.
 66    """
 67
 68    def sub(self, other: 'Timestamp') -> Duration:
 69        """ Return a new duration that is the difference of this and the given duration. """
 70
 71        return Duration(self - other)
 72
 73    def to_pytime(self, timezone: typing.Union[datetime.tzinfo, None] = None) -> datetime.datetime:
 74        """ Convert this timestamp to a Python datetime in the given timezone (local by default). """
 75
 76        if (timezone is None):
 77            timezone = get_local_timezone()
 78
 79        return datetime.datetime.fromtimestamp(self / 1000, timezone)
 80
 81    def to_local_pytime(self) -> datetime.datetime:
 82        """ Convert this timestamp to a Python datetime in the system timezone. """
 83
 84        return self.to_pytime(timezone = get_local_timezone())
 85
 86    def pretty(self, short: bool = False, timezone: typing.Union[datetime.tzinfo, None] = None) -> str:
 87        """
 88        Get a "pretty" string representation of this timestamp.
 89        There is no guarantee that this representation can be parsed back to its original form.
 90
 91        If no timezone is provided, the system's local timezone will be used.
 92        """
 93
 94        if (timezone is None):
 95            timezone = get_local_timezone()
 96
 97        pytime = self.to_pytime(timezone = timezone)
 98
 99        if (short):
100            return pytime.strftime(PRETTY_SHORT_FORMAT)
101
102        return pytime.isoformat(timespec = 'milliseconds')
103
104    def strftime(self, format_string: str, timezone: typing.Union[datetime.tzinfo, None] = None) -> str:
105        """
106        Convert the timesstamp to Python datetime and then call strftime with the given format.
107        """
108
109        if (timezone is None):
110            timezone = get_local_timezone()
111
112        pytime = self.to_pytime(timezone = timezone)
113
114        return pytime.strftime(format_string)
115
116    @staticmethod
117    def from_pytime(pytime: datetime.datetime) -> 'Timestamp':
118        """ Convert a Python datetime to a timestamp. """
119
120        return Timestamp(int(pytime.timestamp() * 1000))
121
122    @staticmethod
123    def now() -> 'Timestamp':
124        """ Get a Timestamp that represents the current moment. """
125
126        return Timestamp(time.time() * 1000)
127
128    @staticmethod
129    def convert_embedded(
130            text: str,
131            embedded_pattern: str = DEFAULT_EMBEDDED_PATTERN,
132            pretty: bool = False,
133            short: bool = True,
134            timezone: typing.Union[datetime.tzinfo, None] = None,
135            ) -> str:
136        """
137        Look for any timestamps embedded in the text and replace them.
138        """
139
140        while True:
141            match = re.search(embedded_pattern, text)
142            if (match is None):
143                break
144
145            initial_text = match.group(0)
146            timestamp_text = match.group(1)
147
148            timestamp = Timestamp()
149            if (timestamp_text != 'nil'):
150                timestamp = Timestamp(int(timestamp_text))
151
152            replacement_text = str(timestamp)
153            if (pretty):
154                replacement_text = timestamp.pretty(short = short, timezone = timezone)
155
156            text = text.replace(initial_text, replacement_text)
157
158        return text
159
160    @staticmethod
161    def guess(
162            value: typing.Any,
163            default_timezone: typing.Union[datetime.tzinfo, None] = None,
164            use_local_timezone: bool = True,
165            ) -> 'Timestamp':
166        """
167        Try to parse a timestamp out of a value.
168        Empty values will get zero timestamps.
169        Purely digit strings will be converted to ints and treated as UNIX times.
170        Floats will be considered UNIX epoch seconds and converted to milliseconds.
171        Other strings will be attempted to be parsed with datetime.fromisoformat().
172
173        If a value, but not a timezone, was parsed,
174        then set the timezone for the parsed value to `default_timezone`.
175        If `default_timezone` is None and `use_local_timezone` is true,
176        then use get_local_timezone() to get the timezone.
177        """
178
179        if ((default_timezone is None) and use_local_timezone):
180            default_timezone = get_local_timezone()
181
182        raw_value = value
183
184        # Empty timestamp.
185        if (value is None):
186            return Timestamp(0)
187
188        # Check for already parsed timestamps.
189        if (isinstance(value, Timestamp)):
190            return value
191
192        # Floats are assumed to be epoch seconds.
193        if (isinstance(value, float)):
194            value = int(1000 * value)
195
196        # At this point, we only want to be dealing with strings or ints.
197        if (not isinstance(value, (int, str))):
198            value = str(value)
199
200        # Check for string specifics.
201        if (isinstance(value, str)):
202            # Check for empty strings.
203            value = value.strip()
204            if (len(value) == 0):
205                return Timestamp(0)
206
207            # Check for digit or float strings.
208            if (re.match(r'^\d+\.\d+$', value) is not None):
209                value = int(1000 * float(value))
210            elif (re.match(r'^\d+$', value) is not None):
211                value = int(value)
212
213        if (isinstance(value, int)):
214		    # Use reasonable thresholds to guess the units of the value (sec, msec, usec, nsec).
215            if (value < UNIXTIME_THRESHOLD_SECS):
216                # Time is in seconds.
217                return Timestamp(value * 1000)
218            elif (value < UNIXTIME_THRESHOLD_MSECS):
219                # Time is in milliseconds.
220                return Timestamp(value)
221            elif (value < UNIXTIME_THRESHOLD_USECS):
222                # Time is in microseconds.
223                return Timestamp(value / 1000)
224            else:
225                # Time is in nanoseconds.
226                return Timestamp(value / 1000 / 1000)
227
228        # Try to convert from an ISO string.
229
230        # Parse out some cases that Python <= 3.10 cannot deal with.
231        # This will remove fractional seconds.
232        value = re.sub(r'Z$', '+00:00', value)
233        value = re.sub(r'(\d\d:\d\d)(\.\d+)', r'\1', value)
234
235        try:
236            value = datetime.datetime.fromisoformat(value)
237        except Exception as ex:
238            raise ValueError(f"Failed to parse timestamp string '{raw_value}'.") from ex
239
240        # If no timezone was parsed, use the default one.
241        if (value.tzinfo is None):
242            value = value.replace(tzinfo = default_timezone)
243
244        return Timestamp.from_pytime(value)
245
246    def to_pod(self,
247            context: typing.Union[edq.util.serial.SerializationContext, None] = None,
248            ) -> int:
249        return self
250
251    @classmethod
252    def from_pod(cls: typing.Type['Timestamp'],
253            data: typing.Any,
254            context: typing.Union[edq.util.serial.SerializationContext, None] = None,
255            ) -> 'Timestamp':
256        return cls.guess(data)
257
258def get_local_timezone() -> datetime.tzinfo:
259    """ Get the local (system) timezone or raise an exception. """
260
261    if (_testing_timezone is not None):
262        return _testing_timezone
263
264    local_timezone = datetime.datetime.now().astimezone().tzinfo
265    if ((local_timezone is None) or (not isinstance(local_timezone, datetime.tzinfo))):
266        raise ValueError("Could not discover local timezone.")
267
268    return local_timezone
PRETTY_SHORT_FORMAT: str = '%Y-%m-%d %H:%M'
DEFAULT_EMBEDDED_PATTERN: str = '<timestamp:(-?\\d+|nil)>'

A regex for matching an embedded timestamp.

UTC: datetime.timezone = datetime.timezone.utc

A shortcut for the UTC timezone.

UNIXTIME_THRESHOLD_SECS: int = 10000000000

Epoch time guessing threshold for seconds.

UNIXTIME_THRESHOLD_MSECS: int = 10000000000000

Epoch time guessing threshold for milliseconds.

UNIXTIME_THRESHOLD_USECS: int = 10000000000000000

Epoch time guessing threshold for nanoseconds.

def set_testing_local_timezone(timezone: Optional[datetime.tzinfo] = datetime.timezone.utc) -> None:
33def set_testing_local_timezone(timezone: typing.Union[datetime.tzinfo, None] = UTC) -> None:
34    """
35    Force the local timezone to be a specific value (UTC by default).
36    This will only affect this package (e.g., the stdlib will not be affected).
37    """
38
39    global _testing_timezone  # pylint: disable=global-statement
40    _testing_timezone = timezone

Force the local timezone to be a specific value (UTC by default). This will only affect this package (e.g., the stdlib will not be affected).

class Duration(builtins.int):
42class Duration(int):
43    """
44    A Duration represents some length in time in milliseconds.
45    """
46
47    def to_secs(self) -> float:
48        """ Convert the duration to float seconds. """
49
50        return self / 1000.0
51
52    def to_msecs(self) -> int:
53        """ Convert the duration to integer milliseconds. """
54
55        return self

A Duration represents some length in time in milliseconds.

def to_secs(self) -> float:
47    def to_secs(self) -> float:
48        """ Convert the duration to float seconds. """
49
50        return self / 1000.0

Convert the duration to float seconds.

def to_msecs(self) -> int:
52    def to_msecs(self) -> int:
53        """ Convert the duration to integer milliseconds. """
54
55        return self

Convert the duration to integer milliseconds.

class Timestamp(builtins.int, edq.util.serial.PODConverter):
 57class Timestamp(int, edq.util.serial.PODConverter):  # type: ignore[misc]
 58    """
 59    A Timestamp represent a moment in time (sometimes called "datetimes").
 60    Timestamps are internally represented by the number of milliseconds since the
 61    (Unix Epoch)[https://en.wikipedia.org/wiki/Unix_time].
 62    This is sometimes referred to as "Unix Time".
 63    Since Unix Time is in UTC, timestamps do not need to carry timestamp information with them.
 64
 65    Note that timestamps are just integers with some decoration,
 66    so they respond to all normal int functionality.
 67    """
 68
 69    def sub(self, other: 'Timestamp') -> Duration:
 70        """ Return a new duration that is the difference of this and the given duration. """
 71
 72        return Duration(self - other)
 73
 74    def to_pytime(self, timezone: typing.Union[datetime.tzinfo, None] = None) -> datetime.datetime:
 75        """ Convert this timestamp to a Python datetime in the given timezone (local by default). """
 76
 77        if (timezone is None):
 78            timezone = get_local_timezone()
 79
 80        return datetime.datetime.fromtimestamp(self / 1000, timezone)
 81
 82    def to_local_pytime(self) -> datetime.datetime:
 83        """ Convert this timestamp to a Python datetime in the system timezone. """
 84
 85        return self.to_pytime(timezone = get_local_timezone())
 86
 87    def pretty(self, short: bool = False, timezone: typing.Union[datetime.tzinfo, None] = None) -> str:
 88        """
 89        Get a "pretty" string representation of this timestamp.
 90        There is no guarantee that this representation can be parsed back to its original form.
 91
 92        If no timezone is provided, the system's local timezone will be used.
 93        """
 94
 95        if (timezone is None):
 96            timezone = get_local_timezone()
 97
 98        pytime = self.to_pytime(timezone = timezone)
 99
100        if (short):
101            return pytime.strftime(PRETTY_SHORT_FORMAT)
102
103        return pytime.isoformat(timespec = 'milliseconds')
104
105    def strftime(self, format_string: str, timezone: typing.Union[datetime.tzinfo, None] = None) -> str:
106        """
107        Convert the timesstamp to Python datetime and then call strftime with the given format.
108        """
109
110        if (timezone is None):
111            timezone = get_local_timezone()
112
113        pytime = self.to_pytime(timezone = timezone)
114
115        return pytime.strftime(format_string)
116
117    @staticmethod
118    def from_pytime(pytime: datetime.datetime) -> 'Timestamp':
119        """ Convert a Python datetime to a timestamp. """
120
121        return Timestamp(int(pytime.timestamp() * 1000))
122
123    @staticmethod
124    def now() -> 'Timestamp':
125        """ Get a Timestamp that represents the current moment. """
126
127        return Timestamp(time.time() * 1000)
128
129    @staticmethod
130    def convert_embedded(
131            text: str,
132            embedded_pattern: str = DEFAULT_EMBEDDED_PATTERN,
133            pretty: bool = False,
134            short: bool = True,
135            timezone: typing.Union[datetime.tzinfo, None] = None,
136            ) -> str:
137        """
138        Look for any timestamps embedded in the text and replace them.
139        """
140
141        while True:
142            match = re.search(embedded_pattern, text)
143            if (match is None):
144                break
145
146            initial_text = match.group(0)
147            timestamp_text = match.group(1)
148
149            timestamp = Timestamp()
150            if (timestamp_text != 'nil'):
151                timestamp = Timestamp(int(timestamp_text))
152
153            replacement_text = str(timestamp)
154            if (pretty):
155                replacement_text = timestamp.pretty(short = short, timezone = timezone)
156
157            text = text.replace(initial_text, replacement_text)
158
159        return text
160
161    @staticmethod
162    def guess(
163            value: typing.Any,
164            default_timezone: typing.Union[datetime.tzinfo, None] = None,
165            use_local_timezone: bool = True,
166            ) -> 'Timestamp':
167        """
168        Try to parse a timestamp out of a value.
169        Empty values will get zero timestamps.
170        Purely digit strings will be converted to ints and treated as UNIX times.
171        Floats will be considered UNIX epoch seconds and converted to milliseconds.
172        Other strings will be attempted to be parsed with datetime.fromisoformat().
173
174        If a value, but not a timezone, was parsed,
175        then set the timezone for the parsed value to `default_timezone`.
176        If `default_timezone` is None and `use_local_timezone` is true,
177        then use get_local_timezone() to get the timezone.
178        """
179
180        if ((default_timezone is None) and use_local_timezone):
181            default_timezone = get_local_timezone()
182
183        raw_value = value
184
185        # Empty timestamp.
186        if (value is None):
187            return Timestamp(0)
188
189        # Check for already parsed timestamps.
190        if (isinstance(value, Timestamp)):
191            return value
192
193        # Floats are assumed to be epoch seconds.
194        if (isinstance(value, float)):
195            value = int(1000 * value)
196
197        # At this point, we only want to be dealing with strings or ints.
198        if (not isinstance(value, (int, str))):
199            value = str(value)
200
201        # Check for string specifics.
202        if (isinstance(value, str)):
203            # Check for empty strings.
204            value = value.strip()
205            if (len(value) == 0):
206                return Timestamp(0)
207
208            # Check for digit or float strings.
209            if (re.match(r'^\d+\.\d+$', value) is not None):
210                value = int(1000 * float(value))
211            elif (re.match(r'^\d+$', value) is not None):
212                value = int(value)
213
214        if (isinstance(value, int)):
215		    # Use reasonable thresholds to guess the units of the value (sec, msec, usec, nsec).
216            if (value < UNIXTIME_THRESHOLD_SECS):
217                # Time is in seconds.
218                return Timestamp(value * 1000)
219            elif (value < UNIXTIME_THRESHOLD_MSECS):
220                # Time is in milliseconds.
221                return Timestamp(value)
222            elif (value < UNIXTIME_THRESHOLD_USECS):
223                # Time is in microseconds.
224                return Timestamp(value / 1000)
225            else:
226                # Time is in nanoseconds.
227                return Timestamp(value / 1000 / 1000)
228
229        # Try to convert from an ISO string.
230
231        # Parse out some cases that Python <= 3.10 cannot deal with.
232        # This will remove fractional seconds.
233        value = re.sub(r'Z$', '+00:00', value)
234        value = re.sub(r'(\d\d:\d\d)(\.\d+)', r'\1', value)
235
236        try:
237            value = datetime.datetime.fromisoformat(value)
238        except Exception as ex:
239            raise ValueError(f"Failed to parse timestamp string '{raw_value}'.") from ex
240
241        # If no timezone was parsed, use the default one.
242        if (value.tzinfo is None):
243            value = value.replace(tzinfo = default_timezone)
244
245        return Timestamp.from_pytime(value)
246
247    def to_pod(self,
248            context: typing.Union[edq.util.serial.SerializationContext, None] = None,
249            ) -> int:
250        return self
251
252    @classmethod
253    def from_pod(cls: typing.Type['Timestamp'],
254            data: typing.Any,
255            context: typing.Union[edq.util.serial.SerializationContext, None] = None,
256            ) -> 'Timestamp':
257        return cls.guess(data)

A Timestamp represent a moment in time (sometimes called "datetimes"). Timestamps are internally represented by the number of milliseconds since the (Unix Epoch)[https://en.wikipedia.org/wiki/Unix_time]. This is sometimes referred to as "Unix Time". Since Unix Time is in UTC, timestamps do not need to carry timestamp information with them.

Note that timestamps are just integers with some decoration, so they respond to all normal int functionality.

def sub(self, other: Timestamp) -> Duration:
69    def sub(self, other: 'Timestamp') -> Duration:
70        """ Return a new duration that is the difference of this and the given duration. """
71
72        return Duration(self - other)

Return a new duration that is the difference of this and the given duration.

def to_pytime(self, timezone: Optional[datetime.tzinfo] = None) -> datetime.datetime:
74    def to_pytime(self, timezone: typing.Union[datetime.tzinfo, None] = None) -> datetime.datetime:
75        """ Convert this timestamp to a Python datetime in the given timezone (local by default). """
76
77        if (timezone is None):
78            timezone = get_local_timezone()
79
80        return datetime.datetime.fromtimestamp(self / 1000, timezone)

Convert this timestamp to a Python datetime in the given timezone (local by default).

def to_local_pytime(self) -> datetime.datetime:
82    def to_local_pytime(self) -> datetime.datetime:
83        """ Convert this timestamp to a Python datetime in the system timezone. """
84
85        return self.to_pytime(timezone = get_local_timezone())

Convert this timestamp to a Python datetime in the system timezone.

def pretty( self, short: bool = False, timezone: Optional[datetime.tzinfo] = None) -> str:
 87    def pretty(self, short: bool = False, timezone: typing.Union[datetime.tzinfo, None] = None) -> str:
 88        """
 89        Get a "pretty" string representation of this timestamp.
 90        There is no guarantee that this representation can be parsed back to its original form.
 91
 92        If no timezone is provided, the system's local timezone will be used.
 93        """
 94
 95        if (timezone is None):
 96            timezone = get_local_timezone()
 97
 98        pytime = self.to_pytime(timezone = timezone)
 99
100        if (short):
101            return pytime.strftime(PRETTY_SHORT_FORMAT)
102
103        return pytime.isoformat(timespec = 'milliseconds')

Get a "pretty" string representation of this timestamp. There is no guarantee that this representation can be parsed back to its original form.

If no timezone is provided, the system's local timezone will be used.

def strftime( self, format_string: str, timezone: Optional[datetime.tzinfo] = None) -> str:
105    def strftime(self, format_string: str, timezone: typing.Union[datetime.tzinfo, None] = None) -> str:
106        """
107        Convert the timesstamp to Python datetime and then call strftime with the given format.
108        """
109
110        if (timezone is None):
111            timezone = get_local_timezone()
112
113        pytime = self.to_pytime(timezone = timezone)
114
115        return pytime.strftime(format_string)

Convert the timesstamp to Python datetime and then call strftime with the given format.

@staticmethod
def from_pytime(pytime: datetime.datetime) -> Timestamp:
117    @staticmethod
118    def from_pytime(pytime: datetime.datetime) -> 'Timestamp':
119        """ Convert a Python datetime to a timestamp. """
120
121        return Timestamp(int(pytime.timestamp() * 1000))

Convert a Python datetime to a timestamp.

@staticmethod
def now() -> Timestamp:
123    @staticmethod
124    def now() -> 'Timestamp':
125        """ Get a Timestamp that represents the current moment. """
126
127        return Timestamp(time.time() * 1000)

Get a Timestamp that represents the current moment.

@staticmethod
def convert_embedded( text: str, embedded_pattern: str = '<timestamp:(-?\\d+|nil)>', pretty: bool = False, short: bool = True, timezone: Optional[datetime.tzinfo] = None) -> str:
129    @staticmethod
130    def convert_embedded(
131            text: str,
132            embedded_pattern: str = DEFAULT_EMBEDDED_PATTERN,
133            pretty: bool = False,
134            short: bool = True,
135            timezone: typing.Union[datetime.tzinfo, None] = None,
136            ) -> str:
137        """
138        Look for any timestamps embedded in the text and replace them.
139        """
140
141        while True:
142            match = re.search(embedded_pattern, text)
143            if (match is None):
144                break
145
146            initial_text = match.group(0)
147            timestamp_text = match.group(1)
148
149            timestamp = Timestamp()
150            if (timestamp_text != 'nil'):
151                timestamp = Timestamp(int(timestamp_text))
152
153            replacement_text = str(timestamp)
154            if (pretty):
155                replacement_text = timestamp.pretty(short = short, timezone = timezone)
156
157            text = text.replace(initial_text, replacement_text)
158
159        return text

Look for any timestamps embedded in the text and replace them.

@staticmethod
def guess( value: Any, default_timezone: Optional[datetime.tzinfo] = None, use_local_timezone: bool = True) -> Timestamp:
161    @staticmethod
162    def guess(
163            value: typing.Any,
164            default_timezone: typing.Union[datetime.tzinfo, None] = None,
165            use_local_timezone: bool = True,
166            ) -> 'Timestamp':
167        """
168        Try to parse a timestamp out of a value.
169        Empty values will get zero timestamps.
170        Purely digit strings will be converted to ints and treated as UNIX times.
171        Floats will be considered UNIX epoch seconds and converted to milliseconds.
172        Other strings will be attempted to be parsed with datetime.fromisoformat().
173
174        If a value, but not a timezone, was parsed,
175        then set the timezone for the parsed value to `default_timezone`.
176        If `default_timezone` is None and `use_local_timezone` is true,
177        then use get_local_timezone() to get the timezone.
178        """
179
180        if ((default_timezone is None) and use_local_timezone):
181            default_timezone = get_local_timezone()
182
183        raw_value = value
184
185        # Empty timestamp.
186        if (value is None):
187            return Timestamp(0)
188
189        # Check for already parsed timestamps.
190        if (isinstance(value, Timestamp)):
191            return value
192
193        # Floats are assumed to be epoch seconds.
194        if (isinstance(value, float)):
195            value = int(1000 * value)
196
197        # At this point, we only want to be dealing with strings or ints.
198        if (not isinstance(value, (int, str))):
199            value = str(value)
200
201        # Check for string specifics.
202        if (isinstance(value, str)):
203            # Check for empty strings.
204            value = value.strip()
205            if (len(value) == 0):
206                return Timestamp(0)
207
208            # Check for digit or float strings.
209            if (re.match(r'^\d+\.\d+$', value) is not None):
210                value = int(1000 * float(value))
211            elif (re.match(r'^\d+$', value) is not None):
212                value = int(value)
213
214        if (isinstance(value, int)):
215		    # Use reasonable thresholds to guess the units of the value (sec, msec, usec, nsec).
216            if (value < UNIXTIME_THRESHOLD_SECS):
217                # Time is in seconds.
218                return Timestamp(value * 1000)
219            elif (value < UNIXTIME_THRESHOLD_MSECS):
220                # Time is in milliseconds.
221                return Timestamp(value)
222            elif (value < UNIXTIME_THRESHOLD_USECS):
223                # Time is in microseconds.
224                return Timestamp(value / 1000)
225            else:
226                # Time is in nanoseconds.
227                return Timestamp(value / 1000 / 1000)
228
229        # Try to convert from an ISO string.
230
231        # Parse out some cases that Python <= 3.10 cannot deal with.
232        # This will remove fractional seconds.
233        value = re.sub(r'Z$', '+00:00', value)
234        value = re.sub(r'(\d\d:\d\d)(\.\d+)', r'\1', value)
235
236        try:
237            value = datetime.datetime.fromisoformat(value)
238        except Exception as ex:
239            raise ValueError(f"Failed to parse timestamp string '{raw_value}'.") from ex
240
241        # If no timezone was parsed, use the default one.
242        if (value.tzinfo is None):
243            value = value.replace(tzinfo = default_timezone)
244
245        return Timestamp.from_pytime(value)

Try to parse a timestamp out of a value. Empty values will get zero timestamps. Purely digit strings will be converted to ints and treated as UNIX times. Floats will be considered UNIX epoch seconds and converted to milliseconds. Other strings will be attempted to be parsed with datetime.fromisoformat().

If a value, but not a timezone, was parsed, then set the timezone for the parsed value to default_timezone. If default_timezone is None and use_local_timezone is true, then use get_local_timezone() to get the timezone.

def to_pod( self, context: Optional[edq.util.common.SerializationContext] = None) -> int:
247    def to_pod(self,
248            context: typing.Union[edq.util.serial.SerializationContext, None] = None,
249            ) -> int:
250        return self

Get a POD representation of this object.

The default implementation will convert to a dict (similar to a DictSerializer).

@classmethod
def from_pod( cls: Type[Timestamp], data: Any, context: Optional[edq.util.common.SerializationContext] = None) -> Timestamp:
252    @classmethod
253    def from_pod(cls: typing.Type['Timestamp'],
254            data: typing.Any,
255            context: typing.Union[edq.util.serial.SerializationContext, None] = None,
256            ) -> 'Timestamp':
257        return cls.guess(data)

Create an instance of this class from a POD.

The default implementation will call the class' constructor with one of two things: a splat/unpacking (**) of the incoming data if the data is a dict, otherwise the data itself.

def get_local_timezone() -> datetime.tzinfo:
259def get_local_timezone() -> datetime.tzinfo:
260    """ Get the local (system) timezone or raise an exception. """
261
262    if (_testing_timezone is not None):
263        return _testing_timezone
264
265    local_timezone = datetime.datetime.now().astimezone().tzinfo
266    if ((local_timezone is None) or (not isinstance(local_timezone, datetime.tzinfo))):
267        raise ValueError("Could not discover local timezone.")
268
269    return local_timezone

Get the local (system) timezone or raise an exception.