Metadata-Version: 2.4
Name: year-calculator
Version: 0.2.0
Summary: Year calculations and calendar intelligence utilities
Author: ZAINUL001
License-Expression: MIT
Keywords: calendar,working days,business days,holidays,schedule,deadline,leave planner
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Intended Audience :: Developers
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Office/Business :: Scheduling
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# Year Calculator

A Python library for year calculations and calendar intelligence.

## Installation

```bash
python -m pip install year-calculator
```

## Calculate a resulting year

Use `calculate_year()` when you want a result that your own Python code can
store, compare, or use in another calculation. It does not prompt or print.

```python
from year_calculator import calculate_year

result = calculate_year(2007, 3287)
print(result)
```

Day counting follows the original `whatyear()` convention: day 0 is January 1.
Leap years are handled automatically.

## Original interactive function

`whatyear()` is retained for backward compatibility. It continues to prompt
for missing values and print its result as before.

```python
import year_calculator

year_calculator.whatyear(2007, 3287)
```

## Working calendar

```python
from year_calculator import WorkCalendar

calendar = WorkCalendar(
    year=2027,
    weekend_days=["Saturday", "Sunday"],
    holidays=["2027-01-26", "2027-08-15"],
)

print(calendar.working_days())
print(calendar.working_days(month=1))
print(calendar.monthly_report())
```

`working_days()` counts dates inclusively. Holidays are supplied by the user
as `datetime.date` objects or `YYYY-MM-DD` strings and must belong to the
years covered by the `WorkCalendar`. No country-specific holiday database is
included.

## Calendars that span several years

By default a `WorkCalendar` covers one year. Pass `end_year` to cover a longer
period, so that date ranges, deadlines and leave plans can cross New Year.

```python
from year_calculator import WorkCalendar

calendar = WorkCalendar(2027, end_year=2028, holidays=["2028-01-03"])

print(calendar.working_days("2027-12-27", "2028-01-07"))  # 9
print(calendar.working_days(month=1, year=2028))          # January 2028
print(calendar.next_working_day("2027-12-31"))            # 2028-01-04
```

`month=` uses the first year unless you also pass `year=`.

## Deadlines in working days

```python
calendar = WorkCalendar(2027, end_year=2028, holidays=["2028-01-03"])

print(calendar.add_working_days("2027-12-30", 3))   # 2028-01-05
print(calendar.add_working_days("2028-01-05", -3))  # 2027-12-30
```

Counting starts the day after the start date, so `days=1` is the first
working day after it. The start date itself may be a weekend or holiday. A
negative number counts backwards, and `0` returns the start date unchanged.
If the calendar ends before enough working days are found, a `ValueError`
tells you to create the `WorkCalendar` with a later `end_year`.

## Explain a date

```python
info = calendar.explain("2028-01-03")
print(info)             # 2028-01-03 (Monday): not a working day: holiday
print(info.is_working)  # False
print(info.reasons)     # ('holiday',)
```

`reasons` can contain `"weekend"`, `"holiday"` and
`"day off (no shifts scheduled)"`. For working days, `info.shifts` and
`info.hours` show the scheduled shifts.

## Find the next working shift

```python
start, end = calendar.next_working_window("2027-12-31T18:00")
```

If the moment falls inside a shift, that shift is returned. Otherwise the next
upcoming shift is returned. Date-times must be naive (no time zone). Shift
ends are exclusive, so 17:00 on a 09:00-17:00 day returns the next day.

## Plan leave for the longest breaks

```python
plans = calendar.best_leave_plan(leave_days=5, top=3)

for plan in plans:
    print(plan.start, plan.end, plan.total_days_off)
    print("take leave on:", plan.leave_dates)
```

Weekends and holidays are free. Each working day inside a stretch costs one
leave day. Plans are returned best first (longest stretch of days off) and
never overlap. Restrict the search with `start_date` and `end_date`.

## Configurable working schedules

By default, every non-weekend, non-holiday weekday has a 09:00-17:00 shift.
You can override individual weekdays and configure multiple shifts to account
for breaks. A weekday explicitly mapped to an empty list is treated as a day
off. Weekend days and holidays always remain non-working days.

```python
from year_calculator import WorkCalendar

calendar = WorkCalendar(
    year=2027,
    holidays=["2027-01-26"],
    working_schedule={
        "Monday": [("09:00", "12:00"), ("13:00", "17:00")],
        "Tuesday": [("09:00", "17:00")],
        "Friday": [("09:00", "13:00")],
    },
)

print(calendar.daily_schedule("2027-01-04"))
print(calendar.scheduled_hours("2027-01-01", "2027-01-31"))
print(calendar.scheduled_hours(month=1))
```

Schedule weekday keys can be full weekday names or integers (`Monday=0` to
`Sunday=6`). Times can be `HH:MM` strings or `datetime.time` objects. Shifts
must have a positive duration and must not overlap. Overnight shifts and
timezone-aware schedule times are not supported in this version.

`working_hours(hours_per_day=8, ...)` multiplies the number of working days
by a fixed number of hours. Use `scheduled_hours(...)` to calculate hours
from the configured weekday shifts.

A weekday mapped to an empty list is a day off everywhere: it is not counted
by `working_days()` and has no hours.

## Run tests

From the project root:

```bash
python -m unittest discover -s tests -v
```

## License

Released under the MIT License. See the `LICENSE` file for details.
