Repository: peopledoc/workalendar
Pull request #216 (2019-05-17 14:41:45)

Florida specific holidays
Some Florida subdivisions are using a different holiday calendar:

* Florida legal holidays
* Florida circuit courts
* Miami-Dade, Florida

~~source: https://en.wikipedia.org/wiki/Public_holidays_in_the_United_States#Florida~~

Updated sources:

* https://en.wikipedia.org/wiki/Holidays_with_paid_time_off_in_the_United_States#Florida
* http://www.leg.state.fl.us/statutes/index.cfm?App_mode=Display_Statute&Search_String=&URL=0600-0699/0683/Sections/0683.01.html (this one contradicts the other, MLK day happens on Jan 15th vs. Jan 18th)

Requires #171 to be solved first

### Task list

- [x] Tests with a significant number of years to be tested for your calendar.
- [x] Docstrings for the Calendar class and specific methods.
- [x] Calendar country / label added to the README.rst file,
- [x] Changelog amended with a mention like: "Added ``<country>`` by ``@pseudo`` (#)"



Patch:
diff --git a/Changelog.md b/Changelog.md
index 80d8ca7..45cd233 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -3,6 +3,7 @@
 ## master (unreleased)
 
 - Drop Python 3.4 support (#352).
+- Added Florida specific calendars: Florida Legal, Florida Circuit Courts, Miami-Dade (#216).
 
 ## v4.4.0 (2019-05-17)
 
diff --git a/README.rst b/README.rst
index 74005e8..0bfb100 100644
--- a/README.rst
+++ b/README.rst
@@ -130,6 +130,8 @@ America
   * Chicago, Illinois
   * Guam
   * Suffolk County, Massachusetts
+  * California Education, Berkeley, San Francisco, West Hollywood
+  * Florida Legal and Florida Circuit Courts, Miami-Dade
 * Canada (including provincial and territory holidays)
 
 Asia
diff --git a/workalendar/usa/__init__.py b/workalendar/usa/__init__.py
index 95a57b9..ef40fb9 100644
--- a/workalendar/usa/__init__.py
+++ b/workalendar/usa/__init__.py
@@ -16,7 +16,9 @@ from .colorado import Colorado
 from .connecticut import Connecticut
 from .delaware import Delaware
 from .district_columbia import DistrictOfColumbia
-from .florida import Florida
+from .florida import (
+    Florida, FloridaLegal, FloridaCircuitCourts, FloridaMiamiDade
+)
 from .georgia import Georgia
 from .hawaii import Hawaii
 from .idaho import Idaho
@@ -77,7 +79,7 @@ __all__ = [
     'Connecticut',
     'Delaware',
     'DistrictOfColumbia',
-    'Florida',
+    'Florida', 'FloridaLegal', 'FloridaCircuitCourts', 'FloridaMiamiDade',
     'Georgia',
     'Hawaii',
     'Idaho',
diff --git a/workalendar/usa/alabama.py b/workalendar/usa/alabama.py
index 516b544..fdd55d2 100644
--- a/workalendar/usa/alabama.py
+++ b/workalendar/usa/alabama.py
@@ -15,20 +15,7 @@ class Alabama(UnitedStates):
     presidents_day_label = "George Washington/Thomas Jefferson Birthday"
     columbus_day_label = ("Columbus Day / Fraternal Day /"
                           " American Indian Heritage Day")
-
-    def get_jefferson_davis_birthday(self, year):
-        """
-        The first MON of June is Jefferson Davis Birthday
-        """
-        return (
-            self.get_nth_weekday_in_month(year, 6, MON, 1),
-            "Jefferson Davis Birthday"
-        )
-
-    def get_variable_days(self, year):
-        days = super(Alabama, self).get_variable_days(year)
-        days.append(self.get_jefferson_davis_birthday(year))
-        return days
+    include_jefferson_davis_birthday = True
 
 
 class AlabamaBaldwinCounty(Alabama):
diff --git a/workalendar/usa/core.py b/workalendar/usa/core.py
index 4caaa7b..f24e9a8 100644
--- a/workalendar/usa/core.py
+++ b/workalendar/usa/core.py
@@ -36,6 +36,8 @@ class UnitedStates(WesternCalendar, ChristianMixin):
     columbus_day_label = "Columbus Day"
     # Confederation day
     include_confederation_day = False
+    # Jefferson Davis Birthday.
+    include_jefferson_davis_birthday = False
 
     # Include Cesar Chavez day(s)
     include_cesar_chavez_day = False
@@ -155,6 +157,15 @@ class UnitedStates(WesternCalendar, ChristianMixin):
         day = self.get_nth_weekday_in_month(year, 4, MON, 4)
         return (day, "Confederate Memorial Day")
 
+    def get_jefferson_davis_birthday(self, year):
+        """
+        The first MON of June is Jefferson Davis Birthday
+        """
+        return (
+            self.get_nth_weekday_in_month(year, 6, MON, 1),
+            "Jefferson Davis Birthday"
+        )
+
     def get_martin_luther_king_date(self, year):
         """
         Martin Luther King is on 3rd MON of January, starting of 1985.
@@ -308,6 +319,9 @@ class UnitedStates(WesternCalendar, ChristianMixin):
         if self.include_confederation_day:
             days.append(self.get_confederate_day(year))
 
+        if self.include_jefferson_davis_birthday:
+            days.append(self.get_jefferson_davis_birthday(year))
+
         if self.include_inauguration_day:
             # Is it a "Inauguration year"?
             if UnitedStates.is_presidential_year(year - 1):
diff --git a/workalendar/usa/florida.py b/workalendar/usa/florida.py
index 5585c97..7a7ec5b 100644
--- a/workalendar/usa/florida.py
+++ b/workalendar/usa/florida.py
@@ -1,11 +1,70 @@
 # -*- coding: utf-8 -*-
-from __future__ import (absolute_import, division, print_function,
-                        unicode_literals)
+from __future__ import absolute_import, unicode_literals
+
+from datetime import date, timedelta
+import warnings
+from pyluach.dates import GregorianDate
 
 from .core import UnitedStates
 from ..registry import iso_register
 
 
+class HebrewHolidays(object):
+
+    hebrew_calendars = {}
+
+    @classmethod
+    def get_hebrew_calendar(cls, gregorian_year):
+        """
+        Build and cache the Hebrew calendar for the given Gregorian Year.
+        """
+        if gregorian_year not in cls.hebrew_calendars:
+            # Build the hebrew calendar for year
+            days = []
+            current_date = date(gregorian_year, 1, 1)
+
+            while current_date.year == gregorian_year:
+                hebrew_date = GregorianDate(
+                    year=current_date.year,
+                    month=current_date.month,
+                    day=current_date.day,
+                ).to_heb()
+                days.append(
+                    (hebrew_date, current_date)
+                )
+                current_date += timedelta(days=1)
+            # Store it in the class property
+            cls.hebrew_calendars[gregorian_year] = days
+
+        # Return the hebrew calendar
+        return cls.hebrew_calendars[gregorian_year]
+
+    @classmethod
+    def search_hebrew_calendar(cls, gregorian_year, hebrew_month, hebrew_day):
+        """
+        Search for a specific Hebrew month and day in the Hebrew calendar.
+        """
+        calendar = cls.get_hebrew_calendar(gregorian_year)
+        search = filter(lambda item: item[0].month == hebrew_month, calendar)
+        search = filter(lambda item: item[0].day == hebrew_day, search)
+        for item in search:
+            return item[1]
+
+    @classmethod
+    def get_rosh_hashanah(cls, year):
+        """
+        Return the gregorian date of the first day of Rosh Hashanah
+        """
+        return cls.search_hebrew_calendar(year, 7, 1)
+
+    @classmethod
+    def get_yom_kippur(cls, year):
+        """
+        Return the gregorian date of Yom Kippur.
+        """
+        return cls.search_hebrew_calendar(year, 7, 10)
+
+
 @iso_register('US-FL')
 class Florida(UnitedStates):
     """Florida"""
@@ -13,3 +72,68 @@ class Florida(UnitedStates):
     thanksgiving_friday_label = "Friday after Thanksgiving"
     include_columbus_day = False
     include_federal_presidents_day = False
+
+
+class FloridaLegal(Florida):
+    """Florida Legal Holidays"""
+    FIXED_HOLIDAYS = Florida.FIXED_HOLIDAYS + (
+        (2, 15, 'Susan B. Anthony Day'),
+        (4, 2, 'Pascua Florida Day'),
+        (6, 14, 'Flag Day'),
+    )
+    include_mardi_gras = True
+    include_lincoln_birthday = True
+    include_federal_presidents_day = True
+    include_good_friday = True
+    include_confederation_day = True
+    include_jefferson_davis_birthday = True
+    include_columbus_day = True
+    columbus_day_label = "Columbus Day and Farmers' Day"
+    include_election_day_every_year = True
+
+    def __init__(self, *args, **kwargs):
+        super(FloridaLegal, self).__init__(*args, **kwargs)
+        warnings.warn(
+            "Florida's laws separate the definitions between paid versus legal"
+            " holidays. Be warned that Florida Legal specific Holidays are not"
+            " paid holidays."
+        )
+
+    def get_confederate_day(self, year):
+        """
+        Confederation memorial day is on the April 26th for Florida Legal.
+        """
+        return (date(year, 4, 26), "Confederate Memorial Day")
+
+    def get_jefferson_davis_birthday(self, year):
+        """
+        Jefferson Davis Birthday appears to be a fixed holiday (June 3rd)
+        """
+        return (date(year, 6, 3), "Jefferson Davis Birthday")
+
+
+class FloridaCircuitCourts(HebrewHolidays, Florida):
+    """Florida Circuits Courts"""
+    include_federal_presidents_day = True
+    include_good_friday = True
+
+    def get_variable_days(self, year):
+        days = super(FloridaCircuitCourts, self).get_variable_days(year)
+
+        days.append((
+            self.get_rosh_hashanah(year),
+            "Rosh Hashanah"
+        ))
+
+        days.append((
+            self.get_yom_kippur(year),
+            "Yom Kippur"
+        ))
+
+        return days
+
+
+class FloridaMiamiDade(Florida):
+    """Miami-Dade, Florida"""
+    include_federal_presidents_day = True
+    include_columbus_day = True


Test patch:
diff --git a/workalendar/tests/test_usa.py b/workalendar/tests/test_usa.py
index dd1f450..85bf935 100644
--- a/workalendar/tests/test_usa.py
+++ b/workalendar/tests/test_usa.py
@@ -1,12 +1,19 @@
 # -*- coding: utf-8 -*-
-from unittest import skip
+from unittest import skip, skipIf
 from datetime import date
+import sys
+import warnings
+
 from workalendar.tests import GenericCalendarTest
 from workalendar.usa import (
     UnitedStates,
     Alabama, AlabamaBaldwinCounty, AlabamaMobileCounty, AlabamaPerryCounty,
-    Florida, Arkansas, Alaska, Arizona, California, CaliforniaEducation,
-    CaliforniaBerkeley, CaliforniaSanFrancisco, CaliforniaWestHollywood,
+    Arkansas, Alaska, Arizona,
+    # California and others
+    California, CaliforniaEducation, CaliforniaBerkeley,
+    CaliforniaSanFrancisco, CaliforniaWestHollywood,
+    # Florida and others
+    Florida, FloridaLegal, FloridaCircuitCourts, FloridaMiamiDade,
     Colorado, Connecticut, Delaware, DistrictOfColumbia, Georgia, Hawaii,
     Indiana, Illinois, Idaho, Iowa, Kansas, Kentucky, Louisiana, Maine,
     Maryland, Massachusetts, Minnesota, Michigan, Mississippi, Missouri,
@@ -18,6 +25,8 @@ from workalendar.usa import (
     AmericanSamoa, ChicagoIllinois, Guam, SuffolkCountyMassachusetts,
 )
 
+PY2 = sys.version_info[0] == 2
+
 
 class UnitedStatesTest(GenericCalendarTest):
     cal_class = UnitedStates
@@ -595,9 +604,13 @@ class DistrictOfColumbiaTest(InaugurationDay, UnitedStatesTest):
         self.assertIn(date(2016, 4, 16), holidays)  # Emancipation Day
 
 
-class FloridaTest(NoColumbus, NoPresidentialDay, UnitedStatesTest):
-    cal_class = Florida
+class FloridaBasicTest(object):
+    """
+    Core Florida tests.
 
+    The difference is that it includes the Thanksgiving Friday *and* its label
+    is renamed.
+    """
     def test_state_year_2014(self):
         holidays = self.cal.holidays_set(2014)
         self.assertIn(date(2014, 11, 28), holidays)  # Thanksgiving Friday
@@ -613,6 +626,136 @@ class FloridaTest(NoColumbus, NoPresidentialDay, UnitedStatesTest):
         self.assertEqual(label, "Friday after Thanksgiving")
 
 
+class FloridaTest(NoColumbus, NoPresidentialDay, FloridaBasicTest,
+                  UnitedStatesTest):
+    """
+    Florida includes all federal holidays except
+    Washington's Birthday & Columbus day
+    """
+    cal_class = Florida
+
+
+class FloridaLegalTest(IncludeMardiGras, ElectionDayEveryYear,
+                       FloridaBasicTest, UnitedStatesTest):
+    """
+    Florida Legal Holidays include:
+
+    * All Florida State Holidays,
+    * Mardi Gras,
+    * Lincoln's Birthday,
+    * Susan B. Anthony Day,
+    * Washington's Birthday,
+    * Good Friday,
+    * Pascua Florida Day,
+    * Confederate Memorial Day,
+    * Jefferson Davies Birthday,
+    * Flag Day
+    * Columbus Day renamed as "Columbus and Farmers' Day"
+    * Election Day
+    """
+    cal_class = FloridaLegal
+
+    @skipIf(PY2, "Python 2 warnings unsupported")
+    def test_init_warning(self):
+        warnings.simplefilter("always")
+        with warnings.catch_warnings(record=True) as w:
+            # Cause all warnings to always be triggered.
+            # Trigger a warning.
+            self.cal_class()
+            # Verify some things
+            assert len(w) == 1
+            assert issubclass(w[-1].category, UserWarning)
+            assert "Florida's laws separate the definitions between paid versus legal holidays." in str(w[-1].message)  # noqa
+        warnings.simplefilter("ignore")
+
+    def test_specific_lincoln_birthday(self):
+        holidays = self.cal.holidays_set(2014)
+        self.assertIn(date(2014, 2, 12), holidays)  # Lincoln's Birthday
+
+        holidays = self.cal.holidays_set(2015)
+        self.assertIn(date(2015, 2, 12), holidays)  # Lincoln's Birthday
+
+    def test_susan_b_anthony_day(self):
+        holidays = self.cal.holidays_set(2019)
+        self.assertIn(date(2019, 2, 15), holidays)  # Susan B. Anthony Day
+
+    def test_good_friday(self):
+        holidays = self.cal.holidays_set(2014)
+        self.assertIn(date(2014, 4, 18), holidays)  # Good Friday
+        holidays = self.cal.holidays_set(2015)
+        self.assertIn(date(2015, 4, 3), holidays)  # Good Friday
+
+    def test_pascua_florida_day(self):
+        holidays = self.cal.holidays_set(2019)
+        self.assertIn(date(2019, 4, 2), holidays)  # Pascua Florida Day
+
+    def test_confederate_holidays(self):
+        holidays = self.cal.holidays_set(2014)
+        self.assertIn(date(2014, 4, 26), holidays)  # Confederate Memorial Day
+        self.assertIn(date(2014, 6, 3), holidays)  # Jefferson Davis' birthday
+
+        holidays = self.cal.holidays_set(2015)
+        self.assertIn(date(2015, 4, 26), holidays)  # Confederate Memorial Day
+        self.assertIn(date(2015, 6, 3), holidays)  # Jefferson Davis' birthday
+
+        holidays = self.cal.holidays_set(2018)
+        self.assertIn(date(2018, 4, 26), holidays)  # Confederate Memorial Day
+        self.assertIn(date(2018, 6, 3), holidays)  # Jefferson Davis' birthday
+
+    def test_flag_day(self):
+        holidays = self.cal.holidays_set(2018)
+        self.assertIn(date(2018, 6, 14), holidays)  # Flag Day
+
+        holidays = self.cal.holidays_set(2019)
+        self.assertIn(date(2019, 6, 14), holidays)  # Flag Day
+
+    def test_columbus_day_label(self):
+        _, label = self.cal.get_columbus_day(2017)
+        self.assertEqual(label, "Columbus Day and Farmers' Day")
+
+
+class FloridaCircuitCourtsTest(NoColumbus, FloridaBasicTest, UnitedStatesTest):
+    cal_class = FloridaCircuitCourts
+
+    def test_good_friday(self):
+        holidays = self.cal.holidays_set(2014)
+        self.assertIn(date(2014, 4, 18), holidays)  # Good Friday
+        holidays = self.cal.holidays_set(2015)
+        self.assertIn(date(2015, 4, 3), holidays)  # Good Friday
+
+    def test_rosh_hashanah_2018(self):
+        # src: https://www.firstjudicialcircuit.org/about-court/court-holidays
+        rosh_hashanah = self.cal.get_rosh_hashanah(2018)
+        self.assertEqual(rosh_hashanah, date(2018, 9, 10))
+        holidays = self.cal.holidays_set(2018)
+        self.assertIn(rosh_hashanah, holidays)
+
+    def test_rosh_hashanah_2019(self):
+        # src: https://www.firstjudicialcircuit.org/about-court/court-holidays
+        rosh_hashanah = self.cal.get_rosh_hashanah(2019)
+        self.assertEqual(rosh_hashanah, date(2019, 9, 30))
+        holidays = self.cal.holidays_set(2019)
+        self.assertIn(rosh_hashanah, holidays)
+
+    def test_yom_kippur_2018(self):
+        # src: https://www.firstjudicialcircuit.org/about-court/court-holidays
+        yom_kippur = self.cal.get_yom_kippur(2018)
+        self.assertEqual(yom_kippur, date(2018, 9, 19))
+        holidays = self.cal.holidays_set(2018)
+        self.assertIn(yom_kippur, holidays)
+
+    def test_yom_kippur_2019(self):
+        # src: https://www.firstjudicialcircuit.org/about-court/court-holidays
+        yom_kippur = self.cal.get_yom_kippur(2019)
+        self.assertEqual(yom_kippur, date(2019, 10, 9))
+        holidays = self.cal.holidays_set(2019)
+        self.assertIn(yom_kippur, holidays)
+
+
+class FloridaMiamiDadeTests(FloridaBasicTest, UnitedStatesTest):
+    cal_class = FloridaMiamiDade
+
+
 class GeorgiaTest(NoPresidentialDay, UnitedStatesTest):
     cal_class = Georgia