306 lines
11 KiB
Python
306 lines
11 KiB
Python
|
import calendar
|
||
|
from datetime import datetime
|
||
|
import locale
|
||
|
import unicodedata
|
||
|
|
||
|
import numpy as np
|
||
|
import pytest
|
||
|
|
||
|
import pandas as pd
|
||
|
from pandas import (
|
||
|
DatetimeIndex,
|
||
|
Index,
|
||
|
Timedelta,
|
||
|
Timestamp,
|
||
|
date_range,
|
||
|
offsets,
|
||
|
)
|
||
|
import pandas._testing as tm
|
||
|
from pandas.core.arrays import DatetimeArray
|
||
|
|
||
|
from pandas.tseries.frequencies import to_offset
|
||
|
|
||
|
|
||
|
class TestDatetime64:
|
||
|
def test_no_millisecond_field(self):
|
||
|
msg = "type object 'DatetimeIndex' has no attribute 'millisecond'"
|
||
|
with pytest.raises(AttributeError, match=msg):
|
||
|
DatetimeIndex.millisecond
|
||
|
|
||
|
msg = "'DatetimeIndex' object has no attribute 'millisecond'"
|
||
|
with pytest.raises(AttributeError, match=msg):
|
||
|
DatetimeIndex([]).millisecond
|
||
|
|
||
|
def test_datetimeindex_accessors(self):
|
||
|
dti_naive = date_range(freq="D", start=datetime(1998, 1, 1), periods=365)
|
||
|
# GH#13303
|
||
|
dti_tz = date_range(
|
||
|
freq="D", start=datetime(1998, 1, 1), periods=365, tz="US/Eastern"
|
||
|
)
|
||
|
for dti in [dti_naive, dti_tz]:
|
||
|
assert dti.year[0] == 1998
|
||
|
assert dti.month[0] == 1
|
||
|
assert dti.day[0] == 1
|
||
|
assert dti.hour[0] == 0
|
||
|
assert dti.minute[0] == 0
|
||
|
assert dti.second[0] == 0
|
||
|
assert dti.microsecond[0] == 0
|
||
|
assert dti.dayofweek[0] == 3
|
||
|
|
||
|
assert dti.dayofyear[0] == 1
|
||
|
assert dti.dayofyear[120] == 121
|
||
|
|
||
|
assert dti.isocalendar().week[0] == 1
|
||
|
assert dti.isocalendar().week[120] == 18
|
||
|
|
||
|
assert dti.quarter[0] == 1
|
||
|
assert dti.quarter[120] == 2
|
||
|
|
||
|
assert dti.days_in_month[0] == 31
|
||
|
assert dti.days_in_month[90] == 30
|
||
|
|
||
|
assert dti.is_month_start[0]
|
||
|
assert not dti.is_month_start[1]
|
||
|
assert dti.is_month_start[31]
|
||
|
assert dti.is_quarter_start[0]
|
||
|
assert dti.is_quarter_start[90]
|
||
|
assert dti.is_year_start[0]
|
||
|
assert not dti.is_year_start[364]
|
||
|
assert not dti.is_month_end[0]
|
||
|
assert dti.is_month_end[30]
|
||
|
assert not dti.is_month_end[31]
|
||
|
assert dti.is_month_end[364]
|
||
|
assert not dti.is_quarter_end[0]
|
||
|
assert not dti.is_quarter_end[30]
|
||
|
assert dti.is_quarter_end[89]
|
||
|
assert dti.is_quarter_end[364]
|
||
|
assert not dti.is_year_end[0]
|
||
|
assert dti.is_year_end[364]
|
||
|
|
||
|
assert len(dti.year) == 365
|
||
|
assert len(dti.month) == 365
|
||
|
assert len(dti.day) == 365
|
||
|
assert len(dti.hour) == 365
|
||
|
assert len(dti.minute) == 365
|
||
|
assert len(dti.second) == 365
|
||
|
assert len(dti.microsecond) == 365
|
||
|
assert len(dti.dayofweek) == 365
|
||
|
assert len(dti.dayofyear) == 365
|
||
|
assert len(dti.isocalendar()) == 365
|
||
|
assert len(dti.quarter) == 365
|
||
|
assert len(dti.is_month_start) == 365
|
||
|
assert len(dti.is_month_end) == 365
|
||
|
assert len(dti.is_quarter_start) == 365
|
||
|
assert len(dti.is_quarter_end) == 365
|
||
|
assert len(dti.is_year_start) == 365
|
||
|
assert len(dti.is_year_end) == 365
|
||
|
|
||
|
dti.name = "name"
|
||
|
|
||
|
# non boolean accessors -> return Index
|
||
|
for accessor in DatetimeArray._field_ops:
|
||
|
res = getattr(dti, accessor)
|
||
|
assert len(res) == 365
|
||
|
assert isinstance(res, Index)
|
||
|
assert res.name == "name"
|
||
|
|
||
|
# boolean accessors -> return array
|
||
|
for accessor in DatetimeArray._bool_ops:
|
||
|
res = getattr(dti, accessor)
|
||
|
assert len(res) == 365
|
||
|
assert isinstance(res, np.ndarray)
|
||
|
|
||
|
# test boolean indexing
|
||
|
res = dti[dti.is_quarter_start]
|
||
|
exp = dti[[0, 90, 181, 273]]
|
||
|
tm.assert_index_equal(res, exp)
|
||
|
res = dti[dti.is_leap_year]
|
||
|
exp = DatetimeIndex([], freq="D", tz=dti.tz, name="name")
|
||
|
tm.assert_index_equal(res, exp)
|
||
|
|
||
|
def test_datetimeindex_accessors2(self):
|
||
|
dti = date_range(freq="BQ-FEB", start=datetime(1998, 1, 1), periods=4)
|
||
|
|
||
|
assert sum(dti.is_quarter_start) == 0
|
||
|
assert sum(dti.is_quarter_end) == 4
|
||
|
assert sum(dti.is_year_start) == 0
|
||
|
assert sum(dti.is_year_end) == 1
|
||
|
|
||
|
def test_datetimeindex_accessors3(self):
|
||
|
# Ensure is_start/end accessors throw ValueError for CustomBusinessDay,
|
||
|
bday_egypt = offsets.CustomBusinessDay(weekmask="Sun Mon Tue Wed Thu")
|
||
|
dti = date_range(datetime(2013, 4, 30), periods=5, freq=bday_egypt)
|
||
|
msg = "Custom business days is not supported by is_month_start"
|
||
|
with pytest.raises(ValueError, match=msg):
|
||
|
dti.is_month_start
|
||
|
|
||
|
def test_datetimeindex_accessors4(self):
|
||
|
dti = DatetimeIndex(["2000-01-01", "2000-01-02", "2000-01-03"])
|
||
|
|
||
|
assert dti.is_month_start[0] == 1
|
||
|
|
||
|
def test_datetimeindex_accessors5(self):
|
||
|
freq_m = to_offset("M")
|
||
|
bm = to_offset("BM")
|
||
|
qfeb = to_offset("Q-FEB")
|
||
|
qsfeb = to_offset("QS-FEB")
|
||
|
bq = to_offset("BQ")
|
||
|
bqs_apr = to_offset("BQS-APR")
|
||
|
as_nov = to_offset("AS-NOV")
|
||
|
|
||
|
tests = [
|
||
|
(freq_m.is_month_start(Timestamp("2013-06-01")), 1),
|
||
|
(bm.is_month_start(Timestamp("2013-06-01")), 0),
|
||
|
(freq_m.is_month_start(Timestamp("2013-06-03")), 0),
|
||
|
(bm.is_month_start(Timestamp("2013-06-03")), 1),
|
||
|
(qfeb.is_month_end(Timestamp("2013-02-28")), 1),
|
||
|
(qfeb.is_quarter_end(Timestamp("2013-02-28")), 1),
|
||
|
(qfeb.is_year_end(Timestamp("2013-02-28")), 1),
|
||
|
(qfeb.is_month_start(Timestamp("2013-03-01")), 1),
|
||
|
(qfeb.is_quarter_start(Timestamp("2013-03-01")), 1),
|
||
|
(qfeb.is_year_start(Timestamp("2013-03-01")), 1),
|
||
|
(qsfeb.is_month_end(Timestamp("2013-03-31")), 1),
|
||
|
(qsfeb.is_quarter_end(Timestamp("2013-03-31")), 0),
|
||
|
(qsfeb.is_year_end(Timestamp("2013-03-31")), 0),
|
||
|
(qsfeb.is_month_start(Timestamp("2013-02-01")), 1),
|
||
|
(qsfeb.is_quarter_start(Timestamp("2013-02-01")), 1),
|
||
|
(qsfeb.is_year_start(Timestamp("2013-02-01")), 1),
|
||
|
(bq.is_month_end(Timestamp("2013-06-30")), 0),
|
||
|
(bq.is_quarter_end(Timestamp("2013-06-30")), 0),
|
||
|
(bq.is_year_end(Timestamp("2013-06-30")), 0),
|
||
|
(bq.is_month_end(Timestamp("2013-06-28")), 1),
|
||
|
(bq.is_quarter_end(Timestamp("2013-06-28")), 1),
|
||
|
(bq.is_year_end(Timestamp("2013-06-28")), 0),
|
||
|
(bqs_apr.is_month_end(Timestamp("2013-06-30")), 0),
|
||
|
(bqs_apr.is_quarter_end(Timestamp("2013-06-30")), 0),
|
||
|
(bqs_apr.is_year_end(Timestamp("2013-06-30")), 0),
|
||
|
(bqs_apr.is_month_end(Timestamp("2013-06-28")), 1),
|
||
|
(bqs_apr.is_quarter_end(Timestamp("2013-06-28")), 1),
|
||
|
(bqs_apr.is_year_end(Timestamp("2013-03-29")), 1),
|
||
|
(as_nov.is_year_start(Timestamp("2013-11-01")), 1),
|
||
|
(as_nov.is_year_end(Timestamp("2013-10-31")), 1),
|
||
|
(Timestamp("2012-02-01").days_in_month, 29),
|
||
|
(Timestamp("2013-02-01").days_in_month, 28),
|
||
|
]
|
||
|
|
||
|
for ts, value in tests:
|
||
|
assert ts == value
|
||
|
|
||
|
def test_datetimeindex_accessors6(self):
|
||
|
# GH 6538: Check that DatetimeIndex and its TimeStamp elements
|
||
|
# return the same weekofyear accessor close to new year w/ tz
|
||
|
dates = ["2013/12/29", "2013/12/30", "2013/12/31"]
|
||
|
dates = DatetimeIndex(dates, tz="Europe/Brussels")
|
||
|
expected = [52, 1, 1]
|
||
|
assert dates.isocalendar().week.tolist() == expected
|
||
|
assert [d.weekofyear for d in dates] == expected
|
||
|
|
||
|
# GH 12806
|
||
|
# error: Unsupported operand types for + ("List[None]" and "List[str]")
|
||
|
@pytest.mark.parametrize(
|
||
|
"time_locale", [None] + tm.get_locales() # type: ignore[operator]
|
||
|
)
|
||
|
def test_datetime_name_accessors(self, time_locale):
|
||
|
# Test Monday -> Sunday and January -> December, in that sequence
|
||
|
if time_locale is None:
|
||
|
# If the time_locale is None, day-name and month_name should
|
||
|
# return the english attributes
|
||
|
expected_days = [
|
||
|
"Monday",
|
||
|
"Tuesday",
|
||
|
"Wednesday",
|
||
|
"Thursday",
|
||
|
"Friday",
|
||
|
"Saturday",
|
||
|
"Sunday",
|
||
|
]
|
||
|
expected_months = [
|
||
|
"January",
|
||
|
"February",
|
||
|
"March",
|
||
|
"April",
|
||
|
"May",
|
||
|
"June",
|
||
|
"July",
|
||
|
"August",
|
||
|
"September",
|
||
|
"October",
|
||
|
"November",
|
||
|
"December",
|
||
|
]
|
||
|
else:
|
||
|
with tm.set_locale(time_locale, locale.LC_TIME):
|
||
|
expected_days = calendar.day_name[:]
|
||
|
expected_months = calendar.month_name[1:]
|
||
|
|
||
|
# GH#11128
|
||
|
dti = date_range(freq="D", start=datetime(1998, 1, 1), periods=365)
|
||
|
english_days = [
|
||
|
"Monday",
|
||
|
"Tuesday",
|
||
|
"Wednesday",
|
||
|
"Thursday",
|
||
|
"Friday",
|
||
|
"Saturday",
|
||
|
"Sunday",
|
||
|
]
|
||
|
for day, name, eng_name in zip(range(4, 11), expected_days, english_days):
|
||
|
name = name.capitalize()
|
||
|
assert dti.day_name(locale=time_locale)[day] == name
|
||
|
assert dti.day_name(locale=None)[day] == eng_name
|
||
|
ts = Timestamp(datetime(2016, 4, day))
|
||
|
assert ts.day_name(locale=time_locale) == name
|
||
|
dti = dti.append(DatetimeIndex([pd.NaT]))
|
||
|
assert np.isnan(dti.day_name(locale=time_locale)[-1])
|
||
|
ts = Timestamp(pd.NaT)
|
||
|
assert np.isnan(ts.day_name(locale=time_locale))
|
||
|
|
||
|
# GH#12805
|
||
|
dti = date_range(freq="M", start="2012", end="2013")
|
||
|
result = dti.month_name(locale=time_locale)
|
||
|
expected = Index([month.capitalize() for month in expected_months])
|
||
|
|
||
|
# work around different normalization schemes
|
||
|
# https://github.com/pandas-dev/pandas/issues/22342
|
||
|
result = result.str.normalize("NFD")
|
||
|
expected = expected.str.normalize("NFD")
|
||
|
|
||
|
tm.assert_index_equal(result, expected)
|
||
|
|
||
|
for date, expected in zip(dti, expected_months):
|
||
|
result = date.month_name(locale=time_locale)
|
||
|
expected = expected.capitalize()
|
||
|
|
||
|
result = unicodedata.normalize("NFD", result)
|
||
|
expected = unicodedata.normalize("NFD", result)
|
||
|
|
||
|
assert result == expected
|
||
|
dti = dti.append(DatetimeIndex([pd.NaT]))
|
||
|
assert np.isnan(dti.month_name(locale=time_locale)[-1])
|
||
|
|
||
|
def test_nanosecond_field(self):
|
||
|
dti = DatetimeIndex(np.arange(10))
|
||
|
expected = Index(np.arange(10, dtype=np.int32))
|
||
|
|
||
|
tm.assert_index_equal(dti.nanosecond, expected)
|
||
|
|
||
|
|
||
|
def test_iter_readonly():
|
||
|
# GH#28055 ints_to_pydatetime with readonly array
|
||
|
arr = np.array([np.datetime64("2012-02-15T12:00:00.000000000")])
|
||
|
arr.setflags(write=False)
|
||
|
dti = pd.to_datetime(arr)
|
||
|
list(dti)
|
||
|
|
||
|
|
||
|
def test_add_timedelta_preserves_freq():
|
||
|
# GH#37295 should hold for any DTI with freq=None or Tick freq
|
||
|
tz = "Canada/Eastern"
|
||
|
dti = date_range(
|
||
|
start=Timestamp("2019-03-26 00:00:00-0400", tz=tz),
|
||
|
end=Timestamp("2020-10-17 00:00:00-0400", tz=tz),
|
||
|
freq="D",
|
||
|
)
|
||
|
result = dti + Timedelta(days=1)
|
||
|
assert result.freq == dti.freq
|