from datetime import datetime
import pandas.util.testing as tm
from pandas import compat
from pandas import DatetimeIndex
from pandas.tseries.holiday import (USFederalHolidayCalendar, USMemorialDay,
                                    USThanksgivingDay, nearest_workday,
                                    next_monday_or_tuesday, next_monday,
                                    previous_friday, sunday_to_monday, Holiday,
                                    DateOffset, MO, SA, Timestamp,
                                    AbstractHolidayCalendar, get_calendar,
                                    HolidayCalendarFactory, next_workday,
                                    previous_workday, before_nearest_workday,
                                    EasterMonday, GoodFriday,
                                    after_nearest_workday, weekend_to_monday,
                                    USLaborDay, USColumbusDay,
                                    USMartinLutherKingJr, USPresidentsDay)
from pytz import utc
import nose


class TestCalendar(tm.TestCase):
    def setUp(self):
        self.holiday_list = [
            datetime(2012, 1, 2),
            datetime(2012, 1, 16),
            datetime(2012, 2, 20),
            datetime(2012, 5, 28),
            datetime(2012, 7, 4),
            datetime(2012, 9, 3),
            datetime(2012, 10, 8),
            datetime(2012, 11, 12),
            datetime(2012, 11, 22),
            datetime(2012, 12, 25)]

        self.start_date = datetime(2012, 1, 1)
        self.end_date = datetime(2012, 12, 31)

    def test_calendar(self):

        calendar = USFederalHolidayCalendar()
        holidays = calendar.holidays(self.start_date, self.end_date)

        holidays_1 = calendar.holidays(
            self.start_date.strftime('%Y-%m-%d'),
            self.end_date.strftime('%Y-%m-%d'))
        holidays_2 = calendar.holidays(
            Timestamp(self.start_date),
            Timestamp(self.end_date))

        self.assertEqual(list(holidays.to_pydatetime()), self.holiday_list)
        self.assertEqual(list(holidays_1.to_pydatetime()), self.holiday_list)
        self.assertEqual(list(holidays_2.to_pydatetime()), self.holiday_list)

    def test_calendar_caching(self):
        # Test for issue #9552

        class TestCalendar(AbstractHolidayCalendar):
            def __init__(self, name=None, rules=None):
                super(TestCalendar, self).__init__(name=name, rules=rules)

        jan1 = TestCalendar(rules=[Holiday('jan1', year=2015, month=1, day=1)])
        jan2 = TestCalendar(rules=[Holiday('jan2', year=2015, month=1, day=2)])

        tm.assert_index_equal(jan1.holidays(), DatetimeIndex(['01-Jan-2015']))
        tm.assert_index_equal(jan2.holidays(), DatetimeIndex(['02-Jan-2015']))

    def test_calendar_observance_dates(self):
        # Test for issue 11477
        USFedCal = get_calendar('USFederalHolidayCalendar')
        holidays0 = USFedCal.holidays(datetime(2015, 7, 3), datetime(
            2015, 7, 3))  # <-- same start and end dates
        holidays1 = USFedCal.holidays(datetime(2015, 7, 3), datetime(
            2015, 7, 6))  # <-- different start and end dates
        holidays2 = USFedCal.holidays(datetime(2015, 7, 3), datetime(
            2015, 7, 3))  # <-- same start and end dates

        tm.assert_index_equal(holidays0, holidays1)
        tm.assert_index_equal(holidays0, holidays2)

    def test_rule_from_name(self):
        USFedCal = get_calendar('USFederalHolidayCalendar')
        self.assertEqual(USFedCal.rule_from_name(
            'Thanksgiving'), USThanksgivingDay)


class TestHoliday(tm.TestCase):
    def setUp(self):
        self.start_date = datetime(2011, 1, 1)
        self.end_date = datetime(2020, 12, 31)

    def check_results(self, holiday, start, end, expected):
        self.assertEqual(list(holiday.dates(start, end)), expected)
        # Verify that timezone info is preserved.
        self.assertEqual(
            list(
                holiday.dates(
                    utc.localize(Timestamp(start)),
                    utc.localize(Timestamp(end)),
                )
            ),
            [utc.localize(dt) for dt in expected],
        )

    def test_usmemorialday(self):
        self.check_results(holiday=USMemorialDay,
                           start=self.start_date,
                           end=self.end_date,
                           expected=[
                               datetime(2011, 5, 30),
                               datetime(2012, 5, 28),
                               datetime(2013, 5, 27),
                               datetime(2014, 5, 26),
                               datetime(2015, 5, 25),
                               datetime(2016, 5, 30),
                               datetime(2017, 5, 29),
                               datetime(2018, 5, 28),
                               datetime(2019, 5, 27),
                               datetime(2020, 5, 25),
                           ], )

    def test_non_observed_holiday(self):

        self.check_results(
            Holiday('July 4th Eve', month=7, day=3),
            start="2001-01-01",
            end="2003-03-03",
            expected=[
                Timestamp('2001-07-03 00:00:00'),
                Timestamp('2002-07-03 00:00:00')
            ]
        )

        self.check_results(
            Holiday('July 4th Eve', month=7, day=3, days_of_week=(0, 1, 2, 3)),
            start="2001-01-01",
            end="2008-03-03",
            expected=[
                Timestamp('2001-07-03 00:00:00'),
                Timestamp('2002-07-03 00:00:00'),
                Timestamp('2003-07-03 00:00:00'),
                Timestamp('2006-07-03 00:00:00'),
                Timestamp('2007-07-03 00:00:00'),
            ]
        )

    def test_easter(self):

        self.check_results(EasterMonday,
                           start=self.start_date,
                           end=self.end_date,
                           expected=[
                               Timestamp('2011-04-25 00:00:00'),
                               Timestamp('2012-04-09 00:00:00'),
                               Timestamp('2013-04-01 00:00:00'),
                               Timestamp('2014-04-21 00:00:00'),
                               Timestamp('2015-04-06 00:00:00'),
                               Timestamp('2016-03-28 00:00:00'),
                               Timestamp('2017-04-17 00:00:00'),
                               Timestamp('2018-04-02 00:00:00'),
                               Timestamp('2019-04-22 00:00:00'),
                               Timestamp('2020-04-13 00:00:00'),
                           ], )
        self.check_results(GoodFriday,
                           start=self.start_date,
                           end=self.end_date,
                           expected=[
                               Timestamp('2011-04-22 00:00:00'),
                               Timestamp('2012-04-06 00:00:00'),
                               Timestamp('2013-03-29 00:00:00'),
                               Timestamp('2014-04-18 00:00:00'),
                               Timestamp('2015-04-03 00:00:00'),
                               Timestamp('2016-03-25 00:00:00'),
                               Timestamp('2017-04-14 00:00:00'),
                               Timestamp('2018-03-30 00:00:00'),
                               Timestamp('2019-04-19 00:00:00'),
                               Timestamp('2020-04-10 00:00:00'),
                           ], )

    def test_usthanksgivingday(self):

        self.check_results(USThanksgivingDay,
                           start=self.start_date,
                           end=self.end_date,
                           expected=[
                               datetime(2011, 11, 24),
                               datetime(2012, 11, 22),
                               datetime(2013, 11, 28),
                               datetime(2014, 11, 27),
                               datetime(2015, 11, 26),
                               datetime(2016, 11, 24),
                               datetime(2017, 11, 23),
                               datetime(2018, 11, 22),
                               datetime(2019, 11, 28),
                               datetime(2020, 11, 26),
                           ], )

    def test_holidays_within_dates(self):
        # Fix holiday behavior found in #11477
        # where holiday.dates returned dates outside start/end date
        # or observed rules could not be applied as the holiday
        # was not in the original date range (e.g., 7/4/2015 -> 7/3/2015)
        start_date = datetime(2015, 7, 1)
        end_date = datetime(2015, 7, 1)

        calendar = get_calendar('USFederalHolidayCalendar')
        new_years = calendar.rule_from_name('New Years Day')
        july_4th = calendar.rule_from_name('July 4th')
        veterans_day = calendar.rule_from_name('Veterans Day')
        christmas = calendar.rule_from_name('Christmas')

        # Holiday: (start/end date, holiday)
        holidays = {USMemorialDay: ("2015-05-25", "2015-05-25"),
                    USLaborDay: ("2015-09-07", "2015-09-07"),
                    USColumbusDay: ("2015-10-12", "2015-10-12"),
                    USThanksgivingDay: ("2015-11-26", "2015-11-26"),
                    USMartinLutherKingJr: ("2015-01-19", "2015-01-19"),
                    USPresidentsDay: ("2015-02-16", "2015-02-16"),
                    GoodFriday: ("2015-04-03", "2015-04-03"),
                    EasterMonday: [("2015-04-06", "2015-04-06"),
                                   ("2015-04-05", [])],
                    new_years: [("2015-01-01", "2015-01-01"),
                                ("2011-01-01", []),
                                ("2010-12-31", "2010-12-31")],
                    july_4th: [("2015-07-03", "2015-07-03"),
                               ("2015-07-04", [])],
                    veterans_day: [("2012-11-11", []),
                                   ("2012-11-12", "2012-11-12")],
                    christmas: [("2011-12-25", []),
                                ("2011-12-26", "2011-12-26")]}

        for rule, dates in compat.iteritems(holidays):
            empty_dates = rule.dates(start_date, end_date)
            self.assertEqual(empty_dates.tolist(), [])

            if isinstance(dates, tuple):
                dates = [dates]

            for start, expected in dates:
                if len(expected):
                    expected = [Timestamp(expected)]
                self.check_results(rule, start, start, expected)

    def test_argument_types(self):
        holidays = USThanksgivingDay.dates(self.start_date, self.end_date)

        holidays_1 = USThanksgivingDay.dates(
            self.start_date.strftime('%Y-%m-%d'),
            self.end_date.strftime('%Y-%m-%d'))

        holidays_2 = USThanksgivingDay.dates(
            Timestamp(self.start_date),
            Timestamp(self.end_date))

        self.assert_index_equal(holidays, holidays_1)
        self.assert_index_equal(holidays, holidays_2)

    def test_special_holidays(self):
        base_date = [datetime(2012, 5, 28)]
        holiday_1 = Holiday('One-Time', year=2012, month=5, day=28)
        holiday_2 = Holiday('Range', month=5, day=28,
                            start_date=datetime(2012, 1, 1),
                            end_date=datetime(2012, 12, 31),
                            offset=DateOffset(weekday=MO(1)))

        self.assertEqual(base_date,
                         holiday_1.dates(self.start_date, self.end_date))
        self.assertEqual(base_date,
                         holiday_2.dates(self.start_date, self.end_date))

    def test_get_calendar(self):
        class TestCalendar(AbstractHolidayCalendar):
            rules = []

        calendar = get_calendar('TestCalendar')
        self.assertEqual(TestCalendar, calendar.__class__)

    def test_factory(self):
        class_1 = HolidayCalendarFactory('MemorialDay',
                                         AbstractHolidayCalendar,
                                         USMemorialDay)
        class_2 = HolidayCalendarFactory('Thansksgiving',
                                         AbstractHolidayCalendar,
                                         USThanksgivingDay)
        class_3 = HolidayCalendarFactory('Combined', class_1, class_2)

        self.assertEqual(len(class_1.rules), 1)
        self.assertEqual(len(class_2.rules), 1)
        self.assertEqual(len(class_3.rules), 2)


class TestObservanceRules(tm.TestCase):
    def setUp(self):
        self.we = datetime(2014, 4, 9)
        self.th = datetime(2014, 4, 10)
        self.fr = datetime(2014, 4, 11)
        self.sa = datetime(2014, 4, 12)
        self.su = datetime(2014, 4, 13)
        self.mo = datetime(2014, 4, 14)
        self.tu = datetime(2014, 4, 15)

    def test_next_monday(self):
        self.assertEqual(next_monday(self.sa), self.mo)
        self.assertEqual(next_monday(self.su), self.mo)

    def test_next_monday_or_tuesday(self):
        self.assertEqual(next_monday_or_tuesday(self.sa), self.mo)
        self.assertEqual(next_monday_or_tuesday(self.su), self.tu)
        self.assertEqual(next_monday_or_tuesday(self.mo), self.tu)

    def test_previous_friday(self):
        self.assertEqual(previous_friday(self.sa), self.fr)
        self.assertEqual(previous_friday(self.su), self.fr)

    def test_sunday_to_monday(self):
        self.assertEqual(sunday_to_monday(self.su), self.mo)

    def test_nearest_workday(self):
        self.assertEqual(nearest_workday(self.sa), self.fr)
        self.assertEqual(nearest_workday(self.su), self.mo)
        self.assertEqual(nearest_workday(self.mo), self.mo)

    def test_weekend_to_monday(self):
        self.assertEqual(weekend_to_monday(self.sa), self.mo)
        self.assertEqual(weekend_to_monday(self.su), self.mo)
        self.assertEqual(weekend_to_monday(self.mo), self.mo)

    def test_next_workday(self):
        self.assertEqual(next_workday(self.sa), self.mo)
        self.assertEqual(next_workday(self.su), self.mo)
        self.assertEqual(next_workday(self.mo), self.tu)

    def test_previous_workday(self):
        self.assertEqual(previous_workday(self.sa), self.fr)
        self.assertEqual(previous_workday(self.su), self.fr)
        self.assertEqual(previous_workday(self.tu), self.mo)

    def test_before_nearest_workday(self):
        self.assertEqual(before_nearest_workday(self.sa), self.th)
        self.assertEqual(before_nearest_workday(self.su), self.fr)
        self.assertEqual(before_nearest_workday(self.tu), self.mo)

    def test_after_nearest_workday(self):
        self.assertEqual(after_nearest_workday(self.sa), self.mo)
        self.assertEqual(after_nearest_workday(self.su), self.tu)
        self.assertEqual(after_nearest_workday(self.fr), self.mo)


class TestFederalHolidayCalendar(tm.TestCase):

    # Test for issue 10278
    def test_no_mlk_before_1984(self):
        class MLKCalendar(AbstractHolidayCalendar):
            rules = [USMartinLutherKingJr]

        holidays = MLKCalendar().holidays(start='1984',
                                          end='1988').to_pydatetime().tolist()
        # Testing to make sure holiday is not incorrectly observed before 1986
        self.assertEqual(holidays, [datetime(1986, 1, 20, 0, 0), datetime(
            1987, 1, 19, 0, 0)])

    def test_memorial_day(self):
        class MemorialDay(AbstractHolidayCalendar):
            rules = [USMemorialDay]

        holidays = MemorialDay().holidays(start='1971',
                                          end='1980').to_pydatetime().tolist()
        # Fixes 5/31 error and checked manually against wikipedia
        self.assertEqual(holidays, [datetime(1971, 5, 31, 0, 0),
                                    datetime(1972, 5, 29, 0, 0),
                                    datetime(1973, 5, 28, 0, 0),
                                    datetime(1974, 5, 27, 0,
                                             0), datetime(1975, 5, 26, 0, 0),
                                    datetime(1976, 5, 31, 0,
                                             0), datetime(1977, 5, 30, 0, 0),
                                    datetime(1978, 5, 29, 0,
                                             0), datetime(1979, 5, 28, 0, 0)])


class TestHolidayConflictingArguments(tm.TestCase):

    # GH 10217

    def test_both_offset_observance_raises(self):
        with self.assertRaises(NotImplementedError):
            Holiday("Cyber Monday", month=11, day=1,
                    offset=[DateOffset(weekday=SA(4))],
                    observance=next_monday)


if __name__ == '__main__':
    nose.runmodule(argv=[__file__, '-vvs', '-x', '--pdb', '--pdb-failure'],
                   exit=False)
