Repository: vacanza/python-holidays

Issue:
The calculation of the lunar start date throws a TypeError exception for Thailand and Cambodia
version: 0.47
stack trace:
"python39\lib\site-packages\holidays\calendars\thai.py", line 233, in _get_start_date
    return _ThaiLunisolar.START_DATE + td(days=delta_days)
TypeError: unsupported type for timedelta days component: numpy.int32


Discussion:
I'm not really sure if that's a PH bug as it involves `numpy` based on what I'm seeing.
Could you provide more information on how to reproduce the issue?

Thank you!
> I'm not really sure if that's a PH bug as it involves `numpy` based on what I'm seeing. Could you provide more information on how to reproduce the issue?
> 
> Thank you!

Apologies, I encountered a TypeError while using the 'get_holiday_names' method from the Prophet algorithm provided(version:1.1.5) by Facebook. 
This error specifically occurred when calculating holidays for only Thailand and Cambodia among the available countries.
The numpy type is passed into the holidays code from Prophet. 
I attempted to resolve the issue by modifying line 233, adding an integer type conversion, which prevents the error (return _ThaiLunisolar.START_DATE + td(days=int(delta_days)))

【Here is the full stack trace】
File "D:\Program Files (x86)\language\python39\lib\site-packages\prophet\forecaster.py", line 769, in add_country_holidays
    for name in get_holiday_names(country_name):
  File "D:\Program Files (x86)\language\python39\lib\site-packages\prophet\make_holidays.py", line 50, in get_holiday_names
    return set(country_holidays(language="en_US", years=np.arange(1995, 2045)).values())
  File "D:\Program Files (x86)\language\python39\lib\site-packages\holidays\registry.py", line 207, in __call__
    return cls(*args, **kwargs)  # type: ignore[misc, operator]
  File "D:\Program Files (x86)\language\python39\lib\site-packages\holidays\countries\thailand.py", line 151, in __init__
    super().__init__(*args, **kwargs)
  File "D:\Program Files (x86)\language\python39\lib\site-packages\holidays\observed_holiday_base.py", line 103, in __init__
    super().__init__(*args, **kwargs)
  File "D:\Program Files (x86)\language\python39\lib\site-packages\holidays\holiday_base.py", line 372, in __init__
    self._populate(year)
  File "D:\Program Files (x86)\language\python39\lib\site-packages\holidays\holiday_base.py", line 793, in _populate
    self._populate_common_holidays()
  File "D:\Program Files (x86)\language\python39\lib\site-packages\holidays\observed_holiday_base.py", line 183, in _populate_common_holidays
    super()._populate_common_holidays()
  File "D:\Program Files (x86)\language\python39\lib\site-packages\holidays\holiday_base.py", line 800, in _populate_common_holidays
    pch_method()
  File "D:\Program Files (x86)\language\python39\lib\site-packages\holidays\countries\thailand.py", line 436, in _populate_public_holidays
    makha_bucha_date = self._add_makha_bucha(tr("วันมาฆบูชา"))
  File "D:\Program Files (x86)\language\python39\lib\site-packages\holidays\groups\thai.py", line 138, in _add_makha_bucha
    name, self._thai_calendar.makha_bucha_date(self._year, calendar)
  File "D:\Program Files (x86)\language\python39\lib\site-packages\holidays\calendars\thai.py", line 266, in makha_bucha_date
    start_date = self._get_start_date(year)
  File "D:\Program Files (x86)\language\python39\lib\site-packages\holidays\calendars\thai.py", line 233, in _get_start_date
    return _ThaiLunisolar.START_DATE + td(days=delta_days)
TypeError: unsupported type for timedelta days component: numpy.int32
Could be related to https://github.com/facebook/prophet/issues/2563, but I'm not sure.
@coderX-thinkerL thanks for providing more details. I'll look into our options here this week.

Resolving patch:
diff --git a/holidays/helpers.py b/holidays/helpers.py
--- a/holidays/helpers.py
+++ b/holidays/helpers.py
@@ -24,13 +24,19 @@ def _normalize_arguments(cls, value):
         A set created from `value` argument.
 
     """
+    if value is None:
+        return set()
+
     if isinstance(value, cls):
         return {value}
 
-    return set(value) if value is not None else set()
+    try:
+        return {v if isinstance(v, cls) else cls(v) for v in value}
+    except TypeError:  # non-iterable
+        return {value if isinstance(value, cls) else cls(value)}
 
 
-def _normalize_tuple(data):
+def _normalize_tuple(value):
     """Normalize tuple.
 
     :param data:
@@ -40,4 +46,4 @@ def _normalize_tuple(data):
         An unchanged object for tuple of tuples, e.g., ((JAN, 10), (DEC, 31)).
         An object put into a tuple otherwise, e.g., ((JAN, 10),).
     """
-    return data if not data or isinstance(data[0], tuple) else (data,)
+    return value if not value or isinstance(value[0], tuple) else (value,)


Test patch:
diff --git a/requirements/tests.txt b/requirements/tests.txt
--- a/requirements/tests.txt
+++ b/requirements/tests.txt
@@ -1,7 +1,8 @@
 # Test requirements.
 coverage
 importlib-metadata
+numpy
+polib
 pytest
 pytest-cov
 pytest-xdist
-polib
diff --git a/tests/third_party/test_numpy.py b/tests/third_party/test_numpy.py
new file mode 100644
--- /dev/null
+++ b/tests/third_party/test_numpy.py
@@ -0,0 +1,42 @@
+#  holidays
+#  --------
+#  A fast, efficient Python library for generating country, province and state
+#  specific sets of holidays on the fly. It aims to make determining whether a
+#  specific date is a holiday as fast and flexible as possible.
+#
+#  Authors: Vacanza Team and individual contributors (see AUTHORS file)
+#           dr-prodigy <dr.prodigy.github@gmail.com> (c) 2017-2023
+#           ryanss <ryanssdev@icloud.com> (c) 2014-2017
+#  Website: https://github.com/vacanza/python-holidays
+#  License: MIT (see LICENSE file)
+
+from platform import platform, python_implementation, python_version
+from unittest import TestCase
+
+import pytest
+
+from holidays.countries.cambodia import Cambodia
+from holidays.countries.thailand import Thailand
+from holidays.countries.ukraine import Ukraine
+
+
+class TestNumpy(TestCase):
+    @pytest.mark.skipif(
+        platform().startswith("Windows")
+        and python_implementation().startswith("PyPy")
+        and python_version().startswith("3.10"),
+        reason='Avoid "make: *** [Makefile:63: test] Error 5" for pypy3.10 on Windows',
+    )
+    def test_years_int_conversion(self):
+        import numpy as np  # It seems the import causes the error mentioned above.
+
+        years = (1995, 2000)
+        years_range = set(range(*years))
+
+        for cls in (Cambodia, Thailand, Ukraine):
+            # Test single value.
+            for int_x in (np.int16, np.int32, np.int64):
+                self.assertEqual(cls(years=int_x(2024)).years, {2024})
+
+            # Test iterable.
+            self.assertEqual(cls(years=np.arange(*years)).years, years_range)