Exemplo n.º 1
0
def test_validate_datetime_iterable():
    """Functional test to ensure the validate_datetime() method
    runs properly on naive datetime iterables."""
    iterable = [
        pd.to_datetime("January 1, 2020"),
        pd.to_datetime("January 13, 2020"),
    ]
    assert isinstance(validate_datetime(iterable), pd.DatetimeIndex)
    assert isinstance(validate_datetime(iterable)[0], pd.Timestamp)
Exemplo n.º 2
0
def calculate_day_number(
    date: Union[datetime, str, Iterable[Union[datetime, str]]]
) -> Union[int, Iterable[int]]:
    """
    Method to calculate the day number of the year
    given a proper date.

    :param date: A proper datetime object or a string
        that can be parsed into a proper datetime object.

    :returns: An integer corresponding to the day number of `date`.
    """

    # Ensure `date` can be parsed into a datetime object
    date = validate_datetime(datetime_object=date)
    # Return the day number corresponding to `date`
    if isinstance(date, pd.Timestamp):
        return date.dayofyear
    elif isinstance(date, pd.Series):
        return date.dt.dayofyear
    else:
        return list(date.dayofyear)
Exemplo n.º 3
0
def test_invalid_string():
    """Test to ensure validate_datetime() will throw a
    ValueError with an invalid string input."""
    with pytest.raises(ValueError):
        assert validate_datetime("January 1, blah blah blah")
Exemplo n.º 4
0
def test_valid_string():
    """Test to ensure validate_datetime() can parse a typical
    datetime string."""
    assert isinstance(validate_datetime("January 1, 2019 12:00 PM"),
                      pd.Timestamp)
Exemplo n.º 5
0
def test_validate_datetime(dt):
    """Functional test to ensure the validate_datetime() method
    runs properly on naive datetime objects."""
    assert isinstance(validate_datetime(dt), pd.Timestamp)
Exemplo n.º 6
0
def calculate_solar_noon_in_local_standard_time(
    local_standard_time: Union[datetime, str, Iterable[Union[datetime, str]]],
    longitude_degrees: Union[int, float],
) -> Union[datetime, Iterable[datetime]]:
    """
    Method to calculate solar noon given a local standard timestamp
    (including date and time zone offset from UTC) and a location's
    longitude (in degrees west).

    The equation used is from Duffie & Beckman (2006)
    Equation 1.5.2.

    :param local_standard_time: A `datetime` object,
        containing a timezone offset, representing the time that
        will be converted to solar time.
    :param longitude_degrees: A numeric value representing a location's
        angular distance west of the meridian at Greenwich, England.
        `longitude_degrees` should be between 0 and 360 degrees.

    :returns: A datetime object representing the local standard
        time that corresponds to solar noon for the given
        date in `local_standard_time` and `longitude_degrees`.
    """

    # Type- and range-check `longitude_degrees`
    validate_numeric_value(longitude_degrees, minimum=0, maximum=360)
    # Validate `local_standard_time`
    local_ts = validate_datetime(datetime_object=local_standard_time)

    if isinstance(local_ts, pd.Series):
        # Ensure local_ts has time zone information
        if local_ts.dt.tz is None:
            raise ValueError(
                """`local_standard_time` must provide a time zone offset,
            such as `1/1/2019 12:00 PM -06:00`.""")

        # Calculate offset from UTC, using timezone offset in `local_ts`
        utc_offset = (local_ts.dt.tz.utcoffset(local_ts).total_seconds() //
                      3_600)
    else:
        # Ensure local_ts has time zone information
        if local_ts.tzinfo is None:
            raise ValueError(
                """`local_standard_time` must provide a time zone offset,
            such as `1/1/2019 12:00 PM -06:00`.""")

        # Calculate offset from UTC, using timezone offset in `local_ts`
        utc_offset = (local_ts.tzinfo.utcoffset(local_ts).total_seconds() //
                      3_600)
    """Determine the standard meridian for the given `longitude_degrees`,
    which corresponds to 15 degrees per hour offset."""
    standard_meridian = 15 * abs(utc_offset)

    E = calculate_E_min(
        calculate_B_degrees(calculate_day_number(local_standard_time)))
    longitude_correction_mins = 4.0 * (standard_meridian - longitude_degrees)

    # Create a datetime object for noon on the same date as `solar_ts`
    try:
        solar_noon = np.array([
            datetime(
                year=x.date().year,
                month=x.date().month,
                day=x.date().day,
                hour=12,
                minute=0,
                second=0,
                tzinfo=x.tzinfo,
            ) for x in local_ts
        ])
    except TypeError:
        solar_noon = datetime(
            year=local_ts.date().year,
            month=local_ts.date().month,
            day=local_ts.date().day,
            hour=12,
            minute=0,
            second=0,
            tzinfo=local_ts.tzinfo,
        )

    if isinstance(solar_noon, np.ndarray):
        result = np.array([
            x - timedelta(minutes=E[i] + longitude_correction_mins)
            for i, x in enumerate(solar_noon)
        ])
    else:
        result = solar_noon - timedelta(minutes=E + longitude_correction_mins)

    return result
Exemplo n.º 7
0
def convert_to_solar_time(
    local_standard_time: Union[datetime, str, Iterable[Union[datetime, str]]],
    longitude_degrees: Union[int, float],
) -> Union[datetime, Iterable[datetime]]:
    """
    Method to calculate solar time given a local standard timestamp
    (including date and time zone offset from UTC) and a location's
    longitude (in degrees west).

    The equation used is from Duffie & Beckman (2006)
    Equation 1.5.2.

    :param local_standard_time: A `datetime` object,
        containing a timezone offset, representing the time that
        will be converted to solar time.
    :param longitude_degrees: A numeric value representing a location's
        angular distance west of the meridian at Greenwich, England.
        `longitude_degrees` should be between 0 and 360 degrees.

    :returns: A datetime object representing the solar time
        corresponding to `local_standard_time` at the given
        `longitude_degrees`.
    """

    # Type- and range-check `longitude_degrees`
    validate_numeric_value(longitude_degrees, minimum=0, maximum=360)
    # Validate `local_standard_time`
    local_ts = validate_datetime(datetime_object=local_standard_time)

    if isinstance(local_ts, pd.Series):
        # Ensure local_ts has time zone information
        if local_ts.dt.tz is None:
            raise ValueError(
                """`local_standard_time` must provide a time zone offset,
            such as `1/1/2019 12:00 PM -06:00`.""")

        # Calculate offset from UTC, using timezone offset in `local_ts`
        utc_offset = (local_ts.dt.tz.utcoffset(local_ts).total_seconds() //
                      3_600)
    else:
        # Ensure local_ts has time zone information
        if local_ts.tzinfo is None:
            raise ValueError(
                """`local_standard_time` must provide a time zone offset,
            such as `1/1/2019 12:00 PM -06:00`.""")

        # Calculate offset from UTC, using timezone offset in `local_ts`
        utc_offset = (local_ts.tzinfo.utcoffset(local_ts).total_seconds() //
                      3_600)
    """Determine the standard meridian for the given `longitude_degrees`,
    which corresponds to 15 degrees per hour offset."""
    standard_meridian = 15 * np.abs(utc_offset)

    E = calculate_E_min(
        calculate_B_degrees(calculate_day_number(local_standard_time)))
    longitude_correction_mins = 4.0 * (standard_meridian - longitude_degrees)

    try:
        return local_ts + timedelta(minutes=longitude_correction_mins + E)
    except TypeError:
        # When working with iterables
        return [
            x + timedelta(minutes=longitude_correction_mins + E[i])
            for i, x in enumerate(local_ts)
        ]