Repository: vacanza/python-holidays

Issue:
2022-01-03 was not a holiday for NYSE (XNYS)
More info: https://www.nyse.com/markets/hours-calendars


Discussion:
I will look into this issue.

Resolving patch:
diff --git a/holidays/financial/ny_stock_exchange.py b/holidays/financial/ny_stock_exchange.py
--- a/holidays/financial/ny_stock_exchange.py
+++ b/holidays/financial/ny_stock_exchange.py
@@ -12,57 +12,47 @@
 from datetime import date, timedelta
 
 from dateutil.easter import easter
-from dateutil.relativedelta import (
-    relativedelta as rd,
-    MO,
-    TU,
-    TH,
-    FR,
-)
-
+from dateutil.relativedelta import FR, MO, TH, TU
+from dateutil.relativedelta import relativedelta as rd
 from holidays.constants import (
-    JAN,
+    APR,
+    AUG,
+    DEC,
     FEB,
+    JAN,
+    JUL,
+    JUN,
     MAR,
-    APR,
     MAY,
-    JUN,
-    JUL,
-    AUG,
-    SEP,
-    OCT,
     NOV,
-    DEC,
+    OCT,
+    SEP,
 )
-
 from holidays.holiday_base import HolidayBase
 
 
 class NewYorkStockExchange(HolidayBase):
+    # Official regulations:
+    # https://www.nyse.com/publicdocs/nyse/regulation/nyse/NYSE_Rules.pdf
     # https://www.nyse.com/markets/hours-calendars
-    # historical data:
+    # Historical data:
     # s3.amazonaws.com/armstrongeconomics-wp/2013/07/NYSE-Closings.pdf
-    # also available for 1901- at:
-    # nyseholidays.blogspot.com/2012/11/nyse-holidays-from-<year1>-<year2>.html
-    # where year1 ends in 01, year2 ends in 00 (e.g. 1901-1910)
 
     country = "NYSE"
 
     def __init__(self, **kwargs):
         HolidayBase.__init__(self, **kwargs)
 
-    def _get_observed(self, d, always_post=False):
+    def _get_observed(self, d):
         wdnum = d.isoweekday()
-        if always_post and wdnum == 6:  # treat sat as sun
-            wdnum = 7
         if wdnum == 6:
             return d + rd(weekday=FR(-1))
         if wdnum == 7:
             return d + rd(weekday=MO(+1))
         return d
 
-    def _set_observed_date(self, date, name, always_post=False):
-        date_obs = self._get_observed(date, always_post=always_post)
+    def _set_observed_date(self, date, name):
+        date_obs = self._get_observed(date)
         if date_obs == date:
             self[date] = name
         else:
@@ -72,9 +62,17 @@ def _populate(self, year):
         ##############################################################
         # REGULAR HOLIDAYS
         ##############################################################
+
         # NYD
-        nyd = date(year, JAN, 1)
-        self._set_observed_date(nyd, "New Year's Day", always_post=True)
+        # This year's New Year Day.
+        self._set_observed_date(date(year, JAN, 1), "New Year's Day")
+
+        # https://www.nyse.com/publicdocs/nyse/regulation/nyse/NYSE_Rules.pdf
+        # As per Rule 7.2.: check if next year's NYD falls on Saturday and
+        # needs to be observed on Friday (Dec 31 of previous year).
+        dec_31 = date(year, DEC, 31)
+        if dec_31.isoweekday() == 5:
+            self._set_observed_date(dec_31 + rd(days=+1), "New Year's Day")
 
         # MLK - observed 1998 - 3rd Monday of Jan
         if year >= 1998:


Test patch:
diff --git a/test/financial/test_ny_stock_exchange.py b/test/financial/test_ny_stock_exchange.py
--- a/test/financial/test_ny_stock_exchange.py
+++ b/test/financial/test_ny_stock_exchange.py
@@ -41,13 +41,14 @@ def test_new_years(self):
             date(1930, JAN, 1),
             date(1950, JAN, 2),
             date(1999, JAN, 1),
-            date(2000, JAN, 3),
+            date(1999, DEC, 31),
             date(2010, JAN, 1),
             date(2018, JAN, 1),
             date(2019, JAN, 1),
             date(2020, JAN, 1),
             date(2021, JAN, 1),
-            date(2022, JAN, 3),
+            date(2021, DEC, 31),
+            date(2027, DEC, 31),
         ]:
             self.assertIn(dt, self.holidays)
             self.assertNotIn(dt + relativedelta(days=-1), self.holidays)