workalendar

repository·master·Indexed 21 days ago

https://github.com/workalendar/workalendar

A Python module for handling global calendars, listing legal and religious holidays, and performing working-day-related computations. It supports various regions including Europe, America, Asia, Oceania, and Africa. Key features include calculating working days between dates, finding the nth weekday of a month, and providing tools for implementing custom calendar classes with support for astronomical computations.

Tokens
5.9K
Snippets
22
Records
29
Agent score
75%

What's inside workalendar

  1. Best practices and limitations for calendar options

    master

    While custom options provide flexibility, follow these guidelines to avoid complexity:

    • Avoid Class Explosion: Use options instead of creating new inherited classes when you have many combinations of rules.
    • Limit the Number of Options: Avoid adding dozens of options to a single constructor. High numbers of options increase runtime complexity and make testing all possible combinations difficult. A general rule of thumb is to avoid combining more than 5 distinct options in one class.
    • Complexity Warning: Excessive options can lead to a state referred to conceptually as ERR_TOO_MANY_OPTIONS, where the logic becomes hard to maintain and cover with tests.
  2. Use non-boolean options for calendar customization

    master

    Calendar options are not limited to boolean flags. You can use any Python type to drive complex holiday logic. Common patterns include:

    • Strings: For customizing labels (e.g., day_of_the_founder_label='Custom Name') or specifying a region.
    • Numbers: For defining variable durations (e.g., number_of_days_after_new_year=3).
    • Objects: For passing complex configuration or context objects.

    This approach is preferred over creating multiple derivative classes when the number of possible combinations of rules becomes too large to manage via inheritance.

  3. Maintain astronomical data (Equinoxes and Solar terms)

    master

    To minimize computation time, equinoxes and solar terms are precomputed for 30 years before and after the current year. If you modify astronomical data or add timezones to it, you must:

    1. Run the create-astronomical-data script to update the data files.
    2. Regenerate the .json.gz files if adding a timezone to the astronomical data.
  4. Handle datetime types and the keep_datetime option

    master

    Workalendar accepts both datetime.date and datetime.datetime objects from the Python standard library.

    Important Notes:

    • Only standard library types are supported. Using types from external libraries will raise an UnsupportedDateType error.
    • By default, most methods return a datetime.date object even if a datetime.datetime was provided as input.
    • To preserve the input type (e.g., to return a datetime.datetime instead of a datetime.date), use the keep_datetime=True argument in methods like add_working_days.
    from datetime import date, datetime
    from workalendar.europe import France
    cal = France()
    
    # Default behavior: returns datetime.date
    res = cal.add_working_days(datetime(2012, 12, 23, 14, 0, 39), 5)
    # datetime.date(2012, 12, 31)
    
    # Using keep_datetime=True to return datetime.datetime
    res_dt = cal.add_working_days(datetime(2012, 12, 23, 14, 0, 39), 5, keep_datetime=True)
    # datetime.datetime(2012, 12, 31, 14, 0, 39)
  5. Set up a local development environment for Workalendar

    master

    To contribute to Workalendar, you should set up a local development environment using virtualenv or virtualenvwrapper. This ensures you have the necessary dependencies installed for testing and development.

    1. Create a virtual environment named WORKALENDAR.
    2. Install the package in editable mode using pip install -e ./ from the root of the cloned repository.
    mkvirtualenv WORKALENDAR
    pip install -e ./
  6. Implement custom calendar options via class constructors

    master

    Since v13.0.0, you can define custom options for a calendar class by overriding the __init__ method. This allows you to pass arguments (flags, strings, numbers, or objects) to the constructor to dynamically change holiday behavior based on runtime context (e.g., employee location, region, or specific years).

    To implement this:

    1. Define your custom arguments in the __init__ method.
    2. Call super().__init__(**kwargs) to ensure the base class is initialized correctly.
    3. Store the options as instance attributes (self.option_name).
    4. Use these attributes within methods like get_variable_days to conditionally add holidays.
    @iso_register('ZK')
    class Zhraa(WesternCalendar):
        def __init__(self, include_january_2nd=False, **kwargs):
            super().__init__(**kwargs)
            self.include_january_2nd = include_january_2nd
    
        def get_variable_days(self, year):
            days = super().get_variable_days(year)
            # ... add other days ...
            if self.include_january_2nd:
                days.append((date(year, 1, 2), "January 2nd"))
            return days
    
    # Usage in business logic
    calendar = Zhraa(include_january_2nd=True)
  7. Verify code quality and standards

    master

    All contributions must follow PEP8 guidelines and pass specific linting and compatibility checks:

    • PEP8: Use tox -e flake8 to check for style violations.
    • Python Compatibility: The project supports Python 3.7, 3.8, 3.9, 3.10, and 3.11.
    • Code Upgrading: If pyupgrade fails, run tox -r pyupgrade to automatically modify your code to meet standards (note: verify this doesn't break flake8 compliance).
    • Testing: All new calendars must include a test class in the workalendar/tests/ directory (e.g., inheriting from GenericCalendarTest) to verify holiday computations.
    # Check PEP8 compliance
    tox -e flake8
    
    # Automatically fix pyupgrade issues
    tox -r pyupgrade
    
    # Install tox for running tests
    pip install tox
  8. Implement a new Calendar class

    master

    Workalendar is built around configuration variables and generic methods. To add a new calendar:

    1. Inherit from a base class: Use WesternCalendar for Gregorian-based calendars or other existing Mixins/Calendars.
    2. Add fixed holidays: Extend the FIXED_HOLIDAYS tuple from the base class.
    3. Enable flags: Use boolean flags like include_labour_day or include_easter_monday to activate standard holidays.
    4. Handle variable holidays: Override the get_variable_days(self, year) method. This method should call super().get_variable_days(year) and return a list of tuples containing (date_object, 'Label').
    5. Register with ISO code: If the calendar has an ISO code, use the @iso_register('CODE') decorator from ..registry_tools to make it queryable via the global registry.
    from ..core import WesternCalendar
    from ..registry_tools import iso_register
    
    @iso_register('ZK')
    class Zhraa(WesternCalendar):
        """Kingdom of Zhraa"""
        include_easter_monday = True
        include_labour_day = True
        
        FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + (
            (8, 2, "King Birthday"),
        )
    
        def get_variable_days(self, year):
            # usual variable days
            days = super().get_variable_days(year)
    
            # Example: Adding a day based on a specific weekday rule
            days.append(
                (Zhraa.get_nth_weekday_in_month(year, 6, MON), 'Day of the Founder'),
            )
            return days
  9. Install astronomical dependencies for specific calendars

    master

    Some calendars (like certain Asian calendars) require astronomical computations for equinoxes or solar terms.

    By default, Workalendar provides pre-computed values for the range 1991 to 2051. If you prefer to compute these yourself using astronomical libraries, install the [astronomy] extra dependency:

    pip install workalendar[astronomy]

    Note on performance: If you have skyfield and skyfield-data installed, Workalendar will use them for computation. To switch back to the faster pre-computed cache, you must uninstall those packages.

    pip install workalendar[astronomy]
  10. Explore Workalendar usage examples

    master

    Workalendar provides several levels of usage guidance depending on your needs:

    • Basic usage: For simple holiday listing and working day calculations.
    • Advanced usage: For more complex calendar logic.
    • Class options: To customize calendar behavior.
    • ISO Registry: To work with ISO-standardized calendar identifiers.
    • iCal Export: To export calendar data into the iCal format.