Repository: vacanza/python-holidays

Issue:
Some holidays of Uruguay are incorrect.
Hello.
Some holidays for Uruguay are incorrect.

- The Battle of stones was in May, 18.
- The Respect for Cultural Diversity Day or Columbus day is October, 12.

In Uruguay are 2 categories of holidays (laborables and not laborables). In the not laborable holidays nobody works while in the laborable holidays the work is optional.

The not laborables holidays are:
- 01/01
- 01/05
- 18/07
- 25/08
- 25/12

The rest of holidays are laborables, and depending of the day of the week, some of them are moved to earlier monday or to the next friday. The rule is:
- If the holiday is tuesday or wednesday, they moved to earlier monday.
- If the holiday is thursday, is moved to next friday.
- If the holiday is in monday or friday, doesn't move.

That is the reason of the error.
For example, the Battle of the Stones is the May, 18. In 2021 was tuesday and was moved to the earlier monday that was May, 17.

One exception to this rule is the November, 2 ("Día de los difuntos"). This holiday is laborable, but doesn't move.

Best regards.



Discussion:
@oteroalejandro  I'm working on this issue. Thanks for pointing out the problem.
One question:  do you get an extra day off (observance) if not laborable holiday falls on weekend? 
By the way, this is what [Wikipedia](https://en.wikipedia.org/wiki/Public_holidays_in_Uruguay#Moveable_holidays) states:

According to Uruguayan Law 16,805 with modifications of Law 17,414, the holidays declared by law, subject to the commemoration of them, follow the following scheme (whose commemoration as “moveable holidays”):

- If coincide on Saturday, Sunday or Monday will be observed in those days.
- If occur on Tuesday or Wednesday, will be observed on Monday immediately preceding.
- If occur on Thursday or Friday shall be observed on the Monday immediately following

It is a bit different from your explanation. Could you please clarify this? Thanks!

Hi Kate.

In the case a not laborable holiday falls on weekend, we don't get any
extra day.



[image: Logo]
<https://mailtrack.io/trace/link/1198366dea37c3e1f186c6569157d85010525afa?url=https%3A%2F%2Fwww.acceleanation.com&userId=7259985&signature=f5b9c321d1b4c8b6>
*Cr. Alejandro Otero*
*Consulting Manager*
*e:* ***@***.***
*w:* acceleanation.com
<https://mailtrack.io/trace/link/796c3c61a2a904b5f2e5e6a20a7bcc6f31db93fe?url=https%3A%2F%2Fwww.acceleanation.com&userId=7259985&signature=034bc373cc95696f>
*m:* +598 99119120
Book a meeting with me: 15min
<https://mailtrack.io/trace/link/7d344c211d5d322a1276818422b980f22bbe6057?url=https%3A%2F%2Fcalendly.com%2Foteroalejandro%2F15min&userId=7259985&signature=e0a6ac9c0fbe139d>
 | 25min
<https://mailtrack.io/trace/link/2332a2f58c83ca7c5f312b106a0793f9a2a7c7fe?url=https%3A%2F%2Fcalendly.com%2Foteroalejandro%2F25min&userId=7259985&signature=56ab97f153373533>
 | 50min
<https://mailtrack.io/trace/link/cb411452873f7e8a99c7f015769f7fa632e58206?url=https%3A%2F%2Fcalendly.com%2Foteroalejandro%2F50min&userId=7259985&signature=02b6aca2a820a339>


El vie, 21 oct 2022 a la(s) 15:12, Kate ***@***.***) escribió:

> @oteroalejandro <https://github.com/oteroalejandro> I'm working on this
> issue. Thanks for pointing out the problem.
> One question: do you get an extra day off (observance) if not laborable
> holiday falls on weekend?
>
> —
> Reply to this email directly, view it on GitHub
> <https://github.com/dr-prodigy/python-holidays/issues/745#issuecomment-1287288094>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/ARMB7HUKBESZJPI5XRWA3ALWELMI5ANCNFSM6AAAAAARJCG7LE>
> .
> You are receiving this because you were mentioned.Message ID:
> ***@***.***>
>

Hi Kate.

I checked with colleagues, and wikipedia is right. If the holiday is
Thursday or Friday, it's moved to next monday.

Another thing... In addition to November 2, exceptions to the holiday
movement rules are on January 06 and June 19. These holidays do not move
despite being working days.

Thanks a lot for your work.
Best regards.






[image: Logo] <https://www.acceleanation.com>
*Cr. Alejandro Otero*
*Consulting Manager*
*e:* ***@***.***
*w:* acceleanation.com <https://www.acceleanation.com>
*m:* +598 99119120
Book a meeting with me: 15min <https://calendly.com/oteroalejandro/15min> |
25min <https://calendly.com/oteroalejandro/25min> | 50min
<https://calendly.com/oteroalejandro/50min>


El vie, 21 oct 2022 a la(s) 15:17, Kate ***@***.***) escribió:

> By the way, this is what Wikipedia
> <https://en.wikipedia.org/wiki/Public_holidays_in_Uruguay#Moveable_holidays>
> states:
>
> According to Uruguayan Law 16,805 with modifications of Law 17,414, the
> holidays declared by law, subject to the commemoration of them, follow the
> following scheme (whose commemoration as “moveable holidays”):
>
>    - If coincide on Saturday, Sunday or Monday will be observed in those
>    days.
>    - If occur on Tuesday or Wednesday, will be observed on Monday
>    immediately preceding.
>    - If occur on Thursday or Friday shall be observed on the Monday
>    immediately following
>
> It is a bit different from your explanation. Could you please clarify
> this? Thanks!
>
> —
> Reply to this email directly, view it on GitHub
> <https://github.com/dr-prodigy/python-holidays/issues/745#issuecomment-1287293377>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/ARMB7HX47FOE744ZFT2KQQ3WELM4FANCNFSM6AAAAAARJCG7LE>
> .
> You are receiving this because you were mentioned.Message ID:
> ***@***.***>
>

> In addition to November 2, exceptions to the holiday movement rules are on January 06 and June 19. These holidays do not move despite being working days. Thanks a lot for your work. Best regards. >

Yes, I noticed that as well in Wikipedia. 
Thanks for the clarification!



Resolving patch:
diff --git a/holidays/countries/uruguay.py b/holidays/countries/uruguay.py
--- a/holidays/countries/uruguay.py
+++ b/holidays/countries/uruguay.py
@@ -12,26 +12,16 @@
 from datetime import date
 
 from dateutil.easter import easter
+from dateutil.relativedelta import FR, MO, TH, TU, WE
 from dateutil.relativedelta import relativedelta as rd
-from dateutil.relativedelta import TH, FR
-
-from holidays.constants import (
-    WEEKEND,
-    JAN,
-    APR,
-    MAY,
-    JUN,
-    JUL,
-    AUG,
-    OCT,
-    NOV,
-    DEC,
-)
+
+from holidays.constants import APR, AUG, DEC, JAN, JUL, JUN, MAY, NOV, OCT
 from holidays.holiday_base import HolidayBase
 
 
 class Uruguay(HolidayBase):
     # https://www.ute.com.uy/clientes/tramites-y-servicios/potencia-contratada
+    # https://en.wikipedia.org/wiki/Public_holidays_in_Uruguay
 
     country = "UY"
 
@@ -39,104 +29,82 @@ def __init__(self, **kwargs):
         HolidayBase.__init__(self, **kwargs)
 
     def _populate(self, year):
-        # New Year's Day
-        if not self.observed and date(year, JAN, 1).weekday() in WEEKEND:
-            pass
-        else:
-            self[date(year, JAN, 1)] = "Año Nuevo [New Year's Day]"
-
-        # Carnival days
-        # revisar este día para futuros casos
+        # Mandatory paid holidays:
+
+        # New Year's Day.
+        self[date(year, JAN, 1)] = "Año Nuevo [New Year's Day]"
+
+        # Día de los Trabajadores.
+        self[date(year, MAY, 1)] = "Día del Trabajo [Labour Day]"
+
+        # Jura de la Constitución.
+        self[date(year, JUL, 18)] = "Jura de la constitución"
+
+        # Declaratoria de la Independencia.
+        self[
+            date(year, AUG, 25)
+        ] = "Día de la Independencia [Independence Day]"
+
+        # Christmas.
+        self[date(year, DEC, 25)] = "Navidad [Christmas]"
+
+        # Partially paid holidays:
+
+        # Día de Reyes - Feriado en el cual se conmemora la llegada de
+        # los reyes magos a Jesus.
+        self[date(year, JAN, 6)] = "Día de Reyes"
+
+        # Natalicio de José Gervacio Artigas.
+        self[date(year, JUN, 19)] = "Natalicio de José Gervacio Artigas"
+
+        # Día de los difuntos.
+        self[date(year, NOV, 2)] = "Día de los difuntos"
+
+        # Moveable holidays:
+
+        # Carnival days.
         name = "Día de Carnaval [Carnival's Day]"
         self[easter(year) - rd(days=48)] = name
         self[easter(year) - rd(days=47)] = name
 
-        # Día de Reyes - Feriado en el cual se conmemora la llegada de
-        # los reyes magos a Jesus
-        if not self.observed and date(year, JAN, 6).weekday() in WEEKEND:
-            pass
-        else:
-            self[date(year, JAN, 6)] = "Día de Reyes"
-
-        # Holy Week
-        name_thu = "Semana Santa (Jueves Santo) [Holy day (Holy Thursday)]"
-        name_fri = "Semana Santa (Viernes Santo) [Holy day (Holy Friday)]"
-        name_easter = "Día de Pascuas [Easter Day]"
-
-        self[easter(year) + rd(weekday=TH(-1))] = name_thu
-        self[easter(year) + rd(weekday=FR(-1))] = name_fri
-
-        if not self.observed and easter(year).weekday() in WEEKEND:
-            pass
-        else:
-            self[easter(year)] = name_easter
-
-        # Desembarco de los 33 Orientales en la playa de la Agraciada
-        if not self.observed and date(year, APR, 19).weekday() in WEEKEND:
-            pass
-        else:
-            self[date(year, APR, 19)] = (
-                "Desembarco de los 33 Orientales "
-                "Landing of the 33 Orientals"
-                " Aterrissagem dos 33 Orientais"
-                " Sbarco dei 33 orientali"
-            )
-
-        # Día de los Trabajadores
-        name = "Día del Trabajo [Labour Day]"
-        if not self.observed and date(year, MAY, 1).weekday() in WEEKEND:
-            pass
-        else:
-            self[date(year, MAY, 1)] = name
-
-        # Batalla de las piedras
-        name = "Batalla de las Piedras [Battle of the stones]"
-        if not self.observed and date(year, MAY, 17).weekday() in WEEKEND:
-            pass
-        else:
-            self[date(year, MAY, 17)] = name
-
-        # Natalicio de José Gervacio Artigas
-        name = "Natalicio de José Gervacio Artigas"
-        if not self.observed and date(year, JUN, 19).weekday() in WEEKEND:
-            pass
-        else:
-            self[date(year, JUN, 19)] = name
-
-        # Jura de la Constitución
-        name = "Jura de la constitución"
-        if not self.observed and date(year, JUL, 18).weekday() in WEEKEND:
-            pass
-        else:
-            self[date(year, JUL, 18)] = name
-
-        # Declaratoria de la Independencia
-        name = "Día de la Independencia [Independence Day]"
-        if not self.observed and date(year, AUG, 25).weekday() in WEEKEND:
-            pass
-        else:
-            self[date(year, AUG, 25)] = name
-
-        # Respect for Cultural Diversity Day or Columbus day
-        if not self.observed and date(year, OCT, 11).weekday() in WEEKEND:
-            pass
-        elif year < 2010:
-            self[date(year, OCT, 11)] = "Día de la Raza [Columbus day]"
-        else:
-            self[date(year, OCT, 11)] = (
-                "Día del Respeto a la Diversidad"
-                " Cultural [Respect for"
-                " Cultural Diversity Day]"
-            )
-        # Día de los difuntos
-        name = "Día de los difuntos"
-        if not self.observed and date(year, NOV, 2).weekday() in WEEKEND:
-            pass
-        else:
-            self[date(year, NOV, 2)] = name
-
-        # Christmas
-        self[date(year, DEC, 25)] = "Navidad [Christmas]"
+        # Holy Week.
+        self[
+            easter(year) + rd(weekday=TH(-1))
+        ] = "Semana Santa (Jueves Santo)  [Holy day (Holy Thursday)]"
+        self[
+            easter(year) + rd(weekday=FR(-1))
+        ] = "Semana Santa (Viernes Santo)  [Holy day (Holy Friday)]"
+
+        self[easter(year)] = "Día de Pascuas [Easter Day]"
+
+        holiday_pairs = (
+            (
+                # Desembarco de los 33 Orientales en la playa de la Agraciada.
+                date(year, APR, 19),
+                "Desembarco de los 33 Orientales Landing of the 33 Orientals "
+                "Aterrissagem dos 33 Orientais Sbarco dei 33 orientali",
+            ),
+            (
+                # Batalla de las Piedras [Battle of the stones].
+                date(year, MAY, 18),
+                "Batalla de las Piedras [Battle of the stones]",
+            ),
+            (
+                # "Día del Respeto a la Diversidad Cultural
+                # [Respect for Cultural Diversity Day].
+                date(year, OCT, 12),
+                "Día del Respeto a la Diversidad Cultural "
+                "[Respect for Cultural Diversity Day]",
+            ),
+        )
+
+        for dt, name in holiday_pairs:
+            if dt.weekday() in (TU.weekday, WE.weekday):
+                self[dt + rd(weekday=MO(-1))] = name
+            elif dt.weekday() in (TH.weekday, FR.weekday):
+                self[dt + rd(weekday=MO(+1))] = name
+            else:
+                self[dt] = name
 
 
 class UY(Uruguay):


Test patch:
diff --git a/test/countries/test_uruguay.py b/test/countries/test_uruguay.py
--- a/test/countries/test_uruguay.py
+++ b/test/countries/test_uruguay.py
@@ -21,6 +21,8 @@ class TestUY(unittest.TestCase):
     def setUp(self):
         self.holidays = holidays.UY(observed=True)
 
+    # Mandatory holidays.
+
     def test_new_years(self):
         self.holidays.observed = False
         self.assertNotIn(date(2010, 12, 31), self.holidays)
@@ -32,31 +34,7 @@ def test_new_years(self):
             self.assertIn(dt, self.holidays)
             self.assertNotIn(dt + relativedelta(days=-1), self.holidays)
             self.assertNotIn(dt + relativedelta(days=+1), self.holidays)
-
-    def test_carnival_day(self):
-        for dt in [
-            date(2021, 2, 15),
-            date(2021, 2, 16),
-            date(2018, 2, 12),
-            date(2018, 2, 13),
-            date(2017, 2, 27),
-            date(2017, 2, 28),
-            date(2016, 2, 8),
-            date(2016, 2, 9),
-        ]:
-            self.assertIn(dt, self.holidays)
-
-    def test_dia_de_reyes(self):
-        self.holidays.observed = False
-        for year in range(1900, 2100):
-            dt = date(year, 1, 6)
-        self.assertIn(dt, self.holidays)
-
-    def test_holy_week_day(self):
-        for dt in [
-            date(2021, 1, 6),
-        ]:
-            self.assertIn(dt, self.holidays)
+            self.assertEqual(self.holidays[dt], "Año Nuevo [New Year's Day]")
 
     def test_labor_day(self):
         self.holidays.observed = False
@@ -69,46 +47,53 @@ def test_labor_day(self):
             self.assertIn(dt, self.holidays)
             self.assertNotIn(dt + relativedelta(days=-1), self.holidays)
             self.assertNotIn(dt + relativedelta(days=+1), self.holidays)
+            self.assertEqual(self.holidays[dt], "Día del Trabajo [Labour Day]")
 
-    def test_batalla_de_las_piedras_day(self):
-        self.holidays.observed = False
-        self.assertNotIn(date(1811, 5, 18), self.holidays)
-        self.assertNotIn(date(2021, 5, 18), self.holidays)
-        self.holidays.observed = True
+    def test_jura_de_la_constitucion_day(self):
         for year in range(1900, 2100):
-            dt = date(year, 5, 1)
+            dt = date(year, 7, 18)
             self.assertIn(dt, self.holidays)
             self.assertNotIn(dt + relativedelta(days=-1), self.holidays)
             self.assertNotIn(dt + relativedelta(days=+1), self.holidays)
+            self.assertEqual(self.holidays[dt], "Jura de la constitución")
 
-    def test_natalicio_artigas_day(self):
+    def test_declaratoria_de_la_independencia_day(self):
         for year in range(1900, 2100):
-            dt = date(year, 6, 19)
+            dt = date(year, 8, 25)
             self.assertIn(dt, self.holidays)
             self.assertNotIn(dt + relativedelta(days=-1), self.holidays)
             self.assertNotIn(dt + relativedelta(days=+1), self.holidays)
+            self.assertEqual(
+                self.holidays[dt], "Día de la Independencia [Independence Day]"
+            )
 
-    def test_jura_de_la_constitucion_day(self):
+    def test_christmas(self):
         for year in range(1900, 2100):
-            dt = date(year, 7, 18)
+            dt = date(year, 12, 25)
             self.assertIn(dt, self.holidays)
             self.assertNotIn(dt + relativedelta(days=-1), self.holidays)
             self.assertNotIn(dt + relativedelta(days=+1), self.holidays)
+            self.assertEqual(self.holidays[dt], "Navidad [Christmas]")
 
-    def test_declaratoria_de_la_independencia_day(self):
-        self.holidays.observed = True
+    # Partially paid holidays.
+
+    def test_dia_de_reyes(self):
+        self.holidays.observed = False
         for year in range(1900, 2100):
-            dt = date(year, 8, 25)
+            dt = date(year, 1, 6)
             self.assertIn(dt, self.holidays)
-            self.assertNotIn(dt + relativedelta(days=-1), self.holidays)
-            self.assertNotIn(dt + relativedelta(days=+1), self.holidays)
+            self.assertEqual(self.holidays[dt], "Día de Reyes")
 
-    def test_dia_de_la_raza_day(self):
+    def test_natalicio_artigas_day(self):
         for year in range(1900, 2100):
-            dt = date(year, 10, 11)
+            dt = date(year, 6, 19)
             self.assertIn(dt, self.holidays)
             self.assertNotIn(dt + relativedelta(days=-1), self.holidays)
             self.assertNotIn(dt + relativedelta(days=+1), self.holidays)
+            self.assertEqual(
+                self.holidays[dt],
+                "Natalicio de José Gervacio Artigas",
+            )
 
     def test_dia_de_los_difuntos_day(self):
         for year in range(1900, 2100):
@@ -116,10 +101,81 @@ def test_dia_de_los_difuntos_day(self):
             self.assertIn(dt, self.holidays)
             self.assertNotIn(dt + relativedelta(days=-1), self.holidays)
             self.assertNotIn(dt + relativedelta(days=+1), self.holidays)
+            self.assertEqual(self.holidays[dt], "Día de los difuntos")
 
-    def test_christmas(self):
-        for year in range(1900, 2100):
-            dt = date(year, 12, 25)
+    # Moveable holidays.
+
+    def test_carnival_day(self):
+        for dt in (
+            date(2016, 2, 8),
+            date(2016, 2, 9),
+            date(2017, 2, 27),
+            date(2017, 2, 28),
+            date(2018, 2, 12),
+            date(2018, 2, 13),
+            date(2021, 2, 15),
+            date(2021, 2, 16),
+        ):
             self.assertIn(dt, self.holidays)
-            self.assertNotIn(dt + relativedelta(days=-1), self.holidays)
-            self.assertNotIn(dt + relativedelta(days=+1), self.holidays)
+            self.assertEqual(
+                self.holidays[dt], "Día de Carnaval [Carnival's Day]"
+            )
+
+    def test_holy_week_day(self):
+        for dt in (
+            (
+                date(2021, 4, 1),
+                "Semana Santa (Jueves Santo)  [Holy day (Holy Thursday)]",
+            ),
+            (
+                date(2021, 4, 2),
+                "Semana Santa (Viernes Santo)  [Holy day (Holy Friday)]",
+            ),
+            (date(2021, 4, 4), "Día de Pascuas [Easter Day]"),
+        ):
+            self.assertIn(dt[0], self.holidays)
+            self.assertEqual(self.holidays[dt[0]], dt[1])
+
+    def test_desembarco_de_los_33_orientales(self):
+        for dt in (
+            date(2017, 4, 17),
+            date(2018, 4, 23),
+            date(2019, 4, 22),
+            date(2020, 4, 19),
+            date(2021, 4, 19),
+        ):
+            self.assertIn(dt, self.holidays)
+            self.assertEqual(
+                self.holidays[dt],
+                "Desembarco de los 33 Orientales Landing of the 33 Orientals "
+                "Aterrissagem dos 33 Orientais Sbarco dei 33 orientali",
+            )
+
+    def test_batalla_de_las_piedras_day(self):
+        for dt in (
+            date(2017, 5, 22),
+            date(2018, 5, 21),
+            date(2019, 5, 18),
+            date(2020, 5, 18),
+            date(2021, 5, 17),
+        ):
+            self.assertIn(dt, self.holidays)
+            self.assertEqual(
+                self.holidays[dt],
+                "Batalla de las Piedras [Battle of the stones]",
+            )
+
+    def test_dia_del_respeto_a_la_diversidad_cultural(self):
+        for dt in (
+            date(2017, 10, 16),
+            date(2018, 10, 15),
+            date(2019, 10, 12),
+            date(2020, 10, 12),
+            date(2021, 10, 11),
+        ):
+            self.assertIn(dt, self.holidays)
+            self.assertEqual(
+                self.holidays[dt],
+                "Día del Respeto a la Diversidad Cultural "
+                "[Respect for Cultural Diversity Day]",
+            )