Skip to content

Add generator for Tibetan calendar holidays#3330

Open
Abiram08 wants to merge 6 commits intovacanza:devfrom
Abiram08:feature/tibetan-calendar-generator-clean
Open

Add generator for Tibetan calendar holidays#3330
Abiram08 wants to merge 6 commits intovacanza:devfrom
Abiram08:feature/tibetan-calendar-generator-clean

Conversation

@Abiram08
Copy link
Contributor

Proposed change

Implemented a generator script for Tibetan calendar holidays to make future updates reproducible and transparent.

This PR addresses issue #2782 by creating a data-driven generator that:

  • Downloads Tibetan calendar data from the WordPress plugin source
  • Parses Gregorian-to-Tibetan date mappings
  • Extracts specific Tibetan calendar dates for each holiday
  • Generates Python code matching the existing tibetan.py format

Type of change

  • New country/market holidays support (thank you!)
  • Supported country/market holidays update (calendar discrepancy fix, localization)
  • Existing code/documentation/test/process quality improvement (best practice, cleanup, refactoring, optimization)
  • Dependency update (version deprecation/pin/upgrade)
  • Bugfix (non-breaking change which fixes an issue)
  • Breaking change (a code change causing existing functionality to break)
  • New feature (new holidays functionality in general)

Checklist

Copilot AI review requested due to automatic review settings March 15, 2026 14:37
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 15, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds a new generator script that downloads and parses Tibetan calendar data and emits a generated holidays/calendars/tibetan.py; also updates the Tibetan calendar module with extensive year→(MONTH, DAY) mappings for many Tibetan-related holidays through 2100.

Changes

Cohort / File(s) Summary
Tibetan generator
scripts/calendar/tibetan_generator.py
New generator script that downloads a remote data file, parses Tibetan holiday records, derives per-holiday year→date mappings (including logic for BLESSED_RAINY_DAY and WINTER_SOLSTICE), and writes a templated holidays/calendars/tibetan.py. Adds constants, templates, helper functions, and generate_data() entry point.
Tibetan calendar data
holidays/calendars/tibetan.py
Large generated data expansion and minor import reformatting: adds or updates many per-holiday dictionaries (e.g., LOSAR_DATES, BIRTH_OF_GURU_RINPOCHE_DATES, BLESSED_RAINY_DAY_DATES, THIMPHU_DRUBCHEN_DATES, THIMPHU_TSHECHU_DATES, WINTER_SOLSTICE_DATES, BUDDHA_FIRST_SERMON_DATES, BUDDHA_PARINIRVANA_DATES, DAY_OF_OFFERING_DATES, DEATH_OF_ZHABDRUNG_DATES, DESCENDING_DAY_OF_LORD_BUDDHA_DATES) with many added/adjusted year→(month, day) entries through 2100. No method signatures changed.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • KJhellico
  • PPsyrius
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding a generator script for Tibetan calendar holidays.
Description check ✅ Passed The description is directly related to the changeset, explaining the generator's purpose, functionality, and how it addresses the referenced issue.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Tip

CodeRabbit can approve the review once all CodeRabbit's comments are resolved.

Enable the reviews.request_changes_workflow setting to automatically approve the review once all CodeRabbit's comments are resolved.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a reproducible generator script intended to (re)generate Tibetan lunisolar holiday date mappings from the upstream “bhutanese-calendar” plugin data, and updates holidays/calendars/tibetan.py with newly generated output.

Changes:

  • Added scripts/calendar/tibetan_generator.py to download/parse upstream Tibetan calendar data and emit holidays/calendars/tibetan.py.
  • Regenerated holidays/calendars/tibetan.py with new date dictionaries and updated Gregorian month imports/constants formatting.

Reviewed changes

Copilot reviewed 1 out of 2 changed files in this pull request and generated 6 comments.

File Description
scripts/calendar/tibetan_generator.py New generator for producing Tibetan holiday date dictionaries from upstream data.
holidays/calendars/tibetan.py Regenerated Tibetan holiday date tables (and import formatting) based on the new generator output.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

2097: (JUN, 19),
2098: (JUL, 8),
2099: (JUN, 27),
BIRTH_OF_GURU_RINPOCHE_DATES_DATES = {
Comment on lines +2008 to +2013
2020: (SEP, 22),
2021: (SEP, 22),
2022: (SEP, 22),
2023: (SEP, 22),
2024: (SEP, 22),
2025: (SEP, 22),
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@scripts/calendar/tibetan_generator.py`:
- Around line 204-214: The exception handler around urlretrieve(DATA_URL,
data_file) should preserve the original exception chain; update the except block
that catches Exception as e to re-raise the RuntimeError with the original
exception as its cause (i.e., use "raise RuntimeError('No data file available
and download failed!') from e") so callers can see the original error
originating from urlretrieve / DATA_URL / data_file while keeping the existing
cached-file fallback and the return of data_file intact.
- Around line 338-340: Before calling output_path.write_text(...) ensure the
target directory exists and is valid: check Path(__file__).parent.parent.parent
/ "holidays" / "calendars" (the parent of output_path and the directory that
will contain OUT_FILE_NAME) and either create it (e.g., mkdir(parents=True,
exist_ok=True)) or raise a clear exception with the resolved path; update the
code around the output_path and write_text calls to perform this
existence/creation check and fail with a helpful error message if creation is
not possible.
- Around line 274-277: The nested condition can be flattened: replace the inner
if within the block that checks g_month == greg_month so a single if checks both
g_month == greg_month and greg_day in greg_days (and the existing greg_year not
in years_seen) in one expression; update the block that writes to dates_dict
(using MONTH_NAMES[g_month], greg_day) and adds greg_year to years_seen to
remain the same, just under the single combined if that references g_month,
greg_month, greg_day, greg_days, greg_year, years_seen, dates_dict, and
MONTH_NAMES.
- Around line 222-238: The loop currently reassigns the loop variable line which
obscures intent and triggers PLW2901; change the loop to read a distinct name
(e.g., raw_line) in the for-loop and use a separate variable for the processed
string (e.g., stripped or eval_text) before calling ast.literal_eval and
appending day_tuple; update references in the try block (the rstrip,
strip/checks, and literal_eval) to use these new names so the original loop
variable is not overwritten.
- Around line 244-261: The function extract_holiday_dates blindly indexes into
each day_data tuple (indices 2,3,6,7,8) which can raise IndexError if the input
format changes; add a defensive check—either validate len(day_data) >= 9 before
unpacking or wrap the unpacking in a try/except that catches
IndexError/TypeError and raises a clearer ValueError with context including the
offending day_data and its index (or log and continue), then proceed to use
tib_day, tib_month, greg_day, greg_month, greg_year and MONTH_NAMES as before to
populate dates_dict.
- Around line 129-132: HOLIDAY_DATA_TEMPLATE currently appends a literal
"_DATES" to {hol_name}, causing double-suffix names like
BIRTH_OF_GURU_RINPOCHE_DATES_DATES; change the template so it uses {hol_name}
(no added suffix) — i.e. replace "{hol_name}_DATES" with "{hol_name}" in
HOLIDAY_DATA_TEMPLATE — so generated attributes match what _get_holiday expects
(it looks up f"{holiday}_DATES") and work with callers that already pass
f"{hol_name}_DATES".
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 2718dbe6-09c2-4793-9903-e38a0133c159

📥 Commits

Reviewing files that changed from the base of the PR and between 661581b and d3ac2c6.

📒 Files selected for processing (2)
  • holidays/calendars/tibetan.py
  • scripts/calendar/tibetan_generator.py

@KJhellico KJhellico linked an issue Mar 15, 2026 that may be closed by this pull request
@Abiram08 Abiram08 force-pushed the feature/tibetan-calendar-generator-clean branch from d3ac2c6 to 3584f90 Compare March 15, 2026 16:26
@Abiram08
Copy link
Contributor Author

Fixed those bugs. Let me know if there are any changes!

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@scripts/calendar/tibetan_generator.py`:
- Around line 312-314: The generator currently prints "ERROR: No data parsed!"
and returns when all_days is empty (the branch checking the all_days variable in
the top-level generation function), which leaves the process exit code as 0;
change this to raise an exception (for example RuntimeError or a custom
ParseError) with a clear message so the script fails fast when no rows are
parsed (update the branch that currently prints and returns to raise instead),
ensuring callers and CI detect the failure instead of silently keeping a stale
tibetan.py.
- Around line 182-204: The download_data function currently writes directly to
DATA_FILENAME (data_file) so a failed refresh can leave a truncated cache;
change download logic to write to a temporary path (e.g.,
data_file.with_suffix('.tmp') or similar) when calling urlretrieve(DATA_URL,
...), only replace/rename the temp file to data_file on successful completion,
and ensure partial temp files are cleaned up on failure; reference
download_data, DATA_FILENAME/DATA_URL, urlretrieve and data_file when applying
this fix.
- Around line 268-282: The helper extract_fixed_holiday() is being misused for
BLESSED_RAINY_DAY because it simply picks the first matching greg_day per year
(so SEP 22 wins), but BLESSED_RAINY_DAY is equinox-based and needs a
year-specific resolver; stop using extract_fixed_holiday() for BLESSED_RAINY_DAY
and implement a dedicated per-year resolver (e.g.,
compute_blessed_rainy_day_for_year or resolve_equinox_based_holiday) that
inspects all_days for the specific year and applies the correct equinox/year
rule (or an astronomical/lookup algorithm), validates the candidate with
date(...), and returns the correct (MONTH_NAMES[...], day) for that year so
BLESSED_RAINY_DAY_DATES is built from year-by-year resolution rather than
first-match logic; update call sites to use the new resolver and keep
extract_fixed_holiday() only for truly fixed Gregorian dates.
- Around line 34-35: DATA_URL currently points to the branch ref
"refs/heads/master" which makes generated output non-reproducible; instead pin
the upstream snapshot by replacing DATA_URL with a raw URL that references a
specific commit SHA or tag (not a branch) so the script always fetches the same
data; update the constant DATA_URL in tibetan_generator.py to use the raw GitHub
URL with the chosen commit SHA (keep DATA_FILENAME and any downstream usages the
same) and verify fetching still works.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: c4bab4a5-3ba6-480f-8ffc-3073c9ecd99a

📥 Commits

Reviewing files that changed from the base of the PR and between d3ac2c6 and 3584f90.

📒 Files selected for processing (2)
  • holidays/calendars/tibetan.py
  • scripts/calendar/tibetan_generator.py

Comment on lines +34 to +35
DATA_URL = "https://github.com/wp-plugins/bhutanese-calendar/raw/refs/heads/master/data/data.txt"
DATA_FILENAME = "tibetan_data.txt"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

❓ Verification inconclusive

Script executed:

#!/bin/bash
set -euo pipefail
python - <<'PY'
import json
import urllib.request

with urllib.request.urlopen(
    "https://api.github.com/repos/wp-plugins/bhutanese-calendar/branches/master"
) as resp:
    data = json.load(resp)

print(data["commit"]["sha"])
PY

Repository: vacanza/holidays


Repository: vacanza/holidays
Exit code: 1

stderr:

Traceback (most recent call last):
  File "/usr/lib/python3.11/urllib/request.py", line 1348, in do_open
    h.request(req.get_method(), req.selector, req.data, headers,
  File "/usr/lib/python3.11/http/client.py", line 1282, in request
    self._send_request(method, url, body, headers, encode_chunked)
  File "/usr/lib/python3.11/http/client.py", line 1328, in _send_request
    self.endheaders(body, encode_chunked=encode_chunked)
  File "/usr/lib/python3.11/http/client.py", line 1277, in endheaders
    self._send_output(message_body, encode_chunked=encode_chunked)
  File "/usr/lib/python3.11/http/client.py", line 1037, in _send_output
    self.send(msg)
  File "/usr/lib/python3.11/http/client.py", line 975, in send
    self.connect()
  File "/usr/lib/python3.11/http/client.py", line 1454, in connect
    self.sock = self._context.wrap_socket(self.sock,
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/ssl.py", line 517, in wrap_socket
    return self.sslsocket_class._create(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/ssl.py", line 1108, in _create
    self.do_handshake()
  File "/usr/lib/python3.11/ssl.py", line 1379, in do_handshake
    self._sslobj.do_handshake()
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:992)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
  File "/usr/lib/python3.11/urllib/request.py", line 216, in urlopen
    return opener.open(url, data, timeout)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/urllib/request.py", line 519, in open
    response = self._open(req, data)
               ^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/urllib/request.py", line 536, in _open
    result = self._call_chain(self.handle_open, protocol, protocol +
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/urllib/request.py", line 496, in _call_chain
    result = func(*args)
             ^^^^^^^^^^^
  File "/usr/lib/python3.11/urllib/request.py", line 1391, in https_open
    return self.do_open(http.client.HTTPSConnection, req,
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/urllib/request.py", line 1351, in do_open
    raise URLError(err)
urllib.error.URLError: <urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:992)>

Script executed:

# Check if the file exists and inspect lines 34-35
fd tibetan_generator.py --type f

Repository: vacanza/holidays


Repository: vacanza/holidays
Exit code: 0

stdout:

scripts/calendar/tibetan_generator.py

Script executed:

# Check the file around lines 34-35 and get context
head -50 scripts/calendar/tibetan_generator.py | cat -n

Repository: vacanza/holidays


Repository: vacanza/holidays
Exit code: 0

stdout:

     1	#!/usr/bin/env python3
     2	
     3	#  holidays
     4	#  --------
     5	#  A fast, efficient Python library for generating country, province and state
     6	#  specific sets of holidays on the fly. It aims to make determining whether a
     7	#  specific date is a holiday as fast and flexible as possible.
     8	#
     9	#  Authors: Vacanza Team and individual contributors (see CONTRIBUTORS file)
    10	#           dr-prodigy <dr.prodigy.github@gmail.com> (c) 2017-2023
    11	#           ryanss <ryanssdev@icloud.com> (c) 2014-2017
    12	#  Website: https://github.com/vacanza/holidays
    13	#  License: MIT (see LICENSE file)
    14	
    15	"""
    16	Generate Tibetan calendar holidays from WordPress plugin data.
    17	
    18	Downloads calendar data from bhutanese-calendar plugin, extracts Gregorian
    19	dates for each Tibetan holiday, and generates the _TibetanLunisolar class.
    20	
    21	Usage:
    22	    python tibetan_generator.py          # use cached data
    23	    python tibetan_generator.py --download   # force fresh download
    24	"""
    25	
    26	# ruff: noqa: T201, FBT001, FBT002, S310
    27	
    28	import ast
    29	import sys
    30	from datetime import date
    31	from pathlib import Path
    32	from urllib.request import urlretrieve
    33	
    34	DATA_URL = "https://github.com/wp-plugins/bhutanese-calendar/raw/refs/heads/master/data/data.txt"
    35	DATA_FILENAME = "tibetan_data.txt"
    36	
    37	# Tibetan calendar dates for each holiday (day, month in Tibetan calendar)
    38	HOLIDAY_DATES = {
    39	    "BIRTH_OF_GURU_RINPOCHE": (10, 5),
    40	    "BUDDHA_PARINIRVANA": (15, 4),
    41	    "BUDDHA_FIRST_SERMON": (4, 6),
    42	    "DESCENDING_DAY_OF_LORD_BUDDHA": (22, 9),
    43	    "THIMPHU_DRUBCHEN": (16, 8),
    44	    "THIMPHU_TSHECHU": (20, 8),
    45	    "DEATH_OF_ZHABDRUNG": (10, 3),
    46	    "DAY_OF_OFFERING": (30, 11),
    47	    "LOSAR": (1, 1),
    48	}
    49	
    50	# These follow Gregorian calendar, not Tibetan

Script executed:

# Get more context - check download_data function and understand the flow
sed -n '180,210p' scripts/calendar/tibetan_generator.py | cat -n

Repository: vacanza/holidays


Repository: vacanza/holidays
Exit code: 0

stdout:

     1	
     2	
     3	def download_data(force: bool = False) -> Path:
     4	    """Download calendar data or use cached version."""
     5	    data_file = Path(__file__).parent / DATA_FILENAME
     6	
     7	    if data_file.exists() and not force:
     8	        print(f"Using existing data file: {data_file}")
     9	        return data_file
    10	
    11	    print(f"Downloading data from: {DATA_URL}")
    12	    print(f"Saving to: {data_file}")
    13	
    14	    try:
    15	        urlretrieve(DATA_URL, data_file)
    16	        print(f"✅ Download complete! ({data_file.stat().st_size} bytes)")
    17	    except Exception as e:
    18	        print(f"❌ Download failed: {e}")
    19	        if data_file.exists():
    20	            print(f"Using existing cached file: {data_file}")
    21	        else:
    22	            msg = "No data file available and download failed!"
    23	            raise RuntimeError(msg) from e
    24	
    25	    return data_file
    26	
    27	
    28	def parse_data_file(filename: Path) -> list:
    29	    """Parse data file into list of day tuples.
    30	
    31	    Each line is a tuple:

Script executed:

# Check if there are other calendar generators and how they handle URLs
find scripts/calendar -name "*_generator.py" -type f | head -10

Repository: vacanza/holidays


Repository: vacanza/holidays
Exit code: 0

stdout:

scripts/calendar/asian_generator.py
scripts/calendar/hebrew_generator.py
scripts/calendar/islamic_generator.py
scripts/calendar/mongolian_generator.py
scripts/calendar/tibetan_generator.py

Script executed:

# Check how other generators handle DATA_URL - look for patterns
for file in scripts/calendar/{asian,hebrew,islamic,mongolian}_generator.py; do
  echo "=== $file ==="
  grep -n "DATA_URL\|refs/heads\|commit\|SHA" "$file" || echo "No URL found"
done

Repository: vacanza/holidays


Repository: vacanza/holidays
Exit code: 0

stdout:

=== scripts/calendar/asian_generator.py ===
No URL found
=== scripts/calendar/hebrew_generator.py ===
38:    (3, 6, "SHAVUOT"),
39:    (5, 9, "TISHA_BAV"),
40:    (7, 1, "ROSH_HASHANAH"),
=== scripts/calendar/islamic_generator.py ===
No URL found
=== scripts/calendar/mongolian_generator.py ===
No URL found

Script executed:

# Check if other generators actually fetch from URLs
for file in scripts/calendar/{asian,hebrew,islamic,mongolian}_generator.py; do
  echo "=== $(basename $file) ==="
  head -50 "$file" | grep -E "http|url|URL"
done

Repository: vacanza/holidays


Repository: vacanza/holidays
Exit code: 0

stdout:

=== asian_generator.py ===
#  Website: https://github.com/vacanza/holidays
        <https://en.wikipedia.org/wiki/Chinese_New_Year#Dates_in_Chinese_\
        http://putidea.blogspot.com/2010/10/python.html
        https://github.com/BohanZhang/python-MagicLamp/blob/master/bin/utils/lunar_calendar.py
=== hebrew_generator.py ===
#  Website: https://github.com/vacanza/holidays
=== islamic_generator.py ===
#  Website: https://github.com/vacanza/holidays
=== mongolian_generator.py ===
#  Website: https://github.com/vacanza/holidays
See the Wikipedia article: https://en.wikipedia.org/wiki/Mongolian_calendar
- https://www.math.mcgill.ca/gantumur/cal/year.html

Script executed:

# Check the full context of tibetan_generator.py - especially around the main function
tail -50 scripts/calendar/tibetan_generator.py | cat -n

Repository: vacanza/holidays


Repository: vacanza/holidays
Exit code: 0

stdout:

     1	    print(f"Data source: {DATA_URL}")
     2	    print("=" * 70)
     3	
     4	    data_file = download_data(force=force_download)
     5	    all_days = parse_data_file(data_file)
     6	
     7	    if not all_days:
     8	        print("ERROR: No data parsed!")
     9	        return
    10	
    11	    holiday_data = []
    12	
    13	    # Extract Tibetan calendar holidays
    14	    for hol_name, (tib_day, tib_month) in sorted(HOLIDAY_DATES.items()):
    15	        print(f"Extracting {hol_name} (Tibetan {tib_day}/{tib_month})...")
    16	        dates_dict = extract_holiday_dates(all_days, tib_day, tib_month)
    17	        print(f"  Found {len(dates_dict)} occurrences")
    18	        # Template adds _DATES suffix, so pass base name only
    19	        holiday_data.append(generate_holiday_dict_code(hol_name, dates_dict))
    20	
    21	    # Extract Gregorian-based holidays
    22	    for hol_name, (greg_month, greg_days) in sorted(FIXED_HOLIDAYS.items()):
    23	        print(f"Extracting {hol_name} (Gregorian month {greg_month})...")
    24	        dates_dict = extract_fixed_holiday(all_days, greg_month, greg_days)
    25	        print(f"  Found {len(dates_dict)} occurrences")
    26	        holiday_data.append(generate_holiday_dict_code(hol_name, dates_dict))
    27	
    28	    # Assemble complete file
    29	    holiday_data_str = "\n".join(holiday_data)
    30	    class_str = (
    31	        HEADER_TEMPLATE.format(class_name=CLASS_NAME) + "\n" + holiday_data_str + FOOTER_TEMPLATE
    32	    )
    33	
    34	    # Write output
    35	    output_path = Path(__file__).parent.parent.parent / "holidays" / "calendars" / OUT_FILE_NAME
    36	
    37	    if not output_path.parent.exists():
    38	        msg = f"Output directory does not exist: {output_path.parent}"
    39	        raise RuntimeError(msg)
    40	
    41	    output_path.write_text(class_str, encoding="UTF-8")
    42	
    43	    print(f"\n✅ Generated: {output_path}")
    44	    print(f"File size: {len(class_str)} bytes")
    45	    print("\nDone!")
    46	
    47	
    48	if __name__ == "__main__":
    49	    force_download = "--download" in sys.argv
    50	    generate_data(force_download=force_download)

Script executed:

# Check the main function signature and error handling
sed -n '300,330p' scripts/calendar/tibetan_generator.py | cat -n

Repository: vacanza/holidays


Repository: vacanza/holidays
Exit code: 0

stdout:

     1	
     2	def generate_data(force_download: bool = False) -> None:
     3	    """Generate tibetan.py from calendar data."""
     4	    print("=" * 70)
     5	    print("Tibetan Calendar Holiday Generator")
     6	    print("=" * 70)
     7	    print(f"Data source: {DATA_URL}")
     8	    print("=" * 70)
     9	
    10	    data_file = download_data(force=force_download)
    11	    all_days = parse_data_file(data_file)
    12	
    13	    if not all_days:
    14	        print("ERROR: No data parsed!")
    15	        return
    16	
    17	    holiday_data = []
    18	
    19	    # Extract Tibetan calendar holidays
    20	    for hol_name, (tib_day, tib_month) in sorted(HOLIDAY_DATES.items()):
    21	        print(f"Extracting {hol_name} (Tibetan {tib_day}/{tib_month})...")
    22	        dates_dict = extract_holiday_dates(all_days, tib_day, tib_month)
    23	        print(f"  Found {len(dates_dict)} occurrences")
    24	        # Template adds _DATES suffix, so pass base name only
    25	        holiday_data.append(generate_holiday_dict_code(hol_name, dates_dict))
    26	
    27	    # Extract Gregorian-based holidays
    28	    for hol_name, (greg_month, greg_days) in sorted(FIXED_HOLIDAYS.items()):
    29	        print(f"Extracting {hol_name} (Gregorian month {greg_month})...")
    30	        dates_dict = extract_fixed_holiday(all_days, greg_month, greg_days)
    31	        print(f"  Found {len(dates_dict)} occurrences")

Script executed:

# Verify the exit behavior - check if there's any sys.exit() call in main
sed -n '320,350p' scripts/calendar/tibetan_generator.py | cat -n

Repository: vacanza/holidays


Repository: vacanza/holidays
Exit code: 0

stdout:

     1	        print(f"Extracting {hol_name} (Tibetan {tib_day}/{tib_month})...")
     2	        dates_dict = extract_holiday_dates(all_days, tib_day, tib_month)
     3	        print(f"  Found {len(dates_dict)} occurrences")
     4	        # Template adds _DATES suffix, so pass base name only
     5	        holiday_data.append(generate_holiday_dict_code(hol_name, dates_dict))
     6	
     7	    # Extract Gregorian-based holidays
     8	    for hol_name, (greg_month, greg_days) in sorted(FIXED_HOLIDAYS.items()):
     9	        print(f"Extracting {hol_name} (Gregorian month {greg_month})...")
    10	        dates_dict = extract_fixed_holiday(all_days, greg_month, greg_days)
    11	        print(f"  Found {len(dates_dict)} occurrences")
    12	        holiday_data.append(generate_holiday_dict_code(hol_name, dates_dict))
    13	
    14	    # Assemble complete file
    15	    holiday_data_str = "\n".join(holiday_data)
    16	    class_str = (
    17	        HEADER_TEMPLATE.format(class_name=CLASS_NAME) + "\n" + holiday_data_str + FOOTER_TEMPLATE
    18	    )
    19	
    20	    # Write output
    21	    output_path = Path(__file__).parent.parent.parent / "holidays" / "calendars" / OUT_FILE_NAME
    22	
    23	    if not output_path.parent.exists():
    24	        msg = f"Output directory does not exist: {output_path.parent}"
    25	        raise RuntimeError(msg)
    26	
    27	    output_path.write_text(class_str, encoding="UTF-8")
    28	
    29	    print(f"\n✅ Generated: {output_path}")
    30	    print(f"File size: {len(class_str)} bytes")
    31	    print("\nDone!")

Script executed:

# Check the actual __main__ block
sed -n '348,360p' scripts/calendar/tibetan_generator.py | cat -n

Repository: vacanza/holidays


Repository: vacanza/holidays
Exit code: 0

stdout:

     1	    print(f"\n✅ Generated: {output_path}")
     2	    print(f"File size: {len(class_str)} bytes")
     3	    print("\nDone!")
     4	
     5	
     6	if __name__ == "__main__":
     7	    force_download = "--download" in sys.argv
     8	    generate_data(force_download=force_download)

Pin the upstream data snapshot.

DATA_URL tracks refs/heads/master, so rerunning the generator later can rewrite historical tables from a different upstream state. That misses the reproducibility goal of this script.

♻️ Proposed fix
-DATA_URL = "https://github.com/wp-plugins/bhutanese-calendar/raw/refs/heads/master/data/data.txt"
+UPSTREAM_DATA_SHA = "<pin-the-reviewed-upstream-commit>"
+DATA_URL = (
+    "https://raw.githubusercontent.com/"
+    f"wp-plugins/bhutanese-calendar/{UPSTREAM_DATA_SHA}/data/data.txt"
+)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/calendar/tibetan_generator.py` around lines 34 - 35, DATA_URL
currently points to the branch ref "refs/heads/master" which makes generated
output non-reproducible; instead pin the upstream snapshot by replacing DATA_URL
with a raw URL that references a specific commit SHA or tag (not a branch) so
the script always fetches the same data; update the constant DATA_URL in
tibetan_generator.py to use the raw GitHub URL with the chosen commit SHA (keep
DATA_FILENAME and any downstream usages the same) and verify fetching still
works.

@KJhellico
Copy link
Collaborator

General remark: the code should be simple; take a look at other generators.

        print(f"❌ Download failed: {e}")

A very clear example of AI code.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
scripts/calendar/tibetan_generator.py (1)

180-188: ⚠️ Potential issue | 🟠 Major

This still hard-codes September 23.

days is built from Gregorian dates 22/23/24, so a complete year will always contain 23 and chosen collapses to 23. That means the generated BLESSED_RAINY_DAY_DATES table is not actually resolving an equinox-based holiday year by year. Either source a per-year lookup or make the fixed approximation explicit instead of deriving it from all_days.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/calendar/tibetan_generator.py` around lines 180 - 188, The code
incorrectly assumes Sept 23 will always be present in rainy because days is
prefiltered to (22,23,24); instead choose the day closest to the autumnal
equinox per year (or explicitly document a fixed approximation). Replace the
current chosen selection (chosen = 23 if 23 in days else ...) with logic that
picks the day from rainy[year] minimizing abs(day - 23) (tie-breaker: pick the
earlier date), so BLESSED_RAINY_DAY for each year is the closest of the
available Gregorian dates; alternatively, if you intend a fixed approximation,
stop deriving from all_days and set chosen = 23 unconditionally and add a
comment explaining it. Ensure changes reference rainy, days, chosen,
BLESSED_RAINY_DAY, and dates.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@scripts/calendar/tibetan_generator.py`:
- Around line 177-178: The code uses _date(...).strftime("%b").upper() to build
month_str which is locale-dependent; replace this with an explicit static
month-abbreviation mapping (e.g. MONTH_ABBR list or dict) and set month_str =
MONTH_ABBR[g_month - 1] before constructing the dates.setdefault(hol_name,
{})[g_year] = f"{month_str}, {g_day}"; update or add the MONTH_ABBR constant
near the top of tibetan_generator.py and reference it instead of strftime to
ensure consistent English abbreviations.

---

Duplicate comments:
In `@scripts/calendar/tibetan_generator.py`:
- Around line 180-188: The code incorrectly assumes Sept 23 will always be
present in rainy because days is prefiltered to (22,23,24); instead choose the
day closest to the autumnal equinox per year (or explicitly document a fixed
approximation). Replace the current chosen selection (chosen = 23 if 23 in days
else ...) with logic that picks the day from rainy[year] minimizing abs(day -
23) (tie-breaker: pick the earlier date), so BLESSED_RAINY_DAY for each year is
the closest of the available Gregorian dates; alternatively, if you intend a
fixed approximation, stop deriving from all_days and set chosen = 23
unconditionally and add a comment explaining it. Ensure changes reference rainy,
days, chosen, BLESSED_RAINY_DAY, and dates.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0cf488dc-4d2d-4077-97c3-daf501987eb9

📥 Commits

Reviewing files that changed from the base of the PR and between 3584f90 and 0c68b32.

📒 Files selected for processing (2)
  • holidays/calendars/tibetan.py
  • scripts/calendar/tibetan_generator.py

Comment on lines +177 to +178
month_str = _date(g_year, g_month, 1).strftime("%b").upper()
dates.setdefault(hol_name, {})[g_year] = f"{month_str}, {g_day}"
Copy link
Contributor

@coderabbitai coderabbitai bot Mar 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Does Python datetime.strftime("%b") depend on the active locale?

💡 Result:

Yes. In Python, datetime.strftime() ultimately delegates to the platform C library’s strftime(), and directives like "%b" are defined as “month as locale’s abbreviated name”, so the output depends on the active LC_TIME locale (e.g., "Jan" vs a non-English abbreviation). [1], [2]

You can change what "%b" produces by changing the process locale via locale.setlocale(locale.LC_TIME, ...) (which affects locale-sensitive formatting). [3]

Sources:
[1] Python datetime docs: "%b" is locale’s abbreviated month name; notes Python calls platform C strftime() (platform/locale dependent). (docs.python.org)
[2] POSIX strftime spec: "%b" replaced by the locale’s abbreviated month name. (man7.org)
[3] Python locale module docs (locale affects time.strftime()-style formatting). (docs.python.org)

Citations:


🏁 Script executed:

# First, check if the file exists and get its structure
find . -name "tibetan_generator.py" -type f

Repository: vacanza/holidays

Length of output: 99


🏁 Script executed:

# Get the file size to determine how to read it
wc -l scripts/calendar/tibetan_generator.py

Repository: vacanza/holidays

Length of output: 101


🏁 Script executed:

# Examine the relevant section around lines 177-178
sed -n '1,30p' scripts/calendar/tibetan_generator.py

Repository: vacanza/holidays

Length of output: 1150


🏁 Script executed:

# Get context around the flagged lines
sed -n '170,185p' scripts/calendar/tibetan_generator.py

Repository: vacanza/holidays

Length of output: 734


🏁 Script executed:

# Search for uses of strftime in the file to understand the scope
rg -n "strftime" scripts/calendar/tibetan_generator.py

Repository: vacanza/holidays

Length of output: 140


🏁 Script executed:

# Check if MONTH_NAMES or similar month mappings already exist
rg -n "MONTH" scripts/calendar/tibetan_generator.py

Repository: vacanza/holidays

Length of output: 42


🏁 Script executed:

# Check all uses of _date to see if removing the import would break anything
rg -n "_date" scripts/calendar/tibetan_generator.py

Repository: vacanza/holidays

Length of output: 1424


Replace locale-dependent strftime() with explicit month mapping.

strftime("%b") returns the locale's abbreviated month name. Running this under non-English locales emits tokens like MÄR or MAI instead of the expected "MAR" or "MAI" constants, breaking the generated output. Use a static month-number mapping instead.

Suggested fix
 import ast
-from datetime import date as _date
 from pathlib import Path
 from urllib.request import urlretrieve
 
+MONTH_NAMES = {
+    1: "JAN",
+    2: "FEB",
+    3: "MAR",
+    4: "APR",
+    5: "MAY",
+    6: "JUN",
+    7: "JUL",
+    8: "AUG",
+    9: "SEP",
+    10: "OCT",
+    11: "NOV",
+    12: "DEC",
+}
+
 ...
-                month_str = _date(g_year, g_month, 1).strftime("%b").upper()
+                month_str = MONTH_NAMES[g_month]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/calendar/tibetan_generator.py` around lines 177 - 178, The code uses
_date(...).strftime("%b").upper() to build month_str which is locale-dependent;
replace this with an explicit static month-abbreviation mapping (e.g. MONTH_ABBR
list or dict) and set month_str = MONTH_ABBR[g_month - 1] before constructing
the dates.setdefault(hol_name, {})[g_year] = f"{month_str}, {g_day}"; update or
add the MONTH_ABBR constant near the top of tibetan_generator.py and reference
it instead of strftime to ensure consistent English abbreviations.

✅ Addressed in commit 04a1824

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@coderabbitai, this approach is used in all other generators.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check strftime usage across all generator scripts
rg -n "strftime" scripts/calendar/ --type py -C2

Length of output: 1804


@KJhellico, you're right. strftime('%b').upper() is the established pattern used consistently across mongolian_generator.py, islamic_generator.py, hebrew_generator.py, and asian_generator.py. My comment was incorrect — apologies for the noise.


✏️ Learnings added
Learnt from: KJhellico
Repo: vacanza/holidays PR: 3330
File: scripts/calendar/tibetan_generator.py:0-0
Timestamp: 2026-03-16T16:44:16.288Z
Learning: In the vacanza/holidays project, generator scripts (scripts/calendar/) consistently use `dt.strftime('%b').upper()` to produce uppercase month abbreviations (e.g., "JAN", "FEB") for generating date strings. This is the established project-wide convention; do not flag it as locale-dependent or suggest replacing it with a static mapping.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: PPsyrius
Repo: vacanza/holidays PR: 2354
File: holidays/countries/fiji.py:146-159
Timestamp: 2025-03-19T16:54:58.657Z
Learning: In the holidays library implementation, explicit holiday dates (like Diwali in Fiji) are only defined for historical years with official sources (2016-2025). Future dates beyond the explicitly defined range are automatically calculated by methods like `_add_diwali`, which provide approximations when official dates aren't yet available.

Learnt from: PPsyrius
Repo: vacanza/holidays PR: 2416
File: tests/countries/test_turkmenistan.py:52-64
Timestamp: 2025-04-05T04:47:27.213Z
Learning: For holiday tests in the vacanza/holidays project, structure tests by individual holidays rather than by years. Each test method should focus on a specific holiday and test it across multiple years (from start_year through 2050) using helper methods like `assertHolidayName`. For fixed holidays, use generators like `(f"{year}-01-01" for year in range(1991, 2051))`. For movable holidays, specify individual dates for specific years followed by a range check.

Learnt from: PPsyrius
Repo: vacanza/holidays PR: 2843
File: holidays/countries/burundi.py:15-16
Timestamp: 2025-08-21T05:56:33.276Z
Learning: In the holidays library, when importing Gregorian month constants from holidays.calendars.gregorian, only import the months that are actually used in the date data. For example, if Islamic holiday dates only reference JUN, JUL, SEP, OCT, then only import those specific constants rather than importing additional unused months.

Learnt from: PPsyrius
Repo: vacanza/holidays PR: 2643
File: holidays/countries/mauritius.py:144-169
Timestamp: 2025-06-19T02:34:18.382Z
Learning: Custom holiday classes that extend _CustomHinduHolidays, _CustomIslamicHolidays, _CustomBuddhistHolidays, etc. in the holidays library do not use docstrings. They follow a pattern of using only inline comments above date dictionaries, as seen in Malaysia, Singapore, UAE, and other country implementations.

Learnt from: PPsyrius
Repo: vacanza/holidays PR: 2881
File: tests/countries/test_iran.py:28-28
Timestamp: 2025-09-14T16:05:55.205Z
Learning: In tests/countries/test_iran.py, using years=(self.start_year - 1, 2102) in the no-holiday test is intentional because Iran uses the Persian Calendar which has specific supported year range constraints, and 2102 represents the upper limit of the Persian Calendar's supported range, not just an arbitrary far-future date.

Learnt from: PPsyrius
Repo: vacanza/holidays PR: 2706
File: holidays/countries/cayman_islands.py:80-97
Timestamp: 2025-07-10T03:36:16.461Z
Learning: In the holidays library, date dictionaries that map years to specific dates (like queens_birthday_dates, spring_bank_dates, thanksgiving_day_dates, etc.) are typically defined within the _populate_public_holidays method rather than as module-level constants. This is the established library-wide pattern seen across multiple country implementations including United Kingdom, United States, Sri Lanka, and others.

Learnt from: KJhellico
Repo: vacanza/holidays PR: 2614
File: holidays/countries/guyana.py:146-179
Timestamp: 2025-06-14T20:12:37.212Z
Learning: The `_CustomHinduHolidays` mechanism works through `_CustomCalendarType` metaclass which renames public attributes (like `DIWALI_INDIA_DATES`) with a postfix, allowing `_HinduLunisolar::_get_holiday` to find and use custom holiday dates. When `_add_diwali_india()` is called, it uses the custom dates if available rather than calculated dates.

Learnt from: KJhellico
Repo: vacanza/holidays PR: 2614
File: holidays/countries/guyana.py:146-179
Timestamp: 2025-06-14T20:12:37.212Z
Learning: The `_CustomHinduHolidays` mechanism works through `_CustomCalendarType` metaclass which renames public attributes (like `DIWALI_INDIA_DATES`) with a postfix, allowing `_HinduLunisolar::_get_holiday` to find and use custom holiday dates. When `_add_diwali_india()` is called, it uses the custom dates if available rather than calculated dates.

Learnt from: Wasif-Shahzad
Repo: vacanza/holidays PR: 2409
File: holidays/countries/qatar.py:27-46
Timestamp: 2025-04-03T16:58:27.175Z
Learning: In the holidays library, method names like `_add_holiday_2nd_tue_of_feb()` and `_add_holiday_1st_sun_of_mar()` use calendar constants like FEB, TUE, MAR, and SUN internally through parent class implementations even when these constants don't appear directly in the file.

Learnt from: PPsyrius
Repo: vacanza/holidays PR: 2601
File: holidays/countries/mongolia.py:140-146
Timestamp: 2025-06-15T11:50:44.366Z
Learning: The holidays library doesn't currently have helper functions like `_add_holiday_span` for adding consecutive multi-day holidays. Countries like Mongolia, Thailand, and Vietnam implement multi-day holiday spans using inline loops with `_timedelta(dt, delta)` pattern.

Learnt from: Wasif-Shahzad
Repo: vacanza/holidays PR: 2409
File: holidays/countries/qatar.py:27-46
Timestamp: 2025-04-03T16:58:27.175Z
Learning: In the holidays library, method names like `_add_holiday_2nd_tue_of_feb()` and `_add_holiday_1st_sun_of_mar()` use calendar constants internally through parent class implementations even when these constants don't appear directly in the file. Removing imports that seem unused based on a simple text search could break functionality.

Learnt from: PPsyrius
Repo: vacanza/holidays PR: 2860
File: holidays/groups/eastern.py:36-36
Timestamp: 2025-08-28T02:34:11.657Z
Learning: The default estimated_label change from "%s (estimated)" to "%s" in holidays/groups/eastern.py is intentional to remove English-only fallback text from localized holiday names. Countries that need estimated labels should define them properly with localization support rather than relying on the English fallback.

Learnt from: KJhellico
Repo: vacanza/holidays PR: 2860
File: holidays/countries/burkina_faso.py:27-30
Timestamp: 2025-08-26T21:24:41.827Z
Learning: Countries in the holidays library that don't have localization support yet should use plain English strings for labels (e.g., `estimated_label = "%s (estimated)"`), while only countries with existing .po translation files should use `tr()` wrapping. Check for the presence of .po files in holidays/locale to determine if a country has localization support.

Learnt from: PPsyrius
Repo: vacanza/holidays PR: 2794
File: holidays/groups/christian.py:328-343
Timestamp: 2025-08-11T10:14:28.517Z
Learning: For Ethiopian holidays in the `holidays/groups/christian.py` file, docstring wording should maintain source-accurate phrasing (e.g., "in coincidence of" for Ethiopian New Year/Enkutatash), even when it might read awkwardly in English, to ensure consistency with official Ethiopian documentation.

Learnt from: KJhellico
Repo: vacanza/holidays PR: 2960
File: holidays/locale/ca/LC_MESSAGES/ES.po:97-100
Timestamp: 2025-09-26T13:44:12.652Z
Learning: When fixing localization issues in the holidays project, corrections should be made in both the Python source code comments and the en_US .po file msgstr entries, then all other language .po files should be regenerated to pick up the corrected en_US comments.

Learnt from: PPsyrius
Repo: vacanza/holidays PR: 2843
File: holidays/countries/burundi.py:63-101
Timestamp: 2025-08-21T04:56:03.780Z
Learning: In the holidays library, countries with localization support consistently use tr() wrappers around holiday names when calling _add_* methods (e.g., self._add_new_years_day(tr("Holiday Name"))). This is the established pattern across United States, Thailand, and other l10n-enabled countries, contrary to any suggestion that translation is handled internally by _add_* methods.

Learnt from: PPsyrius
Repo: vacanza/holidays PR: 2843
File: holidays/countries/burundi.py:63-101
Timestamp: 2025-08-21T04:56:03.780Z
Learning: In the holidays library, countries with localization support DO use tr() wrappers around holiday names when calling _add_* methods. This is the correct pattern for l10n-enabled country implementations, contrary to previous learning about translation being handled internally by _add_* methods.

Learnt from: PPsyrius
Repo: vacanza/holidays PR: 2881
File: holidays/locale/fr/LC_MESSAGES/DZ.po:37-41
Timestamp: 2025-09-03T17:10:35.672Z
Learning: In Algerian French localization (holidays/locale/fr/LC_MESSAGES/DZ.po), capitalization should follow Algerian source-accurate conventions rather than standard French orthographic rules. For example, "Jour de l'An Grégorien" with capital "G" is correct for Algerian usage, even though standard French would typically use lowercase "grégorien".

Learnt from: ankushhKapoor
Repo: vacanza/holidays PR: 2601
File: holidays/calendars/mongolian.py:1-1
Timestamp: 2025-06-10T12:43:10.577Z
Learning: The holidays project's calendar files (in holidays/calendars/) follow a consistent pattern of NOT having module-level docstrings. They only use class-level docstrings for their main classes. When reviewing calendar files, maintain this consistency and do not suggest adding module docstrings.

Learnt from: PPsyrius
Repo: vacanza/holidays PR: 2794
File: holidays/calendars/ethiopian.py:13-13
Timestamp: 2025-08-11T13:48:45.953Z
Learning: The holidays library does not use `__all__` declarations in calendar modules (holidays/calendars/). Calendar files follow a standard pattern of defining constants and functions directly without explicit exports, similar to the convention used in country modules.

Learnt from: KJhellico
Repo: vacanza/holidays PR: 2653
File: holidays/locale/th/LC_MESSAGES/TW.po:17-21
Timestamp: 2025-06-21T18:06:50.027Z
Learning: KJhellico's username includes a tilde character (~) as part of their nickname (appears as "~Jhellico" in Last-Translator headers), which is intentional formatting and not an error.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
scripts/calendar/tibetan_generator.py (1)

176-200: 🧹 Nitpick | 🔵 Trivial

Consider defensive tuple validation.

These functions access indices 2, 3, 6, 7, 8 directly. If the upstream data format ever changes, an IndexError would occur with a non-obvious message.

A simple guard could help:

if len(d) < 9:
    continue

Given the data source is frozen, this is optional but would make debugging easier if data corruption occurs.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/calendar/tibetan_generator.py` around lines 176 - 200, Add defensive
tuple-length guards to the three extraction functions to avoid IndexError if
upstream day tuples are short: in _extract_tibetan_holidays,
_extract_blessed_rainy_day and _extract_winter_solstice, skip any day tuple d
where len(d) < 9 (or the number of indices you access) before reading d[2],
d[3], d[6], d[7], d[8]; keep the rest of the logic unchanged so HOLIDAY_DATES,
MONTH_NAMES and _pick_equinox_day usages remain the same.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@scripts/calendar/tibetan_generator.py`:
- Around line 205-208: The temporary file created as tmp_file before calling
urlretrieve(DATA_URL, tmp_file) can be left behind if urlretrieve raises; wrap
the download and replace steps in a try/finally (or try/except/finally) around
urlretrieve and tmp_file.replace(data_file) so that in the finally block you
check for tmp_file.exists() and unlink it on failure, ensuring tmp_file is
removed unless the replace succeeded; reference tmp_file, urlretrieve, DATA_URL
and data_file to locate and modify the logic.

---

Duplicate comments:
In `@scripts/calendar/tibetan_generator.py`:
- Around line 176-200: Add defensive tuple-length guards to the three extraction
functions to avoid IndexError if upstream day tuples are short: in
_extract_tibetan_holidays, _extract_blessed_rainy_day and
_extract_winter_solstice, skip any day tuple d where len(d) < 9 (or the number
of indices you access) before reading d[2], d[3], d[6], d[7], d[8]; keep the
rest of the logic unchanged so HOLIDAY_DATES, MONTH_NAMES and _pick_equinox_day
usages remain the same.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 877591de-7714-490b-9f41-4225c20afba4

📥 Commits

Reviewing files that changed from the base of the PR and between 0c68b32 and 04a1824.

📒 Files selected for processing (1)
  • scripts/calendar/tibetan_generator.py

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
scripts/calendar/tibetan_generator.py (1)

207-217: ⚠️ Potential issue | 🟠 Major

Restore cached-file fallback on download failure.

Line 213 currently re-raises immediately after cleanup, so a transient fetch failure aborts generation even when tibetan_data.txt already exists locally.

♻️ Proposed fix
 def generate_data() -> None:
     data_file = Path(__file__).parent / DATA_FILENAME
     tmp_file = data_file.with_suffix(".tmp")
 
     try:
         urlretrieve(DATA_URL, tmp_file)
-    except Exception:
+        tmp_file.replace(data_file)
+    except Exception as err:
         if tmp_file.exists():
             tmp_file.unlink()
-        raise
-    tmp_file.replace(data_file)
+        if data_file.exists():
+            print(f"Download failed; using cached file: {data_file}")
+        else:
+            raise RuntimeError("No data file available and download failed.") from err
#!/bin/bash
set -euo pipefail
# Verify whether generate_data has cached fallback logic inside the download exception path.
rg -n -C4 'def generate_data|urlretrieve|except Exception|data_file\.exists\(|tmp_file\.replace|RuntimeError' scripts/calendar/tibetan_generator.py
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/calendar/tibetan_generator.py` around lines 207 - 217, The download
in generate_data currently always re-raises on Exception after removing
tmp_file, aborting when a cached data_file already exists; change the except
block handling for urlretrieve so that after cleaning up tmp_file it checks if
data_file.exists() and if so returns (or logs a warning) to use the cached
DATA_FILENAME, otherwise re-raises the exception; reference generate_data,
urlretrieve, tmp_file, data_file, DATA_URL and DATA_FILENAME when making this
change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@scripts/calendar/tibetan_generator.py`:
- Around line 207-217: The download in generate_data currently always re-raises
on Exception after removing tmp_file, aborting when a cached data_file already
exists; change the except block handling for urlretrieve so that after cleaning
up tmp_file it checks if data_file.exists() and if so returns (or logs a
warning) to use the cached DATA_FILENAME, otherwise re-raises the exception;
reference generate_data, urlretrieve, tmp_file, data_file, DATA_URL and
DATA_FILENAME when making this change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: c7a21098-4940-438c-b7d3-c6cab1ae386a

📥 Commits

Reviewing files that changed from the base of the PR and between 04a1824 and 821f293.

📒 Files selected for processing (1)
  • scripts/calendar/tibetan_generator.py

@Abiram08
Copy link
Contributor Author

@KJhellico Is the current approach of always re-downloading and failing hard on network errors acceptable, or should the generator fall back to a cached tibetan_data.txt when a download fails ?

@KJhellico
Copy link
Collaborator

Is the current approach of always re-downloading and failing hard on network errors acceptable

The approach should be simple: if there's a local file, we work with it; if not, we download it. And that's it.

@Abiram08
Copy link
Contributor Author

Should the download logic be simplified to use the local file if it exists, download only if it doesn't? No temp file, no fallback handling. Is this approach fine ?

@KJhellico
Copy link
Collaborator

use the local file if it exists, download only if it doesn't?

Yes, exactly.

@sonarqubecloud
Copy link

@Abiram08
Copy link
Contributor Author

I've removed Winter Solstice and Blessed Rainy Day from the generator as they are solar holidays, but the existing tests in test_bhutan.py and BH.py still expect them in tibetan.py. Should these be hardcoded directly in tibetan.py, or moved into BH.py? What would be the preferred approach here?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement generator for calendars/tibetian

3 participants