Source code for speasy.core.time

"""
.. testsetup:: *

   from speasy.core.time import *
   import numpy as np
"""
from typing import Callable
from datetime import datetime, timezone
from functools import wraps
import numpy as np
from dateutil.parser import parse
from .typing import AnyDateTimeType

# numpy wraps around int64 instead of raising when a date falls outside the datetime64[ns]
# range, silently returning a wrong date, so conversions have to check for themselves.
# Bounds are the real ones rounded inwards to the microsecond, the finest resolution any
# input can carry once parsed.
_NS_RANGE = (np.datetime64('1677-09-21T00:12:43.145225'), np.datetime64('2262-04-11T23:47:16.854775'))


def _to_datetime64_ns(input_dt: datetime) -> np.datetime64:
    value = np.datetime64(input_dt, 'us')
    if not (_NS_RANGE[0] <= value <= _NS_RANGE[1]):
        raise ValueError(
            f"{input_dt} is outside the datetime64[ns] range Speasy uses ({_NS_RANGE[0]} to {_NS_RANGE[1]})")
    return value.astype('datetime64[ns]')


[docs] def make_utc_datetime(input_dt: AnyDateTimeType) -> datetime: """Makes UTC datetime from given input. Parameters ---------- input_dt: str or datetime or np.datetime64 or np.float64 or float Datetime to convert, can be either an Epoch, a datetime, a numpy datetime64 (any unit) or a string. Naive datetimes and strings without timezone information are assumed to be UTC, timezone aware ones are converted to UTC. Returns ------- datetime A datetime.datetime object forced to UTC time zone Examples -------- >>> make_utc_datetime('2018-01-02') datetime.datetime(2018, 1, 2, 0, 0, tzinfo=datetime.timezone.utc) >>> make_utc_datetime(0.) datetime.datetime(1970, 1, 1, 0, 0, tzinfo=datetime.timezone.utc) >>> from datetime import datetime >>> make_utc_datetime(datetime(2020,1,1)) datetime.datetime(2020, 1, 1, 0, 0, tzinfo=datetime.timezone.utc) >>> make_utc_datetime(np.datetime64('2016-06-02')) datetime.datetime(2016, 6, 2, 0, 0, tzinfo=datetime.timezone.utc) >>> make_utc_datetime('2018-01-01T01:00:00+02:00') datetime.datetime(2017, 12, 31, 23, 0, tzinfo=datetime.timezone.utc) """ if type(input_dt) in (np.float64, float): return datetime.fromtimestamp(input_dt, tz=timezone.utc) if type(input_dt) is str: input_dt = parse(input_dt) if type(input_dt) is np.datetime64: if input_dt.dtype == np.dtype('datetime64[ns]'): return datetime.fromtimestamp(input_dt.astype(np.int64) * 1e-9, tz=timezone.utc) # going through datetime64[us] first allows any input unit without # overflowing int64 for realistic dates input_dt = input_dt.astype('datetime64[us]').astype(datetime) if isinstance(input_dt, datetime): if input_dt.tzinfo is not None: return input_dt.astimezone(timezone.utc) return input_dt.replace(tzinfo=timezone.utc) raise TypeError(f"Unsupported datetime type: {type(input_dt)}, expected str, datetime, np.datetime64 or float")
[docs] def make_utc_datetime64(input_dt: AnyDateTimeType) -> np.datetime64: """Makes UTC np.datetime64 from given input. Parameters ---------- input_dt: str or datetime or np.datetime64 or np.float64 or float Datetime to convert, can be either an Epoch, a datetime, a numpy datetime64 (any unit) or a string. Naive datetimes and strings without timezone information are assumed to be UTC, timezone aware ones are converted to UTC. Returns ------- np.datetime64 A numpy datetime64 object forced to UTC time zone Examples -------- >>> make_utc_datetime64('2018-01-02') np.datetime64('2018-01-02T00:00:00.000000000') >>> make_utc_datetime64(0.) np.datetime64('1970-01-01T00:00:00.000000') >>> from datetime import datetime >>> make_utc_datetime64(datetime(2020,1,1)) np.datetime64('2020-01-01T00:00:00.000000000') >>> make_utc_datetime64(np.datetime64('2016-06-02')) np.datetime64('2016-06-02T00:00:00.000000000') >>> make_utc_datetime64('2018-01-01T01:00:00+02:00') np.datetime64('2017-12-31T23:00:00.000000000') """ if type(input_dt) in (np.float64, float): return np.datetime64(datetime.fromtimestamp(input_dt, tz=timezone.utc)) if type(input_dt) is str: input_dt = parse(input_dt) if type(input_dt) is np.datetime64: if input_dt.dtype == np.dtype('datetime64[ns]'): return input_dt # going through datetime64[us] first allows any input unit without # overflowing int64 for realistic dates input_dt = input_dt.astype('datetime64[us]').astype(datetime) if isinstance(input_dt, datetime): if input_dt.tzinfo is not None: input_dt = input_dt.astimezone(timezone.utc).replace(tzinfo=None) return _to_datetime64_ns(input_dt) raise TypeError(f"Unsupported datetime type: {type(input_dt)}, expected str, datetime, np.datetime64 or float")
[docs] def epoch_to_datetime64(epoch_array: np.ndarray) -> np.ndarray: """Converts an array of floats encoded as Unix Epoch (seconds since 1970) to an array of numpy datetime64[ns] Parameters ---------- epoch_array : np.array Input array of folats (Epoch) Returns ------- np.array Output array of datetime64[ns] Examples -------- >>> epoch_to_datetime64(np.arange(2)) array(['1970-01-01T00:00:00.000000000', '1970-01-01T00:00:01.000000000'], dtype='datetime64[ns]') """ return (epoch_array * 1e9).astype("datetime64[ns]")
[docs] def datetime64_to_epoch(datetime64_array: np.ndarray) -> np.ndarray: """Converts an array of numpy datetime64[ns] to an array of floats encoded as Unix Epoch (seconds since 1970) Parameters ---------- datetime64_array : np.array Input array of datetime64[ns] Returns ------- np.array Output array of floats (Epoch) Examples -------- >>> datetime64_to_epoch(np.array(['1970-01-01T00:00:00.000000000', '1970-01-01T00:00:01.000000000'], ... dtype='datetime64[ns]')) array([0., 1.]) """ # .view avoids a copy (.astype always copies); the *1e-9 multiply # already upcasts to float64, so no trailing .astype("float64") either. return datetime64_array.view("int64").astype("float64") * 1e-9
[docs] class EnsureUTCDateTime(object): def __call__(self, get_data: Callable): @wraps(get_data) def wrapped(wrapped_self, product, start_time, stop_time, **kwargs): return get_data(wrapped_self, product=product, start_time=make_utc_datetime(start_time), stop_time=make_utc_datetime(stop_time), **kwargs) return wrapped