diff --git a/.github/workflows/ci-browser-security.yml b/.github/workflows/ci-browser-security.yml index 916fd867c..bd0fc218f 100644 --- a/.github/workflows/ci-browser-security.yml +++ b/.github/workflows/ci-browser-security.yml @@ -18,7 +18,7 @@ concurrency: jobs: browser-security: name: Packaged app · 3 browsers · axe-core · OWASP ZAP - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 75 permissions: contents: read # Build and scan the checked-out application. diff --git a/.github/workflows/ci-database-compatibility.yml b/.github/workflows/ci-database-compatibility.yml index 6f6ca9d07..f32a499ed 100644 --- a/.github/workflows/ci-database-compatibility.yml +++ b/.github/workflows/ci-database-compatibility.yml @@ -18,7 +18,7 @@ concurrency: jobs: database: name: ${{ matrix.database.name }} schema and behavior - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 35 permissions: contents: read # Read application, schema and test sources. diff --git a/.github/workflows/ci-deep-regression.yml b/.github/workflows/ci-deep-regression.yml index 9e02d7164..896da77d3 100644 --- a/.github/workflows/ci-deep-regression.yml +++ b/.github/workflows/ci-deep-regression.yml @@ -19,7 +19,7 @@ concurrency: jobs: regression: name: Browser regression shard ${{ matrix.shard }}/4 - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 120 strategy: fail-fast: false @@ -250,7 +250,7 @@ jobs: installer-locales: name: Fresh installer (${{ matrix.locale }}) - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 45 strategy: fail-fast: false diff --git a/.github/workflows/ci-e2e.yml b/.github/workflows/ci-e2e.yml index e9c536bca..ea538e7da 100644 --- a/.github/workflows/ci-e2e.yml +++ b/.github/workflows/ci-e2e.yml @@ -17,7 +17,7 @@ concurrency: jobs: e2e: name: Full E2E suite (Playwright · Apache · MySQL) - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 60 services: @@ -389,3 +389,301 @@ jobs: name: apache-error-log-${{ github.run_id }} path: /var/log/apache2/pinakes-error.log retention-days: 7 + + # ── 0.9.0 feature suites ────────────────────────────────────────────────── + # Every Playwright suite added by the 0.9.0 stack (circulation rules, patron + # types, closures calendar, fines ledger, debt block, stocktake, MARC, cards, + # patron photo, user import, statistics, suppliers, RDA types, #501 quoted + # search). Same environment as the deep regression shards — Apache/mod_php, + # MySQL, Mailpit behind a real sendmail transport, the CI MySQL wrapper and + # poppler for the PDF assertions — on a fresh install bootstrapped by the + # full lifecycle suite. Two groups run in parallel; each list is explicit so + # a new spec has to be added here on purpose. + feature-090: + name: 0.9.0 feature suites (${{ matrix.group }}) + runs-on: ubuntu-24.04 + timeout-minutes: 90 + strategy: + fail-fast: false + matrix: + include: + - group: circulation + specs: >- + tests/circulation-card.spec.js + tests/circulation-consultation.spec.js + tests/circulation-interop.spec.js + tests/circulation-patron-types.spec.js + tests/circulation-reservations.spec.js + tests/circulation-rules-matrix.spec.js + tests/circulation-settings-access.spec.js + tests/closures-calendar-admin.spec.js + tests/closures-calendar-circulation.spec.js + tests/closures-calendar-loans.spec.js + tests/debt-block.spec.js + tests/feature-toggle-circulation-rules.spec.js + tests/feature-toggle-closures-calendar.spec.js + tests/feature-toggle-fines.spec.js + tests/fines-closed-days.spec.js + tests/fines-ledger.spec.js + tests/fines-patron-types.spec.js + tests/holidays-preset.spec.js + tests/holidays-world.spec.js + tests/stack-090-integration.spec.js + tests/stack-090-review-fixes.spec.js + tests/stack-090-zero-config.spec.js + - group: catalogue + specs: >- + tests/attendance-090.spec.js + tests/catalogue-cards-090.spec.js + tests/feature-toggle-catalogue-cards.spec.js + tests/feature-toggles-followups.spec.js + tests/feature-toggle-marc.spec.js + tests/feature-toggle-patron-cards.spec.js + tests/feature-toggle-rda-types.spec.js + tests/feature-toggle-stocktake.spec.js + tests/feature-toggle-suppliers.spec.js + tests/feature-toggle-user-import.spec.js + tests/feature-toggle-visitor-log.spec.js + tests/feature-toggles-settings.spec.js + tests/inventory-stocktake.spec.js + tests/issue-501-admin-lists.spec.js + tests/issue-501-exact-quoted-search.spec.js + tests/issue-501-name-pages.spec.js + tests/issue-501-quoted-hint.spec.js + tests/issue-501-quoted-new-author.spec.js + tests/issue-501-search-fields.spec.js + tests/issue-501-search-hint.spec.js + tests/marc-bulk-export.spec.js + tests/marc-bulk-import.spec.js + tests/marc-copy-cataloging.spec.js + tests/patron-cards.spec.js + tests/patron-photo.spec.js + tests/rda-types-090.spec.js + tests/statistics-090.spec.js + tests/suppliers-090.spec.js + tests/user-import.spec.js + + services: + mysql: + image: mysql:8.0@sha256:7dcddc01f13bab2f15cde676d44d01f61fc9f99fe7785e86196dfc07d358ae2b + env: + MYSQL_ROOT_PASSWORD: root + MYSQL_DATABASE: pinakes_test + ports: + - 3306:3306 + options: >- + --health-cmd="mysqladmin ping -h 127.0.0.1" + --health-interval=10s + --health-timeout=5s + --health-retries=10 + mailpit: + image: axllent/mailpit:v1.21@sha256:81370195cd4a0eab9604d17c2617a7525b0486f9365555253b6c5376c6350f1a + ports: + - 1025:1025 + - 8025:8025 + + env: + E2E_BASE_URL: http://localhost:8081 + APP_URL: http://localhost:8081 + E2E_ADMIN_EMAIL: admin@pinakes.test + E2E_ADMIN_PASS: Test1234! + E2E_DB_HOST: 127.0.0.1 + E2E_DB_PORT: 3306 + E2E_DB_USER: root + E2E_DB_PASS: root + E2E_DB_NAME: pinakes_test + E2E_INSTALL_ROOT: ${{ github.workspace }} + MAILPIT_API: http://127.0.0.1:8025/api/v1 + E2E_PHP_UPLOAD_MAX_BYTES: 8388608 + E2E_PHP_POST_MAX_BYTES: 10485760 + + steps: + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + with: + persist-credentials: false + + - name: Install Apache, PHP module, MySQL client, mail transport and poppler + run: | + # This job never installs Google Chrome. Remove its hosted-runner + # apt source before the first refresh so mirror syncs cannot break CI. + sudo rm -f /etc/apt/sources.list.d/google-chrome.list + sudo apt-get update -q + # poppler-utils: patron-cards and patron-photo read the generated + # PDFs back with pdftotext/pdfinfo/pdfimages. + sudo apt-get install -y apache2 libapache2-mod-php php-apcu default-mysql-client msmtp-mta poppler-utils + sudo a2enmod rewrite headers env + php_module="$(find /etc/apache2/mods-available -maxdepth 1 -name 'php*.load' -printf '%f\n' | sed 's/\.load$//' | sort -V | tail -n1)" + [ -z "${php_module}" ] || sudo a2enmod "${php_module}" + sudo a2dissite 000-default || true + sudo tee /etc/msmtprc >/dev/null <<'EOF' + defaults + account default + host 127.0.0.1 + port 1025 + tls off + from pinakes-ci@localhost + EOF + sudo chmod 644 /etc/msmtprc + while IFS= read -r conf_dir; do + sudo tee "${conf_dir}/99-pinakes-ci.ini" >/dev/null <<'EOF' + sendmail_path = "/usr/bin/msmtp -t" + upload_max_filesize = 8M + post_max_size = 10M + apc.enable=1 + apc.enable_cli=1 + EOF + done < <(find /etc/php -type d -path '*/conf.d' | sort -u) + + - name: Setup PHP 8.2 + uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2 + with: + php-version: '8.2' + extensions: mysqli, mbstring, json, curl, openssl, zip, gd, intl, xml, apcu + ini-values: apc.enable=1, apc.enable_cli=1 + coverage: none + + - name: Setup Node 22 + uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5 + with: + node-version: '22' + cache: npm + + - name: Install locked dependencies and Chromium + run: | + composer install --no-interaction --prefer-dist --no-dev --optimize-autoloader + npm ci --silent + npx playwright install chromium --with-deps + + - name: Route legacy MySQL test clients to the service container + run: echo "${GITHUB_WORKSPACE}/scripts/ci-bin" >> "${GITHUB_PATH}" + + - name: Verify test coverage policy + run: npm run test:ci-policy + + - name: Configure Apache on port 8081 + env: + WORKSPACE: ${{ github.workspace }} + run: | + sudo tee /etc/apache2/sites-available/pinakes.conf >/dev/null < + ServerName localhost + DocumentRoot ${WORKSPACE}/public + SetEnv PINAKES_E2E_BYPASS_RATE_LIMIT 1 + SetEnv PINAKES_E2E_SCRAPER_STUB 1 + # E2E only — arms GET /_e2e/flush-cache so specs that write to the + # DB directly can invalidate the page cache; 404s when unset. + SetEnv PINAKES_E2E_CACHE_FLUSH 1 + + Options -Indexes +FollowSymLinks + AllowOverride All + Require all granted + + ErrorLog \${APACHE_LOG_DIR}/pinakes-error.log + CustomLog \${APACHE_LOG_DIR}/pinakes-access.log combined + + EOF + sudo a2ensite pinakes + sudo apachectl configtest + + - name: Grant runtime permissions and start Apache + env: + WORKSPACE: ${{ github.workspace }} + run: | + path="${WORKSPACE}" + while [ "${path}" != "/" ]; do + sudo chmod o+x "${path}" + path="$(dirname "${path}")" + done + sudo chmod 777 "${WORKSPACE}" + sudo chmod -R 777 "${WORKSPACE}/storage" + sudo mkdir -p "${WORKSPACE}/public/uploads" + sudo chmod -R 777 "${WORKSPACE}/public/uploads" + sudo systemctl start apache2 + for _ in $(seq 1 30); do + curl -sf -o /dev/null http://localhost:8081/installer/ && exit 0 + sleep 1 + done + sudo cat /var/log/apache2/pinakes-error.log || true + exit 1 + + - name: Bootstrap a complete fresh installation + env: + PLAYWRIGHT_JSON_OUTPUT_FILE: test-results/bootstrap-${{ matrix.group }}.json + PLAYWRIGHT_HTML_OUTPUT_DIR: playwright-report-bootstrap-${{ matrix.group }} + run: npx playwright test tests/full-test.spec.js --config=tests/playwright.ci.config.js --workers=1 + + - name: Audit bootstrap failures, flakes and suspicious skips + if: always() + env: + RESULT_FILE: test-results/bootstrap-${{ matrix.group }}.json + run: node scripts/ci-audit-playwright-results.js "$RESULT_FILE" + + - name: Seed deterministic cross-suite fixtures + env: + E2E_RUN_SEED: '1' + E2E_OFFLINE_SEED: '1' + PLAYWRIGHT_JSON_OUTPUT_FILE: test-results/setup-${{ matrix.group }}.json + PLAYWRIGHT_HTML_OUTPUT_DIR: playwright-report-setup-${{ matrix.group }} + run: | + npx playwright test \ + tests/interop-00-activate-plugins.spec.js \ + tests/archives-seed-50.spec.js \ + tests/seed-catalog.spec.js \ + tests/persistent-seed-crud.spec.js \ + --config=tests/playwright.ci.config.js \ + --workers=1 + sudo chmod -R 777 "${GITHUB_WORKSPACE}/storage" + sudo chmod -R 777 "${GITHUB_WORKSPACE}/public/uploads" + # The CLI-level helpers (app clock, migrations) read the same + # throwaway CI credentials the installer protected from Apache users. + sudo chmod 640 "${GITHUB_WORKSPACE}/.env" + sudo chgrp "$(id -gn)" "${GITHUB_WORKSPACE}/.env" + + - name: Audit setup fixtures + if: always() + env: + RESULT_FILE: test-results/setup-${{ matrix.group }}.json + run: node scripts/ci-audit-playwright-results.js "$RESULT_FILE" + + - name: Run the 0.9.0 ${{ matrix.group }} suites + id: feature-suites + env: + SPECS: ${{ matrix.specs }} + GROUP: ${{ matrix.group }} + PLAYWRIGHT_JSON_OUTPUT_FILE: test-results/feature-090-${{ matrix.group }}.json + PLAYWRIGHT_HTML_OUTPUT_DIR: playwright-report-feature-090-${{ matrix.group }} + run: | + read -r -a spec_files <<< "${SPECS}" + for spec in "${spec_files[@]}"; do + [ -f "${spec}" ] || { echo "Listed spec does not exist: ${spec}"; exit 1; } + done + printf 'Running %s 0.9.0 specs (%s)\n' "${#spec_files[@]}" "${GROUP}" + npx playwright test "${spec_files[@]}" \ + --config=tests/playwright.ci.config.js \ + --workers=1 + + - name: Audit failures, flakes and skips (zero skips allowed) + if: always() && steps.feature-suites.outcome != 'skipped' + env: + RESULT_FILE: test-results/feature-090-${{ matrix.group }}.json + # The shared audit tolerates skips with an environmental reason. + # These suites were written for this environment: every test runs. + PLAYWRIGHT_MAX_SKIPPED: '0' + run: node scripts/ci-audit-playwright-results.js "$RESULT_FILE" + + - name: Upload browser evidence + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + if: always() + with: + name: feature-090-${{ matrix.group }}-${{ github.run_id }} + path: | + playwright-report-feature-090-${{ matrix.group }}/ + playwright-report-bootstrap-${{ matrix.group }}/ + playwright-report-setup-${{ matrix.group }}/ + test-results/ + storage/logs/app.log + /var/log/apache2/pinakes-error.log + /var/log/apache2/pinakes-access.log + retention-days: 14 + if-no-files-found: warn diff --git a/.github/workflows/ci-quality.yml b/.github/workflows/ci-quality.yml index 48d8b7c40..e00433b95 100644 --- a/.github/workflows/ci-quality.yml +++ b/.github/workflows/ci-quality.yml @@ -40,7 +40,7 @@ concurrency: jobs: quality: name: Static quality checks - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 # MySQL for the DB-backed unit tests (migration-*, book-field-types, # loan-edge-cases, session-fixes). Without it those tests can't read a DB @@ -148,7 +148,7 @@ jobs: - name: PHPStan (level from phpstan.neon) run: | - "$PHPSTAN_BIN" analyse --no-progress --memory-limit=512M + "$PHPSTAN_BIN" analyse --no-progress --memory-limit=1G # ── Route key integrity ─────────────────────────────────────────────── - name: Route key integrity (route_path() keys exist in routes_it_IT.json) @@ -211,6 +211,25 @@ jobs: - name: Soft-delete guard (libri queries must include deleted_at IS NULL) run: python3 scripts/ci-check-soft-delete.py app storage/plugins installer + # ── Circulation rules single source ─────────────────────────────────── + # Loan duration, request window, renewals, active-loan limits and the fine + # rules (daily fine, grace, cap, debt threshold) are read only through App\Services\CirculationPolicy (patron types and the rules + # matrix layer on top of the globals there). A direct read anywhere else + # would apply the global value on one channel and the patron's rule on + # another. SettingsController is the settings form itself. + - name: Circulation policy guard (loans rules read only via CirculationPolicy) + run: | + PATTERN="'loans',[[:space:]]*'(max_active_loans_per_user|max_renewals|loan_duration_days|max_loan_duration_days|daily_fine|fine_grace_days|fine_max_per_loan|debt_block_threshold)'" + HITS=$(grep -rnE "$PATTERN" app storage/plugins cron public installer --include='*.php' \ + | grep -v '^app/Services/CirculationPolicy.php:' \ + | grep -v '^app/Controllers/SettingsController.php:' || true) + if [ -n "$HITS" ]; then + echo "✗ Direct read of a circulation setting outside CirculationPolicy:" + echo "$HITS" + exit 1 + fi + echo "✓ circulation settings read only through CirculationPolicy" + # ── Autoloader safety ───────────────────────────────────────────────── - name: Autoloader phpstan-free run: | @@ -376,7 +395,7 @@ jobs: php-compatibility: name: PHP ${{ matrix.php }} compatibility - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 strategy: fail-fast: false matrix: @@ -403,7 +422,7 @@ jobs: frontend: name: Frontend audit, lint and reproducible build - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 with: diff --git a/.github/workflows/ci-real-upgrade.yml b/.github/workflows/ci-real-upgrade.yml index dea190655..06304ae1c 100644 --- a/.github/workflows/ci-real-upgrade.yml +++ b/.github/workflows/ci-real-upgrade.yml @@ -29,7 +29,7 @@ concurrency: jobs: emeroteca-412: name: Emeroteca 412 — previous release upgraded through the admin UI - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 45 services: diff --git a/.github/workflows/ci-security-supply-chain.yml b/.github/workflows/ci-security-supply-chain.yml index 14c2689f6..da88fa89e 100644 --- a/.github/workflows/ci-security-supply-chain.yml +++ b/.github/workflows/ci-security-supply-chain.yml @@ -18,7 +18,7 @@ concurrency: jobs: workflow-security: name: Workflow, YAML and shell security - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 15 permissions: contents: read # Read workflow and script sources. @@ -58,7 +58,7 @@ jobs: dependency-review: name: Dependency diff policy if: github.event_name == 'pull_request' - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 10 permissions: contents: read # Compare dependency manifests in the pull request. @@ -75,7 +75,7 @@ jobs: repository-security: name: Secrets, vulnerabilities and misconfigurations - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 25 permissions: contents: read # Scan the repository and complete Git history. @@ -108,7 +108,7 @@ jobs: release-artifact: name: Reproducible release, SBOM and archive audit - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 35 permissions: contents: read # Build the release from checked-out sources. diff --git a/.github/workflows/ci-upgrade-smoke.yml b/.github/workflows/ci-upgrade-smoke.yml index 9ddfe81f3..0029b971d 100644 --- a/.github/workflows/ci-upgrade-smoke.yml +++ b/.github/workflows/ci-upgrade-smoke.yml @@ -35,7 +35,7 @@ concurrency: jobs: upgrade-smoke: name: Upgrade from latest release → verify schema - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 services: mysql: diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 28ea70e81..7117aa06f 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -19,7 +19,7 @@ concurrency: jobs: analyze: name: Analyze (javascript-typescript) - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 permissions: security-events: write # Upload CodeQL SARIF results. actions: read # Read workflow metadata for private-repository analysis. diff --git a/.github/workflows/pullfrog.yml b/.github/workflows/pullfrog.yml index 1dfa89ccd..740eb2cdc 100644 --- a/.github/workflows/pullfrog.yml +++ b/.github/workflows/pullfrog.yml @@ -28,7 +28,7 @@ permissions: jobs: pullfrog: name: Pullfrog agent - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 45 # Two bounded attempts plus checkout and cleanup. env: # Whether a second subscription token is configured. Only the presence is diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9a615e0b2..220a7dab1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,7 +14,7 @@ concurrency: jobs: build: name: Verify and build release (read-only) - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 45 permissions: contents: read # Checkout and inspect the tagged source; never publish from this job. @@ -152,7 +152,7 @@ jobs: publish: name: Attest and publish verified release needs: build - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 15 permissions: contents: write # Create the GitHub Release and upload its assets. diff --git a/.github/workflows/test-migrations.yml b/.github/workflows/test-migrations.yml index 02e262490..e660c5be3 100644 --- a/.github/workflows/test-migrations.yml +++ b/.github/workflows/test-migrations.yml @@ -25,7 +25,7 @@ jobs: # version is silently ignored by the updater (version_compare guard). verify-versions: name: Verify migration versions ≤ release - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 with: @@ -62,7 +62,7 @@ jobs: # assertion is the real pass/fail gate. test-full-chain: name: Full migration chain (schema → all migrations) - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 services: mysql: @@ -196,7 +196,7 @@ jobs: # ─── Job 3: existing single-migration test (upgrade 0.3.x → 0.4.0) ───────── test-migrations: name: Legacy migration regression (0.3.x to 0.4.0) - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 services: mysql: diff --git a/.gitignore b/.gitignore index 13ce1ae29..257a5ded3 100644 --- a/.gitignore +++ b/.gitignore @@ -119,6 +119,8 @@ storage/plugins/z39-server/* !storage/plugins/z39-server/*.md !storage/plugins/z39-server/classes/ !storage/plugins/z39-server/classes/*.php +!storage/plugins/z39-server/views/ +!storage/plugins/z39-server/views/*.php !storage/plugins/api-book-scraper/ storage/plugins/api-book-scraper/* !storage/plugins/api-book-scraper/*.php @@ -473,6 +475,9 @@ tests/fixtures/* !tests/fixtures/*.tsv !tests/fixtures/sru/ !tests/fixtures/htaccess-history/ +!tests/fixtures/marc/ +!tests/fixtures/upgrades/ +!tests/fixtures/upgrades/*.sql !tests/helpers/ tests/helpers/* !tests/helpers/*.php diff --git a/.gitleaksignore b/.gitleaksignore new file mode 100644 index 000000000..79f4d3147 --- /dev/null +++ b/.gitleaksignore @@ -0,0 +1,44 @@ +# Gitleaks fingerprints (commit:path:rule:line) for findings in the full-history +# scan that are documentation examples or published test vectors, not secrets. +# The pull_request and push runs only scan the changed range; the scheduled and +# manual runs scan every commit and were failing on these 28 entries. +# +# api-book-scraper docs and views: placeholder keys in curl examples +b868a6b5d2180172b101171ac9eb98ae3e65ce5f:storage/plugins/api-book-scraper/README.md:stripe-access-token:43 +b868a6b5d2180172b101171ac9eb98ae3e65ce5f:storage/plugins/api-book-scraper/SERVER_IMPLEMENTATION_GUIDE.md:stripe-access-token:89 +b868a6b5d2180172b101171ac9eb98ae3e65ce5f:storage/plugins/api-book-scraper/SERVER_IMPLEMENTATION_GUIDE.md:stripe-access-token:113 +b868a6b5d2180172b101171ac9eb98ae3e65ce5f:storage/plugins/api-book-scraper/SERVER_IMPLEMENTATION_GUIDE.md:curl-auth-header:1034 +b868a6b5d2180172b101171ac9eb98ae3e65ce5f:storage/plugins/api-book-scraper/SERVER_IMPLEMENTATION_GUIDE.md:curl-auth-header:1039 +b868a6b5d2180172b101171ac9eb98ae3e65ce5f:storage/plugins/api-book-scraper/SERVER_IMPLEMENTATION_GUIDE.md:curl-auth-header:1043 +b868a6b5d2180172b101171ac9eb98ae3e65ce5f:app/Views/admin/plugins.php:stripe-access-token:426 +84f14515c448bbadf6ee55cec463ba858a32faa4:app/Views/admin/plugins.php:stripe-access-token:474 +c9453c46fe2225531790a2edc90c25b1cb667f05:app/Views/admin/plugins.php:stripe-access-token:474 +# Settings page and admin docs: "your-api-key-here" style curl examples +16ba645f1f990600f69d3842ce3bcd319fbe15f0:app/Views/settings/advanced-tab.php:curl-auth-header:607 +b868a6b5d2180172b101171ac9eb98ae3e65ce5f:app/Views/settings/advanced-tab.php:curl-auth-header:684 +e851035fbe920538a6bf8fc5980312891e4eb878:admin/configurazione.md:curl-auth-header:546 +# z39-server plugin: array literals matched by the generic rule +412253a18455260a5a793b04df36acc23712e7d2:storage/plugins/z39-server/Z39ServerPlugin.php:generic-api-key:122 +b868a6b5d2180172b101171ac9eb98ae3e65ce5f:storage/plugins/z39-server/Z39ServerPlugin.php:generic-api-key:126 +b868a6b5d2180172b101171ac9eb98ae3e65ce5f:storage/plugins/z39-server/Z39ServerPlugin.php:generic-api-key:169 +# French translation strings matched by the generic rule +ad01c97eb68aefad0c1067adfd8377d8f882fa89:locale/fr_FR.json:generic-api-key:2861 +3511b34a1e45f1f59a02cd2ebedf36f7dfe32f3e:locale/fr_FR.json:generic-api-key:2867 +8cfb76efc76183f5fdffb702d682238b2b5dcaac:locale/fr_FR.json:generic-api-key:2871 +320d3a3df0475fbe34d765e50b243499bd5b1414:locale/fr_FR.json:generic-api-key:2874 +9d70038db598cccc7ab2f2f5941e1ff3a66a75cb:locale/fr_FR.json:generic-api-key:4439 +# Update scripts: option names matched by the generic rule +03a8f3e1f75140af13b065d9b52b1d41ebf63e96:scripts/manual-update.php:generic-api-key:443 +5e4ea3f42c694595559f9122932a0eb9fec023e7:scripts/manual-upgrade.php:generic-api-key:569 +f0b1f8b5d473119235892e5dcec8860937eb8f67:test-updater/manual-update.php:generic-api-key:443 +# Tests: RFC 8291 Appendix A web-push vectors and fixture strings +abbe641a9221c29f1ed009bde2873835b53c20f7:tests/mobile-webpush-encryption.unit.php:private-key:25 +abbe641a9221c29f1ed009bde2873835b53c20f7:tests/mobile-webpush-encryption.unit.php:generic-api-key:34 +ac4fcabc00953f3fd5f5c9d59fe5fb5da7f744cb:tests/schema-integrity.spec.js:generic-api-key:91 +5eb52517f88d1ea3fadfffc1f92af21dbd9a1da2:tests/schema-integrity.spec.js:generic-api-key:117 +8cfea8b8d47a8df9efef4d203ca96d8697203908:tests/security-hardening.spec.js:generic-api-key:37 +# +# seo-site-verification.spec.js: made-up Google and Bing verification tokens +# the test pastes into Settings > Advanced (0.9.0 stack) +8c61149bae9b82a4582337fc4220cd8b0968f3cb:tests/seo-site-verification.spec.js:generic-api-key:23 +8c61149bae9b82a4582337fc4220cd8b0968f3cb:tests/seo-site-verification.spec.js:generic-api-key:24 diff --git a/.rsync-filter b/.rsync-filter index 8f301f994..57c4ba508 100644 --- a/.rsync-filter +++ b/.rsync-filter @@ -220,6 +220,8 @@ - /.github/ - /docs/ - /*.md +# Upstream TinyMCE changelog: gitignored, never part of the release. +- /public/assets/tinymce/CHANGELOG.md - .travis.yml - .circleci/ - azure-pipelines.yml diff --git a/CHANGELOG.md b/CHANGELOG.md index 54062985e..e0945f174 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,104 @@ Full version-by-version history for Pinakes. The README shows only the last three releases; everything older lives here. +## [0.9.0-rc.1] + +Release candidate: published as a GitHub prerelease, offered only to installations that opt in with `UPDATER_CHANNEL=rc` or `UPDATER_ALLOW_PRERELEASE=1`. Every new module can be switched off in Settings → Modules and Settings → Loans; an upgraded installation keeps its settings, and stocktake, suppliers, the visitor log, RDA overrides and fines start switched off there (a library that already used one of them on an earlier 0.9.0 build keeps it on). + +### Inventory stocktake + +- **Stocktake sessions** (Admin → Revisione inventariale, `/admin/inventory`). A session is opened with a name, an optional branch and a filter (bookcase, shelf level, genre with its subgenres, media type, collocation range). Opening it freezes the list of expected copies in `inventario_attese`, books removed from the catalogue excluded, so moving a book, adding or deleting a copy while the stocktake runs does not change what the session looks for. Copies that are not on any shelf when the session opens (lost, damaged, being restored, in transit, in maintenance) are left out of the expected set, so they are not reported missing for the whole session; lent copies and copies waiting for pickup stay in, with a bucket of their own. +- **Scanning** works with keyboard-wedge barcode readers (the code field keeps the focus, Enter records), with the self-hosted camera scanner already used at the loan desk, and with an uploaded `.txt` (one code per line, the whole line is the code) or `.csv` (first column, read with a CSV parser) list. Each read is classified as found, misplaced (found on a bookcase other than the expected one, as declared by "Scaffale in cui stai lavorando"), on loan or awaiting pickup, unknown code, out of scope or repeated, with its own colour and sound. Several operators can scan the same session at once: the counters refresh every few seconds and "Solo le mie scansioni" narrows the list to one's own reads. +- **Live report**: expected copies always add up as found + misplaced + on loan (lent, or set aside on the pickup shelf for a reader) + missing; unknown, out-of-scope and repeated reads are counted on the side. PDF and CSV reports are available while the session is open and after it ends. The report of a closed session is frozen at close: the bucket of every expected copy is written to `inventario_attese.esito_chiusura` (found, misplaced, on loan, missing, or lost when the close marked it so), and a loan or a return after the close no longer moves a copy between buckets in the page, the counters or the exports. +- **Closing** either leaves the missing copies as they are or marks them `perso`. Marking them lost is an administrator action, checked on the server: Staff and custom roles can scan and close a session, but not declare copies lost. Copies on loan or waiting to be picked up (by their state or by an active loan row), damaged, being restored, in transit, in maintenance or already lost are never touched; holds on a copy declared lost move to another copy as with a manual change of state; book availability is recalculated through `DataIntegrity`, and each copy gets a "Copia dichiarata persa in revisione inventariale" entry in the book's activity log. The close records its choice (`inventario_sessioni.azione_mancanti`): when a mark-lost close did not reach every copy (the request died, or a book failed), an administrator completes it from the closed session's report, and running it again is harmless. A session can also be cancelled without changing any copy. +- **Permission** `inventory.manage` for custom roles, and a new starting profile "Revisore inventario" (backend access + stocktakes). Staff and administrators have it already. + +### MARC bulk import, export and copy cataloguing +- **Shared MARC library in core** (`app/Support/Marc/`). One neutral `MarcRecord`, streaming ISO 2709 and MARCXML readers and writers written in house (no new dependency), MARC-8 to UTF-8 for Basic and Extended Latin (ANSEL spacing letters and combining diacritics), format detection, and mappers from MARC 21, UNIMARC and danMARC2 to book data. A malformed record, or one in a MARC-8 character set that is not Latin, is reported on its own and the rest of the file goes on. The OAI-PMH server builds its MARCXML, UNIMARC and ISO 2709 records with it (golden files pin the output byte for byte; compared with 0.8.9 the records gain 007, the RDA 336-338 fields and leader/06 by media type, the genre moves from 650 to 655 `$2 local`, every language gets its own 041 `$a` and HTML entities in the summary are decoded), and the z39-server plugin's role reader, danMARC2 reader and UNIMARC reader now live there. +- **Bulk MARC import** (Books → Import → MARC, `/admin/books/import/marc`). A `.mrc`/`.iso` (ISO 2709) or `.xml` (MARCXML) file is read once, a preview of the first ten mapped records says which are already in the catalogue, and the import runs in chunks with a progress bar through the CSV import's own row pipeline: authors and publishers are found with the exact-spelling rules of #501, one copy per book, the import history and its error download work as for CSV. A record matching a book by ISBN-13, ISBN-10 or EAN (deleted books have them cleared, so they never match) is skipped, completed in its empty fields, or overwritten with what the record carries. Covers come from 856. Holdings (852/949/952) are not turned into copies. +- **Bulk MARC export** (Books → Export, and "Esporta in MARC" on a selection). The catalogue or the selected books as MARC 21 `.mrc`, a MARCXML collection or UNIMARC `.mrc`, written while the download is sent, so a large catalogue never sits in memory. Deleted books and desiderata requests are never exported. The records carry the EAN, the LC classification, the Dewey number and the cover, so an export re-imports without duplicates whenever the import can recognise the book: by ISBN-13, ISBN-10 or EAN, or else by title and first author. A book with none of these (a title alone) has nothing to match on and is imported again as a new book. +- **Copy cataloguing** (Books → Import → Copy cataloging, `/admin/books/copy-cataloging`, z39-server plugin). Search by title, author, ISBN/ISSN or all fields in the configured SRU servers, SBN, the Library of Congress, BnF, DNB, K10plus and this installation's own SRU server; look at each record's MARC fields; import the chosen ones with the same duplicate modes and cover download. `SruClient::search()` returns every record of a query. Another Pinakes installation is a preset in the plugin settings, and its SRU server's UNIMARC records now carry the cover in 856 (the MARCXML ones already did). +- **The media type travels in MARC.** A book's media type (libro, disco, audiolibro, dvd, altro) sets leader/06 (a, j, i, g), the 007 category of material and the RDA content, media and carrier types in 336/337/338 (`$a` term, `$b` code, `$2` vocabulary; a book's own RDA codes, when it has them, override the implied ones); UNIMARC records set label/06 the same way. On import the media type is read from leader/06 (a sound recording of performed music is a disco, of spoken word an audiolibro) and RDA codes the media type does not imply are kept as the book's own, once the catalogue has the columns for them; in overwrite mode they replace those of a book already in the catalogue (a DVD record matched to a printed book makes it a DVD), while its format and price are never read from a record. OAI-PMH MARC 21 records now carry leader/06 by media type and RDA 336-338 (the golden files are regenerated). +- **The genre survives a MARC round trip.** A MARC 21 record carries the genre as 655 (ind2 7, `$2 local`), the keywords as 650; before, both were 650 and the genre came back as a keyword. On import a 655 marked `$2 local` becomes the book's genre (created when the catalogue lacks it, as the CSV import does); form headings of other schemes (LCGFT, RBMS) are left alone. In fill mode a book without a genre gets the record's. OAI-PMH MARC 21 records emit the genre as 655 too. UNIMARC does the same with its 606: the genre carries `$2 local` (in the bulk export and in OAI-PMH UNIMARC records), and the UNIMARC import reads that 606 as the genre and the other 606 as keywords. OAI-PMH MARCXML records now declare `xmlns:xsi` on the record, as the oai_dc and UNIMARC ones already did, so a record lifted out of the envelope is a valid document. +- **Corporate and meeting authors are read** from MARC 21 110/111 and 710/711 (`$a` and `$b` joined), with the same roles as personal names; 082 prime marks ("891.73/3") are dropped from the Dewey number. +- **Fill mode completes a family as soon as one of its columns is empty** (an ISBN-10 next to an existing ISBN-13, a series number next to its series) and never overwrites a field the book has. +- **Smaller MARC 21 corrections.** A book in several languages exports one 041 `$a` per language and an import joins every 041 `$a` ("English, Italiano"); an unknown language code is no language rather than a raw code in the field. A monograph's series ISSN (490/830 `$x`) is read when 022 is absent. "2 v." or "24 cm" in 300 is no page count. The number and name of a part (245 `$n` `$p`, UNIMARC 200 `$h` `$i`) are appended to the title. A field over the 9,999-byte ISO 2709 field limit is cut at the last subfield boundary that fits and the cut is logged with the book id; the UNIMARC `.mrc` download of the OAI-PMH plugin shortens a record over 99,999 bytes as the bulk export does. The copy cataloguing presets use `bib.fuzzyIsbn` for the BnF (either ISBN form) and `woe all` for the DNB's all-fields search, a server configuration may name the relation of an index, and a per-record diagnostic in an SRU answer (the BnF's "erreur de traitement") skips that record instead of the whole search. + +### Minor features (visitor log, catalogue cards, suppliers, RDA types, statistics) +- **Visitor log** (`/admin/attendance`, sidebar "Presenze"). A kiosk page records each visit: a member scans the library card (or the desk types the code), a guest is recorded with an optional name and reason, and an optional branch when the installation has more than one. The page shows today's visits, visits per day and per hour slot over 7/30/90/365 days, the latest visits with members' names shortened to "Name S.", and a CSV export of the daily counts. New table `presenze`. +- **GDPR retention for the visitor log.** Visits are kept for 365 days by default; an administrator can change the number of days (0 keeps them forever) and apply it immediately. The scheduled maintenance (`cron/full-maintenance.php`) deletes older visits on every run. Deleting a member keeps the visit, anonymised. +- **ISBD catalogue cards.** "Scheda catalografica" on the book page and "Schede catalografiche" on a selection in the book list produce a PDF of 125 × 75 mm cards, one per book: call number, main entry heading (principal author, surname first), ISBD description, standard number and subject tracings. Deleted books are never printed. +- **Suppliers** (`/admin/suppliers`, sidebar "Fornitori"). New table `fornitori` with name, contact, VAT number, website, address and notes; a supplier select in the book form ("Dettagli Acquisizione"), the supplier on the book page and a "Fornitore" filter in the book list. Deleting a supplier keeps its books and clears their supplier (`libri.fornitore_id`, ON DELETE SET NULL). An e-mail or website that is not one is refused by the supplier form with a message, never saved as empty. The MARC and CSV import pages have a "Fornitore" select: the chosen supplier goes on the books the import creates and, for a MARC import in "Sovrascrivi" mode, on the books it overwrites; skipped duplicates and books only completed in their empty fields keep theirs. +- **RDA content, media and carrier types in cataloguing.** The book form shows the three RDA types derived from the media type and lets the cataloguer override each one for a single record (`libri.rda_content_type`, `rda_media_type`, `rda_carrier_type`, NULL = derived). The book page shows the effective types. The BIBFRAME output carries the same effective types: books keep txt/n/nc as before; DVD, disc and audiobook records now emit their real RDA types (tdi/v/vd, prm/s/sd, spw/s/sd) instead of a book's; an override wins over the derived value. +- **Statistics.** `/admin/statistics` adds loans of the last 12 months by material and the visitors of the last 30 days. The outcomes of the last closed stocktake (expected, found, misplaced, on loan, missing, unknown codes) appear once a session has been closed. Loans of the last 12 months by patron type (readers without a type under the default type) come with the circulation policy; fines have their place on the page and appear as soon as that feature provides data (`App\Support\StatisticsSections`). + +### Library cards, patron photo and user import +- **Printable library cards.** Every reader's page has "Stampa tessera": a TCPDF PDF in CR80 size (85.6 × 54 mm, one card per page) or on A4 sheets of business-card stock with cut lines (10 per sheet, 9 for the portrait layout). The front carries the library logo and name, the reader's name, the card code as a Code128 barcode or a QR code, the photo and the expiry date; the back carries a text of your choice (rules, website, opening hours), mirrored on the A4 sheet so a long-edge duplex print puts each back behind its own card. The users list gained a selection column: selected readers print in one PDF, as single cards or A4 sheets. +- **Settings → Tessere** (`/admin/settings/cards`): three layouts (classic with a header band, minimal without photo, portrait), Code128 or QR, default format, header text, which elements to show, back text. Untouched settings give the classic Code128 card; nothing else changes for an installation that never prints a card. +- **Patron photo from the webcam.** The user form has "Scatta foto" (the browser camera, cropped to a portrait frame on a canvas) and "Carica foto"; both go to the same endpoint, which re-encodes the image as a plain JPEG of at most 600 px without EXIF. Photos live in `storage/patron_photos/`, outside the web root, and reach a browser only through `/admin/users/{id}/photo` behind the same permission as the reader record. "Elimina foto" removes file and reference at once (the reader's right to erasure), and deleting an account deletes its photo, also when the account has a loan history and is therefore only suspended. `Permissions-Policy` keeps `camera=(self)` (now built by `ContentSecurityPolicy::permissionsPolicy()`); the CSP needed no change. +- **User import from CSV** (`/admin/users/import`, "Importa CSV" in the users list). Upload, preview of the first rows, column mapping with automatic recognition of Italian, English, German, French and Danish headers (accented letters included, whatever the server's locale) and of the custom registration fields, then chunked processing with a progress bar. Rows whose email or card code already exists (or already appeared in the file) are skipped as duplicates and never merged; invalid rows are reported with their line number. Passwords are never imported: an optional checkbox sends each new reader the password-setup invitation (`PasswordSetupToken`). Every run is written to the import history (type `users`) with the errors downloadable as CSV. A file that is not UTF-8 (a "CSV" saved by Excel on Windows is in the system code page) is converted from Windows-1252 on upload and the preview says so, instead of the upload failing with an empty response. A patron-type column ("Tipologia") assigns the type by its code or, failing that, by its name (both in any case; a name shared by two types is not guessed), so the users export of another installation with the same types re-imports cleanly; an empty value leaves the reader on the default type, and so does an unknown or deactivated one, reported as a warning with its line. The card expiry follows the user form: when the file gives none, it is computed from the card duration of the assigned patron type (the default type when the row has no type), and an expiry present in the CSV always wins. The separator (";", "," or TAB) is the one that splits the first lines into the same number of columns as the header, quoted fields respected, so a ";" file whose addresses hold commas is no longer read as a one-column "," file. + +### Circulation policy +- **One source for the circulation rules.** Loan duration, request window, renewals and the active-loan limit were read straight from the loans settings in about twenty places (staff desk, reader requests on the website and in the app, approval, queue promotion, renewals, NCIP). They now all go through `App\Services\CirculationPolicy`, which returns immutable `UserPolicy`/`LoanPolicy` objects, and every due date is computed by `CirculationPolicy::dueDateFor()`. With only the global settings the numbers are the same as before (a unit test compares every old read site over a grid of stored values); a CI step rejects any new direct read of those settings. +- **Patron types** (Settings → Circulation). Every reader belongs to a patron type (child, student, teacher…) that decides the circulation rules; the role (`tipo_utente`) keeps deciding only the permissions. A type can set the total active-loan limit, loan duration, longest request, renewals and days per renewal, whether reservations are allowed and how many, and the card duration; an empty field inherits the global loans setting. Types are created, edited, ordered and deleted from the new tab, one of them is the default for patrons without a type. The user forms have a "Tipologia lettore" select, the reader page shows the effective rules, the user list filters by type and the CSV export has the column, followed by the type's code ("Codice tipologia lettore") so a round trip through the user import is exact even when two types share a name. Existing users are not migrated: everyone starts on the default "Standard" type, which inherits everything. +- **Rules matrix patron type × material** (`libri.tipo_media`). A cell can make a material consultation only, cap the loans of that material on top of the total limit, and set its duration, renewals and days per renewal; the matrix shows the inherited value of every empty cell in grey. The book page's renew button and its confirmation say the real renewal length of the loan instead of a fixed "+14 giorni". Per field, the first value wins among rule (type, material) → rule (type, every material) → rule (every type, material) → patron type → global settings. +- **Consultation-only copies.** A single copy can be kept for in-house consultation from the book page (`copie.solo_consultazione`): it is never allocated to a loan nor counted as lendable capacity, and a title whose every copy is kept for consultation is consultation only. The book page shows "Solo consultazione" instead of the request button. The availability counter `libri.copie_disponibili` (book page button and badge, catalogue "available" filter, OPAC and mobile availability) is recalculated without the consultation copies, so a title whose only copy on the shelf is a consultation copy offers "Prenota quando disponibile", not "Richiedi prestito"; `copie_totali` keeps counting every physical copy in circulation. Existing installations get the counters recalculated by the upgrade (the Updater re-derives availability after the migrations) or from Maintenance → "Ricalcola disponibilità". +- **The same rules on every channel.** Staff desk, reader requests, approval, queue promotion, renewals, the Mobile API and NCIP all refuse with the same codes: `media_limit_reached`, `not_loanable`, `reservations_disabled`, `reservation_limit_reached` (Mobile API `CirculationOutcome`, NCIP *Item Does Not Circulate* / *Maximum Check Outs Exceeded*). `GET /api/v1/catalog/books/{id}/availability` gains `loanable` and `reservations_enabled`. NCIP RequestItem due dates are now capped by the maximum loan duration like every other channel (they used to take the bare loan duration). +- **Reservation limits per patron type.** A type without reservations does not see "reserve when available" on the book page and a request that would join the waitlist is refused (`reservations_disabled`); `max_prenotazioni` counts the active queue reservations plus the loan requests still awaiting approval (`reservation_limit_reached`). Website, app, NCIP RequestItem and the admin reservation form apply them; the admin form can override them with an explicit "Ignora i limiti di prenotazione" tick (`override_limits: true` from the staff app). +- **Card duration of the patron type.** When the type sets a card duration, the card expiry is computed on creation and edit ("Calcola la scadenza dalla durata tessera", on by default when creating, where it fills an empty date only: a date typed by the librarian wins), on self-registration and on registration approval; the date can still be set by hand. The reader page has a **"Rinnova tessera"** button: expiry = today + the type's duration (one year when the type sets none), an expired account becomes active again, and the renewal is in the activity log. +- **"Tessera in scadenza" email** (`card_expiring`, five languages). Sent `loans.card_expiry_warning_days` before the card expires (Settings → Loans, default 0 = off) by `cron/automatic-notifications.php` and `cron/full-maintenance.php` (both report the count), once per expiry date (`utenti.card_expiry_warning_sent_for`); a renewed card re-arms it. Fresh installations get the template seeded in the five `data_*.sql` files (row 29) like every other template; upgrades use the shipped text until the template is saved. +- **Administrators only.** Patron types and the rules matrix (Settings → Circulation, the type pages and every form behind them) are reserved to administrators and to custom roles with the settings permission; a staff account is sent back to the dashboard and nothing changes. For staff the Circulation section is absent from the settings navigation and `/admin/settings?tab=circulation` opens the default section without the circulation panel. + +### Closures calendar +- **The library's closed days.** A new admin page, Admin → Calendario chiusure (`/admin/calendar`, also linked from Settings → Loans), holds the weekly closed days (`loans.closed_weekdays`) and the closures: single days or periods, once or every year, for every branch or for one. Public holidays come from a country preset (see below), any other calendar can be imported from an ICS file, and a monthly preview shows which days are open. +- **Due dates land on an open day.** The shift lives in `CirculationPolicy::dueDateFor()`, so the per-patron-type durations and renewal days of the circulation policy land on an open day too. A due date that falls on a closed day moves to the next open day: admin loans (proposed and saved), loan edits, reader requests from the website and the app, reservations and their promotion to a loan, renewals and bulk extensions, NCIP CheckOut, RequestItem and RenewItem. A closure of one branch counts for the loans of its copies: when the staff desk lends a copy, an NCIP CheckOutItem binds one, or a request is approved onto one, the due date moves to that branch's next open day (at approval only if the longer window still fits; the NCIP checkout is refused, like the desk's, when the longer window no longer fits). A refused renewal proposes the last open day before the conflict. +- **Pickup days are open days.** The pickup deadline counts only the days the library is open, and the nightly sweep expires no pickup, no scheduled loan and no queue reservation on a closed day; a deadline that falls on a day closed afterwards moves to the next open day when the calendar is saved, so the desk can still hand the book over that day, but never past the loan's due date (a pickup after the loan window is refused), which the calendar save does not move. +- **The request calendars know the closed days.** On the book page and in the admin loan form a closed day cannot be picked as the start (a request that reaches the server with one starts on the next open day); the availability payload carries `closed_dates`, on the website and in the Mobile API `GET /catalog/books/{id}/availability` (additive field). A request auto-approved on a closed day is reported by `POST /reservations` with its real state, `prenotato` (new enum value) with the start day in `start_date` (additive), instead of `da_ritirare`. +- **ICS imports meet the form's limits.** Every event is checked like a closure typed in the form (a yearly closure within a year, a one-off within ten years, end not before start); the import message counts the events discarded. A yearly closure of a year or more, however stored, is applied once as the interval it names and never closes every day; a one-off closure of any admissible length (up to ten years) is stepped over whole, so a date inside it lands on the day after it ends; an ICS date-time in UTC is read in the application timezone. The calendar page follows the settings permission (`settings.manage`), so a custom role with that permission can use it. +- With no closed weekday and no closure configured every date is computed exactly as before. + +### Public holidays for every country, and the library's own holidays +- **Pick the country, then the holidays.** The holiday card of `/admin/calendar` replaces the Italian-only import: the librarian chooses the country (Italy, United States, United Kingdom, Ireland, Germany, France, Denmark, Spain, Portugal, Netherlands, Belgium, Austria, Switzerland, Sweden, Norway, Finland, Poland, Canada, Australia, Brazil, Argentina, Mexico; preselected from the installation language) and, where holidays differ, the region (German Länder, UK nations, Canadian provinces and territories, Australian states and territories, Alsace-Moselle, six Swiss cantons), sees every holiday with its native name and its date in the preview year, ticks the ones the library closes for (select all / none; customary holidays such as Christmas Eve start unticked) and applies. +- **Rules, not dates.** Fixed dates, Easter offsets (Good Friday, Ascension, Whit Monday, Corpus Christi, Carnival…), the nth weekday of a month (Thanksgiving, Memorial Day, bank holidays), the weekday on or before/after a date (Victoria Day, Buß- und Bettag, Midsommar), the weekend substitutes of the US, UK, Ireland, Canada and Australia (including Christmas and Boxing Day both on a weekend), Koningsdag, the Argentine movable holidays, first and last years (Store bededag until 2023, Juneteenth from 2021, San Francesco in Italy from 2026, Wigilia in Poland from 2025) and the one-year moves of the early May and spring bank holidays (VE Day 2020, the Jubilees). +- **Applying is idempotent and selective.** The choice is stored in `calendar.holiday_country`, `calendar.holiday_region` and `calendar.holiday_rules`; each ticked holiday becomes closures marked with its rule (`calendario_chiusure.regola_festivita`): a fixed date as one yearly closure, any other holiday dated per year up to two years ahead, extended every year by the maintenance run (`holiday_rows_added` in the cron log). Applying again adds nothing, unticking removes only that holiday, switching country replaces the preset; closures added by hand or imported from ICS are never touched. Holiday rows show a "Festività" badge in the list and are changed from the selection only. Rows stored by the former Italian import are recognised and adopted, not duplicated. +- **Holiday rules also cover distant and historical dates.** The dated rows are the admin preview, not a limit on loan policy: due dates, pickup days, renewal caps, availability and late-fine days evaluate the selected rules for the year requested, including an observed New Year that falls in December of the preceding year. Switching the calendar back on works immediately, without waiting for maintenance. Applying a selection and yearly upkeep share a transaction lock and current reads, so a concurrent save cannot restore an unticked holiday or duplicate its rows; manual and ICS closures remain independent. +- **Yearly closures on and off.** The yearly closures added by hand or from ICS are listed in their own card and can be suspended and resumed without deleting them (`calendario_chiusure.attiva`); a suspended closure stays in the list with a "Sospesa" badge and closes no day. +- **Every country of the world.** Besides the 22 hand-written rule tables, the country list holds every country and territory (250) with its official holidays from the open data of the python-holidays project (MIT licence, `app/Support/Holidays/data/`, years 2010-2060, regenerated by `scripts/generate-holiday-dataset.py`): Islamic, Chinese, Hindu, Buddhist, Orthodox and national holidays alike, in the country's own language with the English name beside it. Holidays of public offices, banks, schools, half days and those of a single community (Islamic, Hindu, Jewish, Orthodox, Yazidi…) are offered unticked with their category; dates that depend on the sighting of the moon are marked as estimated. Country names follow the interface language. +- **A chosen holiday that vanished is said, not silently dropped.** When a stored holiday id is no longer in the rule table or the dataset (a regeneration accepted with `--accept-removed`), the holiday card shows which chosen holidays close nothing any more, so the librarian can tick their replacements and save again; `HolidayPresets::selection()` lists them as `missing`. +- **Days in lieu close both days.** When a weekend holiday gets a substitute (US, UK, Ireland, Canada, Australia), the library is closed on the holiday itself and on the substitute; holidays that are moved instead (Koningsdag, the Argentine movable holidays) keep only the new date. Easter Sunday in Western Australia (from 2022), the Northern Territory (2022) and South Australia (2024), and the extra Monday of a weekend Anzac Day in Western Australia. +- **The library's own holidays.** A new card "Festività della biblioteca" (table `festivita_personalizzate`) adds holidays the official lists do not have or that the community celebrates: the same day every year, the nth weekday of a month, days from Western or Orthodox Easter, a date in the Islamic (Umm al-Qura), Hebrew, Chinese (published lunar table, 1900-2100), Persian, Indian national, Ethiopian or Coptic calendar, or dates listed by hand; one or more days, a weekend rule, first and last year, every branch or one. They apply to every year, can be suspended, edited and deleted, and show their next dates. An installation that defines none behaves as before. + +### Fines, patron ledger and debt block (plan §7) +- **Late fines.** Settings → Loans → "Multe e blocco per debito": daily fine, grace days, maximum fine per loan. These are the global layer of `CirculationPolicy`: a patron type (Settings → Circulation) can set its own daily fine, grace days, cap per loan and debt threshold, and a cell of the rules matrix its own fine, cap and grace for one material; an empty field inherits, 0 switches fines (or the block) off for that type or material. Late days are the days the library is open (closures calendar: weekly closed days, closures, yearly closures, branch closures for the copy's branch); with no closure configured every day counts. The grace period is a threshold (SLiMS rule): up to the grace days nothing is charged, beyond it every late day counts from the first. With the daily fine empty or 0 (the default) nothing changes. The three amounts accept up to 9999.99 (the form says so when a larger figure is typed), and the fine of one loan never exceeds what the ledger stores (99,999,999.99), however long the loan was overdue. The fine is written at return, in the same transaction as the close, on every return path (return screen, quick return, NCIP check-in), and only once per return (idempotency key `late:`). Fines accruing on open overdue loans are shown on the loan, the reader record, the reader's own page and the app, never written in advance. +- **Patron ledger.** New append-only table `movimenti_lettore` (charges, payments, waivers, reversals); the balance is the sum of the rows. The reader record has a "Movimenti" section: balance, accruing fines, list, register a payment (cash, card, transfer, other) with a PDF receipt, manual charge (description required), waiver (reason required). Each desk form is recorded once even when it is sent twice (the same form sent again with another amount, method or text is refused with "Richiesta già registrata" instead of being answered with the earlier movement; a page restored from the browser cache gets a fresh form), and a long ledger is shown 200 rows per page. Recording a payment, a charge or a waiver and downloading a receipt are loan-desk actions: they need the loans permission (`loans.manage`, as Staff has), so a custom role that only manages readers sees the ledger read-only. The return screen shows the fine before the confirmation and can waive it on the spot, with a reason; a waived fine no longer appears in the return email, the fine email or on the loan page. A reader with ledger rows is suspended instead of deleted. +- **Debt block.** With a threshold set, a reader who owes more than it gets `debt_block`. The threshold is the one of the reader's patron type; a reader without a type (or whose type no longer exists) is governed by the default type; a type whose field is empty inherits the global `debt_block_threshold`, and 0 switches the block off for that type. A reader who owes more than the applicable threshold gets `debt_block` ("Saldo a debito oltre la soglia consentita…") on loan requests, admin loans, approvals, renewals and the reservation queue, on the website, the Mobile API and NCIP. +- **Reports and email.** Statistics: charged, collected, waived and reversed per month with CSV export. Return email: new placeholder `{{multa}}` (empty when nothing was charged); an upgraded install whose stored "Restituzione confermata" template is still the previously shipped text gets the line when Settings → Email templates is opened, a customised template is left as it is. New optional email "Multa per ritardo addebitata" (`fine_charged`), off by default. +- **Mobile API 1.9.0** (requires 0.9.0): `GET /me/movements` with balance, movements and accruing fines; `debt_block` arrives as `not_eligible` with `meta.reason` (and as `eligibility_reason` in the staff loan lists). The same plugin version carries the 0.9.0 circulation changes: `closed_dates`, `loanable` and `reservations_enabled` in `GET /catalog/books/{id}/availability`, the 409 codes `media_limit_reached`, `not_loanable`, `reservations_disabled` and `reservation_limit_reached`, and `override_limits` on the staff reservation form. +- `app.currency` is now read from the database too (it was a code default only). +- **Fine settings are administrators only.** Staff can still save the Loans tab, but the daily fine, grace days, cap per loan, debt threshold and fine email stay as they are unless an administrator changes them; the fields are read-only for staff. + +### Feature toggles +- **Every optional 0.9.0 module has its own switch** (`features.*` in `system_settings`, `App\Support\Features`). A new Settings → Modules tab, administrators only, switches stocktake, MARC import and export (with copy cataloguing), catalogue cards, suppliers, RDA types, library cards and patron photos, user CSV import and the visitor log; the Loans tab switches the rules by patron type, the closures calendar and fines with the patron ledger, next to their settings. Staff sees the Loans switches locked and does not see the Modules tab; a hand-built post is refused by an inline `mayConfigure()` check. +- **A module switched off is gone**: no sidebar or menu entry, no button, no form field, no settings section; its routes answer as catalogue mode does (302 to the dashboard with "Funzione disattivata nelle impostazioni.", or 404 `feature_disabled` for JSON callers) through `FeatureGateMiddleware`, and the controllers re-check. The services carry the switch for every channel at once: with patron type rules off only the global loan rules apply and consultation-only copies are lendable; with the calendar off due dates ignore closures, the holiday card and the yearly closures card are gone with the calendar page (their posts are refused) and the maintenance run adds no holiday row until it is switched back on; with fines off nothing is charged, nobody is blocked for debt, previews, balances and the fine email are gone and `prestiti.sanzione` is written as before; with RDA types off the stored overrides are kept but exports derive the types from the material; with suppliers off a book save or an import never touches `libri.fornitore_id`; with the visitor log off maintenance purges nothing. Each statistics section follows its module. Data is never deleted: switching a module back on shows it again. +- **Defaults.** A fresh install starts with everything on except the visitor log (personal data) and fines (money). An upgrade keeps on what is reachable only from an explicit action (MARC, catalogue cards, library cards, user import, patron type rules, closures calendar) and starts off what adds sidebar entries, cataloguing fields, money or personal data (stocktake, suppliers, RDA types, visitor log, fines), unless the library already has data for the module, which then stays on. A missing row behaves as the upgrade default. +- **Catalogue mode** still wins: it also switches off the visitor log, the closures calendar, fines and patron type rules, and the Presenze sidebar entry, which used to stay visible in catalogue mode, now disappears. +- **Mobile API**: `/health.features` gains `fines`, `closures_calendar` and `circulation_rules`; with fines off `GET /me/movements` answers 404 `feature_disabled`. + +### Fixes +- **Installer: no false "triggers missing" warning.** Every fresh install ended on a page saying the database user could not create triggers and asking for `installer/database/triggers.sql` to be run by hand, even when every trigger had been created: the importer counted a trigger only when its chunk began with `CREATE TRIGGER`, while each chunk begins with comments and a `DROP TRIGGER`. The count now finds the statement wherever it sits, and when a trigger really fails the final page says that some triggers could not be created, instead of blaming the database user's permissions, and shows the database's own message (escaped) under it. + +### Migration + +- `migrate_0.9.0-rc.1.sql`, section "MARC bulk import / copy cataloguing": `import_logs.import_type` gains the value `marc`, appended to the enum computed from the current one, so the order of the sections does not matter. Idempotent; existing rows are untouched. +- `migrate_0.9.0-rc.1.sql`, section "Inventory stocktake": creates `inventario_sessioni` (with `azione_mancanti`, the close's choice), `inventario_attese` (with `esito_chiusura`, the bucket frozen at close, and `codice_atteso`/`titolo_atteso`, the copy code and title frozen at opening so a copy deleted mid-session stays identifiable in the report; each added with a guard to a table an earlier build created) and `inventario_scansioni` (UNIQUE `sessione_id, copia_id`) and seeds the "Revisore inventario" role. Additive and idempotent; nothing existing changes. +- `migrate_0.9.0-rc.1.sql`, section "minor features": creates `presenze` (visitor log) and `fornitori` (suppliers), adds `libri.fornitore_id` (ON DELETE SET NULL) and the nullable `libri.rda_content_type`, `rda_media_type`, `rda_carrier_type` overrides. Additive and idempotent. +- `migrate_0.9.0-rc.1.sql`, section "patron-cards-import": `import_logs.import_type` gains the value `users`; the new enum is computed from the current one, so `marc` and any value added by another section are kept. +- `migrate_0.9.0-rc.1.sql`, section "Circulation policy": creates `tipologie_lettore` and `regole_prestito`, seeds the default "Standard" type with every rule NULL, adds `utenti.tipologia_id` (FK `ON DELETE SET NULL`), `utenti.card_expiry_warning_sent_for` and `copie.solo_consultazione`. Idempotent; no existing row changes. +- `migrate_0.9.0-rc.1.sql`, section "closures calendar": creates `calendario_chiusure` (closure intervals, optional yearly recurrence and branch, FK to `sedi` ON DELETE CASCADE); the weekly closed days are the setting `loans.closed_weekdays`, no schema. Additive and idempotent. +- `migrate_0.9.0-rc.1.sql`, section "holiday presets": adds `calendario_chiusure.regola_festivita` (varchar(64) NULL, indexed: the holiday rule a preset row stands for, NULL for manual and ICS rows) and `calendario_chiusure.attiva` (default 1). Guarded on `information_schema`, idempotent; existing rows stay manual and active until the first apply adopts the former Italian ones. +- `migrate_0.9.0-rc.1.sql`, section "fines-ledger": creates `movimenti_lettore` and converts every existing `prestiti.sanzione > 0` into one charge (`addebito_perdita` for a lost copy, `addebito_danno` otherwise, key `penalty:`), in batches; maintenance converts anything past the batches and records the scan as complete only once the table exists and a pass has run to the end. Until the migration has run, the queue selection and the eligibility check carry no debt predicate. `prestiti.sanzione` stays and is still written. +- `migrate_0.9.0-rc.1.sql`, section "feature toggles": one `features.` row per module with the upgrade default, insert-if-missing (a saved value is never overwritten); stocktake, suppliers, visitor log, RDA types and fines start on when the library already has sessions, suppliers, visits, RDA overrides on live titles, ledger rows other than the converted legacy penalties, or a configured daily fine. The fresh-install defaults are in the five `data_*.sql` seeds. + ## [0.8.9] ### Security diff --git a/README.md b/README.md index 35167b7c4..6e8732a82 100644 --- a/README.md +++ b/README.md @@ -25,9 +25,18 @@ Pinakes is a self-hosted, full-featured ILS for schools, municipalities, and pri [![Documentation](https://img.shields.io/badge/Documentazione-Docsify-4285f4?style=for-the-badge&logo=readthedocs&logoColor=white)](https://fabiodalez-dev.github.io/Pinakes/) [![Buy Me a Coffee](https://img.shields.io/badge/Buy%20Me%20a%20Coffee-ffdd00?style=for-the-badge&logo=buy-me-a-coffee&logoColor=black)](https://buymeacoffee.com/fabiodalez) -## What's New in v0.8.9 +## What's New in v0.9.0-rc.1 -**Search Console verification, a pickup reminder, and an SEO pass.** Settings → Advanced has a field for the `` verification tags of Google, Bing and the other search engines ([#497](https://github.com/fabiodalez-dev/Pinakes/pull/497)). Readers get a reminder before the pickup deadline passes, "Loan approved" can say how many days they have to pick the book up, and the staff is told when a pickup expires ([#498](https://github.com/fabiodalez-dev/Pinakes/pull/498)); the new approval text ships to new installations only, existing ones keep their template and can add `{{giorni_ritiro}}` and `{{scadenza_ritiro}}`. The 404 page is no longer indexed, empty pages past the last one answer 404, structured data and the sitemap are more complete, and the loan request calendar fills its row. One additive migration (`migrate_0.8.9.sql`). Full details in **[CHANGELOG.md](CHANGELOG.md)**. +**Release candidate. Stocktake, MARC in and out, and the circulation tools of a full library system.** One additive migration (`migrate_0.9.0-rc.1.sql`); an installation that never opens the new pages and settings behaves exactly as before. Full details in **[CHANGELOG.md](CHANGELOG.md)**. + +- **Inventory stocktake** (`/admin/inventory`): sessions over a bookcase, shelf, genre or collocation range, scanning by barcode reader, camera or uploaded list, a live found / misplaced / on loan / missing report with PDF and CSV, and an optional "mark missing as lost" reserved to administrators. +- **MARC bulk import and export, copy cataloguing**: ISO 2709 and MARCXML (MARC 21, UNIMARC, danMARC2, MARC-8) imported in chunks with a preview, the catalogue or a selection exported as MARC 21, MARCXML or UNIMARC, and records searched and imported from other catalogues over SRU. +- **Visitor log, catalogue cards, suppliers and RDA types**: a kiosk visitor log with GDPR retention, ISBD catalogue cards as PDF, a suppliers register linked to books, RDA content/media/carrier overrides, and new statistics sections. +- **Library cards and user import**: printable patron cards (CR80 or A4 sheets, Code128 or QR), patron photo from the webcam, and user import from CSV with column mapping. +- **Patron types and circulation rules**: one circulation policy for every channel (desk, website, app, NCIP); loan limit, duration, renewals, request window, reservations and card duration per patron type, a patron type × material rules matrix, consultation-only copies, card renewal and a card-expiry email. +- **Closures calendar** (`/admin/calendar`): weekly closed days, closures per branch or every year, the public holidays of every country (250, named in the interface language), the library's own holidays and ICS import; due dates (with the patron type's durations and renewals) move to the next open day, pickup deadlines count open days, and the request calendars refuse a closed start day. +- **Fines and patron ledger**: optional late fines (daily amount, grace days, cap per loan) set globally, per patron type or per material in the rules matrix, counting only the days the library is open; an append-only ledger with payments, waivers, receipts and a fines report in the statistics; an optional debt block with its threshold per patron type. Off by default. +- **Every module is optional** (Settings → Modules, Settings → Loans): an upgrade keeps the library's settings, stocktake, suppliers, the visitor log, RDA overrides and fines start switched off, and nothing changes until the administrator turns a module on. --- @@ -53,13 +62,13 @@ The showcase contains 50 catalogue books, 8 desiderata, 3 periodicals with 10 is Highlights of the two releases before the current one are below; the README never lists more than the last three versions. The full version-by-version history lives in **[CHANGELOG.md](CHANGELOG.md)**. -### v0.8.8 +### v0.8.9 -**A French sentence that now includes the last day of a loan.** "retour avant le D" left out the return day the server still accepts; it now says "retour au plus tard le D" ([#490](https://github.com/fabiodalez-dev/Pinakes/pull/490)). No migration. +**Search Console verification, a pickup reminder, and an SEO pass.** Settings → Advanced has a field for the `` verification tags of Google, Bing and the other search engines ([#497](https://github.com/fabiodalez-dev/Pinakes/pull/497)). Readers get a reminder before the pickup deadline passes, "Loan approved" can say how many days they have to pick the book up, and the staff is told when a pickup expires ([#498](https://github.com/fabiodalez-dev/Pinakes/pull/498)); the new approval text ships to new installations only, existing ones keep their template and can add `{{giorni_ritiro}}` and `{{scadenza_ritiro}}`. The 404 page is no longer indexed, empty pages past the last one answer 404, structured data and the sitemap are more complete, and the loan request calendar fills its row. One additive migration (`migrate_0.8.9.sql`). Full details in **[CHANGELOG.md](CHANGELOG.md)**. -### v0.8.7 +### v0.8.8 -**A security notice for PHP, loan windows that explain themselves, and Desiderata codes that follow the book.** CVE-2026-109618 and CVE-2026-109619 are in the PHP runtime, not in Pinakes: the dashboard and the updates page tell administrators when their server's PHP is older than 8.5.12, 8.4.27, 8.3.36 or 8.2.35 ([#491](https://github.com/fabiodalez-dev/Pinakes/issues/491)). Loan requests stop before a scheduled loan and say the longest possible loan ([#490](https://github.com/fabiodalez-dev/Pinakes/pull/490)), renewals say until when they are possible ([#493](https://github.com/fabiodalez-dev/Pinakes/pull/493)), and Desiderata copies get the book's inventory prefix ([#494](https://github.com/fabiodalez-dev/Pinakes/pull/494)). No migration; see **[CHANGELOG.md](CHANGELOG.md)**. +**A French sentence that now includes the last day of a loan.** "retour avant le D" left out the return day the server still accepts; it now says "retour au plus tard le D" ([#490](https://github.com/fabiodalez-dev/Pinakes/pull/490)). No migration. Older releases are in **[CHANGELOG.md](CHANGELOG.md)**. diff --git a/app/Controllers/Admin/LibraryCalendarController.php b/app/Controllers/Admin/LibraryCalendarController.php new file mode 100644 index 000000000..d43c13856 --- /dev/null +++ b/app/Controllers/Admin/LibraryCalendarController.php @@ -0,0 +1,948 @@ +guard($response, $db)) { + return $guard; + } + $query = $request->getQueryParams(); + $today = DateHelper::today(); + + $month = is_string($query['month'] ?? null) && preg_match('/^\d{4}-(0[1-9]|1[0-2])$/', $query['month']) === 1 + ? $query['month'] + : substr($today, 0, 7); + $branches = $this->branches($db); + $previewSede = isset($query['sede']) && is_numeric($query['sede']) && isset($branches[(int) $query['sede']]) ? (int) $query['sede'] : null; + + $calendar = LibraryCalendar::fromDatabase($db, configuration: true); + $closedWeekdays = $calendar->closedWeekdays(); + $closures = $this->closures($db); + + $editId = isset($query['edit']) && is_numeric($query['edit']) ? (int) $query['edit'] : 0; + $editing = null; + foreach ($closures as $row) { + // A preset row changes with the selection, never by hand. + if ((int) $row['id'] === $editId && ($row['regola_festivita'] ?? null) === null) { + $editing = $row; + } + } + $yearlyClosures = array_values(array_filter( + $closures, + static fn (array $row): bool => (int) $row['ricorrente_annuale'] === 1 && ($row['regola_festivita'] ?? null) === null + )); + + $monthStart = $month . '-01'; + $monthEnd = (new \DateTimeImmutable($monthStart, new \DateTimeZone('UTC')))->modify('last day of this month')->format('Y-m-d'); + $monthClosed = array_flip($calendar->closedDatesBetween($monthStart, $monthEnd, $previewSede)); + $prevMonth = (new \DateTimeImmutable($monthStart, new \DateTimeZone('UTC')))->modify('-1 month')->format('Y-m'); + $nextMonth = (new \DateTimeImmutable($monthStart, new \DateTimeZone('UTC')))->modify('+1 month')->format('Y-m'); + $currentYear = (int) substr($today, 0, 4); + $holidays = $this->holidayPreview($db, $query, $currentYear); + $customHolidays = CustomHolidays::all($db); + $customEditId = isset($query['custom_edit']) && is_numeric($query['custom_edit']) ? (int) $query['custom_edit'] : 0; + $customEditing = $customEditId > 0 ? CustomHolidays::find($db, $customEditId) : null; + $customDraft = $_SESSION['custom_holiday_draft'] ?? null; + unset($_SESSION['custom_holiday_draft']); + if (is_array($customDraft) && (int) ($customDraft['id'] ?? 0) === ($customEditing !== null ? $customEditId : 0)) { + $customEditing = $customDraft; + } + $customSchema = CustomHolidays::schemaReady($db); + + ob_start(); + require __DIR__ . '/../../Views/admin/calendar.php'; + $content = ob_get_clean(); + ob_start(); + require __DIR__ . '/../../Views/layout.php'; + $html = ob_get_clean(); + $response->getBody()->write($html); + return $response; + } + + public function saveWeekdays(Request $request, Response $response, \mysqli $db): Response + { + if ($guard = $this->guard($response, $db)) { + return $guard; + } + $data = (array) $request->getParsedBody(); + // No field (every box unticked) reopens the whole week; anything that + // is not a list of weekdays 0-6 is refused whole, never half-saved. + $raw = $data['closed_weekdays'] ?? []; + if (!is_array($raw)) { + $_SESSION['error_message'] = __('Giorno della settimana non valido.'); + return $this->back($response); + } + $days = []; + foreach ($raw as $value) { + if (!is_scalar($value) || !ctype_digit((string) $value) || (int) $value > 6) { + $_SESSION['error_message'] = __('Giorno della settimana non valido.'); + return $this->back($response); + } + $days[(int) $value] = (int) $value; + } + if (count($days) === 7) { + $_SESSION['error_message'] = __('Almeno un giorno della settimana deve restare aperto.'); + return $this->back($response); + } + ksort($days); + $error = $this->commitChange($db, function () use ($db, $days): ?string { + (new SettingsRepository($db))->set('loans', 'closed_weekdays', implode(',', $days)); + // The yearly closures may leave open only days that fall on the + // newly closed weekdays. + return $this->yearCoverageError($db, ['start' => '', 'end' => '', 'description' => '', 'yearly' => true, 'sede' => null], array_values($days)) !== null + ? __('Con questi giorni di chiusura settimanale e le chiusure annuali la biblioteca resterebbe chiusa tutto l\'anno: almeno un giorno deve restare aperto.') + : null; + }); + $_SESSION[$error === null ? 'success_message' : 'error_message'] = $error ?? __('Giorni di chiusura settimanale salvati.'); + return $this->back($response); + } + + public function store(Request $request, Response $response, \mysqli $db): Response + { + if ($guard = $this->guard($response, $db)) { + return $guard; + } + $row = $this->validatedClosure($db, (array) $request->getParsedBody()); + if (is_string($row)) { + $_SESSION['error_message'] = $row; + return $this->back($response); + } + $error = $this->commitChange($db, function () use ($db, $row): ?string { + $this->insertClosure($db, $row); + return $this->yearCoverageError($db, $row); + }); + if ($error !== null) { + $_SESSION['error_message'] = $error; + return $this->back($response); + } + $_SESSION['success_message'] = __('Chiusura aggiunta.'); + return $this->back($response, substr($row['start'], 0, 7)); + } + + public function update(Request $request, Response $response, \mysqli $db, int $id): Response + { + if ($guard = $this->guard($response, $db)) { + return $guard; + } + if (!$this->closureExists($db, $id)) { + $_SESSION['error_message'] = __('Chiusura non trovata.'); + return $this->back($response); + } + if ($this->isPresetRow($db, $id)) { + $_SESSION['error_message'] = __('Le festività si modificano dalla selezione delle festività nazionali.'); + return $this->back($response); + } + $row = $this->validatedClosure($db, (array) $request->getParsedBody()); + if (is_string($row)) { + $_SESSION['error_message'] = $row; + return $this->back($response, null, $id); + } + $error = $this->commitChange($db, function () use ($db, $row, $id): ?string { + // Again under the lock: a closure another operator deleted since + // the check above would make the UPDATE touch no row and still + // report "updated". + if (!$this->closureExists($db, $id)) { + return __('Chiusura non trovata.'); + } + $sede = $row['sede']; + $yearly = $row['yearly'] ? 1 : 0; + $stmt = $db->prepare('UPDATE calendario_chiusure SET data_inizio = ?, data_fine = ?, descrizione = ?, ricorrente_annuale = ?, sede_id = ? WHERE id = ?'); + $stmt->bind_param('sssiii', $row['start'], $row['end'], $row['description'], $yearly, $sede, $id); + $stmt->execute(); + $stmt->close(); + return $this->yearCoverageError($db, $row); + }); + if ($error !== null) { + $_SESSION['error_message'] = $error; + return $this->back($response, null, $id); + } + $_SESSION['success_message'] = __('Chiusura aggiornata.'); + return $this->back($response, substr($row['start'], 0, 7)); + } + + public function delete(Request $request, Response $response, \mysqli $db, int $id): Response + { + if ($guard = $this->guard($response, $db)) { + return $guard; + } + if ($this->isPresetRow($db, $id)) { + $_SESSION['error_message'] = __('Le festività si modificano dalla selezione delle festività nazionali.'); + return $this->back($response); + } + $deleted = false; + $error = $this->commitChange($db, static function () use ($db, $id, &$deleted): ?string { + $stmt = $db->prepare('DELETE FROM calendario_chiusure WHERE id = ?'); + $stmt->bind_param('i', $id); + $stmt->execute(); + $deleted = $stmt->affected_rows > 0; + $stmt->close(); + return null; + }); + if ($error !== null) { + $_SESSION['error_message'] = $error; + return $this->back($response); + } + $_SESSION[$deleted ? 'success_message' : 'error_message'] = $deleted ? __('Chiusura eliminata.') : __('Chiusura non trovata.'); + return $this->back($response); + } + + /** Add a holiday of the library's own (Settings → Calendar → custom holidays). */ + public function storeCustom(Request $request, Response $response, \mysqli $db): Response + { + if ($guard = $this->guard($response, $db)) { + return $guard; + } + return $this->saveCustom($request, $response, $db, null); + } + + public function updateCustom(Request $request, Response $response, \mysqli $db, int $id): Response + { + if ($guard = $this->guard($response, $db)) { + return $guard; + } + if (CustomHolidays::find($db, $id) === null) { + $_SESSION['error_message'] = __('Festività non trovata.'); + return $this->backToCustom($response); + } + return $this->saveCustom($request, $response, $db, $id); + } + + public function deleteCustom(Request $request, Response $response, \mysqli $db, int $id): Response + { + if ($guard = $this->guard($response, $db)) { + return $guard; + } + $deleted = CustomHolidays::schemaReady($db) && CustomHolidays::delete($db, $id); + $this->calendarChanged($db); + $_SESSION[$deleted ? 'success_message' : 'error_message'] = $deleted ? __('Festività eliminata.') : __('Festività non trovata.'); + return $this->backToCustom($response); + } + + /** Switch a custom holiday on or off without deleting it. */ + public function toggleCustom(Request $request, Response $response, \mysqli $db, int $id): Response + { + if ($guard = $this->guard($response, $db)) { + return $guard; + } + $row = CustomHolidays::find($db, $id); + if ($row === null) { + $_SESSION['error_message'] = __('Festività non trovata.'); + return $this->backToCustom($response); + } + $active = (int) $row['attiva'] !== 1; + CustomHolidays::setActive($db, $id, $active); + $this->calendarChanged($db); + $_SESSION['success_message'] = $active ? __('Festività riattivata.') : __('Festività sospesa.'); + return $this->backToCustom($response); + } + + private function saveCustom(Request $request, Response $response, \mysqli $db, ?int $id): Response + { + if (!CustomHolidays::schemaReady($db)) { + $_SESSION['error_message'] = __('Le tabelle del calendario non sono ancora state create: completa l\'aggiornamento del database.'); + return $this->backToCustom($response); + } + $data = (array) $request->getParsedBody(); + $row = CustomHolidays::validate($data, array_keys($this->branches($db))); + if (is_string($row)) { + $_SESSION['error_message'] = $row; + // The form comes back with what was typed, not empty or with the stored values. + $_SESSION['custom_holiday_draft'] = CustomHolidays::draft($data, $id ?? 0); + return $this->backToCustom($response, $id); + } + CustomHolidays::save($db, $row, $id); + $this->calendarChanged($db); + $_SESSION['success_message'] = $id === null ? __('Festività aggiunta.') : __('Festività aggiornata.'); + return $this->backToCustom($response); + } + + private function backToCustom(Response $response, ?int $editId = null): Response + { + $target = url('/admin/calendar') . ($editId !== null ? '?custom_edit=' . $editId : '') . '#calendar-custom'; + return $response->withHeader('Location', $target)->withStatus(302); + } + + /** + * Apply the holidays ticked for a country (and region): the selection is + * saved, rows of unticked holidays are removed, missing ones added. + * Closures added by hand or from an ICS file are not touched. + */ + public function importPreset(Request $request, Response $response, \mysqli $db): Response + { + if ($guard = $this->guard($response, $db)) { + return $guard; + } + $data = (array) $request->getParsedBody(); + $country = is_string($data['country'] ?? null) ? strtoupper(trim($data['country'])) : ''; + if (HolidayCalculator::country($country) === null) { + $_SESSION['error_message'] = __('Paese non valido.'); + return $this->back($response); + } + $region = HolidayCalculator::normaliseRegion($country, is_string($data['region'] ?? null) ? trim($data['region']) : ''); + if (!HolidayPresets::schemaReady($db)) { + $_SESSION['error_message'] = __("Esegui prima l'aggiornamento del database: manca la colonna delle festività."); + return $this->backToPreset($response, $country, $region); + } + $ids = []; + foreach (is_array($data['rules'] ?? null) ? $data['rules'] : [] as $id) { + if (is_string($id)) { + $ids[] = $id; + } + } + $counts = ['added' => 0, 'removed' => 0, 'adopted' => 0]; + $error = $this->commitChange($db, function () use ($db, $country, $region, $ids, &$counts): ?string { + $counts = HolidayPresets::apply($db, $country, $region, $ids); + // Like the form: the holidays must not close the last open day + // the stored yearly closures and weekly closed days left. + return $counts['added'] + $counts['adopted'] > 0 + ? $this->yearCoverageError($db, ['start' => '', 'end' => '', 'description' => '', 'yearly' => true, 'sede' => null]) + : null; + }); + if ($error !== null) { + $_SESSION['error_message'] = $error; + return $this->backToPreset($response, $country, $region); + } + $_SESSION['success_message'] = $counts['adopted'] > 0 + ? __('Festività applicate: %1$d aggiunte, %2$d rimosse, %3$d già presenti riconosciute.', $counts['added'], $counts['removed'], $counts['adopted']) + : __('Festività applicate: %1$d aggiunte, %2$d rimosse.', $counts['added'], $counts['removed']); + return $this->backToPreset($response, $country, $region); + } + + /** + * Switch the yearly closures added by hand (or from a file) on and off + * without deleting them: a switched-off closure closes no day. + */ + public function saveYearly(Request $request, Response $response, \mysqli $db): Response + { + if ($guard = $this->guard($response, $db)) { + return $guard; + } + if (!HolidayPresets::schemaReady($db)) { + $_SESSION['error_message'] = __("Esegui prima l'aggiornamento del database: manca la colonna delle festività."); + return $this->back($response); + } + $data = (array) $request->getParsedBody(); + $ids = static function (mixed $raw): array { + $out = []; + foreach (is_array($raw) ? $raw : [] as $value) { + if (is_scalar($value) && ctype_digit((string) $value)) { + $out[(int) $value] = (int) $value; + } + } + return $out; + }; + $listed = $ids($data['listed'] ?? []); + $active = $ids($data['active'] ?? []); + $changed = 0; + $error = $this->commitChange($db, function () use ($db, $listed, $active, &$changed): ?string { + $switchedOn = 0; + $stmt = $db->prepare('UPDATE calendario_chiusure SET attiva = ? WHERE id = ? AND ricorrente_annuale = 1 AND regola_festivita IS NULL AND attiva <> ?'); + foreach ($listed as $id) { + $flag = isset($active[$id]) ? 1 : 0; + $stmt->bind_param('iii', $flag, $id, $flag); + $stmt->execute(); + $rows = max(0, $stmt->affected_rows); + $changed += $rows; + $switchedOn += $flag * $rows; + } + $stmt->close(); + // A closure switched back on must not close the last open day. + return $switchedOn > 0 + ? $this->yearCoverageError($db, ['start' => '', 'end' => '', 'description' => '', 'yearly' => true, 'sede' => null]) + : null; + }); + if ($error !== null) { + $_SESSION['error_message'] = $error; + return $response->withHeader('Location', url('/admin/calendar') . '#calendar-yearly')->withStatus(302); + } + $_SESSION['success_message'] = __('Chiusure annuali salvate: %d modificate.', $changed); + $target = url('/admin/calendar') . '#calendar-yearly'; + return $response->withHeader('Location', $target)->withStatus(302); + } + + public function importIcs(Request $request, Response $response, \mysqli $db): Response + { + if ($guard = $this->guard($response, $db)) { + return $guard; + } + $files = $request->getUploadedFiles(); + $file = $files['ics_file'] ?? null; + if (!$file instanceof UploadedFileInterface || $file->getError() !== UPLOAD_ERR_OK) { + $_SESSION['error_message'] = __('Seleziona un file ICS da importare.'); + return $this->back($response); + } + if (($file->getSize() ?? 0) > self::MAX_ICS_BYTES) { + $_SESSION['error_message'] = __('Il file ICS supera la dimensione massima di 1 MB.'); + return $this->back($response); + } + try { + $ics = (string) $file->getStream()->getContents(); + // ICS is UTF-8 by the standard, but exports in Windows-1252 / + // ISO-8859-1 exist: their accented SUMMARY bytes would fail the + // utf8mb4 INSERT halfway through the import. + // Unfold first (RFC 5545 3.1): a folded line may split one UTF-8 + // character in two, which the encoding check would take for + // Windows-1252. + $ics = (string) preg_replace("/\r?\n[ \t]/", '', $ics); + if (!mb_check_encoding($ics, 'UTF-8')) { + $ics = (string) mb_convert_encoding($ics, 'UTF-8', 'Windows-1252'); + } + } catch (\Throwable $e) { + SecureLogger::warning('Calendar ICS import: unreadable upload: ' . $e->getMessage()); + $ics = ''; + } + if (!str_contains(strtoupper($ics), 'BEGIN:VCALENDAR')) { + $_SESSION['error_message'] = __('Il file non è un calendario ICS valido.'); + return $this->back($response); + } + $data = (array) $request->getParsedBody(); + $sede = $this->branchFromInput($db, $data['sede_id'] ?? null); + if ($sede === false) { + $_SESSION['error_message'] = __('Sede non trovata.'); + return $this->back($response); + } + $added = 0; + $skipped = 0; + $rejected = 0; + $coverageRejected = 0; + $events = LibraryCalendar::parseIcs($ics); + $error = $this->commitChange($db, function () use ($db, $events, $sede, &$added, &$skipped, &$rejected, &$coverageRejected): ?string { + // The whole-year guard's state, carried from event to event: the + // yearly rows are read once and each check only looks again at + // the years whose first open day the new event closes. + $coverage = null; + foreach ($events as $event) { + // The same limits as the form: a yearly event longer than a + // year would otherwise be stored as a closure of every day. + if ($this->spanError($event['start'], $event['end'], $event['yearly']) !== null) { + $rejected++; + continue; + } + $row = [ + 'start' => $event['start'], + 'end' => $event['end'], + 'description' => $event['description'] !== '' ? $event['description'] : __('Chiusura'), + 'yearly' => $event['yearly'], + 'sede' => $sede, + ]; + if ($this->alreadyClosed($db, $row)) { + $skipped++; + continue; + } + $newId = $this->insertClosure($db, $row); + // Like the form: yearly rows that together close every day + // of the year are refused, the event is not kept. + if ($this->yearCoverageError($db, $row, null, $coverage) !== null) { + $this->deleteClosure($db, $newId); + $coverageRejected++; + continue; + } + $added++; + } + return null; + }); + if ($error !== null) { + $_SESSION['error_message'] = $error; + return $this->back($response); + } + $message = $rejected > 0 + ? __('File ICS importato: %1$d chiusure aggiunte, %2$d già presenti, %3$d scartate perché più lunghe del limite (un anno se annuali, dieci anni altrimenti).', $added, $skipped, $rejected) + : __('File ICS importato: %1$d chiusure aggiunte, %2$d già presenti.', $added, $skipped); + // A different cause, a different fix: the span is fine, the yearly + // rows together would leave no open day. + if ($coverageRejected > 0) { + $message .= ' ' . __('%d scartate perché con le altre chiusure annuali lascerebbero la biblioteca chiusa tutto l\'anno.', $coverageRejected); + } + $_SESSION['success_message'] = $message; + return $this->back($response); + } + + // ── Helpers ───────────────────────────────────────────────────────────── + + /** + * After a write that runs in its own transaction (holiday presets, yearly + * switches, the library's own holidays): drop the cached calendar and move + * the pickup deadlines that now fall on a closed day. + */ + private function calendarChanged(\mysqli $db): void + { + LibraryCalendar::reset(); + try { + LibraryCalendar::extendPickupDeadlines($db); + } catch (\Throwable $e) { + SecureLogger::warning('Calendar: pickup deadlines not extended: ' . $e->getMessage()); + } + } + + /** + * Every write goes through here, under one named lock (two operators + * importing at once cannot both pass alreadyClosed()) and one + * transaction: the write, then the pending pickups whose deadline is now + * a closed day move to the next open day. A write that returns an error + * message, or any failure (the deadline move included), rolls the whole + * change back, so the expiry sweep never reads a calendar whose pickup + * deadlines were not moved. Null on success, otherwise the message. + * + * @param callable(): ?string $write + */ + private function commitChange(\mysqli $db, callable $write): ?string + { + $lockName = $this->lockName($db); + try { + $stmt = $db->prepare('SELECT GET_LOCK(?, 10)'); + $stmt->bind_param('s', $lockName); + $stmt->execute(); + $acquired = (int) ($stmt->get_result()->fetch_row()[0] ?? 0) === 1; + $stmt->close(); + } catch (\Throwable $e) { + SecureLogger::error('Calendar: lock not available: ' . $e->getMessage()); + return __('Errore di salvataggio. Riprova più tardi.'); + } + if (!$acquired) { + return __('Un\'altra modifica del calendario è in corso: riprova tra poco.'); + } + try { + $db->begin_transaction(); + try { + $error = $write(); + if ($error === null) { + // The deadline move reads the new calendar. + LibraryCalendar::reset(); + LibraryCalendar::extendPickupDeadlines($db); + $db->commit(); + return null; + } + $db->rollback(); + return $error; + } catch (\Throwable $e) { + $db->rollback(); + SecureLogger::error('Calendar: change not saved: ' . $e->getMessage()); + return __('Errore di salvataggio. Riprova più tardi.'); + } + } finally { + // Committed or rolled back, this request reads the stored state. + LibraryCalendar::reset(); + \App\Support\ConfigStore::clearCache(); + try { + $release = $db->prepare('SELECT RELEASE_LOCK(?)'); + $release->bind_param('s', $lockName); + $release->execute(); + $release->get_result(); + $release->close(); + } catch (\Throwable) { + // Released with the connection anyway. + } + } + } + + /** Schema-scoped and under GET_LOCK()'s 64 characters (see CopyRepository). */ + private function lockName(\mysqli $db): string + { + $dbName = ''; + try { + $res = $db->query('SELECT DATABASE()'); + if ($res instanceof \mysqli_result) { + $dbName = (string) ($res->fetch_row()[0] ?? ''); + $res->free(); + } + } catch (\Throwable) { + } + return 'pinakes-calendar:' . md5($dbName); + } + + /** + * Yearly closures repeat every year, so two rows each shorter than a year + * can still close every day together; nextOpenDay() would then run to its + * bound and a due date would land on a closed day. Checked after $row is + * written (inside the transaction), on the yearly rows that apply to its + * branch, or to the library and to every branch for a row without one, + * together with the weekly closed days ($closedWeekdays, the stored + * loans.closed_weekdays when null). + * Null when every calendar year keeps an open day: then two open days are + * never more than two years apart, well inside the scan bound. + * + * $state carries the check across the rows of one import. Null on the + * first call: the stored yearly rows are read once. On success it holds + * those rows, the weekly days and the first open day found per branch and + * year; the next call adds its $row (already written, not yet in the + * state) and looks again only at the years whose first open day that row + * closes, from that day on: closures only grow during an import, so an + * earlier day cannot reopen. A failed check leaves $state as it was (the + * caller deletes the row). Without it every event re-read and rebuilt + * every yearly row and re-scanned every year from Jan 1, and a large ICS + * import outlasted the calendar lock other writers wait for. + * + * @param array{start: string, end: string, description: string, yearly: bool, sede: ?int} $row + * @param list|null $closedWeekdays + * @param array{rows: list>, weekdays: list, open: array}|null $state + */ + private function yearCoverageError(\mysqli $db, array $row, ?array $closedWeekdays = null, ?array &$state = null): ?string + { + if (!$row['yearly']) { + return null; + } + $added = null; + if ($state === null) { + // A closure switched off (attiva = 0) closes no day; the column + // exists from the 0.9.0 migration on. + $hasActive = $db->query("SHOW COLUMNS FROM calendario_chiusure LIKE 'attiva'"); + $activeOnly = ($hasActive instanceof \mysqli_result && $hasActive->num_rows > 0) ? ' AND attiva = 1' : ''; + $result = $db->query('SELECT data_inizio, data_fine, ricorrente_annuale, sede_id FROM calendario_chiusure WHERE ricorrente_annuale = 1' . $activeOnly); + $next = [ + 'rows' => $result instanceof \mysqli_result ? $result->fetch_all(MYSQLI_ASSOC) : [], + 'weekdays' => $closedWeekdays ?? LibraryCalendar::parseWeekdays((new SettingsRepository($db))->get('loans', 'closed_weekdays', '')), + 'open' => [], + ]; + } else { + $next = $state; + $stored = ['data_inizio' => $row['start'], 'data_fine' => $row['end'], 'ricorrente_annuale' => 1, 'sede_id' => $row['sede']]; + $next['rows'][] = $stored; + // The new row alone: does it close a day found open before? + $added = new LibraryCalendar([], [$stored]); + } + $calendar = null; + $branches = [$row['sede']]; + if ($row['sede'] === null) { + foreach ($next['rows'] as $stored) { + if ($stored['sede_id'] !== null) { + $branches[] = (int) $stored['sede_id']; + } + } + } + foreach (array_unique($branches, SORT_REGULAR) as $branch) { + // 28 consecutive years hold every pairing of a weekday for Jan 1 + // with a common or leap year, so the weekly closed days meet each + // possible year. A common year with only Feb 29 open fails here. + for ($year = 2025; $year < 2053; $year++) { + $key = ($branch ?? 'library') . '|' . $year; + $day = $year . '-01-01'; + if (isset($next['open'][$key])) { + if ($added !== null && $added->isOpen($next['open'][$key], $branch)) { + continue; + } + $day = LibraryCalendar::shift($next['open'][$key], 1); + } + $calendar ??= new LibraryCalendar($next['weekdays'], $next['rows']); + $open = null; + while ($open === null && str_starts_with($day, (string) $year)) { + if ($calendar->isOpen($day, $branch)) { + $open = $day; + } + $day = LibraryCalendar::shift($day, 1); + } + if ($open === null) { + return __('Con questa chiusura annuale la biblioteca resterebbe chiusa tutto l\'anno: almeno un giorno deve restare aperto.'); + } + $next['open'][$key] = $open; + } + } + $state = $next; + return null; + } + + private function deleteClosure(\mysqli $db, int $id): void + { + $stmt = $db->prepare('DELETE FROM calendario_chiusure WHERE id = ?'); + $stmt->bind_param('i', $id); + $stmt->execute(); + $stmt->close(); + } + + private function guard(Response $response, \mysqli $db): ?Response + { + if (!\App\Support\Permissions::mayConfigure()) { + $_SESSION['error_message'] = __('Operazione riservata agli amministratori'); + return $response->withHeader('Location', url('/admin/dashboard'))->withStatus(302); + } + // Files updated before the 0.9.0 migration ran: the closures table is + // not there yet, and every page and action of the calendar reads it. + try { + $res = $db->query("SELECT 1 FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'calendario_chiusure'"); + $ready = $res instanceof \mysqli_result && $res->num_rows > 0; + } catch (\Throwable) { + $ready = false; + } + if (!$ready) { + $_SESSION['error_message'] = __('Questa funzione sarà disponibile al termine dell\'aggiornamento del database.'); + return $response->withHeader('Location', url('/admin/dashboard'))->withStatus(302); + } + return null; + } + + private function back(Response $response, ?string $month = null, ?int $editId = null): Response + { + $params = []; + if ($month !== null) { + $params['month'] = $month; + } + if ($editId !== null) { + $params['edit'] = $editId; + } + $target = url('/admin/calendar') . ($params !== [] ? '?' . http_build_query($params) : ''); + return $response->withHeader('Location', $target)->withStatus(302); + } + + /** + * @param array $data + * @return array{start: string, end: string, description: string, yearly: bool, sede: ?int}|string + * the closure to store, or the error message + */ + private function validatedClosure(\mysqli $db, array $data): array|string + { + $start = is_string($data['data_inizio'] ?? null) ? trim($data['data_inizio']) : ''; + $end = is_string($data['data_fine'] ?? null) ? trim($data['data_fine']) : ''; + if ($end === '') { + $end = $start; + } + if (!DateHelper::isISODateFormat($start) || !DateHelper::isISODateFormat($end)) { + return __('Data non valida: usa il formato AAAA-MM-GG.'); + } + $yearly = !empty($data['ricorrente_annuale']); + if (($error = $this->spanError($start, $end, $yearly)) !== null) { + return $error; + } + $description = is_string($data['descrizione'] ?? null) ? trim($data['descrizione']) : ''; + $description = mb_substr(preg_replace('/\s+/u', ' ', $description) ?? '', 0, 255); + $sede = $this->branchFromInput($db, $data['sede_id'] ?? null); + if ($sede === false) { + return __('Sede non trovata.'); + } + return ['start' => $start, 'end' => $end, 'description' => $description, 'yearly' => $yearly, 'sede' => $sede]; + } + + /** + * The limits every closure meets, from the form or from a file: end not + * before start, a yearly closure within a year, a one-off within ten. + * Null when the span is fine, otherwise the message for the operator. + */ + private function spanError(string $start, string $end, bool $yearly): ?string + { + if ($end < $start) { + return __('La data di fine non può precedere la data di inizio'); + } + $span = LibraryCalendar::spanDays($start, $end); + if ($span < 0) { + return __('Data non valida: usa il formato AAAA-MM-GG.'); + } + if ($yearly && $span > LibraryCalendar::MAX_YEARLY_SPAN_DAYS) { + return __('Una chiusura annuale non può durare più di un anno.'); + } + if (!$yearly && $span > LibraryCalendar::MAX_ONCE_SPAN_DAYS) { + return __('Una chiusura non può durare più di dieci anni.'); + } + return null; + } + + private function closureExists(\mysqli $db, int $id): bool + { + $stmt = $db->prepare('SELECT 1 FROM calendario_chiusure WHERE id = ? LIMIT 1'); + $stmt->bind_param('i', $id); + $stmt->execute(); + $found = $stmt->get_result()->fetch_row() !== null; + $stmt->close(); + return $found; + } + + /** + * The branch a form field names: null for "every branch" (field absent, + * empty or 0), false for anything else that is not an existing branch + * (an unknown or deleted id, garbage, an array), which the caller refuses + * instead of storing a closure for every branch. + */ + private function branchFromInput(\mysqli $db, mixed $raw): int|false|null + { + if ($raw === null || $raw === '' || $raw === '0' || $raw === 0) { + return null; + } + return $this->branchFrom($db, $raw) ?? false; + } + + private function branchFrom(\mysqli $db, mixed $raw): ?int + { + if (!is_scalar($raw) || !ctype_digit((string) $raw) || (int) $raw <= 0) { + return null; + } + $id = (int) $raw; + return isset($this->branches($db)[$id]) ? $id : null; + } + + /** @param array{start: string, end: string, description: string, yearly: bool, sede: ?int} $row */ + private function insertClosure(\mysqli $db, array $row): int + { + $yearly = $row['yearly'] ? 1 : 0; + $sede = $row['sede']; + $stmt = $db->prepare('INSERT INTO calendario_chiusure (data_inizio, data_fine, descrizione, ricorrente_annuale, sede_id) VALUES (?, ?, ?, ?, ?)'); + $stmt->bind_param('sssii', $row['start'], $row['end'], $row['description'], $yearly, $sede); + $stmt->execute(); + $id = (int) $stmt->insert_id; + $stmt->close(); + return $id; + } + + /** + * An identical closure is already stored (same interval, recurrence and + * branch; a yearly row matches the same month-day span in any year), so + * importing a file twice adds nothing. Holiday preset rows do not count: + * unticking that holiday later removes them, and the day the file + * names must stay closed. + * + * @param array{start: string, end: string, description: string, yearly: bool, sede: ?int} $row + */ + private function alreadyClosed(\mysqli $db, array $row): bool + { + $yearly = $row['yearly'] ? 1 : 0; + $sede = $row['sede'] ?? 0; + $manual = HolidayPresets::schemaReady($db) ? ' AND regola_festivita IS NULL' : ''; + if ($yearly === 1) { + $from = substr($row['start'], 5); + $to = substr($row['end'], 5); + $stmt = $db->prepare("SELECT 1 FROM calendario_chiusure WHERE ricorrente_annuale = 1 AND DATE_FORMAT(data_inizio, '%m-%d') = ? AND DATE_FORMAT(data_fine, '%m-%d') = ? AND COALESCE(sede_id, 0) = ?{$manual} LIMIT 1"); + $stmt->bind_param('ssi', $from, $to, $sede); + } else { + $stmt = $db->prepare("SELECT 1 FROM calendario_chiusure WHERE ricorrente_annuale = 0 AND data_inizio = ? AND data_fine = ? AND COALESCE(sede_id, 0) = ?{$manual} LIMIT 1"); + $stmt->bind_param('ssi', $row['start'], $row['end'], $sede); + } + $stmt->execute(); + $found = $stmt->get_result()->fetch_row() !== null; + $stmt->close(); + return $found; + } + + /** @return list> */ + private function closures(\mysqli $db): array + { + // Before the holiday-presets migration the two columns are missing: + // every row is a manual, active one. + $presetColumns = HolidayPresets::schemaReady($db) ? 'c.regola_festivita, c.attiva' : 'NULL AS regola_festivita, 1 AS attiva'; + $result = $db->query("SELECT c.id, c.data_inizio, c.data_fine, c.descrizione, c.ricorrente_annuale, c.sede_id, {$presetColumns}, s.nome AS sede_nome + FROM calendario_chiusure c LEFT JOIN sedi s ON s.id = c.sede_id + ORDER BY c.ricorrente_annuale DESC, c.data_inizio, c.data_fine, c.id"); + return $result === false ? [] : $result->fetch_all(MYSQLI_ASSOC); + } + + private function isPresetRow(\mysqli $db, int $id): bool + { + if (!HolidayPresets::schemaReady($db)) { + return false; + } + $stmt = $db->prepare('SELECT 1 FROM calendario_chiusure WHERE id = ? AND regola_festivita IS NOT NULL LIMIT 1'); + $stmt->bind_param('i', $id); + $stmt->execute(); + $found = $stmt->get_result()->fetch_row() !== null; + $stmt->close(); + return $found; + } + + private function backToPreset(Response $response, string $country, string $region): Response + { + $params = ['holiday_country' => $country]; + if ($region !== '') { + $params['holiday_region'] = $region; + } + $target = url('/admin/calendar') . '?' . http_build_query($params) . '#calendar-preset'; + return $response->withHeader('Location', $target)->withStatus(302); + } + + /** + * What the holiday card shows: the country / region / preview year asked + * for in the query (the saved choice otherwise) and, for each holiday, + * its date in that year and whether it is ticked. Ticked = the saved + * choice for the saved country and region; for another one the + * statutory holidays (optional ones stay unticked). + * + * @param array $query + * @return array{countries: array, country: string, regions: array, region: string, years: array{min: int, max: int}, data_years: array{int, int}|null, year: int, list: list, yearly: bool, regional: bool, optional: bool, name_en: string, category: string, estimated: bool, checked: bool}>, saved: bool, schema: bool, unknown_country: string} + */ + private function holidayPreview(\mysqli $db, array $query, int $currentYear): array + { + $selection = HolidayPresets::selection($db); + $country = is_string($query['holiday_country'] ?? null) ? strtoupper($query['holiday_country']) : ''; + if (HolidayCalculator::country($country) === null) { + $country = $selection['country']; + } + $rawRegion = is_string($query['holiday_region'] ?? null) + ? $query['holiday_region'] + : ($country === $selection['country'] ? $selection['region'] : ''); + $region = HolidayCalculator::normaliseRegion($country, $rawRegion); + $years = ['min' => 1900, 'max' => 2199]; + $requestedYear = is_scalar($query['holiday_year'] ?? null) ? filter_var($query['holiday_year'], FILTER_VALIDATE_INT) : false; + $year = $requestedYear !== false && $requestedYear >= $years['min'] && $requestedYear <= $years['max'] ? $requestedYear : $currentYear; + $savedHere = $selection['saved'] && $selection['country'] === $country && $selection['region'] === $region; + $ticked = array_flip($selection['rules']); + // Before the first selection, a former Italian closure the library + // suspended starts unticked: applying the defaults keeps it off. + $suspended = !$selection['saved'] && $country === 'IT' ? array_flip(HolidayPresets::suspendedLegacyRules($db)) : []; + $list = []; + foreach (HolidayCalculator::forYear($country, $region, $year) as $holiday) { + $holiday['checked'] = $savedHere ? isset($ticked[$holiday['id']]) : !$holiday['optional'] && !isset($suspended[$holiday['id']]); + $list[] = $holiday; + } + return [ + 'countries' => HolidayCalculator::countryNames(), + 'country' => $country, + 'regions' => HolidayCalculator::regions($country), + 'region' => $region, + 'years' => $years, + // Countries without a hand-written table: dates from the dataset, for its years only. + 'data_years' => (HolidayCalculator::country($country)['dataset'] ?? false) ? \App\Support\Holidays\HolidayDataset::years($country) : null, + 'year' => $year, + 'list' => $list, + 'saved' => $savedHere, + 'schema' => HolidayPresets::schemaReady($db), + // Chosen ids the dataset no longer knows: shown whatever country + // is being previewed, since they are the saved selection's. + 'missing' => $selection['saved'] ? $selection['missing'] : [], + 'missing_country' => HolidayCalculator::countryNames()[$selection['country']] ?? $selection['country'], + // A stored country no longer known: the page warns instead of + // silently showing the default country as if nothing was saved. + 'unknown_country' => $selection['unknown_country'], + ]; + } + + /** @var array|null */ + private ?array $branchCache = null; + + /** @return array id => name */ + private function branches(\mysqli $db): array + { + if ($this->branchCache !== null) { + return $this->branchCache; + } + $this->branchCache = []; + $result = $db->query('SELECT id, nome FROM sedi ORDER BY nome'); + if ($result !== false) { + foreach ($result->fetch_all(MYSQLI_ASSOC) as $row) { + $this->branchCache[(int) $row['id']] = (string) $row['nome']; + } + } + return $this->branchCache; + } +} diff --git a/app/Controllers/Admin/StatsController.php b/app/Controllers/Admin/StatsController.php index bdfd5bd06..827041c68 100644 --- a/app/Controllers/Admin/StatsController.php +++ b/app/Controllers/Admin/StatsController.php @@ -109,6 +109,15 @@ public function index(Request $request, Response $response, \mysqli $db): Respon $statsResult = $db->query($statsQuery); $stats = $statsResult->fetch_assoc(); + // 0.9.0 sections: visitors (attendance log), loans by material and the + // extension points other branches fill (see StatisticsSections). + $visitors = \App\Support\StatisticsSections::visitors($db, 30); + $loansByMedia = \App\Support\StatisticsSections::loansByMediaType($db); + $extraSections = \App\Support\StatisticsSections::extensions($db); + // Fines report (plan §7): charged / collected / waived per month. + [$finesFrom, $finesTo] = self::finesPeriod($request); + $finesByMonth = \App\Services\PatronLedger::forDb($db)->periodTotals($finesFrom, $finesTo); + $title = 'Statistiche Prestiti'; ob_start(); @@ -122,4 +131,61 @@ public function index(Request $request, Response $response, \mysqli $db): Respon $response->getBody()->write($html); return $response; } + + /** CSV of the fines report for the selected period. */ + public function finesCsv(Request $request, Response $response, \mysqli $db): Response + { + [$from, $to] = self::finesPeriod($request); + $rows = \App\Services\PatronLedger::forDb($db)->periodTotals($from, $to); + $currency = \App\Services\FineParameters::currency(); + $out = fopen('php://temp', 'r+'); + fputcsv($out, [__('Mese'), __('Addebitato') . ' (' . $currency . ')', __('Incassato') . ' (' . $currency . ')', __('Condonato') . ' (' . $currency . ')', __('Stornato') . ' (' . $currency . ')'], ',', '"', ''); + $sum = ['addebitato' => 0, 'incassato' => 0, 'condonato' => 0, 'stornato' => 0]; + foreach ($rows as $row) { + fputcsv($out, [ + $row['mese'], + \App\Services\FineParameters::centsToDecimal($row['addebitato']), + \App\Services\FineParameters::centsToDecimal($row['incassato']), + \App\Services\FineParameters::centsToDecimal($row['condonato']), + \App\Services\FineParameters::centsToDecimal($row['stornato']), + ], ',', '"', ''); + foreach ($sum as $k => $v) { + $sum[$k] = $v + $row[$k]; + } + } + fputcsv($out, [ + __('Totale'), + \App\Services\FineParameters::centsToDecimal($sum['addebitato']), + \App\Services\FineParameters::centsToDecimal($sum['incassato']), + \App\Services\FineParameters::centsToDecimal($sum['condonato']), + \App\Services\FineParameters::centsToDecimal($sum['stornato']), + ], ',', '"', ''); + rewind($out); + $csv = "\xEF\xBB\xBF" . (string) stream_get_contents($out); + fclose($out); + $filename = 'multe_' . $from . '_' . $to . '.csv'; + $response->getBody()->write($csv); + return $response + ->withHeader('Content-Type', 'text/csv; charset=UTF-8') + ->withHeader('Content-Disposition', 'attachment; filename="' . $filename . '"'); + } + + /** + * Report period from ?fines_from / ?fines_to (Y-m-d); default: the last + * twelve months up to today, in the application timezone. + * + * @return array{0: string, 1: string} + */ + private static function finesPeriod(Request $request): array + { + $q = $request->getQueryParams(); + $today = \App\Support\DateHelper::today(); + $to = is_string($q['fines_to'] ?? null) && \App\Support\DateHelper::isISODateFormat($q['fines_to']) ? $q['fines_to'] : $today; + $defaultFrom = (new \DateTimeImmutable($to))->modify('first day of this month')->modify('-11 months')->format('Y-m-d'); + $from = is_string($q['fines_from'] ?? null) && \App\Support\DateHelper::isISODateFormat($q['fines_from']) ? $q['fines_from'] : $defaultFrom; + if ($from > $to) { + [$from, $to] = [$to, $from]; + } + return [$from, $to]; + } } diff --git a/app/Controllers/AttendanceController.php b/app/Controllers/AttendanceController.php new file mode 100644 index 000000000..7f184a0e0 --- /dev/null +++ b/app/Controllers/AttendanceController.php @@ -0,0 +1,212 @@ +schemaMissing($db, $response)) !== null) { + return $missing; + } + $repo = new AttendanceRepository($db); + $days = $this->period($request); + $data = [ + 'days' => $days, + 'periods' => self::PERIODS, + 'daily' => $repo->dailyCounts($days), + 'hourly' => $repo->hourlyCounts($days), + 'totals' => $repo->totals($days), + 'today' => $repo->totals(1), + 'recent' => $repo->recent(10), + 'branches' => $repo->branches(), + 'retentionDays' => AttendanceRepository::retentionDays(new SettingsRepository($db)), + 'isAdmin' => ($_SESSION['user']['tipo_utente'] ?? '') === 'admin', + ]; + ob_start(); + require __DIR__ . '/../Views/attendance/index.php'; + $content = ob_get_clean(); + ob_start(); + require __DIR__ . '/../Views/layout.php'; + $html = ob_get_clean(); + $response->getBody()->write((string) $html); + return $response; + } + + public function scan(Request $request, Response $response, mysqli $db): Response + { + if (($off = \App\Support\Features::requireOrRedirect('visitor_log', $response)) !== null) { + return $off; + } + if (($missing = $this->schemaMissing($db, $response)) !== null) { + return $missing; + } + $body = (array) $request->getParsedBody(); + $repo = new AttendanceRepository($db); + $card = is_string($body['codice_tessera'] ?? null) ? $body['codice_tessera'] : ''; + try { + $member = $repo->findMemberByCard($card); + if ($member === null) { + $_SESSION['error_message'] = __('Tessera non riconosciuta. Controlla il codice o registra la visita come ospite.'); + return $this->back($response); + } + $repo->recordMember($member['id'], $this->rememberBranch($repo, $body)); + } catch (\Throwable $e) { + \App\Support\SecureLogger::error('Attendance scan failed', ['exception' => get_class($e), 'code' => $e->getCode()]); + $_SESSION['error_message'] = __('Impossibile registrare la presenza. Riprova.'); + return $this->back($response); + } + $name = AttendanceRepository::shortName($member['nome'], $member['cognome']); + $_SESSION['success_message'] = $member['stato'] === 'attivo' + ? sprintf(__('Benvenuto/a, %s: presenza registrata.'), $name) + : sprintf(__('Presenza registrata per %s, ma la tessera non è attiva.'), $name); + return $this->back($response); + } + + public function guest(Request $request, Response $response, mysqli $db): Response + { + if (($off = \App\Support\Features::requireOrRedirect('visitor_log', $response)) !== null) { + return $off; + } + if (($missing = $this->schemaMissing($db, $response)) !== null) { + return $missing; + } + $body = (array) $request->getParsedBody(); + $repo = new AttendanceRepository($db); + $name = is_string($body['nome_ospite'] ?? null) ? $body['nome_ospite'] : null; + $reason = is_string($body['motivo'] ?? null) ? $body['motivo'] : null; + try { + $repo->recordGuest($name, $reason, $this->rememberBranch($repo, $body)); + $_SESSION['success_message'] = __('Presenza dell\'ospite registrata.'); + } catch (\Throwable $e) { + \App\Support\SecureLogger::error('Attendance guest failed', ['exception' => get_class($e), 'code' => $e->getCode()]); + $_SESSION['error_message'] = __('Impossibile registrare la presenza. Riprova.'); + } + return $this->back($response); + } + + /** Retention setting. AdminAuthMiddleware admits staff: this one is admin-only. */ + public function saveSettings(Request $request, Response $response, mysqli $db): Response + { + if (($off = \App\Support\Features::requireOrRedirect('visitor_log', $response)) !== null) { + return $off; + } + if (!$this->isAdmin()) { + $_SESSION['error_message'] = __('Operazione riservata agli amministratori.'); + return $this->back($response); + } + $body = (array) $request->getParsedBody(); + $days = AttendanceRepository::normalizeRetention($body['retention_days'] ?? null); + (new SettingsRepository($db))->set(AttendanceRepository::SETTINGS_CATEGORY, AttendanceRepository::SETTING_RETENTION, (string) $days); + $_SESSION['success_message'] = $days === 0 + ? __('Conservazione aggiornata: le presenze non verranno cancellate automaticamente.') + : sprintf(__('Conservazione aggiornata: le presenze più vecchie di %d giorni verranno cancellate.'), $days); + return $this->back($response); + } + + /** Apply the retention now instead of waiting for the next maintenance run. */ + public function purge(Request $request, Response $response, mysqli $db): Response + { + if (($off = \App\Support\Features::requireOrRedirect('visitor_log', $response)) !== null) { + return $off; + } + if (($missing = $this->schemaMissing($db, $response)) !== null) { + return $missing; + } + if (!$this->isAdmin()) { + $_SESSION['error_message'] = __('Operazione riservata agli amministratori.'); + return $this->back($response); + } + $days = AttendanceRepository::retentionDays(new SettingsRepository($db)); + $deleted = (new AttendanceRepository($db))->purge($days); + $_SESSION['success_message'] = sprintf(__('Presenze cancellate: %d.'), $deleted); + return $this->back($response); + } + + /** Daily counts for the selected period as CSV. */ + public function exportCsv(Request $request, Response $response, mysqli $db): Response + { + if (($off = \App\Support\Features::requireOrRedirect('visitor_log', $response)) !== null) { + return $off; + } + if (($missing = $this->schemaMissing($db, $response)) !== null) { + return $missing; + } + $days = $this->period($request); + $rows = (new AttendanceRepository($db))->dailyCounts($days); + $out = fopen('php://temp', 'r+'); + if ($out === false) { + return $response->withStatus(500); + } + fwrite($out, "\xEF\xBB\xBF"); + fputcsv($out, [__('Data'), __('Soci'), __('Ospiti'), __('Totale')], ';', '"', ''); + foreach ($rows as $row) { + fputcsv($out, [$row['giorno'], $row['soci'], $row['ospiti'], $row['totale']], ';', '"', ''); + } + rewind($out); + $csv = (string) stream_get_contents($out); + fclose($out); + $response->getBody()->write($csv); + return $response + ->withHeader('Content-Type', 'text/csv; charset=UTF-8') + ->withHeader('Content-Disposition', 'attachment; filename="presenze_' . $days . 'g.csv"'); + } + + private function period(Request $request): int + { + $days = (int) ($request->getQueryParams()['days'] ?? 30); + return in_array($days, self::PERIODS, true) ? $days : 30; + } + + /** + * The kiosk keeps the branch chosen for the previous visit selected. + * + * @param array $body + */ + private function rememberBranch(AttendanceRepository $repo, array $body): ?int + { + $sedeId = $repo->validBranchId($body['sede_id'] ?? null); + $_SESSION['attendance_sede_id'] = $sedeId; + return $sedeId; + } + + private function isAdmin(): bool + { + return ($_SESSION['user']['tipo_utente'] ?? '') === 'admin'; + } + + private function back(Response $response): Response + { + return $response->withHeader('Location', url('/admin/attendance'))->withStatus(302); + } + + /** + * Files updated before the 0.9.0 migration ran (or a deploy that skipped + * it): the visitor log table is not there yet, so every action answers + * with a message instead of a 500. + */ + private function schemaMissing(mysqli $db, Response $response): ?Response + { + if ((new AttendanceRepository($db))->isAvailable()) { + return null; + } + $_SESSION['error_message'] = __('Questa funzione sarà disponibile al termine dell\'aggiornamento del database.'); + return $response->withHeader('Location', url('/admin/dashboard'))->withStatus(302); + } +} diff --git a/app/Controllers/AutoriApiController.php b/app/Controllers/AutoriApiController.php index 578566258..f01c40692 100644 --- a/app/Controllers/AutoriApiController.php +++ b/app/Controllers/AutoriApiController.php @@ -55,33 +55,24 @@ public function list(Request $request, Response $response, mysqli $db): Response $params_for_where = []; $param_types_for_where = ''; - if ($search_text !== '') { - $where_prepared .= " AND (a.nome LIKE ? OR a.pseudonimo LIKE ? OR a.biografia LIKE ?) "; - $escaped = '%' . str_replace(['%', '_'], ['\\%', '\\_'], $search_text) . '%'; - $params_for_where[] = $escaped; - $params_for_where[] = $escaped; - $params_for_where[] = $escaped; - $param_types_for_where .= 'sss'; - } - if ($search_nome !== '') { - $where_prepared .= " AND a.nome LIKE ? "; - $params_for_where[] = '%' . str_replace(['%', '_'], ['\\%', '\\_'], $search_nome) . '%'; - $param_types_for_where .= 's'; - } - if ($search_pseudonimo !== '') { - $where_prepared .= " AND a.pseudonimo LIKE ? "; - $params_for_where[] = '%' . str_replace(['%', '_'], ['\\%', '\\_'], $search_pseudonimo) . '%'; - $param_types_for_where .= 's'; - } - if ($search_sito !== '') { - $where_prepared .= " AND a.sito_web LIKE ? "; - $params_for_where[] = '%' . str_replace(['%', '_'], ['\\%', '\\_'], $search_sito) . '%'; - $param_types_for_where .= 's'; - } - if ($search_naz !== '' && $colNaz !== null) { - $where_prepared .= " AND a.`$colNaz` LIKE ? "; - $params_for_where[] = '%' . str_replace(['%', '_'], ['\\%', '\\_'], $search_naz) . '%'; - $param_types_for_where .= 's'; + // A value wrapped in quotes matches the exact spelling, accents + // included (#501): "Haffner" no longer returns Häffner. + $textFilters = [ + [['a.nome', 'a.pseudonimo', 'a.biografia'], $search_text], + [['a.nome'], $search_nome], + [['a.pseudonimo'], $search_pseudonimo], + [['a.sito_web'], $search_sito], + ]; + if ($colNaz !== null) { + $textFilters[] = [["a.`$colNaz`"], $search_naz]; + } + foreach ($textFilters as [$exprs, $value]) { + $cond = $value !== '' ? \App\Support\ExactSearch::filterCondition($exprs, $value) : null; + if ($cond !== null) { + $where_prepared .= ' AND ' . $cond['sql'] . ' '; + array_push($params_for_where, ...$cond['params']); + $param_types_for_where .= $cond['types']; + } } if ($nascita_from !== '') { $where_prepared .= " AND a.data_nascita >= ? "; diff --git a/app/Controllers/CatalogueCardController.php b/app/Controllers/CatalogueCardController.php new file mode 100644 index 000000000..dd93e1e85 --- /dev/null +++ b/app/Controllers/CatalogueCardController.php @@ -0,0 +1,92 @@ +getById($id); + if ($book === null) { + $_SESSION['error_message'] = __('Libro non trovato.'); + return $response->withHeader('Location', url('/admin/books'))->withStatus(302); + } + return $this->pdf($response, [$book], 'scheda_' . $id . '.pdf'); + } + + public function bulk(Request $request, Response $response, mysqli $db): Response + { + if (($off = \App\Support\Features::requireOrRedirect('catalogue_cards', $response)) !== null) { + return $off; + } + $ids = self::parseIds((string) ($request->getQueryParams()['ids'] ?? '')); + $repo = new BookRepository($db); + $books = []; + foreach ($ids as $id) { + $book = $repo->getById($id); + if ($book !== null) { + $books[] = $book; + } + } + if ($books === []) { + $_SESSION['error_message'] = __('Seleziona almeno un libro per stampare le schede catalografiche.'); + return $response->withHeader('Location', url('/admin/books'))->withStatus(302); + } + return $this->pdf($response, $books, 'schede_catalografiche.pdf'); + } + + /** + * "3,1,3,x,2" → [3, 1, 2]: positive ints, first occurrence order, capped. + * + * @return list + */ + public static function parseIds(string $raw): array + { + $ids = []; + foreach (explode(',', $raw) as $part) { + $id = filter_var(trim($part), FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]); + if ($id !== false && !in_array($id, $ids, true)) { + $ids[] = $id; + if (count($ids) >= self::MAX_BULK) { + break; + } + } + } + return $ids; + } + + /** @param list> $books */ + private function pdf(Response $response, array $books, string $filename): Response + { + $name = (string) \App\Support\ConfigStore::get('app.name', 'Biblioteca'); + try { + $content = CatalogueCard::render($books, $name); + } catch (\Throwable $e) { + \App\Support\SecureLogger::error('Catalogue card PDF failed', ['exception' => get_class($e), 'message' => $e->getMessage()]); + $_SESSION['error_message'] = __('Impossibile generare le schede catalografiche.'); + return $response->withHeader('Location', url('/admin/books'))->withStatus(302); + } + $response->getBody()->write($content); + return $response + ->withHeader('Content-Type', 'application/pdf') + ->withHeader('Content-Disposition', 'inline; filename="' . $filename . '"'); + } +} diff --git a/app/Controllers/CirculationSettingsController.php b/app/Controllers/CirculationSettingsController.php new file mode 100644 index 000000000..7c4bf235e --- /dev/null +++ b/app/Controllers/CirculationSettingsController.php @@ -0,0 +1,622 @@ + [min, max] (NULL when empty). */ + private const TYPE_INT_FIELDS = [ + 'max_prestiti' => [0, 1000], + 'durata_prestito_giorni' => [1, 3650], + 'max_durata_richiesta_giorni' => [1, 3650], + 'max_rinnovi' => [0, 100], + 'giorni_rinnovo' => [1, 3650], + 'max_prenotazioni' => [0, 1000], + 'durata_tessera_giorni' => [1, 36500], + 'giorni_tolleranza' => [0, 365], + ]; + + /** Patron type money fields (DECIMAL(10,2), NULL when empty; 0 = disabled for this type). */ + public const TYPE_MONEY_FIELDS = ['multa_giornaliera', 'multa_massima_prestito', 'soglia_blocco_debito']; + + /** Patron type fields added with the fines: absent from a form rendered before them, kept as stored on edit. */ + private const TYPE_FIELDS_KEPT_WHEN_ABSENT = ['giorni_tolleranza', 'multa_giornaliera', 'multa_massima_prestito', 'soglia_blocco_debito']; + + /** Matrix cell fields: form key => [column, min, max]. */ + private const RULE_FIELDS = [ + 'max' => ['max_prestiti', 0, 1000], + 'duration' => ['durata_prestito_giorni', 1, 3650], + 'renewals' => ['max_rinnovi', 0, 100], + 'renewal_days' => ['giorni_rinnovo', 1, 3650], + 'grace' => ['giorni_tolleranza', 0, 365], + ]; + + /** Matrix cell fields added with the fines: absent from an older form, kept as stored. */ + private const RULE_FIELDS_KEPT_WHEN_ABSENT = ['grace']; + + /** Matrix cell money fields: form key => column. */ + private const RULE_MONEY_FIELDS = [ + 'fine' => 'multa_giornaliera', + 'fine_max' => 'multa_massima_prestito', + ]; + + private const TAB_URL = '/admin/settings?tab=circulation'; + + /** + * Data for the circulation tab of the settings page. + * + * @return array{types: array>, defaultTypeId: ?int, media: array, + * rules: array>, inherited: array>, + * globals: array, userCounts: array, effective: array} + */ + public static function viewData(mysqli $db): array + { + $policy = new CirculationPolicy($db, configuration: true); + $types = $policy->types(); + $media = self::mediaTypes(); + $globals = $policy->globalLayer(); + $rules = $policy->rules(); + + // What an empty cell resolves to: every layer below the cell itself. + $inherited = []; + $rows = array_merge([CirculationPolicy::ALL_TYPES], array_keys($types)); + foreach ($rows as $typeId) { + foreach (array_keys($media) as $m) { + if ($typeId === CirculationPolicy::ALL_TYPES) { + $resolved = \App\Services\Circulation\PolicyResolver::resolve([], $globals, \App\Services\Circulation\PolicyResolver::resolveUser(null, $globals), $m); + } else { + $layers = $policy->ruleLayers($typeId, $m); + array_shift($layers); // drop the cell's own rule + $resolved = \App\Services\Circulation\PolicyResolver::resolve($layers, $globals, \App\Services\Circulation\PolicyResolver::resolveUser(null, $globals), $m); + } + $inherited[$typeId . '|' . $m] = [ + 'loanable' => $resolved->loanable, + 'max' => $resolved->maxActiveForMedia, + 'duration' => $resolved->durationDays, + 'renewals' => $resolved->maxRenewals, + 'renewal_days' => $resolved->renewalDays, + 'grace' => (int) ($resolved->graceDays ?? 0), + 'fine' => $resolved->dailyFine, + 'fine_max' => $resolved->maxFinePerLoan, + ]; + } + } + + $effective = []; + foreach (array_keys($types) as $typeId) { + $effective[$typeId] = $policy->forType($typeId); + } + + $userCounts = []; + if ($policy->schemaReady()) { + $result = $db->query('SELECT tipologia_id, COUNT(*) AS c FROM utenti WHERE tipologia_id IS NOT NULL GROUP BY tipologia_id'); + if ($result instanceof \mysqli_result) { + while ($row = $result->fetch_assoc()) { + $userCounts[(int) $row['tipologia_id']] = (int) $row['c']; + } + } + } + + return [ + 'types' => $types, + 'defaultTypeId' => $policy->defaultTypeId(), + 'media' => $media, + 'rules' => $rules, + 'inherited' => $inherited, + 'globals' => $globals, + 'userCounts' => $userCounts, + 'effective' => $effective, + ]; + } + + /** @return array tipo_media => translated label */ + public static function mediaTypes(): array + { + $out = []; + foreach (MediaLabels::allTypes() as $key => $meta) { + $out[$key] = __($meta['label']); + } + return $out; + } + + /** + * Patron types and the rules matrix are library policy: administrators + * only (or a custom role with settings.manage), like the closures + * calendar. AdminAuthMiddleware admits staff too, so every action checks + * it inline. + */ + private function guard(Response $response): ?Response + { + if (\App\Support\Permissions::mayConfigure()) { + return null; + } + $_SESSION['error_message'] = __('Operazione riservata agli amministratori'); + return $response->withHeader('Location', url('/admin/dashboard'))->withStatus(302); + } + + public function index(Request $request, Response $response): Response + { + if ($guard = $this->guard($response)) { + return $guard; + } + return $response->withHeader('Location', url(self::TAB_URL))->withStatus(302); + } + + public function editForm(Request $request, Response $response, mysqli $db, int $id): Response + { + if ($guard = $this->guard($response)) { + return $guard; + } + $policy = new CirculationPolicy($db, configuration: true); + $type = $policy->types()[$id] ?? null; + if ($type === null) { + $_SESSION['error_message'] = __('Tipologia lettore non trovata.'); + return $response->withHeader('Location', url(self::TAB_URL))->withStatus(302); + } + $globals = $policy->globalLayer(); + $title = __('Modifica tipologia lettore'); + ob_start(); + require __DIR__ . '/../Views/settings/circulation-type-edit.php'; + $content = ob_get_clean(); + ob_start(); + require __DIR__ . '/../Views/layout.php'; + $html = ob_get_clean(); + $response->getBody()->write((string) $html); + return $response; + } + + public function createType(Request $request, Response $response, mysqli $db): Response + { + if ($guard = $this->guard($response)) { + return $guard; + } + $data = (array) ($request->getParsedBody() ?? []); + $fields = $this->typeFields($data, null); + if (is_string($fields)) { + $_SESSION['error_message'] = $fields; + return $response->withHeader('Location', url(self::TAB_URL))->withStatus(302); + } + if (!(new CirculationPolicy($db, configuration: true))->schemaReady()) { + $_SESSION['error_message'] = __('Le tabelle della circolazione non sono ancora state create: completa l\'aggiornamento del database.'); + return $response->withHeader('Location', url(self::TAB_URL))->withStatus(302); + } + if ($this->codeTaken($db, (string) $fields['codice'], null)) { + $_SESSION['error_message'] = __('Esiste già una tipologia con questo codice.'); + return $response->withHeader('Location', url(self::TAB_URL))->withStatus(302); + } + $orderResult = $db->query('SELECT COALESCE(MAX(ordinamento), 0) + 10 FROM tipologie_lettore'); + $fields['ordinamento'] = $orderResult instanceof \mysqli_result ? (int) ($orderResult->fetch_row()[0] ?? 10) : 10; + $makeDefault = !empty($data['predefinita']); + $fields['predefinita'] = 0; + try { + $db->begin_transaction(); + $id = $this->insert($db, 'tipologie_lettore', $fields); + if ($makeDefault) { + $this->setDefaultTx($db, $id); + } + $db->commit(); + $_SESSION['success_message'] = __('Tipologia lettore creata.'); + } catch (\Throwable $e) { + $db->rollback(); + SecureLogger::error('[Circulation] patron type create failed', ['error' => $e->getMessage()]); + $_SESSION['error_message'] = __('Impossibile salvare la tipologia lettore.'); + } + return $response->withHeader('Location', url(self::TAB_URL))->withStatus(302); + } + + public function updateType(Request $request, Response $response, mysqli $db, int $id): Response + { + if ($guard = $this->guard($response)) { + return $guard; + } + $data = (array) ($request->getParsedBody() ?? []); + $policy = new CirculationPolicy($db, configuration: true); + $current = $policy->types()[$id] ?? null; + if ($current === null) { + $_SESSION['error_message'] = __('Tipologia lettore non trovata.'); + return $response->withHeader('Location', url(self::TAB_URL))->withStatus(302); + } + $fields = $this->typeFields($data, $current); + if (is_string($fields)) { + $_SESSION['error_message'] = $fields; + return $response->withHeader('Location', url('/admin/settings/circulation/types/' . $id))->withStatus(302); + } + if ($this->codeTaken($db, (string) $fields['codice'], $id)) { + $_SESSION['error_message'] = __('Esiste già una tipologia con questo codice.'); + return $response->withHeader('Location', url('/admin/settings/circulation/types/' . $id))->withStatus(302); + } + // The default type stays active: new patrons are assigned to it. + if ((int) $current['predefinita'] === 1) { + $fields['attiva'] = 1; + } + try { + $db->begin_transaction(); + $this->update($db, 'tipologie_lettore', $fields, $id); + if (!empty($data['predefinita'])) { + $this->setDefaultTx($db, $id); + } + $db->commit(); + $_SESSION['success_message'] = __('Tipologia lettore aggiornata.'); + } catch (\Throwable $e) { + $db->rollback(); + SecureLogger::error('[Circulation] patron type update failed', ['id' => $id, 'error' => $e->getMessage()]); + $_SESSION['error_message'] = __('Impossibile salvare la tipologia lettore.'); + } + return $response->withHeader('Location', url(self::TAB_URL))->withStatus(302); + } + + public function deleteType(Request $request, Response $response, mysqli $db, int $id): Response + { + if ($guard = $this->guard($response)) { + return $guard; + } + $policy = new CirculationPolicy($db, configuration: true); + $type = $policy->types()[$id] ?? null; + if ($type === null) { + $_SESSION['error_message'] = __('Tipologia lettore non trovata.'); + } elseif ((int) $type['predefinita'] === 1) { + $_SESSION['error_message'] = __('La tipologia predefinita non può essere eliminata: scegli prima un\'altra predefinita.'); + } else { + try { + $db->begin_transaction(); + // Patrons of the removed type fall back to the default one + // (the FK is ON DELETE SET NULL; done explicitly for clarity). + $stmt = $db->prepare('UPDATE utenti SET tipologia_id = NULL WHERE tipologia_id = ?'); + $stmt->bind_param('i', $id); + $stmt->execute(); + $stmt->close(); + $stmt = $db->prepare('DELETE FROM regole_prestito WHERE tipologia_id = ?'); + $stmt->bind_param('i', $id); + $stmt->execute(); + $stmt->close(); + $stmt = $db->prepare('DELETE FROM tipologie_lettore WHERE id = ? AND predefinita = 0'); + $stmt->bind_param('i', $id); + $stmt->execute(); + $stmt->close(); + $db->commit(); + $_SESSION['success_message'] = __('Tipologia lettore eliminata.'); + } catch (\Throwable $e) { + $db->rollback(); + SecureLogger::error('[Circulation] patron type delete failed', ['id' => $id, 'error' => $e->getMessage()]); + $_SESSION['error_message'] = __('Impossibile eliminare la tipologia lettore.'); + } + } + return $response->withHeader('Location', url(self::TAB_URL))->withStatus(302); + } + + public function setDefault(Request $request, Response $response, mysqli $db, int $id): Response + { + if ($guard = $this->guard($response)) { + return $guard; + } + if (!isset((new CirculationPolicy($db, configuration: true))->types()[$id])) { + $_SESSION['error_message'] = __('Tipologia lettore non trovata.'); + return $response->withHeader('Location', url(self::TAB_URL))->withStatus(302); + } + try { + $db->begin_transaction(); + $this->setDefaultTx($db, $id); + $db->commit(); + $_SESSION['success_message'] = __('Tipologia predefinita aggiornata.'); + } catch (\Throwable $e) { + $db->rollback(); + SecureLogger::error('[Circulation] default patron type failed', ['id' => $id, 'error' => $e->getMessage()]); + $_SESSION['error_message'] = __('Impossibile salvare la tipologia lettore.'); + } + return $response->withHeader('Location', url(self::TAB_URL))->withStatus(302); + } + + /** Move a type one place up or down in the display order. */ + public function moveType(Request $request, Response $response, mysqli $db, int $id): Response + { + if ($guard = $this->guard($response)) { + return $guard; + } + $data = (array) ($request->getParsedBody() ?? []); + $direction = ($data['direction'] ?? '') === 'up' ? -1 : 1; + $ids = array_keys((new CirculationPolicy($db, configuration: true))->types()); + $pos = array_search($id, $ids, true); + if ($pos !== false && isset($ids[$pos + $direction])) { + $swap = $ids[$pos + $direction]; + $ids[$pos + $direction] = $id; + $ids[$pos] = $swap; + try { + $db->begin_transaction(); + $stmt = $db->prepare('UPDATE tipologie_lettore SET ordinamento = ? WHERE id = ?'); + foreach ($ids as $i => $typeId) { + $order = ($i + 1) * 10; + $stmt->bind_param('ii', $order, $typeId); + $stmt->execute(); + } + $stmt->close(); + $db->commit(); + } catch (\Throwable $e) { + $db->rollback(); + SecureLogger::error('[Circulation] patron type reorder failed', ['id' => $id, 'error' => $e->getMessage()]); + $_SESSION['error_message'] = __('Impossibile salvare la tipologia lettore.'); + } + } + return $response->withHeader('Location', url(self::TAB_URL))->withStatus(302); + } + + /** Save the whole matrix: an all-empty cell removes its rule. */ + public function saveRules(Request $request, Response $response, mysqli $db): Response + { + if ($guard = $this->guard($response)) { + return $guard; + } + $data = (array) ($request->getParsedBody() ?? []); + $policy = new CirculationPolicy($db, configuration: true); + if (!$policy->schemaReady()) { + $_SESSION['error_message'] = __('Le tabelle della circolazione non sono ancora state create: completa l\'aggiornamento del database.'); + return $response->withHeader('Location', url(self::TAB_URL))->withStatus(302); + } + $cells = is_array($data['rules'] ?? null) ? $data['rules'] : []; + $validTypes = array_merge([CirculationPolicy::ALL_TYPES], array_keys($policy->types())); + $media = array_keys(self::mediaTypes()); + // A form rendered before the fine columns were in the matrix (an open + // tab during the upgrade) does not send them: an absent field keeps + // the stored value, only a field sent empty clears it. + $existing = $policy->rules(); + $rows = []; + foreach ($validTypes as $typeId) { + foreach ($media as $m) { + $cell = $cells[$typeId][$m] ?? []; + $cell = is_array($cell) ? $cell : []; + $stored = $existing[$typeId . '|' . $m] ?? []; + $row = ['prestabile' => null]; + $loanable = (string) ($cell['loanable'] ?? ''); + if ($loanable === '1' || $loanable === '0') { + $row['prestabile'] = (int) $loanable; + } + foreach (self::RULE_FIELDS as $key => [$column, $min, $max]) { + if (in_array($key, self::RULE_FIELDS_KEPT_WHEN_ABSENT, true) && !array_key_exists($key, $cell)) { + $row[$column] = isset($stored[$column]) ? (int) $stored[$column] : null; + continue; + } + $value = $this->intOrNull($cell[$key] ?? null, $min, $max); + if ($value === false) { + $_SESSION['error_message'] = sprintf(__('Valore non valido nella matrice (%s, %s).'), $typeId === 0 ? __('Tutte le tipologie') : (string) ($policy->types()[$typeId]['nome'] ?? $typeId), (string) (self::mediaTypes()[$m] ?? $m)); + return $response->withHeader('Location', url(self::TAB_URL))->withStatus(302); + } + $row[$column] = $value; + } + foreach (self::RULE_MONEY_FIELDS as $key => $column) { + if (!array_key_exists($key, $cell)) { + $row[$column] = isset($stored[$column]) ? (string) $stored[$column] : null; + continue; + } + $value = self::moneyOrNull($cell[$key] ?? null); + if ($value === false) { + $_SESSION['error_message'] = sprintf(__('Valore non valido nella matrice (%s, %s).'), $typeId === 0 ? __('Tutte le tipologie') : (string) ($policy->types()[$typeId]['nome'] ?? $typeId), (string) (self::mediaTypes()[$m] ?? $m)); + return $response->withHeader('Location', url(self::TAB_URL))->withStatus(302); + } + $row[$column] = $value; + } + $rows[$typeId . '|' . $m] = ['tipologia_id' => $typeId, 'tipo_media' => $m] + $row; + } + } + try { + $db->begin_transaction(); + $delete = $db->prepare('DELETE FROM regole_prestito WHERE tipologia_id = ? AND tipo_media = ?'); + $upsert = $db->prepare( + 'INSERT INTO regole_prestito (tipologia_id, tipo_media, prestabile, max_prestiti, durata_prestito_giorni, max_rinnovi, + giorni_rinnovo, giorni_tolleranza, multa_giornaliera, multa_massima_prestito) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ON DUPLICATE KEY UPDATE prestabile = VALUES(prestabile), max_prestiti = VALUES(max_prestiti), + durata_prestito_giorni = VALUES(durata_prestito_giorni), max_rinnovi = VALUES(max_rinnovi), + giorni_rinnovo = VALUES(giorni_rinnovo), giorni_tolleranza = VALUES(giorni_tolleranza), + multa_giornaliera = VALUES(multa_giornaliera), multa_massima_prestito = VALUES(multa_massima_prestito)' + ); + foreach ($rows as $row) { + $typeId = (int) $row['tipologia_id']; + $m = (string) $row['tipo_media']; + $empty = $row['prestabile'] === null && $row['max_prestiti'] === null + && $row['durata_prestito_giorni'] === null && $row['max_rinnovi'] === null + && $row['giorni_rinnovo'] === null && $row['giorni_tolleranza'] === null + && $row['multa_giornaliera'] === null && $row['multa_massima_prestito'] === null; + // Every rule column is edited in the matrix: an all-empty cell removes its rule. + if ($empty) { + $delete->bind_param('is', $typeId, $m); + $delete->execute(); + continue; + } + $loanable = $row['prestabile']; + $max = $row['max_prestiti']; + $duration = $row['durata_prestito_giorni']; + $renewals = $row['max_rinnovi']; + $renewalDays = $row['giorni_rinnovo']; + $grace = $row['giorni_tolleranza']; + $fine = $row['multa_giornaliera']; + $fineMax = $row['multa_massima_prestito']; + $upsert->bind_param('isiiiiiiss', $typeId, $m, $loanable, $max, $duration, $renewals, $renewalDays, $grace, $fine, $fineMax); + $upsert->execute(); + } + $delete->close(); + $upsert->close(); + $db->commit(); + $_SESSION['success_message'] = __('Regole di circolazione salvate.'); + } catch (\Throwable $e) { + $db->rollback(); + SecureLogger::error('[Circulation] rules save failed', ['error' => $e->getMessage()]); + $_SESSION['error_message'] = __('Impossibile salvare le regole di circolazione.'); + } + return $response->withHeader('Location', url(self::TAB_URL . '#circulation-matrix'))->withStatus(302); + } + + // ─── Helpers ──────────────────────────────────────────────────────────── + + /** + * Validated column values of a patron type form, or the error message. + * + * @param array $data + * @param array|null $current + * @return array|string + */ + private function typeFields(array $data, ?array $current): array|string + { + $name = trim(strip_tags((string) ($data['nome'] ?? ''))); + if ($name === '' || mb_strlen($name) > 100) { + return __('Il nome della tipologia è obbligatorio (massimo 100 caratteri).'); + } + $code = strtolower(trim((string) ($data['codice'] ?? ''))); + if ($code === '') { + $code = $current !== null ? (string) $current['codice'] : self::slug($name); + } + if (preg_match('/^[a-z0-9][a-z0-9_-]{0,39}$/', $code) !== 1) { + return __('Il codice può contenere solo lettere minuscole, numeri, trattini e trattini bassi (massimo 40 caratteri).'); + } + $description = trim(strip_tags((string) ($data['descrizione'] ?? ''))); + $fields = [ + 'codice' => $code, + 'nome' => $name, + 'descrizione' => $description !== '' ? mb_substr($description, 0, 2000) : null, + 'prenotazioni_abilitate' => !empty($data['prenotazioni_abilitate']) ? 1 : 0, + 'attiva' => !array_key_exists('attiva', $data) || !empty($data['attiva']) ? 1 : 0, + ]; + foreach (self::TYPE_INT_FIELDS as $column => [$min, $max]) { + if ($current !== null && in_array($column, self::TYPE_FIELDS_KEPT_WHEN_ABSENT, true) && !array_key_exists($column, $data)) { + $fields[$column] = $current[$column] ?? null; + continue; + } + $value = $this->intOrNull($data[$column] ?? null, $min, $max); + if ($value === false) { + return sprintf(__('Valore non valido per il campo "%s".'), self::fieldLabel($column)); + } + $fields[$column] = $value; + } + foreach (self::TYPE_MONEY_FIELDS as $column) { + if ($current !== null && in_array($column, self::TYPE_FIELDS_KEPT_WHEN_ABSENT, true) && !array_key_exists($column, $data)) { + $fields[$column] = $current[$column] ?? null; + continue; + } + $value = self::moneyOrNull($data[$column] ?? null); + if ($value === false) { + $raw = $data[$column] ?? null; + return is_scalar($raw) && \App\Services\FineParameters::exceedsSettingMax((string) $raw) + ? sprintf(__('Importo non valido per "%1$s": il massimo consentito è %2$s.'), self::fieldLabel($column), \App\Services\FineParameters::format(\App\Services\FineParameters::MAX_SETTING_CENTS)) + : sprintf(__('Valore non valido per il campo "%s".'), self::fieldLabel($column)); + } + $fields[$column] = $value; + } + return $fields; + } + + /** + * An amount typed by the operator ("2", "2,50") as a DECIMAL string. + * + * @return string|null|false null for an empty value, false when malformed, negative or above FineParameters::MAX_SETTING_CENTS + */ + private static function moneyOrNull(mixed $raw): string|null|false + { + if ($raw === null || (is_string($raw) && trim($raw) === '')) { + return null; + } + if (!is_scalar($raw)) { + return false; + } + $cents = \App\Services\FineParameters::toCents((string) $raw); + return $cents === null || $cents > \App\Services\FineParameters::MAX_SETTING_CENTS + ? false + : \App\Services\FineParameters::centsToDecimal($cents); + } + + public static function fieldLabel(string $column): string + { + return match ($column) { + 'max_prestiti' => __('Prestiti attivi massimi'), + 'durata_prestito_giorni' => __('Durata prestito (giorni)'), + 'max_durata_richiesta_giorni' => __('Durata massima richiedibile (giorni)'), + 'max_rinnovi' => __('Rinnovi massimi'), + 'giorni_rinnovo' => __('Giorni per rinnovo'), + 'max_prenotazioni' => __('Prenotazioni massime'), + 'durata_tessera_giorni' => __('Durata tessera (giorni)'), + 'multa_giornaliera' => __('Multa giornaliera'), + 'giorni_tolleranza' => __('Giorni di tolleranza'), + 'multa_massima_prestito' => __('Multa massima per prestito'), + 'soglia_blocco_debito' => __('Soglia di blocco per debito'), + default => $column, + }; + } + + /** @return int|null|false null for an empty value, false when out of range or not an integer */ + private function intOrNull(mixed $raw, int $min, int $max): int|null|false + { + if ($raw === null || (is_string($raw) && trim($raw) === '')) { + return null; + } + if (!is_scalar($raw) || preg_match('/^-?\d+$/', trim((string) $raw)) !== 1) { + return false; + } + $value = (int) trim((string) $raw); + return $value < $min || $value > $max ? false : $value; + } + + private static function slug(string $name): string + { + $ascii = function_exists('iconv') ? (string) @iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $name) : $name; + $slug = trim((string) preg_replace('/[^a-z0-9]+/', '-', strtolower($ascii)), '-'); + return $slug !== '' ? substr($slug, 0, 40) : 'tipologia-' . bin2hex(random_bytes(3)); + } + + private function codeTaken(mysqli $db, string $code, ?int $exceptId): bool + { + $stmt = $db->prepare('SELECT id FROM tipologie_lettore WHERE codice = ? AND id <> ?'); + $except = $exceptId ?? 0; + $stmt->bind_param('si', $code, $except); + $stmt->execute(); + $taken = $stmt->get_result()->fetch_row() !== null; + $stmt->close(); + return $taken; + } + + /** Exactly one default type: clear the flag everywhere, then set it (inside the caller's transaction). */ + private function setDefaultTx(mysqli $db, int $id): void + { + $db->query('UPDATE tipologie_lettore SET predefinita = 0 WHERE predefinita = 1'); + $stmt = $db->prepare('UPDATE tipologie_lettore SET predefinita = 1, attiva = 1 WHERE id = ?'); + $stmt->bind_param('i', $id); + $stmt->execute(); + $stmt->close(); + } + + /** @param array $fields */ + private function insert(mysqli $db, string $table, array $fields): int + { + $columns = array_keys($fields); + $sql = 'INSERT INTO ' . $table . ' (' . implode(', ', $columns) . ') VALUES (' . implode(', ', array_fill(0, count($columns), '?')) . ')'; + $stmt = $db->prepare($sql); + $values = array_values($fields); + $stmt->bind_param(str_repeat('s', count($values)), ...$values); + $stmt->execute(); + $id = (int) $db->insert_id; + $stmt->close(); + return $id; + } + + /** @param array $fields */ + private function update(mysqli $db, string $table, array $fields, int $id): void + { + $set = implode(', ', array_map(static fn (string $c): string => $c . ' = ?', array_keys($fields))); + $stmt = $db->prepare('UPDATE ' . $table . ' SET ' . $set . ' WHERE id = ?'); + $values = array_values($fields); + $values[] = $id; + $stmt->bind_param(str_repeat('s', count($values) - 1) . 'i', ...$values); + $stmt->execute(); + $stmt->close(); + } +} diff --git a/app/Controllers/CollaneController.php b/app/Controllers/CollaneController.php index d78478fad..d0a86a9d0 100644 --- a/app/Controllers/CollaneController.php +++ b/app/Controllers/CollaneController.php @@ -483,9 +483,17 @@ public function searchApi(Request $request, Response $response, mysqli $db): Res // SEC2-1 + SEC1-5 (review): require min 2 chars and escape LIKE // wildcards so callers can't broaden the match with `%`/`_` or // probe single-char prefixes for enumeration. - if (mb_strlen($q) >= 2) { - $escaped = str_replace(['\\', '%', '_'], ['\\\\', '\\%', '\\_'], $q); - $search = '%' . $escaped . '%'; + // Each word keeps its own rule (`Mont "Häffner"`): with a quoted word + // filterCondition() builds one condition per word, so the minimum + // applies to every word (`"a" b` must not probe single characters). + // Unquoted input is still one phrase pattern (`%I Meridiani%`), so it + // is measured as a whole and a one-letter article does not block it. + $tokens = \App\Support\ExactSearch::tokenize($q); + $words = in_array(true, array_column($tokens, 'exact'), true) + ? array_column($tokens, 'term') + : [\App\Support\ExactSearch::parseFilter($q)['term']]; + $longEnough = $words !== [] && min(array_map('mb_strlen', $words)) >= 2; + if ($longEnough) { $hasCollane = $this->hasCollaneTable($db); $sqlParts = []; @@ -497,9 +505,13 @@ public function searchApi(Request $request, Response $response, mysqli $db): Res continue; // no collane table — skip its sources } $col = $src['c']; - $where = "`{$col}` LIKE ? AND `{$col}` IS NOT NULL AND `{$col}` != ''"; - $types .= 's'; - $binds[] = $search; + $condition = \App\Support\ExactSearch::filterCondition(["`{$col}`"], $q); + if ($condition === null) { + continue; + } + $where = $condition['sql'] . " AND `{$col}` IS NOT NULL AND `{$col}` != ''"; + $types .= $condition['types']; + array_push($binds, ...$condition['params']); if ($src['t'] === 'libri') { $where .= ' AND deleted_at IS NULL'; } @@ -515,8 +527,15 @@ public function searchApi(Request $request, Response $response, mysqli $db): Res } if (!empty($sqlParts)) { - $sql = 'SELECT DISTINCT nome FROM (' . implode(' UNION ', $sqlParts) - . ') AS combined ORDER BY nome LIMIT 10'; + // One suggestion per spelling: accents keep "Brücke" and + // "Brucke" apart (#501), case does not ("Serie" = "serie", as + // UNION used to merge them). MIN(nome) keeps the column's + // tolerant collation, so the list sorts like the rest of the UI; + // that collation ties "Haffner" with "Häffner", so the binary + // spelling breaks the tie and the order is the same on every + // server. + $sql = 'SELECT MIN(nome) AS nome FROM (' . implode(' UNION ALL ', $sqlParts) + . ') AS combined GROUP BY LOWER(nome) COLLATE utf8mb4_bin ORDER BY nome, MIN(nome) COLLATE utf8mb4_bin LIMIT 10'; $stmt = $db->prepare($sql); if ($stmt) { $stmt->bind_param($types, ...$binds); diff --git a/app/Controllers/CopyController.php b/app/Controllers/CopyController.php index 7f5b655af..468bf7dd1 100644 --- a/app/Controllers/CopyController.php +++ b/app/Controllers/CopyController.php @@ -345,6 +345,107 @@ public function updateCopy(Request $request, Response $response, mysqli $db, int return $response->withHeader('Location', url($this->adminBookPath($libroId)))->withStatus(302); } + /** + * Mark a copy as kept for in-house consultation (never lent) or make it + * lendable again (copie.solo_consultazione). Refused while the copy is + * held by a loan, a pickup or a copy-bound request: those commitments + * were promised on this copy. Making it lendable promotes the queue. + */ + public function toggleConsultation(Request $request, Response $response, mysqli $db, int $copyId): Response + { + $data = (array) $request->getParsedBody(); + $consultation = ($data['solo_consultazione'] ?? '') === '1' ? 1 : 0; + $policy = new \App\Services\CirculationPolicy($db); + if (!$policy->schemaReady()) { + $_SESSION['error_message'] = __('Le tabelle della circolazione non sono ancora state create: completa l\'aggiornamento del database.'); + return $response->withHeader('Location', $this->safeReferer())->withStatus(302); + } + $stmt = $db->prepare('SELECT libro_id, numero_inventario, solo_consultazione FROM copie WHERE id = ?'); + $stmt->bind_param('i', $copyId); + $stmt->execute(); + $copy = $stmt->get_result()->fetch_assoc(); + $stmt->close(); + if (!$copy) { + $_SESSION['error_message'] = __('Copia non trovata.'); + return $response->withHeader('Location', $this->safeReferer())->withStatus(302); + } + $libroId = (int) $copy['libro_id']; + if ((int) $copy['solo_consultazione'] === $consultation) { + return $response->withHeader('Location', url($this->adminBookPath($libroId)))->withStatus(302); + } + + $db->begin_transaction(); + try { + // Canonical lock order: the book row first, then the copy. + $lockBook = $db->prepare('SELECT id FROM libri WHERE id = ? AND deleted_at IS NULL FOR UPDATE'); + $lockBook->bind_param('i', $libroId); + $lockBook->execute(); + $bookLocked = (bool) $lockBook->get_result()->fetch_row(); + $lockBook->close(); + if (!$bookLocked) { + $db->rollback(); + $_SESSION['error_message'] = __('Libro non trovato o non più disponibile.'); + return $response->withHeader('Location', $this->safeReferer())->withStatus(302); + } + $lockCopy = $db->prepare('SELECT id FROM copie WHERE id = ? FOR UPDATE'); + $lockCopy->bind_param('i', $copyId); + $lockCopy->execute(); + $lockCopy->close(); + if ($consultation === 1 && $this->isCopyHeld($db, $copyId)) { + $db->rollback(); + $_SESSION['error_message'] = __('La copia è impegnata da un prestito, un ritiro o una richiesta: chiudili prima di riservarla alla consultazione.'); + return $response->withHeader('Location', url($this->adminBookPath($libroId)))->withStatus(302); + } + $update = $db->prepare('UPDATE copie SET solo_consultazione = ? WHERE id = ?'); + $update->bind_param('ii', $consultation, $copyId); + $update->execute(); + $update->close(); + + $reservationManager = null; + if ($consultation === 0) { + // One more lendable copy: promote every reservation it can serve. + $reservationManager = new \App\Controllers\ReservationManager($db); + $reservationManager->setExternalTransaction(true); + for ($guard = 0; $guard < 1000 && $reservationManager->processBookAvailability($libroId); $guard++) { + // Promote every date-eligible reservation allowed by capacity. + } + } + $integrity = new \App\Support\DataIntegrity($db); + if (!$integrity->recalculateBookAvailability($libroId, insideTransaction: true)) { + throw new \RuntimeException('Impossibile ricalcolare la disponibilità del libro.'); + } + $db->commit(); + if ($reservationManager !== null) { + try { + $reservationManager->flushDeferredNotifications(); + } catch (\Throwable $e) { + SecureLogger::warning(__('Invio notifica cambio stato copia fallito'), ['error' => $e->getMessage()]); + } + } + } catch (\Throwable $e) { + $db->rollback(); + SecureLogger::error('[Copies] consultation flag update failed', ['copia_id' => $copyId, 'error' => $e->getMessage()]); + $_SESSION['error_message'] = __('Impossibile aggiornare la copia senza lasciare dati incoerenti. Nessuna modifica è stata salvata.'); + return $response->withHeader('Location', url($this->adminBookPath($libroId)))->withStatus(302); + } + + \App\Support\ActivityLog::recordBookEvent( + $db, + $libroId, + 'aggiornamento', + 'copy', + 'copy.updated', + ['numero_inventario' => $copy['numero_inventario'] ?? null, 'solo_consultazione' => (int) $copy['solo_consultazione']], + ['numero_inventario' => $copy['numero_inventario'] ?? null, 'solo_consultazione' => $consultation], + entityId: $copyId, + source: 'manual' + ); + $_SESSION['success_message'] = $consultation === 1 + ? __('La copia è ora solo in consultazione.') + : __('La copia è di nuovo prestabile.'); + return $response->withHeader('Location', url($this->adminBookPath($libroId)))->withStatus(302); + } + /** * Crea una o più copie fisiche per un libro, direttamente dalla scheda. * diff --git a/app/Controllers/CsvImportController.php b/app/Controllers/CsvImportController.php index a62f45fdf..1fbfe239c 100644 --- a/app/Controllers/CsvImportController.php +++ b/app/Controllers/CsvImportController.php @@ -30,6 +30,12 @@ class CsvImportController * anno → anno_pubblicazione; pagine → numero_pagine; * dewey → classificazione_dewey. * + * The media type can also be selected apart from the format: a caller + * that passes the optional `tipo_media` family to importParsedRow() + * decides it on its own (the MARC import, whose records carry a media + * type but never a format or a price). The CSV upload never sends it, + * so a CSV row keeps writing both columns through `formato`. + * * @var list */ private const UPDATE_FIELD_FAMILIES = [ @@ -131,31 +137,15 @@ private function safeUnlinkImportTmp(string $path): void * Whether the Emeroteca (periodicals) plugin is active, so the CSV * import page can link to its article importer only when that route * actually exists — otherwise it points to Plugins instead (#article - * link 404 fix). showImportPage() isn't routed with a mysqli connection - * (see web.php), so this opens its own short-lived one — same fallback - * pattern as ReservationsController's constructor — and queries the - * plugins table directly, mirroring frontend/layout.php's - * $publicPluginIsActive() fallback (no DI container available here). + * link 404 fix). Queries the plugins table directly, mirroring + * frontend/layout.php's $publicPluginIsActive() fallback (no DI + * container available here). */ - private function isEmerotecaPluginActive(): bool + private function isEmerotecaPluginActive(\mysqli $db): bool { try { - $settings = require __DIR__ . '/../../config/settings.php'; - $cfg = $settings['db']; - $db = new \mysqli( - $cfg['hostname'], - $cfg['username'], - $cfg['password'], - $cfg['database'], - $cfg['port'], - $cfg['socket'] ?? null - ); - if ($db->connect_error) { - return false; - } $stmt = $db->prepare('SELECT is_active FROM plugins WHERE name = ? LIMIT 1'); if ($stmt === false) { - $db->close(); return false; } $pluginName = 'emeroteca'; @@ -166,7 +156,6 @@ private function isEmerotecaPluginActive(): bool $active = $result instanceof \mysqli_result && (int) ($result->fetch_assoc()['is_active'] ?? 0) === 1; } $stmt->close(); - $db->close(); return $active; } catch (\Throwable $e) { return false; @@ -176,11 +165,13 @@ private function isEmerotecaPluginActive(): bool /** * Mostra la pagina di import CSV */ - public function showImportPage(Request $request, Response $response): Response + public function showImportPage(Request $request, Response $response, \mysqli $db): Response { ob_start(); $title = "Import Libri da CSV"; - $emerotecaAvailable = $this->isEmerotecaPluginActive(); + $emerotecaAvailable = $this->isEmerotecaPluginActive($db); + // Suppliers module off: no supplier select (and assignSupplier() writes nothing). + $fornitori = \App\Support\Features::enabled('suppliers') ? (new \App\Models\SupplierRepository($db))->options() : []; include __DIR__ . '/../Views/admin/csv_import.php'; $content = ob_get_clean(); @@ -319,6 +310,9 @@ public function processImport(Request $request, Response $response, \mysqli $db) 'total_rows' => $totalRows, 'enable_scraping' => !empty($data['enable_scraping']), 'update_fields' => $updateFields, + // The supplier chosen for the batch, validated once; written on + // the books this import creates (CataloguingExtras::assignSupplier). + 'fornitore_id' => \App\Support\CataloguingExtras::existingSupplierId($db, $data['fornitore_id'] ?? null), 'imported' => 0, 'updated' => 0, 'scraped' => 0, @@ -449,182 +443,21 @@ public function processChunk(Request $request, Response $response, \mysqli $db): } $parsedData = $this->parseCsvRow($row); - $db->begin_transaction(); - - // Resolve the target before creating any related entity. On an - // existing book an unchecked publisher/genre family must be a - // true no-op: creating an unlinked editor/genre from the CSV is - // still a catalogue mutation and leaves orphan rows behind. - $existingBookId = $this->findExistingBook($db, $parsedData); - $isExistingBook = $existingBookId !== null; - - // A title is mandatory for new books and when the title family - // is selected. For a row matched by ID/ISBN/EAN with Titolo - // unchecked, however, a blank cell must be a true no-op: the - // existing NOT NULL title is restored by updateBook(). - if (empty($parsedData['titolo']) && (!$isExistingBook || $updateFields['titolo'])) { - throw new \Exception(__('Titolo mancante')); - } - - // Resolve publisher(s). The exporter joins ALL publishers - // (primary + co-publishers from libri_editori, #143) into the - // single `editore` cell with ';'. Treating the whole cell as ONE - // publisher name created a bogus "A;B" editore on round-trip - // (#12): split it, first = primary, the rest = co-publishers. - $editorId = null; - $coPublisherIds = []; - $publisherCellHasMultiple = false; - if ((!$isExistingBook || $updateFields['publisher']) && !empty($parsedData['editore'])) { - $publisherNames = array_values(array_filter( - array_map('trim', explode(';', (string) $parsedData['editore'])), - static fn (string $n): bool => $n !== '' - )); - $publisherCellHasMultiple = count($publisherNames) > 1; - foreach ($publisherNames as $pIndex => $pName) { - $publisherResult = $this->getOrCreatePublisher($db, $pName); - if ($publisherResult['created']) { - $importData['publishers_created']++; - } - if ($pIndex === 0) { - $editorId = $publisherResult['id']; - } elseif ((int) $publisherResult['id'] !== (int) $editorId) { - $coPublisherIds[] = (int) $publisherResult['id']; - } - } - } - - // Resolve genre, creating it when the CSV names a genre absent - // from the catalogue (#16). Previously getGenreId() only looked up - // existing rows and returned NULL for anything new — so a genre - // present in the export vanished on re-import — unlike the - // authors/publishers auto-create paths. - $genreId = (!$isExistingBook || $updateFields['genre']) - ? $this->getOrCreateGenre($db, $parsedData['genere'] ?? '', $importData) - : null; - - // Upsert book - $upsertResult = $this->upsertBook( - $db, - $parsedData, - $editorId, - $genreId, - $updateFields, - $existingBookId - ); + $upsertResult = $this->importParsedRow($db, $parsedData, $updateFields, $importData); $bookId = $upsertResult['id']; $action = $upsertResult['action']; - // #380: on an existing book each unchecked family is fully - // preserved. Created books always import everything. - $applyFamily = static function (string $family) use ($action, $updateFields): bool { - return $action === 'created' || $updateFields[$family]; - }; - - // Import the co-publishers (#12/#143) ONLY when the cell actually - // carried more than one publisher, so a single-publisher CSV never - // wipes co-publishers a librarian curated in the admin form. - if ($publisherCellHasMultiple && $applyFamily('publisher')) { - $this->syncImportedCoPublishers($db, $bookId, $editorId, $coPublisherIds); - } - - // Remove old PRINCIPAL author links if updating — scoped to - // ruolo='principale' so a re-import re-writes the authors the CSV - // owns without wiping co-author/illustrator/translator/curator/ - // colorist ENTITY links (#237): every explicit contributor - // column is synchronized below with importer provenance. A - // blanket delete-all silently lost every contributor entity on - // each re-import. - // Only rewrite the principal links when the row actually carries - // an author. An EMPTY author cell must NOT wipe the existing - // principal authors — a blanket delete-when-updated left the book - // permanently authorless whenever a re-imported row omitted the - // author (#22). - // #380: with the Autori checkbox off, an existing book's - // principal links are fully preserved (no delete, no rewrite). - if ($action === 'updated' && $applyFamily('authors') && !empty($parsedData['autori'])) { - $stmt = $db->prepare("DELETE FROM libri_autori WHERE libro_id = ? AND ruolo = 'principale'"); - $stmt->bind_param('i', $bookId); - $stmt->execute(); - $stmt->close(); - } - - // Handle authors - if (!empty($parsedData['autori']) && $applyFamily('authors')) { - $separator = strpos($parsedData['autori'], ';') !== false ? ';' : '|'; - $authors = array_map('trim', explode($separator, $parsedData['autori'])); - $authorOrder = 1; - - foreach ($authors as $authorName) { - if (empty($authorName)) continue; - - $authorResult = $this->getOrCreateAuthor($db, $authorName); - $authorId = $authorResult['id']; - if ($authorResult['created']) { - $importData['authors_created']++; - } - - \App\Support\ContributorSync::persistImportedPrincipal( - $db, - $bookId, - $authorId, - $authorOrder - ); - $authorOrder++; - } - } - - // Contributor roles are entities too (#237). Replace only - // links owned by the CSV importer and only for columns that - // were actually present. This distinction matters for old - // exports: an absent column must preserve existing data, while - // an explicit empty cell clears this importer's stale links. - $contributorValues = []; - foreach ([ - 'co-autore' => 'co_autori', - 'traduttore' => 'traduttore', - 'illustratore' => 'illustratore', - 'curatore' => 'curatore', - 'colorista' => 'colorista', - ] as $role => $field) { - if (($parsedData[$field . '_provided'] ?? false) === true) { - $contributorValues[$role] = $parsedData[$field] ?? null; - } - } - // #380: with the Contributori checkbox off, an existing book's - // contributor links are fully preserved (no sync at all). - if ($contributorValues !== [] && $applyFamily('contributors')) { - $importData['authors_created'] += \App\Support\ContributorSync::syncImportedLegacyValues( - $db, - $bookId, - $contributorValues, - 'csv' - ); - } - - $db->commit(); - \App\Support\ContentCache::deferBooksChanged(); - - if ($action === 'created') { - $importData['imported']++; - } else { - $importData['updated']++; - } - - // Plugin hook: after book save. Fired OUTSIDE the per-row - // transaction (it just committed) because listeners such as the - // book-club external-proposal reconciler open their own - // transaction — nesting begin_transaction() would implicitly - // commit. Mirrors LibriController's create/edit dispatch so an - // imported book that matches an outstanding club proposal is - // reconciled at import time, not only on the next maintenance run. - // Isolated in its own try/catch: the row is already committed and - // counted, so a throwing listener must NOT reach the per-row catch - // below (which would rollback — a no-op here — and additionally - // record the committed row as a failed import, double-counting it). - try { - \App\Support\Hooks::do('book.save.after', [$bookId, $parsedData]); - } catch (\Throwable $hookError) { - \App\Support\SecureLogger::warning('[CsvImport] book.save.after listener failed (row already committed)', ['book_id' => $bookId, 'error' => $hookError->getMessage()]); + // The batch supplier was written in the row's transaction + // (importParsedRow); a write error failed the whole row. A + // supplier deleted during the import leaves the book + // imported but without it: reported, not a failed row. + if ($upsertResult['supplier_missing']) { + $importData['errors'][] = [ + 'line' => $lineNumber, + 'title' => (string) ($parsedData['titolo'] ?? ''), + 'message' => __('Fornitore non assegnato: è stato eliminato durante l\'importazione.'), + 'type' => 'supplier', + ]; } // Scraping (if enabled and ISBN exists) @@ -725,7 +558,8 @@ public function processChunk(Request $request, Response $response, \mysqli $db): $failedCount = 0; foreach ($importData['errors'] as $err) { $type = is_array($err) ? ($err['type'] ?? 'validation') : 'validation'; - if ($type !== 'scraping') { + // Scraping and supplier notes concern books that were imported. + if (!in_array($type, ['scraping', 'supplier'], true)) { $failedCount++; } } @@ -848,6 +682,263 @@ public function processChunk(Request $request, Response $response, \mysqli $db): return $response->withHeader('Content-Type', 'application/json'); } + /** + * Provenance of the books this importer writes: 'csv' for the CSV import, + * 'marc' for the MARC bulk import and copy cataloguing, which reuse the + * row pipeline below. Recorded in the activity log and on the + * contributor links the importer owns. + */ + private string $importSource = 'csv'; + + public function setImportSource(string $source): void + { + $this->importSource = $source; + } + + /** + * The row pipeline of the CSV import, shared with the MARC bulk import and + * copy cataloguing: one book written in one transaction — publishers, + * genre, the book itself (insert, or update of the families selected in + * $updateFields), co-publishers, principal authors and contributor links + * — then the book.save.after hook. The counters in $importData (imported, + * updated, authors_created, publishers_created, genres_created) are + * advanced. On failure the transaction is rolled back and the exception + * rethrown, so the caller records the row as failed and goes on. + * + * @param array $parsedData a row as parseCsvRow() returns it + * @param array $updateFields one entry per UPDATE_FIELD_FAMILIES family, + * plus the optional `tipo_media` family (defaults to the `formato` one) + * @param array $importData + * @param int|null $existingBookId the book to update when $resolveExisting is false + * @param bool $resolveExisting true: match by ID/ISBN-13/EAN/title+author as the CSV import does + * @return array{id: int, action: string, supplier_missing: bool} supplier_missing: + * the batch supplier ($importData['fornitore_id']) was deleted before + * this created book could be linked to it + */ + public function importParsedRow( + \mysqli $db, + array $parsedData, + array $updateFields, + array &$importData, + ?int $existingBookId = null, + bool $resolveExisting = true + ): array { + foreach (self::UPDATE_FIELD_FAMILIES as $family) { + $updateFields[$family] = (bool) ($updateFields[$family] ?? true); + } + foreach (['imported', 'updated', 'authors_created', 'publishers_created'] as $counter) { + $importData[$counter] = (int) ($importData[$counter] ?? 0); + } + $supplierMissing = false; + $db->begin_transaction(); + try { + // Resolve the target before creating any related entity. On an + // existing book an unchecked publisher/genre family must be a + // true no-op: creating an unlinked editor/genre from the CSV is + // still a catalogue mutation and leaves orphan rows behind. + if ($resolveExisting) { + $existingBookId = $this->findExistingBook($db, $parsedData); + } + $isExistingBook = $existingBookId !== null; + + // A title is mandatory for new books and when the title family + // is selected. For a row matched by ID/ISBN/EAN with Titolo + // unchecked, however, a blank cell must be a true no-op: the + // existing NOT NULL title is restored by updateBook(). + if (empty($parsedData['titolo']) && (!$isExistingBook || $updateFields['titolo'])) { + throw new \Exception(__('Titolo mancante')); + } + + // Resolve publisher(s). The exporter joins ALL publishers + // (primary + co-publishers from libri_editori, #143) into the + // single `editore` cell with ';'. Treating the whole cell as ONE + // publisher name created a bogus "A;B" editore on round-trip + // (#12): split it, first = primary, the rest = co-publishers. + $editorId = null; + $coPublisherIds = []; + $publisherCellHasMultiple = false; + if ((!$isExistingBook || $updateFields['publisher']) && !empty($parsedData['editore'])) { + $publisherNames = array_values(array_filter( + array_map('trim', explode(';', (string) $parsedData['editore'])), + static fn (string $n): bool => $n !== '' + )); + $publisherCellHasMultiple = count($publisherNames) > 1; + foreach ($publisherNames as $pIndex => $pName) { + $publisherResult = $this->getOrCreatePublisher($db, $pName); + if ($publisherResult['created']) { + $importData['publishers_created']++; + } + if ($pIndex === 0) { + $editorId = $publisherResult['id']; + } elseif ((int) $publisherResult['id'] !== (int) $editorId) { + $coPublisherIds[] = (int) $publisherResult['id']; + } + } + } + + // Resolve genre, creating it when the CSV names a genre absent + // from the catalogue (#16). Previously getGenreId() only looked up + // existing rows and returned NULL for anything new — so a genre + // present in the export vanished on re-import — unlike the + // authors/publishers auto-create paths. + $genreId = (!$isExistingBook || $updateFields['genre']) + ? $this->getOrCreateGenre($db, $parsedData['genere'] ?? '', $importData) + : null; + + // Upsert book + $upsertResult = $this->upsertBook( + $db, + $parsedData, + $editorId, + $genreId, + $updateFields, + $existingBookId + ); + $bookId = $upsertResult['id']; + $action = $upsertResult['action']; + + // #380: on an existing book each unchecked family is fully + // preserved. Created books always import everything. + $applyFamily = static function (string $family) use ($action, $updateFields): bool { + return $action === 'created' || $updateFields[$family]; + }; + + // Import the co-publishers (#12/#143) ONLY when the cell actually + // carried more than one publisher, so a single-publisher CSV never + // wipes co-publishers a librarian curated in the admin form. + if ($publisherCellHasMultiple && $applyFamily('publisher')) { + $this->syncImportedCoPublishers($db, $bookId, $editorId, $coPublisherIds); + } + + // Remove old PRINCIPAL author links if updating — scoped to + // ruolo='principale' so a re-import re-writes the authors the CSV + // owns without wiping co-author/illustrator/translator/curator/ + // colorist ENTITY links (#237): every explicit contributor + // column is synchronized below with importer provenance. A + // blanket delete-all silently lost every contributor entity on + // each re-import. + // Only rewrite the principal links when the row actually carries + // an author. An EMPTY author cell must NOT wipe the existing + // principal authors — a blanket delete-when-updated left the book + // permanently authorless whenever a re-imported row omitted the + // author (#22). + // #380: with the Autori checkbox off, an existing book's + // principal links are fully preserved (no delete, no rewrite). + if ($action === 'updated' && $applyFamily('authors') && !empty($parsedData['autori'])) { + $stmt = $db->prepare("DELETE FROM libri_autori WHERE libro_id = ? AND ruolo = 'principale'"); + $stmt->bind_param('i', $bookId); + $stmt->execute(); + $stmt->close(); + } + + // Handle authors + if (!empty($parsedData['autori']) && $applyFamily('authors')) { + $separator = strpos($parsedData['autori'], ';') !== false ? ';' : '|'; + $authors = array_map('trim', explode($separator, $parsedData['autori'])); + $authorOrder = 1; + + foreach ($authors as $authorName) { + if (empty($authorName)) continue; + + $authorResult = $this->getOrCreateAuthor($db, $authorName); + $authorId = $authorResult['id']; + if ($authorResult['created']) { + $importData['authors_created']++; + } + + \App\Support\ContributorSync::persistImportedPrincipal( + $db, + $bookId, + $authorId, + $authorOrder + ); + $authorOrder++; + } + } + + // Contributor roles are entities too (#237). Replace only + // links owned by the CSV importer and only for columns that + // were actually present. This distinction matters for old + // exports: an absent column must preserve existing data, while + // an explicit empty cell clears this importer's stale links. + $contributorValues = []; + foreach ([ + 'co-autore' => 'co_autori', + 'traduttore' => 'traduttore', + 'illustratore' => 'illustratore', + 'curatore' => 'curatore', + 'colorista' => 'colorista', + ] as $role => $field) { + if (($parsedData[$field . '_provided'] ?? false) === true) { + $contributorValues[$role] = $parsedData[$field] ?? null; + } + } + // #380: with the Contributori checkbox off, an existing book's + // contributor links are fully preserved (no sync at all). + if ($contributorValues !== [] && $applyFamily('contributors')) { + $importData['authors_created'] += \App\Support\ContributorSync::syncImportedLegacyValues( + $db, + $bookId, + $contributorValues, + $this->importSource + ); + } + + // The batch supplier (CSV import, 0.9.0) goes on the books the + // import creates, inside the row's transaction: a created book is + // never committed without it, so no retry has to complete it. + // An existing book keeps its own supplier. + $batchSupplierId = (int) ($importData['fornitore_id'] ?? 0); + if ($batchSupplierId > 0 && $action === 'created') { + $supplierMissing = !\App\Support\CataloguingExtras::assignSupplier($db, (int) $bookId, $batchSupplierId); + } + + $db->commit(); + } catch (\Throwable $e) { + $db->rollback(); + throw $e; + } + \App\Support\ContentCache::deferBooksChanged(); + + if ($action === 'created') { + $importData['imported']++; + } else { + $importData['updated']++; + } + + // Plugin hook: after book save. Fired OUTSIDE the per-row + // transaction (it just committed) because listeners such as the + // book-club external-proposal reconciler open their own + // transaction — nesting begin_transaction() would implicitly + // commit. Mirrors LibriController's create/edit dispatch so an + // imported book that matches an outstanding club proposal is + // reconciled at import time, not only on the next maintenance run. + // Isolated in its own try/catch: the row is already committed and + // counted, so a throwing listener must NOT reach the per-row catch + // below (which would rollback — a no-op here — and additionally + // record the committed row as a failed import, double-counting it). + try { + \App\Support\Hooks::do('book.save.after', [$bookId, $parsedData]); + } catch (\Throwable $hookError) { + \App\Support\SecureLogger::warning('[' . $this->importSource . ' import] book.save.after listener failed (row already committed)', ['book_id' => $bookId, 'error' => $hookError->getMessage()]); + } + + return ['id' => (int) $bookId, 'action' => (string) $action, 'supplier_missing' => $supplierMissing]; + } + + /** + * Normalise a canonical import row (the keys mapColumnHeaders() produces) + * into the book data importParsedRow() writes: ISBN/EAN validation, the + * language, the author list, the contributor columns. + * + * @param array $row + * @return array + */ + public function normalizeImportRow(array $row): array + { + return $this->parseCsvRow($row); + } + /** * Genera CSV di esempio con intestazioni e dati di esempio * @@ -1488,17 +1579,13 @@ private function mapColumnHeaders(array $headers): array */ private function getOrCreatePublisher(\mysqli $db, string $name): array { - $stmt = $db->prepare("SELECT id FROM editori WHERE nome = ? LIMIT 1"); - $stmt->bind_param('s', $name); - $stmt->execute(); - $result = $stmt->get_result(); - - if ($row = $result->fetch_assoc()) { - $publisherId = (int) $row['id']; - $stmt->close(); - return ['id' => $publisherId, 'created' => false]; + // Same lookup as the book form: the exact spelling wins, and a name in + // quotes reuses only that spelling (#501). The quotes are not stored. + $existingId = (new \App\Models\PublisherRepository($db))->findByName($name); + if ($existingId !== null) { + return ['id' => $existingId, 'created' => false]; } - $stmt->close(); + $name = \App\Support\ExactSearch::quotedValue($name) ?? $name; // Crea nuovo editore $stmt = $db->prepare("INSERT INTO editori (nome, created_at) VALUES (?, NOW())"); @@ -1702,19 +1789,29 @@ private function findExistingBook(\mysqli $db, array $data): ?int $firstAuthor = $authorsArray[0]; if ($firstAuthor !== '') { - // Normalize author name to match DB format (e.g. "Levi, Primo" → "Primo Levi") + // Normalize author name to match DB format (e.g. "Levi, Primo" → "Primo Levi"). + // A name in quotes keeps them through normalize() (#501): the + // quotes are never part of a stored name, and they mean this + // exact spelling, so a quoted author matches only an author + // written the same way (accents distinct, case ignored), like + // AuthorRepository::findByCanonicalName(). An unquoted author + // keeps the accent-tolerant `a.nome = ?` comparison. $normalizedAuthor = \App\Support\AuthorNormalizer::normalize($firstAuthor); + $quotedAuthor = \App\Support\ExactSearch::quotedValue($normalizedAuthor); + $authorMatch = $quotedAuthor !== null + ? \App\Support\ExactSearch::equalsCondition('a.nome', $quotedAuthor) + : ['sql' => 'a.nome = ?', 'params' => [$normalizedAuthor], 'types' => 's']; $stmt = $db->prepare(" SELECT DISTINCT l.id FROM libri l JOIN libri_autori al ON l.id = al.libro_id JOIN autori a ON al.autore_id = a.id WHERE l.titolo = ? - AND a.nome = ? + AND {$authorMatch['sql']} AND l.deleted_at IS NULL LIMIT 1 "); - $stmt->bind_param('ss', $data['titolo'], $normalizedAuthor); + $stmt->bind_param('s' . $authorMatch['types'], $data['titolo'], ...$authorMatch['params']); $stmt->execute(); $result = $stmt->get_result(); if ($row = $result->fetch_assoc()) { @@ -1736,8 +1833,10 @@ private function findExistingBook(\mysqli $db, array $data): ?int * unchecked keeps the book's current values for its columns: * isbn → isbn10/isbn13; ean → ean; titolo/sottotitolo/anno/lingua/ * edizione/pagine/prezzo/dewey → the matching column; - * formato → formato + tipo_media; collana → collana + numero_serie - * (+ series membership sync skipped); + * formato → formato + tipo_media, unless $updateFields carries its own + * `tipo_media` entry, which then decides the media type alone (the MARC + * import overwrites the media type and never the format); + * collana → collana + numero_serie (+ series membership sync skipped); * genre → genere_id; publisher → editore_id (+ junction sync skipped); * description → descrizione/descrizione_plain; keywords → parole_chiave; * contributors → the legacy traduttore/illustratore/curatore TEXT columns. @@ -1756,6 +1855,9 @@ private function updateBook(\mysqli $db, int $bookId, array $data, ?int $editorI foreach (self::UPDATE_FIELD_FAMILIES as $family) { $apply[$family] = (bool) ($updateFields[$family] ?? true); } + // The media type follows the format family unless the caller selects + // it on its own (see UPDATE_FIELD_FAMILIES). + $apply['tipo_media'] = (bool) ($updateFields['tipo_media'] ?? $apply['formato']); $anyPreserved = in_array(false, $apply, true); $activityBefore = \App\Support\ActivityLog::loadBookSnapshot($db, $bookId); $hasTipoMedia = $this->hasTipoMediaColumn($db); @@ -1858,9 +1960,9 @@ classificazione_dewey = ?, $numeroSerie = !empty($data['numero_serie']) ? $data['numero_serie'] : null; // #7: normalize the free-text contributor columns like every other writer // (BookRepository), so "Rossi, Mario" from a CSV matches "Mario Rossi". - $traduttore = !empty($data['traduttore']) ? \App\Support\AuthorNormalizer::normalize((string) $data['traduttore']) : null; - $illustratore = !empty($data['illustratore']) ? \App\Support\AuthorNormalizer::normalize((string) $data['illustratore']) : null; - $curatore = !empty($data['curatore']) ? \App\Support\AuthorNormalizer::normalize((string) $data['curatore']) : null; + $traduttore = !empty($data['traduttore']) ? \App\Support\AuthorNormalizer::normalizeStored((string) $data['traduttore']) : null; + $illustratore = !empty($data['illustratore']) ? \App\Support\AuthorNormalizer::normalizeStored((string) $data['illustratore']) : null; + $curatore = !empty($data['curatore']) ? \App\Support\AuthorNormalizer::normalizeStored((string) $data['curatore']) : null; $paroleChiave = !empty($data['parole_chiave'] ?? null) ? $data['parole_chiave'] : null; $dewey = !empty($data['classificazione_dewey'] ?? null) ? $data['classificazione_dewey'] : null; @@ -1897,6 +1999,8 @@ classificazione_dewey = ?, } if (!$apply['formato']) { $formato = $existingDesc['formato']; + } + if (!$apply['tipo_media']) { // tipo_media updates via COALESCE(?, tipo_media): NULL keeps it. $tipoMedia = null; } @@ -1982,7 +2086,7 @@ classificazione_dewey = ?, $activityBefore, $activityAfter, bookTitle: (string) ($activityAfter['titolo'] ?? $activityBefore['titolo'] ?? ''), - source: 'csv' + source: $this->importSource ); } @@ -2179,9 +2283,9 @@ classificazione_dewey, stato, created_at{$desiderataCol}{$cataloguedCol} $collana = !empty($data['collana']) ? $data['collana'] : null; $numeroSerie = !empty($data['numero_serie']) ? $data['numero_serie'] : null; // #7: normalize free-text contributor columns like every other writer. - $traduttore = !empty($data['traduttore']) ? \App\Support\AuthorNormalizer::normalize((string) $data['traduttore']) : null; - $illustratore = !empty($data['illustratore']) ? \App\Support\AuthorNormalizer::normalize((string) $data['illustratore']) : null; - $curatore = !empty($data['curatore']) ? \App\Support\AuthorNormalizer::normalize((string) $data['curatore']) : null; + $traduttore = !empty($data['traduttore']) ? \App\Support\AuthorNormalizer::normalizeStored((string) $data['traduttore']) : null; + $illustratore = !empty($data['illustratore']) ? \App\Support\AuthorNormalizer::normalizeStored((string) $data['illustratore']) : null; + $curatore = !empty($data['curatore']) ? \App\Support\AuthorNormalizer::normalizeStored((string) $data['curatore']) : null; $paroleChiave = !empty($data['parole_chiave'] ?? null) ? $data['parole_chiave'] : null; $dewey = !empty($data['classificazione_dewey'] ?? null) ? $data['classificazione_dewey'] : null; @@ -2253,7 +2357,7 @@ classificazione_dewey, stato, created_at{$desiderataCol}{$cataloguedCol} [], $activityAfter, bookTitle: (string) ($activityAfter['titolo'] ?? $titolo), - source: 'csv' + source: $this->importSource ); return $bookId; diff --git a/app/Controllers/DeweyApiController.php b/app/Controllers/DeweyApiController.php index 76047bcb5..ee992e461 100644 --- a/app/Controllers/DeweyApiController.php +++ b/app/Controllers/DeweyApiController.php @@ -319,19 +319,20 @@ public function autocomplete(Request $request, Response $response): Response $query = trim((string) ($request->getQueryParams()['q'] ?? '')); $results = []; try { + ['term' => $query, 'exact' => $exact] = \App\Support\ExactSearch::parseFilter($query); if (mb_strlen($query) >= 2) { $data = $this->loadDeweyData(); if ($this->isNewFormat($data)) { - $needle = self::foldForSearch($query); + $needle = $exact ? mb_strtolower($query, 'UTF-8') : self::foldForSearch($query); $byCode = []; $byName = []; - $walk = function (array $nodes) use (&$walk, &$byCode, &$byName, $query, $needle): void { + $walk = function (array $nodes) use (&$walk, &$byCode, &$byName, $query, $needle, $exact): void { foreach ($nodes as $node) { $code = (string) ($node['code'] ?? ''); $name = (string) ($node['name'] ?? ''); if ($code !== '' && str_starts_with($code, $query)) { $byCode[$code] = ['code' => $code, 'name' => $name]; - } elseif ($name !== '' && str_contains(self::foldForSearch($name), $needle)) { + } elseif ($name !== '' && str_contains($exact ? mb_strtolower($name, 'UTF-8') : self::foldForSearch($name), $needle)) { $byName[$code] = ['code' => $code, 'name' => $name]; } if (!empty($node['children']) && is_array($node['children'])) { diff --git a/app/Controllers/EditoriApiController.php b/app/Controllers/EditoriApiController.php index d3256acb2..07f892206 100644 --- a/app/Controllers/EditoriApiController.php +++ b/app/Controllers/EditoriApiController.php @@ -34,33 +34,23 @@ public function list(Request $request, Response $response, mysqli $db): Response // HAVING clause for libri_count filter (applied after subquery) $having_clause = ''; - if ($search_text !== '') { - $where_prepared .= " AND e.nome LIKE ? "; - $params_for_where[] = "%$search_text%"; - $param_types_for_where .= 's'; - $params[] = "%$search_text%"; - $param_types .= 's'; - } - if ($search_sito !== '') { - $where_prepared .= " AND e.sito_web LIKE ? "; - $params_for_where[] = "%$search_sito%"; - $param_types_for_where .= 's'; - $params[] = "%$search_sito%"; - $param_types .= 's'; - } - if ($search_via !== '') { - $where_prepared .= " AND e.indirizzo LIKE ? "; - $params_for_where[] = "%$search_via%"; - $param_types_for_where .= 's'; - $params[] = "%$search_via%"; - $param_types .= 's'; - } - if ($search_cap !== '') { - $where_prepared .= " AND e.indirizzo LIKE ? "; - $params_for_where[] = "%$search_cap%"; - $param_types_for_where .= 's'; - $params[] = "%$search_cap%"; - $param_types .= 's'; + // A value wrapped in quotes matches the exact spelling, accents + // included (#501). The same fragment feeds both the count query + // ($params_for_where) and the page query ($params). + foreach ([ + ['e.nome', $search_text], + ['e.sito_web', $search_sito], + ['e.indirizzo', $search_via], + ['e.indirizzo', $search_cap], + ] as [$expr, $value]) { + $cond = $value !== '' ? \App\Support\ExactSearch::filterCondition([$expr], $value) : null; + if ($cond !== null) { + $where_prepared .= ' AND ' . $cond['sql'] . ' '; + array_push($params_for_where, ...$cond['params']); + $param_types_for_where .= $cond['types']; + array_push($params, ...$cond['params']); + $param_types .= $cond['types']; + } } // Book count filter - uses HAVING since libri_count is computed if ($filter_libri_count !== '') { diff --git a/app/Controllers/FrontendController.php b/app/Controllers/FrontendController.php index eee4c3d2e..7f1cc894a 100644 --- a/app/Controllers/FrontendController.php +++ b/app/Controllers/FrontendController.php @@ -931,8 +931,18 @@ static function () use ($db, $book_id): array { // default to max_loan_duration_days, so expose the same effective value // to the view instead of hardcoding one calendar month in JavaScript. $loanSettings = new \App\Models\SettingsRepository($db); - $maxRequestDays = $loanSettings->maxLoanDurationDays(); - $defaultRequestLoanDays = $loanSettings->requestLoanDurationDays(); + $circulation = new \App\Services\CirculationPolicy($db, $loanSettings); + // The viewer's circulation rules for this title (default patron type + // when anonymous): consultation-only material shows no request button, + // a patron type without reservations no "reserve when available". + $viewerPolicy = $circulation->forViewer( + !empty($_SESSION['user']['id']) ? (int) $_SESSION['user']['id'] : null, + $book_id + ); + $consultationOnly = !$viewerPolicy->loanable; + $viewerReservationsEnabled = $viewerPolicy->user->reservationsEnabled; + $maxRequestDays = $viewerPolicy->maxRequestDays; + $defaultRequestLoanDays = $viewerPolicy->requestDurationDays(); // Days to collect a booked loan once it starts: the scheduled-loan // message names them instead of a vague "when the date approaches". $pickupExpiryDays = max(1, (int) ($loanSettings->get('loans', 'pickup_expiry_days', '3') ?? 3)); @@ -1660,15 +1670,19 @@ private function buildWhereConditions(array $filters, mysqli $db): array // #143), so the catalog filter results agree with the publisher // facet count (which already counts secondaries). Gate the junction // subquery on table existence (pre-migration safety). + // The name must match exactly, accents included (#501): the + // collation alone would also return the books of "Häffner" for + // the publisher "Haffner". + $primary = \App\Support\ExactSearch::equalsCondition('e.nome', (string) $filters['editore']); if (\App\Support\SchemaInfo::hasLibriEditori($db)) { - $conditions[] = "(e.nome = ? OR EXISTS (SELECT 1 FROM libri_editori le2 JOIN editori e2 ON le2.editore_id = e2.id WHERE le2.libro_id = l.id AND e2.nome = ?))"; - $params[] = $filters['editore']; - $params[] = $filters['editore']; - $types .= 'ss'; + $secondary = \App\Support\ExactSearch::equalsCondition('e2.nome', (string) $filters['editore']); + $conditions[] = "({$primary['sql']} OR EXISTS (SELECT 1 FROM libri_editori le2 JOIN editori e2 ON le2.editore_id = e2.id WHERE le2.libro_id = l.id AND {$secondary['sql']}))"; + array_push($params, ...$primary['params'], ...$secondary['params']); + $types .= $primary['types'] . $secondary['types']; } else { - $conditions[] = "e.nome = ?"; - $params[] = $filters['editore']; - $types .= 's'; + $conditions[] = $primary['sql']; + array_push($params, ...$primary['params']); + $types .= $primary['types']; } } @@ -1714,12 +1728,17 @@ private function buildWhereConditions(array $filters, mysqli $db): array if (!empty($filters['autore'])) { $variants = \App\Services\UnifiedCatalogService::nameVariants((string)$filters['autore']); - $marks = implode(',', array_fill(0, count($variants), '?')); - $conditions[] = "EXISTS (SELECT 1 FROM libri_autori la_name JOIN autori a_name ON a_name.id=la_name.autore_id - WHERE la_name.libro_id=l.id AND la_name.ruolo IN ('principale','co-autore') - AND (TRIM(a_name.nome) IN ($marks) OR TRIM(a_name.pseudonimo) IN ($marks)))"; - array_push($params, ...$variants, ...$variants); - $types .= str_repeat('s', count($variants) * 2); + // Exact spelling, accents included (#501): "Haffner" must not + // also list the books of "Häffner". + $byName = \App\Support\ExactSearch::inCondition('TRIM(a_name.nome)', $variants); + $byPseudonym = \App\Support\ExactSearch::inCondition('TRIM(a_name.pseudonimo)', $variants); + if ($byName !== null && $byPseudonym !== null) { + $conditions[] = "EXISTS (SELECT 1 FROM libri_autori la_name JOIN autori a_name ON a_name.id=la_name.autore_id + WHERE la_name.libro_id=l.id AND la_name.ruolo IN ('principale','co-autore') + AND ({$byName['sql']} OR {$byPseudonym['sql']}))"; + array_push($params, ...$byName['params'], ...$byPseudonym['params']); + $types .= $byName['types'] . $byPseudonym['types']; + } } if (!empty($filters['autore_id'])) { @@ -1982,7 +2001,7 @@ private function computeFilterOptions(mysqli $db, array $filters = []): array ? " OR EXISTS (SELECT 1 FROM libri_editori le WHERE le.libro_id = l.id AND le.editore_id = e.id)" : ""; $queryEditori = " - SELECT e.nome, COUNT(DISTINCT l.id) AS cnt + SELECT MIN(e.nome) AS nome, COUNT(DISTINCT l.id) AS cnt FROM editori e JOIN libri l ON (e.id = l.editore_id{$facetExists}) AND l.deleted_at IS NULL AND " . $visibility . " @@ -2000,8 +2019,10 @@ private function computeFilterOptions(mysqli $db, array $filters = []): array // (editori.nome has no UNIQUE constraint — same-named rows are legitimate), // so grouping per id would render duplicate labels each with a partial count // that no single filtered result could match. Grouping by name merges them - // and makes each badge equal the count the name filter returns. - $queryEditori .= " GROUP BY e.nome HAVING cnt > 0 ORDER BY e.nome"; + // and makes each badge equal the count the name filter returns. The group + // key keeps accents distinct (#501) — "Haffner" and "Häffner" are two + // publishers — while still ignoring case, exactly like the filter does. + $queryEditori .= " GROUP BY LOWER(e.nome) COLLATE utf8mb4_bin HAVING cnt > 0 ORDER BY nome"; $stmt = $db->prepare($queryEditori); if (!empty($paramsEd)) { @@ -2346,9 +2367,13 @@ public function authorArchive(Request $request, Response $response, mysqli $db, // UNIQUE on nome by design), so resolving a single id with LIMIT 1 would // show an arbitrary homonym and hide the others' bibliography — mirrors // publisherArchive(). The header shows the lowest id, deterministically. - $authorQuery = "SELECT id, nome, pseudonimo, biografia, sito_web, foto, collegamenti FROM autori WHERE nome = ? ORDER BY id"; + // Homonyms only: same spelling, accents included (#501). The collation + // alone would treat "Haffner" and "Häffner" as one name and merge two + // people's bibliographies on one page. + $byName = \App\Support\ExactSearch::equalsCondition('nome', $authorName); + $authorQuery = "SELECT id, nome, pseudonimo, biografia, sito_web, foto, collegamenti FROM autori WHERE {$byName['sql']} ORDER BY id"; $stmt = $db->prepare($authorQuery); - $stmt->bind_param('s', $authorName); + $stmt->bind_param($byName['types'], ...$byName['params']); $stmt->execute(); $authorResult = $stmt->get_result(); @@ -2382,9 +2407,11 @@ public function publisherArchive(Request $request, Response $response, mysqli $d // Find EVERY publisher with this name. Duplicates legitimately share a // name (the merge feature exists because of that), so resolving a single // id with LIMIT 1 would hide the books of the other same-named publishers. - $publisherQuery = "SELECT id, nome, indirizzo, sito_web FROM editori WHERE nome = ? ORDER BY id"; + // Same spelling, accents included (#501): see authorArchive(). + $byName = \App\Support\ExactSearch::equalsCondition('nome', $publisherName); + $publisherQuery = "SELECT id, nome, indirizzo, sito_web FROM editori WHERE {$byName['sql']} ORDER BY id"; $stmt = $db->prepare($publisherQuery); - $stmt->bind_param('s', $publisherName); + $stmt->bind_param($byName['types'], ...$byName['params']); $stmt->execute(); $publisherResult = $stmt->get_result(); diff --git a/app/Controllers/GeneriApiController.php b/app/Controllers/GeneriApiController.php index cbdae404f..aebfedf74 100644 --- a/app/Controllers/GeneriApiController.php +++ b/app/Controllers/GeneriApiController.php @@ -14,23 +14,27 @@ public function search(Request $request, Response $response, \mysqli $db): Respo $limit = min(50, (int) ($request->getQueryParams()['limit'] ?? 20)); $results = []; - if (strlen($query) >= 1) { - $searchTerm = "%{$query}%"; + $search = \App\Support\ExactSearch::filterCondition(['g.nome'], $query); + if ($search !== null) { + ['term' => $term, 'exact' => $exact] = \App\Support\ExactSearch::parseFilter($query); + $prefixMatch = \App\Support\ExactSearch::likeSql('g.nome', $exact); $stmt = $db->prepare(" SELECT g.id, g.nome, g.parent_id, p.nome AS parent_nome, CASE WHEN g.parent_id IS NULL THEN 'genere' ELSE 'sottogenere' END AS tipo FROM generi g LEFT JOIN generi p ON g.parent_id = p.id - WHERE g.nome LIKE ? + WHERE {$search['sql']} ORDER BY - CASE WHEN g.nome LIKE ? THEN 0 ELSE 1 END, + CASE WHEN {$prefixMatch} THEN 0 ELSE 1 END, g.parent_id IS NULL DESC, g.nome LIMIT ? "); - $exactMatch = "{$query}%"; - $stmt->bind_param('ssi', $searchTerm, $exactMatch, $limit); + // The WHERE holds one placeholder per word when the box mixes + // words and quotes (`storia "arte"`): bind whatever it carries. + $prefixPattern = substr(\App\Support\ExactSearch::likePattern($term), 1); + $stmt->bind_param($search['types'] . 'si', ...[...$search['params'], $prefixPattern, $limit]); $stmt->execute(); $result = $stmt->get_result(); @@ -251,4 +255,4 @@ public function getSottogeneri(Request $request, Response $response, \mysqli $db $response->getBody()->write(json_encode($results, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_UNESCAPED_UNICODE)); return $response->withHeader('Content-Type', 'application/json'); } -} \ No newline at end of file +} diff --git a/app/Controllers/ImportHistoryController.php b/app/Controllers/ImportHistoryController.php index 6348f80e2..71af1f2be 100644 --- a/app/Controllers/ImportHistoryController.php +++ b/app/Controllers/ImportHistoryController.php @@ -85,7 +85,10 @@ public function index(Request $request, Response $response, \mysqli $db): Respon /** * Download error report as CSV */ - public function downloadErrors(Request $request, Response $response, \mysqli $db): Response + /** + * @param string|null $requiredType only serve imports of this import_type (404 otherwise) + */ + public function downloadErrors(Request $request, Response $response, \mysqli $db, ?string $requiredType = null): Response { $importId = $request->getQueryParams()['import_id'] ?? null; @@ -109,7 +112,7 @@ public function downloadErrors(Request $request, Response $response, \mysqli $db $stmt->execute(); $result = $stmt->get_result(); - if (!($row = $result->fetch_assoc())) { + if (!($row = $result->fetch_assoc()) || ($requiredType !== null && ($row['import_type'] ?? '') !== $requiredType)) { $stmt->close(); $response->getBody()->write('Import non trovato'); return $response->withStatus(404); diff --git a/app/Controllers/InventoryController.php b/app/Controllers/InventoryController.php new file mode 100644 index 000000000..9f7d8ae1b --- /dev/null +++ b/app/Controllers/InventoryController.php @@ -0,0 +1,489 @@ + 0 ? $id : null; + } + + private function isAdmin(): bool + { + return ($_SESSION['user']['tipo_utente'] ?? '') === 'admin'; + } + + private function redirect(Response $response, string $path): Response + { + return $response->withHeader('Location', url($path))->withStatus(302); + } + + /** @param array $payload */ + private function json(Response $response, array $payload, int $status = 200): Response + { + $response->getBody()->write((string) json_encode($payload, JSON_UNESCAPED_UNICODE)); + return $response->withHeader('Content-Type', 'application/json')->withHeader('Cache-Control', 'no-store')->withStatus($status); + } + + private function render(Response $response, string $view, array $vars): Response + { + extract($vars, EXTR_SKIP); + ob_start(); + require __DIR__ . '/../Views/inventario/' . $view . '.php'; + $content = ob_get_clean(); + ob_start(); + require __DIR__ . '/../Views/layout.php'; + $response->getBody()->write((string) ob_get_clean()); + return $response; + } + + /** @return array */ + public static function outcomeLabels(): array + { + return [ + 'trovata' => __('Trovata'), + 'fuori_posto' => __('Fuori posto'), + 'in_prestito' => __('In prestito o in attesa di ritiro'), + 'mancante' => __('Mancante'), + 'sconosciuta' => __('Codice sconosciuto'), + 'fuori_ambito' => __('Fuori ambito'), + 'duplicata' => __('Già scansionata'), + ]; + } + + /** @return array{scaffali:list>,mensole:list>,generi:list,sedi:list>} */ + private function lookups(mysqli $db): array + { + $repo = new \App\Models\CollocationRepository($db); + $genres = []; + $res = $db->query('SELECT id, nome, parent_id FROM generi ORDER BY nome'); + $byParent = []; + if ($res) { + foreach ($res->fetch_all(MYSQLI_ASSOC) as $row) { + $byParent[(int) ($row['parent_id'] ?? 0)][] = $row; + } + } + $walk = static function (int $parent, int $depth, array &$seen) use (&$walk, &$genres, $byParent): void { + foreach ($byParent[$parent] ?? [] as $row) { + $id = (int) $row['id']; + if (isset($seen[$id]) || $depth > 6) { + continue; + } + $seen[$id] = true; + $genres[] = ['id' => $id, 'label' => str_repeat('— ', $depth) . (string) $row['nome']]; + $walk($id, $depth + 1, $seen); + } + }; + $seen = []; + $walk(0, 0, $seen); + $sedi = []; + $res = $db->query('SELECT id, nome FROM sedi WHERE is_active = 1 ORDER BY nome'); + if ($res) { + $sedi = $res->fetch_all(MYSQLI_ASSOC); + } + return ['scaffali' => $repo->getScaffali(), 'mensole' => $repo->getMensole(), 'generi' => $genres, 'sedi' => $sedi]; + } + + public function index(Request $request, Response $response, mysqli $db): Response + { + if (($off = \App\Support\Features::requireOrRedirect('stocktake', $response)) !== null) { + return $off; + } + $sessions = (new StocktakeService($db))->listSessions(); + return $this->render($response, 'index', ['sessions' => $sessions] + $this->lookups($db)); + } + + public function store(Request $request, Response $response, mysqli $db): Response + { + if (($off = \App\Support\Features::requireOrRedirect('stocktake', $response)) !== null) { + return $off; + } + $data = (array) $request->getParsedBody(); + $name = is_string($data['nome'] ?? null) ? $data['nome'] : ''; + $note = is_string($data['note'] ?? null) ? $data['note'] : ''; + $sede = is_scalar($data['sede_id'] ?? null) ? (int) $data['sede_id'] : 0; + try { + $id = (new StocktakeService($db))->open($name, $sede > 0 ? $sede : null, $data, $this->operatorId(), $note); + } catch (\InvalidArgumentException $e) { + $_SESSION['error_message'] = $e->getMessage(); + return $this->redirect($response, '/admin/inventory'); + } catch (\Throwable $e) { + SecureLogger::error('[Stocktake] open failed', ['error' => $e->getMessage()]); + $_SESSION['error_message'] = __('Impossibile aprire la revisione. Riprova più tardi.'); + return $this->redirect($response, '/admin/inventory'); + } + $_SESSION['success_message'] = __('Revisione aperta: le copie attese sono state fissate. Puoi iniziare a scansionare.'); + return $this->redirect($response, '/admin/inventory/' . $id); + } + + public function show(Request $request, Response $response, mysqli $db, int $id): Response + { + if (($off = \App\Support\Features::requireOrRedirect('stocktake', $response)) !== null) { + return $off; + } + $service = new StocktakeService($db); + $session = $service->find($id); + if ($session === null) { + $_SESSION['error_message'] = __('Revisione non trovata.'); + return $this->redirect($response, '/admin/inventory'); + } + $operatorId = $this->operatorId(); + $mine = ($request->getQueryParams()['mine'] ?? '') === '1'; + $filterLabels = $this->filterLabels($db, $service, $session['filtro']); + return $this->render($response, 'show', [ + 'session' => $session, + 'summary' => $service->summary($id, $operatorId), + 'scans' => $service->scans($id, $mine ? $operatorId : null, 50), + 'mineOnly' => $mine, + 'reportRows' => $session['stato'] === 'aperta' ? [] : $service->reportRows($id), + // A mark-lost close that did not reach every copy can be completed here. + 'pendingLost' => $session['stato'] === 'chiusa' && ($session['azione_mancanti'] ?? null) === 'perso' + ? $service->pendingLostCount($id) + : 0, + 'filterLabels' => $filterLabels, + 'isAdmin' => $this->isAdmin(), + 'outcomeLabels' => self::outcomeLabels(), + 'scaffali' => (new \App\Models\CollocationRepository($db))->getScaffali(), + // Who opened, closed, cancelled or resumed the session: the book + // feeds never show these entity events. + 'history' => \App\Support\ActivityLog::forEntity($db, 'inventario_sessioni', $id), + ]); + } + + /** + * @param array $filter + * @return list + */ + private function filterLabels(mysqli $db, StocktakeService $service, array $filter): array + { + $labels = []; + if (isset($filter['scaffale_id'])) { + $labels[] = __('Scaffale') . ': ' . $service->shelfLabel((int) $filter['scaffale_id']); + } + if (isset($filter['mensola_id'])) { + $stmt = $db->prepare('SELECT m.numero_livello, s.codice FROM mensole m JOIN scaffali s ON s.id = m.scaffale_id WHERE m.id = ?'); + if ($stmt !== false) { + $mid = (int) $filter['mensola_id']; + $stmt->bind_param('i', $mid); + $stmt->execute(); + $row = $stmt->get_result()->fetch_assoc(); + $stmt->close(); + if (is_array($row)) { + $labels[] = __('Mensola') . ': ' . (string) $row['codice'] . ' - ' . __('Livello') . ' ' . (int) $row['numero_livello']; + } + } + } + if (isset($filter['genere_id'])) { + $stmt = $db->prepare('SELECT nome FROM generi WHERE id = ?'); + if ($stmt !== false) { + $gid = (int) $filter['genere_id']; + $stmt->bind_param('i', $gid); + $stmt->execute(); + $row = $stmt->get_result()->fetch_row(); + $stmt->close(); + $labels[] = __('Genere') . ': ' . (string) ($row[0] ?? ('#' . $gid)); + } + } + if (isset($filter['tipo_media'])) { + $labels[] = __('Tipo media') . ': ' . \App\Support\MediaLabels::tipoMediaDisplayName((string) $filter['tipo_media']); + } + if (isset($filter['collocazione_da']) || isset($filter['collocazione_a'])) { + $labels[] = __('Collocazione') . ': ' . (string) ($filter['collocazione_da'] ?? '…') . ' → ' . (string) ($filter['collocazione_a'] ?? '…'); + } + return $labels; + } + + private function scanShelf(array $data): ?int + { + $shelf = $data['scaffale_corrente'] ?? null; + // Only a whole positive number is a shelf: (int) '1junk' would be shelf 1 + // and file copies expected elsewhere as misplaced. + if (is_int($shelf)) { + return $shelf > 0 ? $shelf : null; + } + if (is_string($shelf) && ctype_digit($shelf) && (int) $shelf > 0) { + return (int) $shelf; + } + return null; + } + + public function scan(Request $request, Response $response, mysqli $db, int $id): Response + { + if (($off = \App\Support\Features::requireOrRedirect('stocktake', $response)) !== null) { + return $off; + } + $data = (array) $request->getParsedBody(); + if (str_contains($request->getHeaderLine('Content-Type'), 'application/json')) { + $decoded = json_decode((string) $request->getBody(), true); + $data = is_array($decoded) ? $decoded : []; + } + $code = is_string($data['codice'] ?? null) ? $data['codice'] : ''; + $service = new StocktakeService($db); + try { + $result = $service->scan($id, $code, $this->operatorId(), $this->scanShelf($data)); + } catch (\InvalidArgumentException | \DomainException $e) { + return $this->json($response, ['success' => false, 'message' => $e->getMessage()], 422); + } catch (\Throwable $e) { + SecureLogger::error('[Stocktake] scan failed', ['sessione_id' => $id, 'error' => $e->getMessage()]); + return $this->json($response, ['success' => false, 'message' => __('Impossibile registrare la scansione. Riprova.')], 500); + } + $labels = self::outcomeLabels(); + return $this->json($response, [ + 'success' => true, + 'result' => $result + ['label' => $labels[$result['esito']] ?? $result['esito']], + 'summary' => $service->summary($id, $this->operatorId()), + ]); + } + + public function state(Request $request, Response $response, mysqli $db, int $id): Response + { + if (($off = \App\Support\Features::requireOrRedirect('stocktake', $response)) !== null) { + return $off; + } + $service = new StocktakeService($db); + $session = $service->find($id); + if ($session === null) { + return $this->json($response, ['success' => false, 'message' => __('Revisione non trovata.')], 404); + } + $operatorId = $this->operatorId(); + $mine = ($request->getQueryParams()['mine'] ?? '') === '1'; + $labels = self::outcomeLabels(); + $scans = array_map(static fn (array $row): array => [ + 'codice' => (string) $row['codice'], + 'esito' => (string) $row['esito'], + 'label' => $labels[(string) $row['esito']] ?? (string) $row['esito'], + 'titolo' => (string) ($row['titolo'] ?? ''), + 'operatore' => (string) ($row['operatore'] ?? ''), + 'quando' => format_date((string) $row['scansionato_il'], true, '/'), + ], $service->scans($id, $mine ? $operatorId : null, 50)); + return $this->json($response, [ + 'success' => true, + 'stato' => $session['stato'], + 'summary' => $service->summary($id, $operatorId), + 'scans' => $scans, + ]); + } + + public function upload(Request $request, Response $response, mysqli $db, int $id): Response + { + if (($off = \App\Support\Features::requireOrRedirect('stocktake', $response)) !== null) { + return $off; + } + $files = $request->getUploadedFiles(); + $file = $files['lista'] ?? null; + $back = '/admin/inventory/' . $id; + if (!$file instanceof \Psr\Http\Message\UploadedFileInterface || $file->getError() !== UPLOAD_ERR_OK) { + $_SESSION['error_message'] = __('Seleziona un file .txt o .csv con un codice per riga.'); + return $this->redirect($response, $back); + } + $extension = strtolower(pathinfo((string) $file->getClientFilename(), PATHINFO_EXTENSION)); + if (!in_array($extension, ['txt', 'csv'], true) || (int) $file->getSize() > self::UPLOAD_MAX_BYTES) { + $_SESSION['error_message'] = __('Il file deve essere un .txt o .csv di al massimo 2 MB.'); + return $this->redirect($response, $back); + } + $data = (array) $request->getParsedBody(); + $service = new StocktakeService($db); + try { + $codes = StocktakeService::parseCodeList((string) $file->getStream(), $extension === 'csv'); + $result = $service->importCodes($id, $codes, $this->operatorId(), $this->scanShelf($data)); + } catch (\InvalidArgumentException | \DomainException $e) { + $_SESSION['error_message'] = $e->getMessage(); + return $this->redirect($response, $back); + } catch (\Throwable $e) { + SecureLogger::error('[Stocktake] upload failed', ['sessione_id' => $id, 'error' => $e->getMessage()]); + $_SESSION['error_message'] = __('Impossibile elaborare il file. Riprova.'); + return $this->redirect($response, $back); + } + $labels = self::outcomeLabels(); + $parts = []; + foreach ($result['outcomes'] as $outcome => $count) { + if ($count > 0) { + $parts[] = ($labels[$outcome] ?? $outcome) . ': ' . $count; + } + } + $_SESSION['success_message'] = sprintf(__('Elaborati %d codici dal file.'), $result['total']) . ($parts !== [] ? ' ' . implode(' · ', $parts) : ''); + return $this->redirect($response, $back); + } + + public function close(Request $request, Response $response, mysqli $db, int $id): Response + { + if (($off = \App\Support\Features::requireOrRedirect('stocktake', $response)) !== null) { + return $off; + } + $data = (array) $request->getParsedBody(); + $back = '/admin/inventory/' . $id; + $action = $data['azione_mancanti'] ?? 'nessuna'; + if (!in_array($action, ['nessuna', 'perso'], true)) { + // Closing is irreversible: never read an unknown choice as "leave them". + $_SESSION['error_message'] = __('Azione non valida.'); + return $this->redirect($response, $back); + } + $markLost = $action === 'perso'; + if ($markLost && !$this->isAdmin()) { + // Destructive: AdminAuthMiddleware admits Staff and custom roles too. + $_SESSION['error_message'] = __('Solo un amministratore può chiudere la revisione segnando come perse le copie mancanti.'); + return $this->redirect($response, $back); + } + if ($markLost) { + // The session is claimed before the copies are marked: let the + // marking finish even on a whole-catalogue session or if the + // browser goes away. A run cut off anyway is completed by posting + // "perso" again on the closed session (resumeMarkLost()). + @set_time_limit(0); + ignore_user_abort(true); + } + $service = new StocktakeService($db); + $resume = $markLost && ($service->find($id)['stato'] ?? null) === 'chiusa'; + try { + $result = $resume + ? $service->resumeMarkLost($id, $this->operatorId()) + : $service->close($id, $markLost, $this->operatorId()); + } catch (\DomainException $e) { + $_SESSION['error_message'] = $e->getMessage(); + return $this->redirect($response, $back); + } catch (\Throwable $e) { + SecureLogger::error('[Stocktake] close failed', ['sessione_id' => $id, 'error' => $e->getMessage()]); + $_SESSION['error_message'] = __('Impossibile chiudere la revisione. Riprova più tardi.'); + return $this->redirect($response, $back); + } + $message = $resume + ? sprintf(__('%d copie mancanti segnate come perse.'), $result['marked']) + : ($markLost + ? sprintf(__('Revisione chiusa: %d copie mancanti segnate come perse.'), $result['marked']) + : __('Revisione chiusa: le copie mancanti sono rimaste invariate.')); + if ($result['failed'] > 0) { + // One alert: the close happened, but some copies need attention. + $_SESSION['error_message'] = $message . ' ' . sprintf(__('%d copie non sono state aggiornate: controlla il registro degli errori.'), $result['failed']); + } else { + $_SESSION['success_message'] = $message; + } + return $this->redirect($response, $back); + } + + public function cancel(Request $request, Response $response, mysqli $db, int $id): Response + { + if (($off = \App\Support\Features::requireOrRedirect('stocktake', $response)) !== null) { + return $off; + } + try { + (new StocktakeService($db))->cancel($id, $this->operatorId()); + } catch (\DomainException $e) { + $_SESSION['error_message'] = $e->getMessage(); + return $this->redirect($response, '/admin/inventory/' . $id); + } catch (\Throwable $e) { + SecureLogger::error('[Stocktake] cancel failed', ['sessione_id' => $id, 'error' => $e->getMessage()]); + $_SESSION['error_message'] = __('Impossibile annullare la revisione. Riprova più tardi.'); + return $this->redirect($response, '/admin/inventory/' . $id); + } + $_SESSION['success_message'] = __('Revisione annullata. Nessuna copia è stata modificata.'); + return $this->redirect($response, '/admin/inventory'); + } + + private function fileBase(array $session): string + { + $slug = preg_replace('/[^A-Za-z0-9_-]+/', '-', (string) $session['nome']) ?? ''; + $slug = trim($slug, '-'); + return 'revisione-' . (int) $session['id'] . ($slug !== '' ? '-' . strtolower(substr($slug, 0, 40)) : ''); + } + + public function exportCsv(Request $request, Response $response, mysqli $db, int $id): Response + { + if (($off = \App\Support\Features::requireOrRedirect('stocktake', $response)) !== null) { + return $off; + } + $service = new StocktakeService($db); + $session = $service->find($id); + if ($session === null) { + $response->getBody()->write(__('Revisione non trovata.')); + return $response->withStatus(404); + } + $labels = self::outcomeLabels(); + // Written row by row into a temporary stream (spilled to disk past + // 2 MB), never held as one string: a stocktake of the whole catalogue + // has as many rows as copies, and this export is the way to get a + // report too large for the PDF. + $stream = fopen('php://temp', 'w+b'); + if ($stream === false) { + throw new \RuntimeException('Cannot open a temporary stream for the CSV report'); + } + fwrite($stream, "\xEF\xBB\xBF"); + $writer = Csv::writerToStream($stream, ',', "'"); + $writer->insertOne([__('Esito'), __('Codice copia'), __('Titolo'), __('Collocazione'), __('Scaffale atteso'), __('Stato copia'), __('Operatore'), __('Data scansione'), __('Tipo media')]); + foreach ($service->eachReportRow($id) as $row) { + $writer->insertOne([ + $labels[$row['categoria']] ?? $row['categoria'], + $row['codice'], + $row['titolo'], + $row['collocazione'], + $row['scaffale_atteso'], + $row['stato_copia'], + $row['operatore'], + $row['quando'] !== '' ? format_date($row['quando'], true, '/') : '', + // Last column, so the positions spreadsheets and scripts already read stay put. + $row['tipo_media'] !== '' ? \App\Support\MediaLabels::tipoMediaDisplayName($row['tipo_media']) : '', + ]); + } + rewind($stream); + return $response + ->withBody((new StreamFactory())->createStreamFromResource($stream)) + ->withHeader('Content-Type', 'text/csv; charset=utf-8') + ->withHeader('Content-Disposition', 'attachment; filename="' . $this->fileBase($session) . '.csv"') + ->withHeader('Cache-Control', 'no-store'); + } + + public function exportPdf(Request $request, Response $response, mysqli $db, int $id): Response + { + if (($off = \App\Support\Features::requireOrRedirect('stocktake', $response)) !== null) { + return $off; + } + $service = new StocktakeService($db); + $session = $service->find($id); + if ($session === null) { + $response->getBody()->write(__('Revisione non trovata.')); + return $response->withStatus(404); + } + // The count refuses a large report before any row is loaded. The rows + // are then read once, one past the limit, and checked again: scans + // recorded between the two reads may have pushed the report over it, + // and the rows checked are the very ones the PDF is built from. + $total = $service->reportRowCount($id); + $rows = $total <= StocktakePdfGenerator::MAX_ROWS ? $service->reportRows($id, StocktakePdfGenerator::MAX_ROWS + 1) : []; + if ($total > StocktakePdfGenerator::MAX_ROWS || count($rows) > StocktakePdfGenerator::MAX_ROWS) { + $total = max($total, count($rows), $service->reportRowCount($id)); + $_SESSION['error_message'] = sprintf(__('Il report ha troppe righe per il PDF (%d, al massimo %d): scarica il report CSV.'), $total, StocktakePdfGenerator::MAX_ROWS); + return $this->redirect($response, '/admin/inventory/' . $id); + } + $pdf = (new StocktakePdfGenerator())->generate( + $session, + $service->summary($id), + $rows, + $this->filterLabels($db, $service, $session['filtro']), + self::outcomeLabels() + ); + $response->getBody()->write($pdf); + return $response + ->withHeader('Content-Type', 'application/pdf') + ->withHeader('Content-Disposition', 'attachment; filename="' . $this->fileBase($session) . '.pdf"') + ->withHeader('Cache-Control', 'no-store'); + } +} diff --git a/app/Controllers/LibraryThingImportController.php b/app/Controllers/LibraryThingImportController.php index f15dd14b9..b32339a79 100644 --- a/app/Controllers/LibraryThingImportController.php +++ b/app/Controllers/LibraryThingImportController.php @@ -888,7 +888,10 @@ private function normalizeContributorField(?string $value): ?string $normalized = []; foreach ($this->splitContributorList($value, '/\s*[;|]\s*/u') as $part) { - $name = \App\Support\AuthorNormalizer::normalize($part); + // Written to the libri.* display cache only: whole-name quotes mean + // exact spelling (#501) and stay on the value ContributorSync gets + // from parseLibraryThingRow(), never in the stored text. + $name = \App\Support\AuthorNormalizer::normalizeStored($part); if ($name !== '' && !in_array($name, $normalized, true)) { $normalized[] = $name; } @@ -1245,16 +1248,13 @@ private function parseDate(string|int|float|null $dateString): ?string // Reuse methods from CsvImportController private function getOrCreatePublisher(\mysqli $db, string $name): array { - $stmt = $db->prepare("SELECT id FROM editori WHERE nome = ? LIMIT 1"); - $stmt->bind_param('s', $name); - $stmt->execute(); - $result = $stmt->get_result(); - - if ($row = $result->fetch_assoc()) { - $stmt->close(); - return ['id' => (int) $row['id'], 'created' => false]; + // Same lookup as the book form: the exact spelling wins, and a name in + // quotes reuses only that spelling (#501). The quotes are not stored. + $existingId = (new \App\Models\PublisherRepository($db))->findByName($name); + if ($existingId !== null) { + return ['id' => $existingId, 'created' => false]; } - $stmt->close(); + $name = \App\Support\ExactSearch::quotedValue($name) ?? $name; $stmt = $db->prepare("INSERT INTO editori (nome, created_at) VALUES (?, NOW())"); $stmt->bind_param('s', $name); @@ -2156,13 +2156,12 @@ public function exportToLibraryThing(Request $request, Response $response, \mysq $bindTypes = ''; $bindValues = []; - if (!empty($search)) { - $whereClauses[] = "(l.titolo LIKE ? OR l.sottotitolo LIKE ? OR l.isbn13 LIKE ? OR l.isbn10 LIKE ? OR a.nome LIKE ? OR e.nome LIKE ?)"; - $searchParam = "%{$search}%"; - for ($i = 0; $i < 6; $i++) { - $bindTypes .= 's'; - $bindValues[] = $searchParam; - } + // Quoted value = exact spelling, accents included (#501), as in the list. + $searchCond = \App\Support\SearchIndexBuilder::buildSearchCondition($db, 'l.search_index', (string) $search); + if ($searchCond !== null) { + $whereClauses[] = $searchCond['sql']; + $bindTypes .= $searchCond['types']; + array_push($bindValues, ...$searchCond['params']); } if (!empty($stato)) { diff --git a/app/Controllers/LibriApiController.php b/app/Controllers/LibriApiController.php index c5ee3ed47..007657357 100644 --- a/app/Controllers/LibriApiController.php +++ b/app/Controllers/LibriApiController.php @@ -34,6 +34,7 @@ public function list(Request $request, Response $response, mysqli $db): Response $anno_to = trim((string) ($q['anno_to'] ?? '')); $collana = trim((string) ($q['collana'] ?? '')); $tipo_media = trim((string) ($q['tipo_media'] ?? '')); + $fornitore_id = (int) ($q['fornitore_filter'] ?? 0); // Build WHERE clause with prepared statement parameters $where = 'WHERE l.deleted_at IS NULL '; @@ -49,12 +50,12 @@ public function list(Request $request, Response $response, mysqli $db): Response } } if ($search_isbn !== '') { - $where .= " AND (l.isbn10 LIKE ? OR l.isbn13 LIKE ? OR l.ean LIKE ?) "; - $isbnParam = '%' . $search_isbn . '%'; - $params[] = $isbnParam; - $params[] = $isbnParam; - $params[] = $isbnParam; - $types .= 'sss'; + $isbn = \App\Support\ExactSearch::filterCondition(['l.isbn10', 'l.isbn13', 'l.ean'], $search_isbn); + if ($isbn !== null) { + $where .= ' AND ' . $isbn['sql']; + array_push($params, ...$isbn['params']); + $types .= $isbn['types']; + } } if ($genere_id) { // Search both genere_id and sottogenere_id, plus children of the selected genre @@ -137,9 +138,12 @@ public function list(Request $request, Response $response, mysqli $db): Response $types .= 'i'; } if ($collana !== '') { - $where .= " AND l.collana LIKE ?"; - $params[] = '%' . $collana . '%'; - $types .= 's'; + $series = \App\Support\ExactSearch::filterCondition(['l.collana'], $collana); + if ($series !== null) { + $where .= ' AND ' . $series['sql']; + array_push($params, ...$series['params']); + $types .= $series['types']; + } } if ($tipo_media !== '' && $this->hasColumn($db, 'tipo_media')) { if ($tipo_media === 'libro') { @@ -156,6 +160,13 @@ public function list(Request $request, Response $response, mysqli $db): Response } } + // Suppliers module off: the filter is ignored like the hidden field. + if ($fornitore_id > 0 && \App\Support\Features::enabled('suppliers') && $this->hasColumn($db, 'fornitore_id')) { + $where .= ' AND l.fornitore_id = ?'; + $params[] = $fornitore_id; + $types .= 'i'; + } + // Parse DataTables sorting parameters (with robust null checks to avoid notices) $order = $q['order'][0] ?? null; $orderColumn = isset($order['column']) ? (int) $order['column'] : 4; // Default to Info column (title) diff --git a/app/Controllers/LibriController.php b/app/Controllers/LibriController.php index dacbdf7c5..584d1d508 100644 --- a/app/Controllers/LibriController.php +++ b/app/Controllers/LibriController.php @@ -400,7 +400,16 @@ public function index(Request $request, Response $response, mysqli $db): Respons ob_start(); $articleHintState = \App\Support\PeriodicalArticlesHint::state($db); - $data = ['libri' => $libri, 'genreFilterName' => $genreFilterName, 'subgenreFilterName' => $subgenreFilterName]; + // Copy cataloguing (SRU search) is served by the z39-server plugin. + $copyCatalogingAvailable = false; + try { + $pluginRow = $db->query("SELECT is_active FROM plugins WHERE name = 'z39-server' LIMIT 1"); + $copyCatalogingAvailable = \App\Support\Features::enabled('marc') + && $pluginRow instanceof \mysqli_result && (int) ($pluginRow->fetch_row()[0] ?? 0) === 1; + } catch (\Throwable) { + $copyCatalogingAvailable = false; + } + $data = ['libri' => $libri, 'genreFilterName' => $genreFilterName, 'subgenreFilterName' => $subgenreFilterName, 'fornitori' => (new \App\Models\SupplierRepository($db))->options()]; // extract($data); require __DIR__ . '/../Views/libri/index.php'; $content = ob_get_clean(); @@ -555,15 +564,15 @@ public function show(Request $request, Response $response, mysqli $db, int $id): // Renewal cap is admin-configurable (loans.max_renewals); pass it to the // view so the displayed limit and the "Renew" button gate match the // server-side check in PrestitiController::renew (#157). - $loanSettingsRepo = new \App\Models\SettingsRepository($db); - // Validate numeric BEFORE casting: a non-numeric stored value (e.g. "abc") - // would cast to 0, silently flipping the cap to "no renewals" instead of - // falling back to the safe default. Only a genuine number is honoured. - $rawMaxRenewals = $loanSettingsRepo->get('loans', 'max_renewals', '3'); - $maxRenewals = is_numeric($rawMaxRenewals) ? (int) $rawMaxRenewals : 3; - if ($maxRenewals < 0) { - $maxRenewals = 3; - } + // The limit is the one of the active loan's patron and material + // (CirculationPolicy, which also validates a non-numeric stored value + // back to the default 3), the global one when nothing is on loan. + $circulation = new \App\Services\CirculationPolicy($db); + $activeLoanPolicy = !empty($activeLoan['id']) + ? $circulation->forExistingLoan((int) $activeLoan['id']) + : $circulation->defaults(); + $maxRenewals = $activeLoanPolicy->maxRenewals; + $renewalDays = $activeLoanPolicy->renewalDays; // Issue #374: the audit timeline is staff-only. This route is also // reachable by patrons, so do not even query audit/operator data unless @@ -625,6 +634,7 @@ public function createForm(Request $request, Response $response, mysqli $db): Re $sottogeneri = $taxRepo->subgenres(); $libraryThingInstalled = \App\Support\LibraryThingInstaller::isInstalled($db); $articleHintState = \App\Support\PeriodicalArticlesHint::state($db); + $fornitori = (new \App\Models\SupplierRepository($db))->options(); ob_start(); // Variables are available in the template scope via require require __DIR__ . '/../Views/libri/crea_libro.php'; @@ -1395,6 +1405,9 @@ public function store(Request $request, Response $response, mysqli $db): Respons $this->saveLtVisibility($db, $id, $data['lt_visibility']); } + // Supplier and RDA overrides (0.9.0): only the keys the form posts. + \App\Support\CataloguingExtras::save($db, $id, $data); + // Plugin hook: After book save \App\Support\Hooks::do('book.save.after', [$id, $fields]); @@ -1490,6 +1503,7 @@ public function editForm(Request $request, Response $response, mysqli $db, int $ $sottogeneri = $taxRepo->subgenres(); $libraryThingInstalled = \App\Support\LibraryThingInstaller::isInstalled($db); $articleHintState = \App\Support\PeriodicalArticlesHint::state($db); + $fornitori = (new \App\Models\SupplierRepository($db))->options(); ob_start(); // Variables are available in the template scope via require require __DIR__ . '/../Views/libri/modifica_libro.php'; @@ -2026,6 +2040,9 @@ public function update(Request $request, Response $response, mysqli $db, int $id $this->saveLtVisibility($db, $id, $data['lt_visibility']); } + // Supplier and RDA overrides (0.9.0): only the keys the form posts. + \App\Support\CataloguingExtras::save($db, $id, $data); + // Plugin hook: After book save (update) \App\Support\Hooks::do('book.save.after', [$id, $fields]); @@ -3409,13 +3426,12 @@ public function exportCsv(Request $request, Response $response, mysqli $db): Res $bindValues = []; // Global search filter - if (!empty($search)) { - $whereClauses[] = "(l.titolo LIKE ? OR l.sottotitolo LIKE ? OR l.descrizione LIKE ? OR l.isbn13 LIKE ? OR l.isbn10 LIKE ? OR a.nome LIKE ? OR e.nome LIKE ?)"; - $searchParam = "%{$search}%"; - for ($i = 0; $i < 7; $i++) { - $bindTypes .= 's'; - $bindValues[] = $searchParam; - } + // Quoted value = exact spelling, accents included (#501), as in the list. + $searchCond = \App\Support\SearchIndexBuilder::buildSearchCondition($db, 'l.search_index', (string) $search); + if ($searchCond !== null) { + $whereClauses[] = $searchCond['sql']; + $bindTypes .= $searchCond['types']; + array_push($bindValues, ...$searchCond['params']); } // Status filter diff --git a/app/Controllers/LoanApprovalController.php b/app/Controllers/LoanApprovalController.php index b1f672982..955f27d5b 100644 --- a/app/Controllers/LoanApprovalController.php +++ b/app/Controllers/LoanApprovalController.php @@ -336,8 +336,9 @@ public function approveLoan(Request $request, Response $response, mysqli $db): R // applicato solo alla creazione/richiesta, ma approvando più richieste pendenti // su libri diversi un admin poteva superarlo. Conta i prestiti attivi escluso // quello in approvazione; il lock sulla riga libro sopra serializza. - $maxLoans = (int) ((new \App\Models\SettingsRepository($db))->get('loans', 'max_active_loans_per_user', '0') ?? 0); - if ($maxLoans > 0) { + $circulation = new \App\Services\CirculationPolicy($db); + $approvalPolicy = $circulation->forExistingLoan($loanId); + if (!$approvalPolicy->loanable || $approvalPolicy->hasQuantityLimits()) { // Serialize concurrent approvals for the SAME user on DIFFERENT books: // the libri lock above only serializes this loan's book, so two // parallel approvals could both read activeCount below the cap and @@ -347,16 +348,15 @@ public function approveLoan(Request $request, Response $response, mysqli $db): R $userLockStmt->execute(); $userLockStmt->close(); - $cntStmt = $db->prepare("SELECT COUNT(*) AS c FROM prestiti WHERE utente_id = ? AND id != ? AND attivo = 1 AND stato IN ('prenotato','da_ritirare','in_corso','in_ritardo')"); - $cntStmt->bind_param('ii', $utenteId, $loanId); - $cntStmt->execute(); - $activeCount = (int) ($cntStmt->get_result()->fetch_assoc()['c'] ?? 0); - $cntStmt->close(); - if ($activeCount >= $maxLoans) { + $limitError = $circulation->loanLimitError($approvalPolicy, $utenteId, $loanId); + if ($limitError !== null) { $db->rollback(); $response->getBody()->write(json_encode([ 'success' => false, - 'message' => __('L\'utente ha raggiunto il numero massimo di prestiti attivi consentiti') + 'error' => $limitError, + 'message' => $limitError === 'max_loans_reached' + ? __('L\'utente ha raggiunto il numero massimo di prestiti attivi consentiti') + : \App\Support\LoanEligibility::errorMessage($limitError), ])); return $response->withHeader('Content-Type', 'application/json')->withStatus(409); } @@ -374,7 +374,8 @@ public function approveLoan(Request $request, Response $response, mysqli $db): R $pickupDays = (int) ($settingsRepo->get('loans', 'pickup_expiry_days', '3') ?? 3); // Base su $today (timezone applicativo) per coerenza con la // classificazione immediato/futuro a cavallo della mezzanotte. - $pickupDeadline = date('Y-m-d', strtotime("{$today} +{$pickupDays} days")); + // Open days only (closures calendar); identical to +N days without closures. + $pickupDeadline = \App\Support\LibraryCalendar::pickupDeadline($db, $today, $pickupDays); // Cap alla fine della finestra del prestito (L1): una deadline oltre // data_scadenza permetterebbe di confermare il ritiro di un prestito // già scaduto e terrebbe la copia impegnata oltre la finestra. @@ -399,6 +400,7 @@ public function approveLoan(Request $request, Response $response, mysqli $db): R WHERE c.id = ? AND c.libro_id = ? AND c.stato NOT IN ('perso', 'danneggiato', 'manutenzione', 'in_restauro', 'in_trasferimento') + AND " . (new \App\Services\CirculationPolicy($db))->lendableCopySql('c') . " AND NOT EXISTS ( SELECT 1 FROM prestiti p WHERE p.copia_id = c.id @@ -455,6 +457,7 @@ public function approveLoan(Request $request, Response $response, mysqli $db): R SELECT c.id FROM copie c WHERE c.libro_id = ? AND c.stato IN ('disponibile', 'prenotato') + AND " . (new \App\Services\CirculationPolicy($db))->lendableCopySql('c') . " AND NOT EXISTS ( SELECT 1 FROM prestiti own WHERE own.copia_id = c.id @@ -535,6 +538,37 @@ public function approveLoan(Request $request, Response $response, mysqli $db): R $lockCopyStmt->execute(); $lockCopyStmt->close(); + // The copy is known only now: a due date closed at its branch moves + // to the branch's next open day when the longer window still fits + // (otherwise the requested date stays, as before the calendar). + if ($dataScadenza !== null && $dataScadenza !== '') { + $branchDue = \App\Services\CirculationPolicy::dueDateFor((string) $dataScadenza, 0, $db, (int) $selectedCopy['id']); + if ($branchDue !== $dataScadenza + && (new \App\Services\CapacityService($db))->hasFreeCapacity($libroId, (string) $dataPrestito, $branchDue, excludePrestitoId: $loanId)) { + // The selected copy must be free on the added days too + // (same occupancy predicate as the recheck below). + $extraStart = \App\Support\LibraryCalendar::shift((string) $dataScadenza, 1); + $extraStmt = $db->prepare(" + SELECT 1 FROM prestiti + WHERE copia_id = ? AND id != ? + AND data_prestito <= ? + AND (stato = 'in_ritardo' OR (stato = 'in_corso' AND data_scadenza < ?) OR data_scadenza >= ?) + AND ( + (attivo = 1 AND stato IN ('in_corso','prenotato','da_ritirare','in_ritardo')) + OR (stato = 'pendente' AND copia_id IS NOT NULL) + ) + LIMIT 1 + "); + $extraStmt->bind_param('iisss', $selectedCopy['id'], $loanId, $branchDue, $today, $extraStart); + $extraStmt->execute(); + $extraBusy = (bool) $extraStmt->get_result()->fetch_row(); + $extraStmt->close(); + if (!$extraBusy) { + $dataScadenza = $branchDue; + } + } + } + // Occupazione canonica #157: la copia è tenuta da un prestito attivo // OPPURE da un pending di conversione prenotazione che ha già una // copia (attivo=0, stato='pendente', copia_id valorizzato). Omettere @@ -600,21 +634,29 @@ public function approveLoan(Request $request, Response $response, mysqli $db): R throw new \RuntimeException(__('Impossibile aggiornare lo stato della copia')); } + // The pickup days count the open days of the chosen copy's branch. + if ($pickupDeadline !== null) { + $pickupDeadline = \App\Support\LibraryCalendar::pickupDeadline($db, $today, $pickupDays, null, (int) $selectedCopy['id']); + if ($dataScadenza !== null && $dataScadenza !== '' && $pickupDeadline > $dataScadenza) { + $pickupDeadline = $dataScadenza; + } + } + // Assegna la copia al prestito con lo stato corretto e pickup_deadline se applicabile if ($pickupDeadline !== null) { $stmt = $db->prepare(" UPDATE prestiti - SET stato = ?, attivo = 1, copia_id = ?, pickup_deadline = ?{$pickupNotificationResetSql} + SET stato = ?, attivo = 1, copia_id = ?, pickup_deadline = ?, data_scadenza = ?{$pickupNotificationResetSql} WHERE id = ? AND stato = 'pendente' "); - $stmt->bind_param('sisi', $newState, $selectedCopy['id'], $pickupDeadline, $loanId); + $stmt->bind_param('sissi', $newState, $selectedCopy['id'], $pickupDeadline, $dataScadenza, $loanId); } else { $stmt = $db->prepare(" UPDATE prestiti - SET stato = ?, attivo = 1, copia_id = ?, pickup_deadline = NULL{$pickupNotificationResetSql} + SET stato = ?, attivo = 1, copia_id = ?, pickup_deadline = NULL, data_scadenza = ?{$pickupNotificationResetSql} WHERE id = ? AND stato = 'pendente' "); - $stmt->bind_param('sii', $newState, $selectedCopy['id'], $loanId); + $stmt->bind_param('sisi', $newState, $selectedCopy['id'], $dataScadenza, $loanId); } $stmt->execute(); $stmt->close(); @@ -669,6 +711,10 @@ public function approveLoan(Request $request, Response $response, mysqli $db): R // drifting from the authoritative computation above. 'loan_state' => $newState, 'is_future_loan' => $isFutureLoan, + // The window as written: the due date may have moved over a + // closure of the assigned copy's branch. + 'data_scadenza' => $dataScadenza, + 'copia_id' => (int) $selectedCopy['id'], ])); return $response->withHeader('Content-Type', 'application/json'); @@ -1491,6 +1537,12 @@ public function returnLoan(Request $request, Response $response, mysqli $db): Re $stmt->execute(); $stmt->close(); + // Late fine into the patron ledger in the same transaction (plan §7). + $lateFineCents = \App\Services\PatronLedger::forDb($db)->chargeLateReturn( + $loanId, + isset($_SESSION['user']['id']) ? (int) $_SESSION['user']['id'] : null + ); + // Update copy status (only if not in a non-lendable state) $copyAvailable = false; if ($copiaId) { @@ -1545,6 +1597,14 @@ public function returnLoan(Request $request, Response $response, mysqli $db): Re } $reservationManager->flushDeferredNotifications(); + if ($lateFineCents > 0) { + try { + (new \App\Support\NotificationService($db))->sendFineChargedNotification($loanId); + } catch (\Throwable $e) { + SecureLogger::warning('Fine charged notification failed', ['loan_id' => $loanId, 'error' => $e->getMessage()]); + } + } + // Notify wishlist users AFTER commit, only if book has available copies try { $availStmt = $db->prepare("SELECT copie_disponibili FROM libri WHERE id = ? AND deleted_at IS NULL"); diff --git a/app/Controllers/MarcExportController.php b/app/Controllers/MarcExportController.php new file mode 100644 index 000000000..7613dbecb --- /dev/null +++ b/app/Controllers/MarcExportController.php @@ -0,0 +1,325 @@ +getQueryParams(); + $format = (string) ($params['format'] ?? 'marc21'); + if (!in_array($format, self::FORMATS, true)) { + $format = 'marc21'; + } + $ids = null; + if (array_key_exists('ids', $params)) { + // ids[]=12&ids[]=13 is not the comma list this endpoint reads: + // refused, instead of answering with an empty download. + if (!is_string($params['ids'])) { + $_SESSION['error_message'] = __('Selezione non valida per l\'esportazione: riprova dalla lista dei libri.'); + return $response->withHeader('Location', url('/admin/books'))->withStatus(302); + } + $ids = self::parseIds($params['ids']); + if (count($ids) > self::MAX_SELECTED) { + $_SESSION['error_message'] = sprintf( + __('Selezione troppo grande per l\'esportazione: %d libri (massimo 1000). Riduci la selezione e riprova.'), + count($ids) + ); + return $response->withHeader('Location', url('/admin/books'))->withStatus(302); + } + } + + $stamp = date('Y-m-d'); + [$contentType, $filename] = match ($format) { + 'marcxml' => ['application/marcxml+xml; charset=UTF-8', "catalogo_marcxml_{$stamp}.xml"], + 'unimarc' => ['application/marc', "catalogo_unimarc_{$stamp}.mrc"], + default => ['application/marc', "catalogo_marc21_{$stamp}.mrc"], + }; + + \App\Support\SecureLogger::info('[MARC export]', [ + 'format' => $format, + 'selected' => $ids === null ? null : count($ids), + 'user_id' => $_SESSION['user']['id'] ?? null, + ]); + + // The records are written while the download is sent: release the + // session lock first, or every other request of this user (another + // tab, an import chunk) waits for the whole export. + if (session_status() === PHP_SESSION_ACTIVE) { + session_write_close(); + } + + return $response + ->withBody(new GeneratorStream($this->generate($db, $format, $ids))) + ->withHeader('Content-Type', $contentType) + ->withHeader('Content-Disposition', 'attachment; filename="' . $filename . '"') + ->withHeader('Cache-Control', 'no-cache, no-store, must-revalidate') + ->withHeader('X-Accel-Buffering', 'no'); + } + + /** + * @param list|null $ids + * @return \Generator + */ + public function generate(\mysqli $db, string $format, ?array $ids): \Generator + { + $xml = null; + if ($format === 'marcxml') { + $xml = new MarcXmlWriter(MarcXmlWriter::NS_MARC21); + $xml->startCollection(); + yield $xml->output(); + } + foreach ($this->books($db, $ids) as [$row, $authors, $publishers, $genre]) { + if ($xml !== null) { + $xml->writeRecord(BookMarcBuilder::marc21($row, $authors, $publishers, $genre, true)); + yield $xml->output(); + continue; + } + $cuts = []; + $bytes = self::serializeIso($format, $row, $authors, $publishers, $genre, $cuts, $level); + if ($bytes === null) { + \App\Support\SecureLogger::error('[MARC export] record too long for ISO 2709 even reduced, skipped', ['book_id' => $row['id'] ?? null]); + continue; + } + if ($level !== 'full') { + \App\Support\SecureLogger::warning('[MARC export] record over the ISO 2709 limit, exported ' . ($level === 'shortened' ? 'with long texts shortened' : 'as a reduced record'), ['book_id' => $row['id'] ?? null]); + } + // A field over the 9,999-byte ISO 2709 field limit is cut at a + // subfield boundary: the file is still readable, the log says what + // was lost. + foreach ($cuts as $cut) { + \App\Support\SecureLogger::warning('[MARC export] field cut to the ISO 2709 field limit', ['book_id' => $row['id'] ?? null, 'detail' => $cut]); + } + yield $bytes; + } + if ($xml !== null) { + $xml->endCollection(); + yield $xml->output(); + } + } + + /** + * Book ids from a comma-separated list: only tokens made of digits count + * ("12abc" is not book 12), positive, first-seen order, no duplicates. + * + * @return list + */ + public static function parseIds(string $raw): array + { + $ids = []; + foreach (explode(',', $raw) as $token) { + $token = trim($token); + if ($token === '' || !ctype_digit($token)) { + continue; + } + $id = (int) $token; + if ($id > 0) { + $ids[$id] = $id; + } + } + return array_values($ids); + } + + /** + * One book as an ISO 2709 record that always fits the 99,999-byte limit, + * so an export never leaves a book out. The full record first; if it is + * too long, the same record with every text over 1000 characters + * shortened; if even that is too long (hundreds of contributors or + * subjects), a reduced record: texts cut to 200 characters and at most + * 20 authors and 5 publishers, which is bounded far below the limit. + * $level says which of the three was written ("full", "shortened", + * "reduced"); $cuts holds the field cuts of that record only. + * + * @param array $row + * @param list> $authors + * @param list> $publishers + * @param array|null $genre + * @param list $cuts + */ + public static function serializeIso(string $format, array $row, array $authors, array $publishers, ?array $genre, array &$cuts, ?string &$level = null): ?string + { + $attempts = [ + 'full' => [$row, $authors, $publishers], + 'shortened' => [self::shortenTexts($row), $authors, $publishers], + 'reduced' => [self::shortenTexts($row, self::REDUCED_TEXT_LENGTH), array_slice($authors, 0, self::REDUCED_AUTHORS), array_slice($publishers, 0, self::REDUCED_PUBLISHERS)], + ]; + foreach ($attempts as $name => [$r, $a, $p]) { + $record = $format === 'unimarc' + ? BookMarcBuilder::unimarc($r, $a, $p, $genre, true) + : BookMarcBuilder::marc21($r, $a, $p, $genre, true); + // Only the cuts of the record that is written are reported. + $notes = []; + try { + $bytes = Iso2709Writer::serialize($record, $notes); + $cuts = $notes ?? []; + $level = $name; + return $bytes; + } catch (\OverflowException) { + continue; + } + } + $cuts = []; + $level = null; + return null; + } + + /** + * A copy of the book row with every text longer than $max characters + * cut there, for a record that would not fit ISO 2709 otherwise. + * + * @param array $row + * @return array + */ + public static function shortenTexts(array $row, int $max = 1000): array + { + foreach ($row as $key => $value) { + if (is_string($value) && mb_strlen($value) > $max) { + $row[$key] = rtrim(mb_substr($value, 0, $max)) . '…'; + } + } + return $row; + } + + /** + * The books to export, page by page, with their authors (in credit + * order), publishers and genre. + * + * @param list|null $ids + * @return \Generator, 1: list>, 2: list>, 3: array|null}> + */ + private function books(\mysqli $db, ?array $ids): \Generator + { + if ($ids === []) { + return; + } + $hasJunction = \App\Support\SchemaInfo::hasLibriEditori($db); + $lastId = 0; + while (true) { + // A desiderata request is not a holding: BookMarcBuilder has no + // field for the flag, so exporting it would read as a real copy. + $sql = 'SELECT * FROM libri WHERE deleted_at IS NULL AND ' + . \App\Support\BookVisibility::catalogue($db) . ' AND id > ?'; + $types = 'i'; + $params = [$lastId]; + if ($ids !== null) { + $sql .= ' AND id IN (' . implode(',', array_fill(0, count($ids), '?')) . ')'; + $types .= str_repeat('i', count($ids)); + $params = array_merge($params, $ids); + } + $sql .= ' ORDER BY id LIMIT ' . self::PAGE; + $stmt = $db->prepare($sql); + if ($stmt === false) { + return; + } + $stmt->bind_param($types, ...$params); + $stmt->execute(); + $rows = $stmt->get_result()->fetch_all(MYSQLI_ASSOC); + $stmt->close(); + if ($rows === []) { + return; + } + $pageIds = array_map(static fn (array $r): int => (int) $r['id'], $rows); + $authors = $this->grouped($db, + "SELECT la.libro_id, a.nome, la.ruolo, la.ordine_credito + FROM libri_autori la JOIN autori a ON a.id = la.autore_id + WHERE la.libro_id IN (%s) + ORDER BY la.libro_id, CASE la.ruolo WHEN 'principale' THEN 0 WHEN 'co-autore' THEN 1 ELSE 2 END, + la.ordine_credito IS NULL, la.ordine_credito, a.nome", + $pageIds); + $publishers = $hasJunction ? $this->grouped($db, + 'SELECT le.libro_id, e.id, e.nome FROM libri_editori le JOIN editori e ON e.id = le.editore_id + WHERE le.libro_id IN (%s) ORDER BY le.libro_id, le.ordine, e.nome', + $pageIds) : []; + $primary = $this->byId($db, 'SELECT id, nome FROM editori WHERE id IN (%s)', array_values(array_filter(array_map(static fn (array $r): int => (int) ($r['editore_id'] ?? 0), $rows)))); + $genres = $this->byId($db, 'SELECT id, nome FROM generi WHERE id IN (%s)', array_values(array_filter(array_map(static fn (array $r): int => (int) ($r['genere_id'] ?? 0), $rows)))); + foreach ($rows as $row) { + $id = (int) $row['id']; + $bookPublishers = $publishers[$id] ?? []; + $primaryId = (int) ($row['editore_id'] ?? 0); + if ($bookPublishers === [] && $primaryId > 0 && isset($primary[$primaryId])) { + $bookPublishers = [$primary[$primaryId]]; + } + $genreId = (int) ($row['genere_id'] ?? 0); + yield [$row, $authors[$id] ?? [], $bookPublishers, $genreId > 0 ? ($genres[$genreId] ?? null) : null]; + $lastId = $id; + } + if (count($rows) < self::PAGE) { + return; + } + } + } + + /** + * @param list $ids + * @return array>> + */ + private function grouped(\mysqli $db, string $sql, array $ids): array + { + $out = []; + foreach ($this->fetch($db, $sql, $ids) as $row) { + $out[(int) $row['libro_id']][] = $row; + } + return $out; + } + + /** + * @param list $ids + * @return array> + */ + private function byId(\mysqli $db, string $sql, array $ids): array + { + $out = []; + foreach ($this->fetch($db, $sql, array_values(array_unique($ids))) as $row) { + $out[(int) $row['id']] = $row; + } + return $out; + } + + /** + * @param list $ids + * @return list> + */ + private function fetch(\mysqli $db, string $sql, array $ids): array + { + if ($ids === []) { + return []; + } + $stmt = $db->prepare(sprintf($sql, implode(',', array_fill(0, count($ids), '?')))); + if ($stmt === false) { + return []; + } + $stmt->bind_param(str_repeat('i', count($ids)), ...$ids); + $stmt->execute(); + $rows = $stmt->get_result()->fetch_all(MYSQLI_ASSOC); + $stmt->close(); + return $rows; + } +} diff --git a/app/Controllers/MarcImportController.php b/app/Controllers/MarcImportController.php new file mode 100644 index 000000000..1a6cca54f --- /dev/null +++ b/app/Controllers/MarcImportController.php @@ -0,0 +1,543 @@ +options() : []; + include __DIR__ . '/../Views/admin/marc_import.php'; + $content = (string) ob_get_clean(); + $response->getBody()->write($content); + return $response->withHeader('Content-Type', 'text/html'); + } + + /** + * Stage the uploaded file and answer with the record count and a preview. + */ + public function upload(Request $request, Response $response, \mysqli $db): Response + { + if (($off = \App\Support\Features::requireOrRedirect('marc', $response)) !== null) { + return $off; + } + @ini_set('display_errors', '0'); + @set_time_limit(300); + $data = (array) $request->getParsedBody(); + $file = $request->getUploadedFiles()['marc_file'] ?? null; + if (!$file instanceof \Psr\Http\Message\UploadedFileInterface || $file->getError() !== UPLOAD_ERR_OK) { + return $this->json($response, ['success' => false, 'error' => __('Nessun file caricato')], 400); + } + $name = (string) $file->getClientFilename(); + $extension = strtolower(pathinfo($name, PATHINFO_EXTENSION)); + if (!in_array($extension, self::EXTENSIONS, true)) { + return $this->json($response, ['success' => false, 'error' => __('Il file deve essere un file MARC (.mrc, .marc, .iso) o MARCXML (.xml)')], 400); + } + if ((int) $file->getSize() > self::MAX_FILE_BYTES) { + return $this->json($response, ['success' => false, 'error' => __('File troppo grande. Dimensione massima %d MB.', (int) (self::MAX_FILE_BYTES / 1048576))], 400); + } + $tmp = (string) $file->getStream()->getMetadata('uri'); + if ($tmp === '' || !is_file($tmp)) { + return $this->json($response, ['success' => false, 'error' => __('Impossibile leggere il file caricato')], 400); + } + $container = MarcFormat::containerOfFile($tmp); + if ($container === null) { + return $this->json($response, ['success' => false, 'error' => __('Il file non contiene record MARC riconoscibili (ISO 2709 o MARCXML)')], 400); + } + + $flavour = (string) ($data['flavour'] ?? MarcFormat::AUTO); + if ($flavour !== MarcFormat::AUTO && !in_array($flavour, MarcFormat::FLAVOURS, true)) { + $flavour = MarcFormat::AUTO; + } + $encoding = (string) ($data['encoding'] ?? Iso2709Reader::ENCODING_AUTO); + if (!in_array($encoding, [Iso2709Reader::ENCODING_AUTO, Iso2709Reader::ENCODING_UTF8, Iso2709Reader::ENCODING_MARC8], true)) { + $encoding = Iso2709Reader::ENCODING_AUTO; + } + $mode = (string) ($data['duplicates'] ?? MarcBookImporter::MODE_SKIP); + if (!in_array($mode, MarcBookImporter::MODES, true)) { + $mode = MarcBookImporter::MODE_SKIP; + } + + $dir = __DIR__ . '/../../writable/uploads'; + if (!is_dir($dir)) { + @mkdir($dir, 0755, true); + } + $staging = $dir . '/marc_import_' . preg_replace('/[^A-Za-z0-9]/', '', session_id()) . '_' . bin2hex(random_bytes(6)) . '.jsonl'; + $out = @fopen($staging, 'wb'); + if ($out === false) { + return $this->json($response, ['success' => false, 'error' => __('Impossibile salvare il file caricato')], 500); + } + + $total = 0; + $unreadable = 0; + $truncated = false; + $preview = []; + $importer = new MarcBookImporter($db, $mode, false); + $csv = new CsvImportController(); + try { + $reader = $container === MarcFormat::CONTAINER_XML + ? MarcXmlReader::fromPath($tmp) + : Iso2709Reader::fromPath($tmp, $encoding); + foreach ($reader as $position => $item) { + if ($total >= self::MAX_RECORDS) { + $truncated = true; + break; + } + $total++; + $line = $this->stageLine((int) $position, $item, $flavour); + $encoded = json_encode($line, JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_SUBSTITUTE); + $bytes = ($encoded === false ? '' : $encoded) . "\n"; + // A short or failed write (full disk, quota) must not be counted: + // the chunks would import a truncated staging file and report success. + if ($encoded === false || fwrite($out, $bytes) !== strlen($bytes)) { + fclose($out); + @unlink($staging); + \App\Support\SecureLogger::error('[MARC import] cannot write staging file', ['record' => $total]); + return $this->json($response, ['success' => false, 'error' => __('Impossibile salvare il file caricato')], 500); + } + if (isset($line['e'])) { + $unreadable++; + } + if (count($preview) < self::PREVIEW_SIZE) { + $preview[] = $this->previewRow($line, $importer, $csv); + } + } + } catch (\Throwable $e) { + fclose($out); + @unlink($staging); + \App\Support\SecureLogger::error('[MARC import] cannot read upload', ['error' => $e->getMessage()]); + return $this->json($response, ['success' => false, 'error' => __('Impossibile leggere il file caricato')], 400); + } + if (!fclose($out)) { + // The last buffered bytes are flushed here: a failure loses records too. + @unlink($staging); + \App\Support\SecureLogger::error('[MARC import] cannot close staging file', ['records' => $total]); + return $this->json($response, ['success' => false, 'error' => __('Impossibile salvare il file caricato')], 500); + } + if ($total === 0) { + @unlink($staging); + return $this->json($response, ['success' => false, 'error' => __('Il file non contiene record MARC riconoscibili (ISO 2709 o MARCXML)')], 400); + } + + // Every chunk and discard request names the preview it belongs to: a + // second upload (another tab) replaces the session state, and a page + // still showing the first preview must not import the second file. + $stagingId = bin2hex(random_bytes(12)); + // The previous preview is dropped only now: a file that cannot be + // staged leaves it (its file and session state) importable. + $this->discardStaging(); + $_SESSION[self::SESSION_KEY] = [ + 'staging_id' => $stagingId, + 'file_path' => $staging, + 'original_filename' => $name, + 'total_rows' => $total, + 'mode' => $mode, + 'covers' => !empty($data['download_covers']), + // Validated once here; a supplier deleted during the import is + // not assigned, and each book left without it is listed. + 'fornitore_id' => CataloguingExtras::existingSupplierId($db, $data['fornitore_id'] ?? null), + 'offset' => 0, + 'processed' => 0, + 'counters' => [], + 'errors' => [], + ]; + + return $this->json($response, [ + 'success' => true, + 'staging_id' => $stagingId, + 'total_rows' => $total, + 'unreadable' => $unreadable, + 'truncated' => $truncated ? sprintf(__('Il file contiene più di %d record: vengono importati solo i primi %d'), self::MAX_RECORDS, self::MAX_RECORDS) : null, + 'container' => $container, + 'chunk_size' => !empty($data['download_covers']) ? 2 : self::CHUNK_SIZE, + 'preview' => $preview, + ]); + } + + /** + * Import the next records of the staged file. A repeated request for a + * chunk already done (a retry after a lost response) does nothing. + */ + public function chunk(Request $request, Response $response, \mysqli $db): Response + { + if (($off = \App\Support\Features::requireOrRedirect('marc', $response)) !== null) { + return $off; + } + @set_time_limit(600); + @ini_set('display_errors', '0'); + $payload = json_decode((string) $request->getBody(), true); + $state = $_SESSION[self::SESSION_KEY] ?? null; + $stagingId = is_array($payload) ? (string) ($payload['staging_id'] ?? '') : ''; + if (is_array($payload) && (!is_array($state) || !hash_equals((string) ($state['staging_id'] ?? ''), $stagingId))) { + // The import of this preview already completed (its last response + // was lost): the same final answer again, nothing re-imported. + $done = $_SESSION[self::DONE_KEY] ?? null; + if ($stagingId !== '' && is_array($done) && hash_equals((string) ($done['staging_id'] ?? ''), $stagingId) && is_array($done['result'] ?? null)) { + return $this->json($response, $done['result']); + } + if (is_array($state)) { + return $this->json($response, ['success' => false, 'stale' => true, 'error' => __('Questa anteprima è stata sostituita da un altro file caricato: carica di nuovo il file per importarlo.')], 409); + } + } + if (!is_array($payload) || !is_array($state)) { + return $this->json($response, ['success' => false, 'error' => __('Sessione import scaduta o dati non validi')], 400); + } + $start = max(0, (int) ($payload['start'] ?? 0)); + $size = max(1, min((int) ($payload['size'] ?? self::CHUNK_SIZE), self::CHUNK_SIZE)); + $processed = (int) $state['processed']; + + if ($start === $processed && $processed < (int) $state['total_rows']) { + $handle = @fopen((string) $state['file_path'], 'rb'); + if ($handle === false) { + return $this->json($response, ['success' => false, 'error' => __('Impossibile aprire il file caricato')], 500); + } + fseek($handle, (int) $state['offset']); + $importer = new MarcBookImporter($db, (string) $state['mode'], (bool) $state['covers']); + $counters = (array) $state['counters']; + $supplierId = (int) ($state['fornitore_id'] ?? 0); + for ($i = 0; $i < $size; $i++) { + $raw = fgets($handle); + if ($raw === false) { + break; + } + $processed++; + $line = json_decode($raw, true); + if (!is_array($line)) { + self::listIssue($state, ['line' => $processed, 'title' => '', 'message' => __('Record non leggibile'), 'type' => 'parse']); + continue; + } + $position = (int) ($line['p'] ?? $processed); + if (isset($line['e'])) { + self::listIssue($state, [ + 'line' => $position, + 'title' => (string) ($line['x'] ?? ''), + 'message' => (string) $line['e'], + 'type' => 'parse', + ]); + continue; + } + $bookData = is_array($line['d'] ?? null) ? $line['d'] : []; + $title = (string) ($bookData['titolo'] ?? ''); + try { + $warnings = []; + $result = $importer->import($bookData, $counters, $warnings); + if ($result['action'] === 'skipped') { + self::listSkippedDuplicate($state, $position, $title, (int) $result['id']); + } + if ($supplierId > 0 && $result['id'] !== null && self::supplierApplies((string) $result['action'], (string) $state['mode'])) { + // Listed under its own type: the book is imported + // and counted, only its supplier link is missing. + try { + if (!CataloguingExtras::assignSupplier($db, (int) $result['id'], $supplierId)) { + self::listIssue($state, ['line' => $position, 'title' => $title, 'message' => __('Fornitore non assegnato: è stato eliminato durante l\'importazione.'), 'type' => 'supplier']); + } + } catch (\Throwable $e) { + self::listIssue($state, ['line' => $position, 'title' => $title, 'message' => __('Fornitore non assegnato') . ': ' . $e->getMessage(), 'type' => 'supplier']); + \App\Support\SecureLogger::warning('[MARC import] supplier not assigned', ['libro_id' => (int) $result['id'], 'error' => $e->getMessage()]); + } + } + foreach ($warnings as $warning) { + self::listIssue($state, ['line' => $position, 'title' => $title, 'message' => $warning, 'type' => 'cover']); + } + } catch (\Throwable $e) { + self::listIssue($state, ['line' => $position, 'title' => $title, 'message' => $e->getMessage(), 'type' => 'validation']); + \App\Support\SecureLogger::warning('[MARC import] record failed', ['position' => $position, 'error' => $e->getMessage()]); + } + } + $offset = ftell($handle); + fclose($handle); + if ($processed === $start) { + // The staged file ended before total_rows (truncated or replaced): + // answering "success" with the same position would make the + // page ask for this chunk forever. + \App\Support\SecureLogger::warning('[MARC import] staging file ended early', ['processed' => $processed, 'total' => (int) $state['total_rows']]); + return $this->json($response, ['success' => false, 'error' => __('Il file caricato è incompleto: import interrotto.')], 500); + } + $state['offset'] = $offset === false ? (int) $state['offset'] : $offset; + $state['processed'] = $processed; + $state['counters'] = $counters; + $_SESSION[self::SESSION_KEY] = $state; + } + + $complete = $processed >= (int) $state['total_rows']; + $counters = (array) $state['counters']; + $errors = (array) $state['errors']; + $failed = self::failedCount($state); + $importId = null; + if ($complete) { + $importId = $this->persistHistory($db, $state, $failed); + // The history row (once created) is kept in the state, so a retry + // completes that row instead of adding a second one. + $_SESSION[self::SESSION_KEY] = $state; + if ($importId === null) { + // The books are in, but the history and the error list are not: + // keep the session so the page can ask again (a retry at the + // final position imports nothing and only saves the history). + return $this->json($response, ['success' => false, 'error' => __('Import completato, ma la cronologia non è stata salvata. Riprova per salvarla.')], 500); + } + } + + $result = [ + 'success' => true, + 'processed' => $processed, + 'total_rows' => (int) $state['total_rows'], + 'imported' => (int) ($counters['imported'] ?? 0), + 'updated' => (int) ($counters['updated'] ?? 0), + 'skipped' => (int) ($counters['skipped'] ?? 0), + 'covers' => (int) ($counters['covers'] ?? 0), + 'authors_created' => (int) ($counters['authors_created'] ?? 0), + 'publishers_created' => (int) ($counters['publishers_created'] ?? 0), + 'failed' => $failed, + 'error_details' => array_map( + static fn (array $e): string => sprintf('#%d (%s): %s', (int) ($e['line'] ?? 0), (string) ($e['title'] ?? ''), (string) ($e['message'] ?? '')), + array_slice(array_values(array_filter($errors, static fn (array $e): bool => ($e['type'] ?? '') !== 'duplicate')), 0, 50) + ), + 'complete' => $complete, + 'import_id' => $importId, + ]; + if ($complete) { + $_SESSION[self::DONE_KEY] = ['staging_id' => (string) ($state['staging_id'] ?? ''), 'result' => $result]; + $this->discardStaging(); + } + return $this->json($response, $result); + } + + /** + * Whether the supplier chosen for the batch is written on a record's + * book: a book the import created, or one it overwrote. A skipped + * duplicate and a book only completed in its empty fields ("fill") keep + * the supplier they have. + */ + public static function supplierApplies(string $action, string $mode): bool + { + return $action === 'created' || ($action === 'updated' && $mode === MarcBookImporter::MODE_OVERWRITE); + } + + /** + * Record a parse error, a failed record or a cover warning. Every one is + * counted; only the first MAX_LISTED_ISSUES of each type are kept in the + * session and in import_logs, so a 20,000-record file with an unreachable + * cover host does not rewrite an ever-growing session on every chunk. + * + * @param array $state + * @param array{line:int,title:string,message:string,type:string} $issue + */ + public static function listIssue(array &$state, array $issue): void + { + $type = $issue['type']; + $counts = (array) ($state['issue_counts'] ?? []); + $counts[$type] = (int) ($counts[$type] ?? 0) + 1; + $state['issue_counts'] = $counts; + if ($counts[$type] <= self::MAX_LISTED_ISSUES) { + $state['errors'][] = $issue; + } + } + + /** + * Records that could not be imported (parse + validation), including the + * ones beyond the listed cap. + * + * @param array $state + */ + public static function failedCount(array $state): int + { + if (isset($state['issue_counts']) && is_array($state['issue_counts'])) { + return (int) ($state['issue_counts']['parse'] ?? 0) + (int) ($state['issue_counts']['validation'] ?? 0); + } + $errors = (array) ($state['errors'] ?? []); + return count(array_filter($errors, static fn (array $e): bool => in_array($e['type'] ?? '', ['validation', 'parse'], true))); + } + + /** + * Note a skipped duplicate in the run's report, up to MAX_LISTED_DUPLICATES. + * + * @param array $state + */ + public static function listSkippedDuplicate(array &$state, int $position, string $title, int $bookId): void + { + $listed = (int) ($state['duplicates_listed'] ?? 0); + if ($listed >= self::MAX_LISTED_DUPLICATES) { + return; + } + $state['duplicates_listed'] = $listed + 1; + $state['errors'][] = [ + 'line' => $position, + 'title' => $title, + 'message' => sprintf(__('Già in catalogo (libro #%d): saltato'), $bookId), + 'type' => 'duplicate', + ]; + } + + /** + * @param MarcRecord|MarcReadError $item + * @return array + */ + private function stageLine(int $position, MarcRecord|MarcReadError $item, string $flavour): array + { + if ($item instanceof MarcReadError) { + return ['p' => $position, 'e' => $item->message, 'x' => $item->excerpt]; + } + $actual = $flavour === MarcFormat::AUTO ? MarcFormat::detectFlavour($item) : $flavour; + try { + $data = MarcFormat::mapperFor($actual)->map($item); + } catch (\Throwable $e) { + return ['p' => $position, 'e' => $e->getMessage(), 'x' => '']; + } + return ['p' => $position, 'f' => $actual, 'd' => $data]; + } + + /** + * @param array $line + * @return array + */ + private function previewRow(array $line, MarcBookImporter $importer, CsvImportController $csv): array + { + if (isset($line['e'])) { + return ['position' => $line['p'], 'error' => (string) $line['e']]; + } + $data = (array) $line['d']; + $existing = null; + try { + $existing = $importer->findExisting($csv->normalizeImportRow(\App\Support\Marc\MarcBookData::toImportRow($data))); + } catch (\Throwable) { + $existing = null; + } + return [ + 'position' => $line['p'], + 'flavour' => $line['f'] ?? '', + 'titolo' => (string) ($data['titolo'] ?? ''), + 'sottotitolo' => (string) ($data['sottotitolo'] ?? ''), + 'autori' => implode('; ', (array) ($data['autori'] ?? [])), + 'editore' => implode('; ', (array) ($data['editori'] ?? [])), + 'anno' => (string) ($data['anno_pubblicazione'] ?? ''), + 'isbn' => (string) (($data['isbn13'] ?? '') !== '' ? $data['isbn13'] : ($data['isbn10'] ?? '')), + 'copertina' => (string) ($data['copertina_url'] ?? '') !== '', + 'existing_id' => $existing, + ]; + } + + /** + * Save the run's history. The import_logs row is created once and its id + * kept in $state: a retry after a failed save completes that same row + * (left 'failed' or 'processing' by the attempt before), never a second. + * + * @param array $state + */ + private function persistHistory(\mysqli $db, array &$state, int $failed): ?string + { + try { + $userId = isset($_SESSION['user']['id']) ? (int) $_SESSION['user']['id'] : null; + $logger = !empty($state['import_id']) ? \App\Support\ImportLogger::resume($db, (string) $state['import_id']) : null; + $logger ??= new \App\Support\ImportLogger($db, 'marc', (string) $state['original_filename'], $userId); + $state['import_id'] = $logger->getImportId(); + $counters = (array) $state['counters']; + $logger->setStats([ + 'imported' => (int) ($counters['imported'] ?? 0), + 'updated' => (int) ($counters['updated'] ?? 0), + 'failed' => $failed, + 'authors_created' => (int) ($counters['authors_created'] ?? 0), + 'publishers_created' => (int) ($counters['publishers_created'] ?? 0), + 'scraped' => (int) ($counters['covers'] ?? 0), + ]); + foreach ((array) $state['errors'] as $error) { + $logger->addError((int) ($error['line'] ?? 0), (string) ($error['title'] ?? ''), (string) ($error['message'] ?? ''), (string) ($error['type'] ?? 'validation')); + } + if (!$logger->complete((int) $state['total_rows'])) { + $logger->fail('Failed to persist import history', (int) $state['total_rows']); + return null; + } + return $logger->getImportId(); + } catch (\Throwable $e) { + \App\Support\SecureLogger::error('[MARC import] history not saved', ['error' => $e->getMessage()]); + return null; + } + } + + /** + * The preview was cancelled: drop the staged records now instead of at + * the next upload. Only the preview named by the request: a page whose + * preview another upload already replaced leaves the new one alone. + */ + public function discard(Request $request, Response $response): Response + { + $payload = json_decode((string) $request->getBody(), true); + $stagingId = is_array($payload) ? (string) ($payload['staging_id'] ?? '') : ''; + $state = $_SESSION[self::SESSION_KEY] ?? null; + if (is_array($state) && $stagingId !== '' && !hash_equals((string) ($state['staging_id'] ?? ''), $stagingId)) { + return $this->json($response, ['success' => true, 'stale' => true]); + } + $this->discardStaging(); + return $this->json($response, ['success' => true]); + } + + private function discardStaging(): void + { + $state = $_SESSION[self::SESSION_KEY] ?? null; + if (is_array($state) && !empty($state['file_path'])) { + $path = (string) $state['file_path']; + $dir = realpath(__DIR__ . '/../../writable/uploads'); + $real = realpath($path); + if ($dir !== false && $real !== false && str_starts_with($real, $dir . DIRECTORY_SEPARATOR) && str_starts_with(basename($real), 'marc_import_')) { + @unlink($real); + } + } + unset($_SESSION[self::SESSION_KEY]); + } + + /** @param array $payload */ + private function json(Response $response, array $payload, int $status = 200): Response + { + $response->getBody()->write((string) json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_SUBSTITUTE)); + return $response->withHeader('Content-Type', 'application/json')->withStatus($status); + } +} diff --git a/app/Controllers/PatronCardsController.php b/app/Controllers/PatronCardsController.php new file mode 100644 index 000000000..ddf917e58 --- /dev/null +++ b/app/Controllers/PatronCardsController.php @@ -0,0 +1,274 @@ +withStatus(403); + } + $rows = $this->loadUsers($db, [$id]); + if ($rows === []) { + return $response->withStatus(404); + } + if (!$this->canRead($rows[0])) { + return $response->withStatus(403); + } + $code = preg_replace('/[^A-Za-z0-9_-]/', '', (string) $rows[0]['codice_tessera']) ?: (string) $id; + return $this->pdfResponse($response, $db, $rows, $request->getQueryParams(), 'tessera_' . $code . '.pdf'); + } + + public function bulkPdf(Request $request, Response $response, mysqli $db): Response + { + if (($off = \App\Support\Features::requireOrRedirect('patron_cards', $response)) !== null) { + return $off; + } + if (!\App\Support\Permissions::can('users.manage')) { + return $response->withStatus(403); + } + $query = $request->getQueryParams(); + $ids = []; + foreach (explode(',', is_string($query['ids'] ?? null) ? $query['ids'] : '') as $part) { + $n = (int) trim($part); + if ($n > 0) { + $ids[$n] = $n; + } + } + if ($ids === []) { + return $response->withStatus(400); + } + if (count($ids) > self::MAX_BULK) { + // Never a PDF with fewer cards than were selected: the page warns + // before opening it, and a request that gets here anyway is refused. + $response->getBody()->write(sprintf(__('Si possono stampare al massimo %d tessere alla volta: selezionane di meno.'), self::MAX_BULK)); + return $response->withStatus(422)->withHeader('Content-Type', 'text/plain; charset=UTF-8'); + } + $rows = array_values(array_filter($this->loadUsers($db, array_values($ids)), fn (array $row): bool => $this->canRead($row))); + if ($rows === []) { + return $response->withStatus(404); + } + return $this->pdfResponse($response, $db, $rows, $query, 'tessere.pdf'); + } + + /** Stream the stored photo (never cached by shared caches). */ + public function photo(Request $request, Response $response, mysqli $db, int $id): Response + { + if (($off = \App\Support\Features::requireOrRedirect('patron_photos', $response)) !== null) { + return $off; + } + if (!\App\Support\Permissions::can('users.manage')) { + return $response->withStatus(403); + } + $rows = $this->loadUsers($db, [$id]); + if ($rows === [] || !$this->canRead($rows[0])) { + return $response->withStatus($rows === [] ? 404 : 403); + } + $path = PatronPhoto::absolutePath($rows[0]['foto_profilo_url'] ?? null); + if ($path === null) { + return $response->withStatus(404); + } + $bytes = (string) file_get_contents($path); + $response->getBody()->write($bytes); + return $response + ->withHeader('Content-Type', 'image/jpeg') + ->withHeader('Content-Length', (string) strlen($bytes)) + ->withHeader('Cache-Control', 'private, no-store') + ->withHeader('X-Content-Type-Options', 'nosniff'); + } + + /** Save a photo sent as multipart field `photo` (webcam frame or picked file). JSON reply. */ + public function uploadPhoto(Request $request, Response $response, mysqli $db, int $id): Response + { + if (($off = \App\Support\Features::requireOrRedirect('patron_photos', $response)) !== null) { + return $off; + } + $guard = $this->editGuard($db, $id); + if ($guard !== null) { + return $this->json($response, ['success' => false, 'error' => $guard[1]], $guard[0]); + } + $files = $request->getUploadedFiles(); + $file = $files['photo'] ?? null; + if (!$file instanceof \Psr\Http\Message\UploadedFileInterface || $file->getError() !== UPLOAD_ERR_OK) { + $tooLarge = $file instanceof \Psr\Http\Message\UploadedFileInterface + && in_array($file->getError(), [UPLOAD_ERR_INI_SIZE, UPLOAD_ERR_FORM_SIZE], true); + return $this->json($response, ['success' => false, 'error' => $tooLarge ? __('La foto supera la dimensione massima consentita.') : __('Nessuna foto ricevuta.')], 400); + } + $tmp = $file->getStream()->getMetadata('uri'); + if (!is_string($tmp) || !is_file($tmp)) { + return $this->json($response, ['success' => false, 'error' => __('Nessuna foto ricevuta.')], 400); + } + $error = PatronPhoto::store($db, $id, $tmp); + if ($error !== null) { + $message = match ($error) { + 'too_large' => __('La foto supera la dimensione massima consentita.'), + 'invalid_image' => __('Il file non è un\'immagine JPEG, PNG o WebP valida.'), + default => __('Impossibile salvare la foto. Riprova più tardi.'), + }; + return $this->json($response, ['success' => false, 'error' => $message], $error === 'storage' || $error === 'db' ? 500 : 422); + } + \App\Support\SecureLogger::info('Patron photo updated', ['user_id' => $id, 'by' => $_SESSION['user']['id'] ?? null]); + return $this->json($response, [ + 'success' => true, + 'url' => url('/admin/users/' . $id . '/photo') . '?v=' . substr(sha1((string) PatronPhoto::storedFor($db, $id)), 0, 10), + ]); + } + + /** Remove the photo (the reader's right to erasure). JSON for fetch(), redirect for a plain form. */ + public function deletePhoto(Request $request, Response $response, mysqli $db, int $id): Response + { + if (($off = \App\Support\Features::requireOrRedirect('patron_photos', $response)) !== null) { + return $off; + } + $wantsJson = str_contains(strtolower($request->getHeaderLine('Accept')), 'application/json'); + $guard = $this->editGuard($db, $id); + if ($guard !== null) { + return $wantsJson + ? $this->json($response, ['success' => false, 'error' => $guard[1]], $guard[0]) + : $response->withStatus($guard[0]); + } + $ok = PatronPhoto::delete($db, $id); + if ($ok) { + \App\Support\SecureLogger::info('Patron photo deleted', ['user_id' => $id, 'by' => $_SESSION['user']['id'] ?? null]); + } + if ($wantsJson) { + return $this->json($response, $ok ? ['success' => true] : ['success' => false, 'error' => __('Impossibile eliminare la foto. Riprova più tardi.')], $ok ? 200 : 500); + } + $_SESSION[$ok ? 'success_message' : 'error_message'] = $ok ? __('Foto eliminata.') : __('Impossibile eliminare la foto. Riprova più tardi.'); + return $response->withHeader('Location', url('/admin/users/details/' . $id))->withStatus(302); + } + + /** + * @param list> $rows + * @param array $query + */ + private function pdfResponse(Response $response, mysqli $db, array $rows, array $query, string $filename): Response + { + $repository = new SettingsRepository($db); + $settings = PatronCardSettings::resolve($repository); + $format = is_string($query['format'] ?? null) && in_array($query['format'], PatronCardSettings::FORMATS, true) + ? $query['format'] + : $settings['format']; + + $cards = []; + foreach ($rows as $row) { + $expiry = (string) ($row['data_scadenza_tessera'] ?? ''); + $cards[] = [ + 'name' => trim((string) ($row['nome'] ?? '') . ' ' . (string) ($row['cognome'] ?? '')), + 'code' => (string) ($row['codice_tessera'] ?? ''), + 'expiry' => $expiry !== '' && function_exists('format_date') ? format_date($expiry, false, '/') : $expiry, + 'photo' => $settings['show_photo'] ? PatronPhoto::absolutePath($row['foto_profilo_url'] ?? null) : null, + ]; + } + + $appName = (string) ConfigStore::get('app.name', 'Biblioteca'); + $generator = new PatronCardPdf($settings, $appName, $this->localLogo()); + $pdf = $generator->render($cards, $format); + + $response->getBody()->write($pdf); + return $response + ->withHeader('Content-Type', 'application/pdf') + ->withHeader('Content-Disposition', 'inline; filename="' . $filename . '"') + ->withHeader('Cache-Control', 'private, no-store'); + } + + /** The configured logo as a local file (remote logos are not fetched while rendering). */ + private function localLogo(): ?string + { + $logo = Branding::logo(); + if ($logo === '' || preg_match('#^https?://#i', $logo) === 1) { + return null; + } + $path = (string) (parse_url($logo, PHP_URL_PATH) ?? ''); + $public = realpath(dirname(__DIR__, 2) . '/public'); + if ($path === '' || $public === false) { + return null; + } + $file = realpath($public . '/' . ltrim($path, '/')); + return $file !== false && str_starts_with($file, $public . DIRECTORY_SEPARATOR) ? $file : null; + } + + /** + * @param list $ids + * @return list> + */ + private function loadUsers(mysqli $db, array $ids): array + { + $ids = array_values(array_filter(array_map('intval', $ids), static fn (int $n): bool => $n > 0)); + if ($ids === []) { + return []; + } + $placeholders = implode(',', array_fill(0, count($ids), '?')); + $stmt = $db->prepare("SELECT id, nome, cognome, codice_tessera, data_scadenza_tessera, foto_profilo_url, tipo_utente, ruolo_id, accesso_digitale + FROM utenti WHERE id IN ({$placeholders}) + ORDER BY cognome, nome, id"); + if ($stmt === false) { + return []; + } + $stmt->bind_param(str_repeat('i', count($ids)), ...$ids); + $stmt->execute(); + $result = $stmt->get_result(); + $rows = []; + while ($row = $result->fetch_assoc()) { + $rows[] = $row; + } + $stmt->close(); + return $rows; + } + + /** @param array $row */ + private function canRead(array $row): bool + { + return in_array($_SESSION['user']['tipo_utente'] ?? '', ['admin', 'staff'], true) + || \App\Support\UserRoleAssignment::canEdit($row); + } + + /** @return array{0:int,1:string}|null status and message when refused */ + private function editGuard(mysqli $db, int $id): ?array + { + if (!\App\Support\Permissions::can('users.manage')) { + return [403, __('Accesso negato')]; + } + $rows = $this->loadUsers($db, [$id]); + if ($rows === []) { + return [404, __('Utente non trovato')]; + } + if (!\App\Support\UserRoleAssignment::canEdit($rows[0])) { + return [403, __('Accesso negato')]; + } + return null; + } + + /** @param array $payload */ + private function json(Response $response, array $payload, int $status = 200): Response + { + $response->getBody()->write((string) json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)); + return $response->withHeader('Content-Type', 'application/json')->withStatus($status); + } +} diff --git a/app/Controllers/PatronLedgerController.php b/app/Controllers/PatronLedgerController.php new file mode 100644 index 000000000..30162fdf1 --- /dev/null +++ b/app/Controllers/PatronLedgerController.php @@ -0,0 +1,178 @@ +write($request, $response, $db, $userId, function (PatronLedger $ledger, array $data, int $cents, ?int $staffId) use ($userId): int { + $method = is_scalar($data['metodo_pagamento'] ?? null) ? (string) $data['metodo_pagamento'] : ''; + $note = trim(is_scalar($data['note'] ?? null) ? (string) $data['note'] : ''); + return $ledger->recordPayment($userId, $cents, $method, $note !== '' ? $note : null, $staffId, self::requestKey($data)); + }, 'paid'); + } + + public function charge(Request $request, Response $response, mysqli $db, int $userId): Response + { + return $this->write($request, $response, $db, $userId, function (PatronLedger $ledger, array $data, int $cents, ?int $staffId) use ($userId): int { + $description = is_scalar($data['descrizione'] ?? null) ? (string) $data['descrizione'] : ''; + return $ledger->manualCharge($userId, $cents, $description, $staffId, self::requestKey($data)); + }, 'charged'); + } + + public function waive(Request $request, Response $response, mysqli $db, int $userId): Response + { + return $this->write($request, $response, $db, $userId, function (PatronLedger $ledger, array $data, int $cents, ?int $staffId) use ($userId): int { + $reason = is_scalar($data['motivazione'] ?? null) ? (string) $data['motivazione'] : ''; + return $ledger->waive($userId, $cents, $reason, $staffId, null, self::requestKey($data)); + }, 'waived'); + } + + public function receipt(Request $request, Response $response, mysqli $db, int $userId, int $movementId): Response + { + if (($off = \App\Support\Features::requireOrRedirect('fines', $response)) !== null) { + return $off; + } + if (!\App\Support\Permissions::can('loans.manage') || !$this->mayActOn($db, $userId)) { + return $response->withStatus(403); + } + try { + $pdf = (new \App\Support\PaymentReceiptPdf($db))->generateForPayment($userId, $movementId); + } catch (\Throwable $e) { + // Only a payment that is not there is a 404; anything else (the + // database, the PDF library) is a server error, logged. + if ($e->getMessage() === 'payment_not_found') { + return $response->withStatus(404); + } + SecureLogger::error('Payment receipt PDF failed', ['movement_id' => $movementId, 'error' => $e->getMessage()]); + return $response->withStatus(500); + } + $filename = sprintf('ricevuta_pagamento_%06d.pdf', $movementId); + $response->getBody()->write($pdf); + return $response + ->withHeader('Content-Type', 'application/pdf') + ->withHeader('Content-Disposition', "attachment; filename=\"{$filename}\"") + ->withHeader('Content-Length', (string) strlen($pdf)) + ->withHeader('Cache-Control', 'no-cache, must-revalidate'); + } + + /** + * @param callable(PatronLedger, array, int, ?int): int $operation + */ + private function write(Request $request, Response $response, mysqli $db, int $userId, callable $operation, string $okFlag): Response + { + if (($off = \App\Support\Features::requireOrRedirect('fines', $response)) !== null) { + return $off; + } + if (!\App\Support\Permissions::can('loans.manage') || !$this->mayActOn($db, $userId)) { + return $response->withStatus(403); + } + $data = (array) $request->getParsedBody(); + // CSRF validated by CsrfMiddleware + // The reader record needs users.manage: a loan-desk role without it + // lands on the loans page, with the outcome as a flash message. + $base = \App\Support\Permissions::can('users.manage') + ? url('/admin/users/details/' . $userId) + : null; + // Every desk form carries its request_key: without it a resent + // request could not be recognised and would move money twice. + if (self::requestKey($data) === null) { + return $this->back($response, $base, ['ledger_error' => 'missing_request_key']); + } + $amountRaw = is_scalar($data['importo'] ?? null) ? (string) $data['importo'] : ''; + $cents = FineParameters::toCents($amountRaw); + if ($cents === null || $cents <= 0) { + return $this->back($response, $base, ['ledger_error' => 'invalid_amount']); + } + $staffId = isset($_SESSION['user']['id']) ? (int) $_SESSION['user']['id'] : null; + try { + $movementId = $operation(PatronLedger::forDb($db), $data, $cents, $staffId); + } catch (\InvalidArgumentException $e) { + return $this->back($response, $base, ['ledger_error' => $e->getMessage()]); + } catch (\Throwable $e) { + SecureLogger::error('Patron ledger write failed', ['user_id' => $userId, 'error' => $e->getMessage()]); + return $this->back($response, $base, ['ledger_error' => 'save_failed']); + } + return $this->back($response, $base, ['ledger' => $okFlag, 'movement' => (string) $movementId]); + } + + /** + * The idempotency key of one rendered form (hidden request_key, 32 hex), + * or null when absent or malformed (write() refuses such a request). + * + * @param array $data + */ + private static function requestKey(array $data): ?string + { + $raw = is_scalar($data['request_key'] ?? null) ? (string) $data['request_key'] : ''; + return preg_match('/^[a-f0-9]{32}$/', $raw) === 1 ? 'form:' . $raw : null; + } + + /** @param array $params */ + private function back(Response $response, ?string $base, array $params): Response + { + if ($base === null) { + if (isset($params['ledger_error'])) { + $_SESSION['error_message'] = self::errorMessage($params['ledger_error']); + } else { + $_SESSION['success_message'] = match ($params['ledger'] ?? '') { + 'paid' => __('Pagamento registrato.'), + 'charged' => __('Addebito registrato.'), + default => __('Condono registrato.'), + }; + } + return $response->withHeader('Location', url('/admin/loans'))->withStatus(302); + } + return $response->withHeader('Location', $base . '?' . http_build_query($params) . '#movimenti')->withStatus(302); + } + + /** + * Whether the signed-in operator may move money on $userId's account: the + * same borrower scope as the loan desk (Permissions::accountScopeSql), so a + * custom role reaches reader accounts only, never an administrator or a + * Staff account. Administrators and Staff keep every account. + */ + private function mayActOn(mysqli $db, int $userId): bool + { + $scope = \App\Support\Permissions::accountScopeSql('u', \App\Support\Permissions::SCOPE_BORROWER); + if ($scope === '') { + return true; + } + $stmt = $db->prepare('SELECT 1 FROM utenti u WHERE u.id = ?' . $scope); + $stmt->bind_param('i', $userId); + $stmt->execute(); + $found = $stmt->get_result()->fetch_row() !== null; + $stmt->close(); + return $found; + } + + /** Operator-facing message for a ledger error code. */ + public static function errorMessage(string $code): string + { + return match ($code) { + 'invalid_amount' => __('Importo non valido.'), + 'amount_exceeds_balance' => __("L'importo supera il saldo a debito del lettore."), + 'reason_required' => __('La motivazione del condono è obbligatoria.'), + 'description_required' => __("La descrizione dell'addebito è obbligatoria."), + 'invalid_method' => __('Metodo di pagamento non valido.'), + 'duplicate_request' => __('Richiesta già registrata: ricarica la pagina e riprova.'), + 'missing_request_key' => __('Errore di sicurezza. Ricarica la pagina e riprova.'), + 'user_not_found' => __('Utente non trovato'), + default => __('Operazione non riuscita. Riprova.'), + }; + } +} diff --git a/app/Controllers/PrestitiApiController.php b/app/Controllers/PrestitiApiController.php index 67e2e9bf6..ff8894ebe 100644 --- a/app/Controllers/PrestitiApiController.php +++ b/app/Controllers/PrestitiApiController.php @@ -64,15 +64,14 @@ public function list(Request $request, Response $response, mysqli $db): Response $params[] = $statoSpecifico; $param_types .= 's'; } - if ($search_value !== '') { - $where_prepared .= " AND (l.titolo LIKE ? - OR CONCAT(u.nome, ' ', u.cognome) LIKE ? - OR CONCAT(staff.nome, ' ', staff.cognome) LIKE ?) "; - $search_param = "%$search_value%"; - $params[] = $search_param; - $params[] = $search_param; - $params[] = $search_param; - $param_types .= 'sss'; + // Quoted value = exact spelling, accents included (#501). + $search = $search_value !== '' + ? \App\Support\ExactSearch::filterCondition(['l.titolo', "CONCAT(u.nome, ' ', u.cognome)", "CONCAT(staff.nome, ' ', staff.cognome)"], $search_value) + : null; + if ($search !== null) { + $where_prepared .= ' AND ' . $search['sql'] . ' '; + array_push($params, ...$search['params']); + $param_types .= $search['types']; } // Count total records with prepared statement diff --git a/app/Controllers/PrestitiController.php b/app/Controllers/PrestitiController.php index 28cdc5644..972ac7168 100644 --- a/app/Controllers/PrestitiController.php +++ b/app/Controllers/PrestitiController.php @@ -187,9 +187,11 @@ public function createForm(Request $request, Response $response, mysqli $db): Re // mezzanotte, e il '+1 month' della view divergeva dal default server (30gg). $defaultDataPrestito = \App\Support\DateHelper::today(); $loanSettings = new \App\Models\SettingsRepository($db); - $defaultLoanDays = $loanSettings->loanDurationDays(); + $defaultLoanDays = (new \App\Services\CirculationPolicy($db, $loanSettings))->defaults()->durationDays; $allowMultipleLoansSameBook = $loanSettings->allowsMultipleLoansSameBook(); - $defaultDataScadenza = date('Y-m-d', strtotime($defaultDataPrestito . " +{$defaultLoanDays} days")); + $defaultDataScadenza = \App\Services\CirculationPolicy::dueDateFor($defaultDataPrestito, $defaultLoanDays, $db); + // Closed days for the admin pickers (closures calendar), one year ahead. + $closedDates = \App\Support\LibraryCalendar::forDb($db)->closedDatesBetween($defaultDataPrestito, \App\Support\LibraryCalendar::shift($defaultDataPrestito, 400)); $loanSubmissionToken = \App\Support\OneTimeFormToken::issue('loan.create'); ob_start(); @@ -266,14 +268,16 @@ public function store(Request $request, Response $response, mysqli $db): Respons if (!\App\Support\DateHelper::isISODateFormat($data_prestito)) { return $response->withHeader('Location', url('/admin/loans/create') . '?error=invalid_date_format')->withStatus(302); } - if (empty($data_scadenza)) { - // Default loan duration read from admin settings (fallback: 30 days) - $loanDays = (new \App\Models\SettingsRepository($db))->loanDurationDays(); + $typedDueDate = !empty($data_scadenza); + if (!$typedDueDate) { + // Default loan duration from the circulation rules of this patron + // and title (fallback: the global loans settings, 30 days). + $loanDays = (new \App\Services\CirculationPolicy($db))->forLoan($utente_id, $libro_id)->durationDays; $startDate = \DateTimeImmutable::createFromFormat('!Y-m-d', $data_prestito); if ($startDate === false) { return $response->withHeader('Location', url('/admin/loans/create') . '?error=invalid_date_format')->withStatus(302); } - $data_scadenza = $startDate->modify("+{$loanDays} days")->format('Y-m-d'); + $data_scadenza = \App\Services\CirculationPolicy::dueDateFor($startDate->format('Y-m-d'), $loanDays, $db); } if ($utente_id <= 0 || $libro_id <= 0) { @@ -299,6 +303,10 @@ public function store(Request $request, Response $response, mysqli $db): Respons if ($data_scadenza <= $data_prestito) { return $response->withHeader('Location', url('/admin/loans/create') . '?error=invalid_dates')->withStatus(302); } + if ($typedDueDate) { + // A typed due date on a closed day moves to the next open day. + $data_scadenza = \App\Services\CirculationPolicy::dueDateFor($data_scadenza, 0, $db); + } // A newly-created active loan whose entire window is already over is // physically open-ended until returned, even if its nominal dates do @@ -386,19 +394,14 @@ public function store(Request $request, Response $response, mysqli $db): Respons return $response->withHeader('Location', url('/admin/loans/create') . '?error=' . $eligibilityError)->withStatus(302); } - // Enforce max active loans per user (admin setting; 0 = no limit) - $maxLoans = (int) ((new \App\Models\SettingsRepository($db))->get('loans', 'max_active_loans_per_user', '0') ?? 0); - if ($maxLoans > 0) { - $cntStmt = $db->prepare("SELECT COUNT(*) FROM prestiti WHERE utente_id = ? AND attivo = 1 AND stato IN ('prenotato','da_ritirare','in_corso','in_ritardo')"); - $cntStmt->bind_param('i', $utente_id); - $cntStmt->execute(); - $cntResult = $cntStmt->get_result(); - $activeCount = (int) ($cntResult ? $cntResult->fetch_row()[0] : 0); - $cntStmt->close(); - if ($activeCount >= $maxLoans) { - $db->rollback(); - return $response->withHeader('Location', url('/admin/loans/create') . '?error=max_loans_reached')->withStatus(302); - } + // Circulation rules of this patron and title (CirculationPolicy): + // loanability and active-loan limits (0 = no limit), counted under + // the user lock above. + $circulation = new \App\Services\CirculationPolicy($db); + $limitError = $circulation->loanLimitError($circulation->forLoan($utente_id, $libro_id), $utente_id); + if ($limitError !== null) { + $db->rollback(); + return $response->withHeader('Location', url('/admin/loans/create') . '?error=' . $limitError)->withStatus(302); } // Check if loan starts today (immediate loan) or in the future (scheduled loan). @@ -451,6 +454,12 @@ public function store(Request $request, Response $response, mysqli $db): Respons $forcedCopyId = (int) $codeRow['id']; + // A copy kept for in-house consultation is never lent. + if ((new \App\Services\CirculationPolicy($db))->isConsultationCopy($forcedCopyId)) { + $db->rollback(); + return $response->withHeader('Location', url('/admin/loans/create') . '?error=not_loanable')->withStatus(302); + } + // Multiple open rows for one borrower/title must represent // different physical items even when their date windows do not // overlap. The book lock makes this early snapshot race-safe. @@ -519,6 +528,7 @@ public function store(Request $request, Response $response, mysqli $db): Respons SELECT c.id FROM copie c WHERE c.libro_id = ? AND c.stato NOT IN ('perso', 'danneggiato', 'manutenzione', 'in_restauro', 'in_trasferimento') + AND " . (new \App\Services\CirculationPolicy($db))->lendableCopySql('c') . " AND NOT EXISTS ( SELECT 1 FROM prestiti own WHERE own.copia_id = c.id @@ -568,6 +578,19 @@ public function store(Request $request, Response $response, mysqli $db): Respons return $response->withHeader('Location', url('/admin/loans/create') . '?error=copy_not_available')->withStatus(302); } + // The copy is known only now: a due date closed at its branch moves + // to the branch's next open day, and the longer window is checked + // again for capacity (the copy overlap check below uses it too). + $branchDue = \App\Services\CirculationPolicy::dueDateFor($data_scadenza, 0, $db, (int) $selectedCopy['id']); + if ($branchDue !== $data_scadenza) { + $branchCapacity = new \App\Services\CapacityService($db); + if (!$branchCapacity->hasFreeCapacity($libro_id, $data_prestito, $branchDue)) { + $db->rollback(); + return $this->noCapacityRedirect($response, $branchCapacity, $libro_id, $data_prestito, $branchDue); + } + $data_scadenza = $branchDue; + } + // Race condition check to prevent double-booking $overlapCopyStmt = $db->prepare(" SELECT 1 FROM prestiti @@ -643,7 +666,8 @@ public function store(Request $request, Response $response, mysqli $db): Respons // immediato/futuro ed evitare lo scarto di un giorno a mezzanotte. $settingsRepo = new \App\Models\SettingsRepository($db); $pickupDays = (int) ($settingsRepo->get('loans', 'pickup_expiry_days', '3') ?? 3); - $pickupDeadline = date('Y-m-d', strtotime("{$today} +{$pickupDays} days")); + // Open days only (closures calendar); identical to +N days without closures. + $pickupDeadline = \App\Support\LibraryCalendar::pickupDeadline($db, $today, $pickupDays, null, (int) $selectedCopy['id']); // Cap alla fine della finestra del prestito (L1), come // approveLoan/activateScheduledLoans: una deadline oltre // data_scadenza permetterebbe di confermare il ritiro di un @@ -861,7 +885,7 @@ public function update(Request $request, Response $response, mysqli $db, int $id // Lettura NON bloccante della riga corrente: serve libro_id per il lock // canonico (P3) e i valori correnti per completare i campi non inviati. - $check = $db->prepare('SELECT attivo, data_restituzione, libro_id, utente_id, data_prestito, data_scadenza FROM prestiti WHERE id=?'); + $check = $db->prepare('SELECT attivo, data_restituzione, libro_id, utente_id, data_prestito, data_scadenza, copia_id FROM prestiti WHERE id=?'); $check->bind_param('i', $id); $check->execute(); $current = $check->get_result()->fetch_assoc(); @@ -879,6 +903,10 @@ public function update(Request $request, Response $response, mysqli $db, int $id $libroId = (int) $current['libro_id']; $newUserId = isset($updateData['utente_id']) ? (int) $updateData['utente_id'] : (int) $current['utente_id']; $newPrestito = (string) ($updateData['data_prestito'] ?? $current['data_prestito']); + // Closures calendar: a submitted due date on a closed day moves forward. + if (isset($updateData['data_scadenza']) && \App\Support\DateHelper::isISODateFormat((string) $updateData['data_scadenza'])) { + $updateData['data_scadenza'] = \App\Services\CirculationPolicy::dueDateFor((string) $updateData['data_scadenza'], 0, $db, $current['copia_id'] !== null ? (int) $current['copia_id'] : null); + } $newScadenza = (string) ($updateData['data_scadenza'] ?? $current['data_scadenza']); // Valida il range date (M6a): una scadenza precedente alla partenza @@ -1060,18 +1088,11 @@ public function update(Request $request, Response $response, mysqli $db, int $id // Cap prestiti attivi del NUOVO utente (M6): stesso enforcement di // store() — senza, la riassegnazione admin aggira silenziosamente // max_active_loans_per_user. - $maxLoans = (int) ((new \App\Models\SettingsRepository($db))->get('loans', 'max_active_loans_per_user', '0') ?? 0); - if ($maxLoans > 0) { - $cntStmt = $db->prepare("SELECT COUNT(*) FROM prestiti WHERE utente_id = ? AND attivo = 1 AND stato IN ('prenotato','da_ritirare','in_corso','in_ritardo')"); - $cntStmt->bind_param('i', $newUserId); - $cntStmt->execute(); - $cntResult = $cntStmt->get_result(); - $activeCount = (int) ($cntResult ? $cntResult->fetch_row()[0] : 0); - $cntStmt->close(); - if ($activeCount >= $maxLoans) { - $db->rollback(); - return $response->withHeader('Location', url('/admin/loans') . '?error=max_loans_reached')->withStatus(302); - } + $circulation = new \App\Services\CirculationPolicy($db); + $limitError = $circulation->loanLimitError($circulation->forExistingLoan($id, $newUserId), $newUserId); + if ($limitError !== null) { + $db->rollback(); + return $response->withHeader('Location', url('/admin/loans') . '?error=' . $limitError)->withStatus(302); } } @@ -1260,7 +1281,7 @@ public function update(Request $request, Response $response, mysqli $db, int $id $pickupRecalc->close(); } elseif ((string) $locked['stato'] === 'da_ritirare') { $pickupDays = (int) ((new \App\Models\SettingsRepository($db))->get('loans', 'pickup_expiry_days', '3') ?? 3); - $pickupDeadline = (new \DateTimeImmutable($todayApp))->modify('+' . $pickupDays . ' days')->format('Y-m-d'); + $pickupDeadline = \App\Support\LibraryCalendar::pickupDeadline($db, $todayApp, $pickupDays, null, !empty($locked['copia_id']) ? (int) $locked['copia_id'] : null); if ($pickupDeadline > $newScadenza) { $pickupDeadline = $newScadenza; } @@ -1298,10 +1319,7 @@ public function update(Request $request, Response $response, mysqli $db, int $id if ($newScadenza > $oldScadenza && in_array((string) $locked['stato'], ['in_corso', 'in_ritardo'], true)) { try { - $maxRenewals = (int) ((new \App\Models\SettingsRepository($db))->get('loans', 'max_renewals', '3') ?? 3); - if ($maxRenewals < 0) { - $maxRenewals = 3; - } + $maxRenewals = (new \App\Services\CirculationPolicy($db))->forExistingLoan($id)->maxRenewals; (new NotificationService($db))->sendLoanRenewedNotification($id, $maxRenewals); } catch (\Throwable $notifError) { SecureLogger::warning('Loan update renewal notification failed', ['loan_id' => $id, 'error' => $notifError->getMessage()]); @@ -1386,6 +1404,10 @@ public function returnForm(Request $request, Response $response, mysqli $db, int $prestito = $result->fetch_assoc(); $stmt->close(); + // Plan §7: the late fine a return today would charge, shown before the + // operator confirms (with the option to waive it on the spot). + $finePreview = \App\Services\PatronLedger::forDb($db)->previewLateFine($id); + ob_start(); require __DIR__ . '/../Views/prestiti/restituito_prestito.php'; $content = ob_get_clean(); @@ -1429,6 +1451,15 @@ public function processReturn(Request $request, Response $response, mysqli $db, } $sanzione = number_format((float) $penaltyRaw, 2, '.', ''); + // Late-fine waiver at the desk (plan §7): the operator may waive the + // fine shown on the return screen, but only with a reason. Checked + // before any write so a refused waiver leaves the loan open. + $waiveFine = isset($data['waive_fine']) && is_scalar($data['waive_fine']) && (string) $data['waive_fine'] === '1'; + $waiveReason = trim((string) (is_scalar($data['waive_reason'] ?? null) ? $data['waive_reason'] : '')); + if ($waiveFine && $waiveReason === '') { + return $response->withHeader('Location', url('/admin/loans/returned/' . $id) . '?error=waiver_reason_required')->withStatus(302); + } + // La riparazione NON è un esito di prestito: `prestiti.stato` non ha stati // 'manutenzione'/'in_restauro'. Un rientro-con-riparazione chiude il prestito // come 'restituito' (a tutti gli effetti il libro è tornato) e devia la COPIA @@ -1478,7 +1509,7 @@ public function processReturn(Request $request, Response $response, mysqli $db, // (attivo=1, in_corso/in_ritardo) è restituibile (BUG4/D10 — niente // doppia elaborazione, niente TypeError su copia_id nullo). $stmt = $db->prepare(" - SELECT libro_id, copia_id, stato, data_scadenza + SELECT libro_id, copia_id, utente_id, stato, data_scadenza FROM prestiti WHERE id = ? AND attivo = 1 AND stato IN ('in_corso','in_ritardo') FOR UPDATE @@ -1523,6 +1554,18 @@ public function processReturn(Request $request, Response $response, mysqli $db, throw new \RuntimeException('Prestito non più restituibile (race).'); } + // Patron ledger (plan §7), same transaction as the close: the late + // fine (idempotent key late:, 0 when fines are disabled) and the + // amount assessed for a lost/damaged copy (penalty:, mirrored + // from prestiti.sanzione, which stays the source for emails/audit). + $ledger = \App\Services\PatronLedger::forDb($db); + $ledgerStaffId = isset($_SESSION['user']['id']) ? (int) $_SESSION['user']['id'] : null; + $lateFineCents = $ledger->chargeLateReturn($id, $ledgerStaffId); + $ledger->chargeLossPenalty($id, $ledgerStaffId); + if ($waiveFine && $lateFineCents > 0) { + $ledger->waive((int) $loan['utente_id'], $lateFineCents, $waiveReason, $ledgerStaffId, $id); + } + // Mappa esito-rientro → stato copia (form di RESTITUZIONE). Le due voci // di riparazione chiudono il prestito come 'restituito' ($loan_stato) ma // portano la copia fuori circolazione: il ricalcolo disponibilità la @@ -1672,6 +1715,13 @@ public function processReturn(Request $request, Response $response, mysqli $db, } catch (\Throwable $e) { SecureLogger::warning('Loan returned notification failed', ['loan_id' => $id, 'error' => $e->getMessage()]); } + if ($lateFineCents > 0 && !$waiveFine) { + try { + (new NotificationService($db))->sendFineChargedNotification($id); + } catch (\Throwable $e) { + SecureLogger::warning('Fine charged notification failed', ['loan_id' => $id, 'error' => $e->getMessage()]); + } + } } elseif (in_array($loan_stato, ['perso', 'danneggiato'], true)) { try { (new NotificationService($db))->sendLoanCopyOutcomeNotification($id); @@ -1787,6 +1837,15 @@ public function details(Request $request, Response $response, mysqli $db, int $i $prestito = $result->fetch_assoc(); $stmt->close(); + // Plan §7: fine accruing on an open overdue loan (shown, written only at + // return) and the late fine already charged on a closed one. + $ledger = \App\Services\PatronLedger::forDb($db); + $loanFineAccruedCents = ((int) ($prestito['attivo'] ?? 0) === 1 && in_array((string) ($prestito['stato'] ?? ''), ['in_corso', 'in_ritardo'], true)) + ? (int) $ledger->previewLateFine($id)['cents'] + : 0; + // Fines module off: the charged fine is not shown (the row stays). + $loanFineChargedCents = \App\Services\PatronLedger::active($db) ? $ledger->lateFineNetCents($id) : 0; + ob_start(); require __DIR__ . '/../Views/prestiti/dettagli_prestito.php'; $content = ob_get_clean(); @@ -2002,7 +2061,8 @@ public function bulkExtend(Request $request, Response $response, mysqli $db): Re $copyOverlap, $update, $expectedBookByLoan, - $conflict + $conflict, + $db ); if ($applied === null) { $copyOverlap->close(); @@ -2032,15 +2092,11 @@ public function bulkExtend(Request $request, Response $response, mysqli $db): Re // PER prestito: un invio fallito non ferma né il batch (già durevole) // né le conferme degli altri prestiti. if ($extendedLoanIds !== []) { - $settingsRepo = new \App\Models\SettingsRepository($db); - $maxRenewals = (int) ($settingsRepo->get('loans', 'max_renewals', '3') ?? 3); - if ($maxRenewals < 0) { - $maxRenewals = 3; - } + $circulation = new \App\Services\CirculationPolicy($db); $notifier = new NotificationService($db); foreach ($extendedLoanIds as $extendedLoanId) { try { - $notifier->sendLoanRenewedNotification($extendedLoanId, $maxRenewals); + $notifier->sendLoanRenewedNotification($extendedLoanId, $circulation->forExistingLoan($extendedLoanId)->maxRenewals); } catch (\Throwable $notifError) { SecureLogger::warning('Bulk extend renewal notification failed', ['loan_id' => $extendedLoanId, 'error' => $notifError->getMessage()]); } @@ -2067,7 +2123,8 @@ private function applyBulkLoanExtension( \mysqli_stmt $copyOverlap, \mysqli_stmt $update, array $expectedBookByLoan, - array &$conflict = [] + array &$conflict = [], + ?\mysqli $db = null ): ?int { $loanId = (int) $loan['id']; $bookId = (int) $loan['libro_id']; @@ -2086,7 +2143,8 @@ private function applyBulkLoanExtension( // extending from its due date. $todayDate = \DateTimeImmutable::createFromFormat('!Y-m-d', $today); $base = ($todayDate !== false && $todayDate > $dueDate) ? $todayDate : $dueDate; - $newDueDate = $base->modify('+' . $days . ' days')->format('Y-m-d'); + // With the connection, a new due date on a closed day moves forward. + $newDueDate = \App\Services\CirculationPolicy::dueDateFor($base->format('Y-m-d'), $days, $db, $loan['copia_id'] !== null ? (int) $loan['copia_id'] : null); // Apply each accepted extension immediately inside the transaction, so the // next capacity check sees all earlier proposed extensions too. @@ -2100,7 +2158,7 @@ private function applyBulkLoanExtension( $copyId = $loan['copia_id'] !== null ? (int) $loan['copia_id'] : null; if (!$capacity->hasFreeCapacity($bookId, $extensionStart, $newDueDate, excludePrestitoId: $loanId)) { $blocked = $capacity->renewalConflictDates($bookId, $loanId, $copyId, $currentDue, $newDueDate); - $conflict = \App\Support\LoanWindow::renewalRedirectParams($currentDue, $blocked, $base->format('Y-m-d')); + $conflict = \App\Support\LoanWindow::renewalRedirectParams($currentDue, $blocked, $base->format('Y-m-d'), $db !== null ? \App\Support\LibraryCalendar::forDb($db) : null, $db !== null ? \App\Support\LibraryCalendar::branchOfCopy($db, $copyId) : null); return null; } @@ -2110,7 +2168,7 @@ private function applyBulkLoanExtension( if ((bool) $copyOverlap->get_result()->fetch_row()) { // The loan's own copy is needed by a later loan: name that day. $blocked = $capacity->renewalConflictDates($bookId, $loanId, $copyId, $currentDue, $newDueDate); - $conflict = \App\Support\LoanWindow::renewalRedirectParams($currentDue, $blocked, $base->format('Y-m-d')); + $conflict = \App\Support\LoanWindow::renewalRedirectParams($currentDue, $blocked, $base->format('Y-m-d'), $db !== null ? \App\Support\LibraryCalendar::forDb($db) : null, $db !== null ? \App\Support\LibraryCalendar::branchOfCopy($db, $copyId) : null); return null; } } @@ -2143,7 +2201,7 @@ public function renew(Request $request, Response $response, mysqli $db, int $id) // Get current loan $stmt = $db->prepare(" - SELECT id, libro_id, utente_id, data_scadenza, stato, renewals, attivo + SELECT id, libro_id, utente_id, data_scadenza, stato, renewals, attivo, copia_id FROM prestiti WHERE id = ? "); @@ -2202,12 +2260,11 @@ public function renew(Request $request, Response $response, mysqli $db, int $id) return $response->withHeader('Location', url($errorUrl . $separator . 'error=' . $eligibilityError))->withStatus(302); } - // Check renewal limit (max_renewals configurabile, default 3) — F2 - $settingsRepo = new \App\Models\SettingsRepository($db); - $maxRenewals = (int) ($settingsRepo->get('loans', 'max_renewals', '3') ?? 3); - if ($maxRenewals < 0) { - $maxRenewals = 3; - } + // Check renewal limit (max_renewals configurabile, default 3) — F2. + // Limit and renewal length come from the circulation rules of this + // loan's patron and material (CirculationPolicy). + $renewPolicy = (new \App\Services\CirculationPolicy($db))->forExistingLoan($id); + $maxRenewals = $renewPolicy->maxRenewals; $currentRenewals = (int) $loan['renewals']; if ($currentRenewals >= $maxRenewals) { $errorUrl = $redirectTo ?? url('/admin/loans'); @@ -2217,17 +2274,21 @@ public function renew(Request $request, Response $response, mysqli $db, int $id) // Durata del rinnovo dalla setting di durata prestito (M5b): il vecchio // '+14 days' hardcoded ignorava la configurazione dell'admin. - $renewDays = $settingsRepo->loanDurationDays(); + $renewDays = $renewPolicy->renewalDays; // Calculate proposed new due date for conflict checking $currentDueDate = $loan['data_scadenza']; - $proposedNewDueDate = date('Y-m-d', strtotime($currentDueDate . " +{$renewDays} days")); + $loanCopyId = isset($loan['copia_id']) ? (int) $loan['copia_id'] : null; + $proposedNewDueDate = \App\Services\CirculationPolicy::dueDateFor((string) $currentDueDate, $renewDays, $db, $loanCopyId); // Optional shorter renewal: the day before a scheduled loan or a // reservation, as the refusal below proposes ("Rinnova fino al D"). // A real day, after the current due date, never past the standard // renewal. $rawDue = $data['new_due_date'] ?? ''; $requestedDue = is_string($rawDue) ? trim($rawDue) : ''; + if ($requestedDue !== '' && \App\Support\DateHelper::isISODateFormat($requestedDue)) { + $requestedDue = \App\Services\CirculationPolicy::dueDateFor($requestedDue, 0, $db, $loanCopyId); + } if (!is_string($rawDue) || ($requestedDue !== '' && !\App\Support\DateHelper::isISODateFormat($requestedDue))) { $errorUrl = $redirectTo ?? url('/admin/loans'); $separator = strpos($errorUrl, '?') === false ? '?' : '&'; @@ -2308,7 +2369,7 @@ public function renew(Request $request, Response $response, mysqli $db, int $id) // Use the locked values for calculations (in case they changed) $currentDueDate = $lockedLoan['data_scadenza']; - $proposedNewDueDate = date('Y-m-d', strtotime($currentDueDate . " +{$renewDays} days")); + $proposedNewDueDate = \App\Services\CirculationPolicy::dueDateFor((string) $currentDueDate, $renewDays, $db, $lockedLoan['copia_id'] !== null ? (int) $lockedLoan['copia_id'] : null); if ($requestedDue !== '') { // Re-bound under the lock: the due date may have moved meanwhile. if ($requestedDue <= (string) $currentDueDate || $requestedDue > $proposedNewDueDate) { @@ -2339,9 +2400,9 @@ public function renew(Request $request, Response $response, mysqli $db, int $id) $capacity = new \App\Services\CapacityService($db); // A refusal names the longest renewal still possible (LoanWindow), // and which loan it is about, so the page can offer "renew until D". - $explainRefusal = static function (array $blocked) use ($id, $currentDueDate): string { + $explainRefusal = static function (array $blocked) use ($id, $currentDueDate, $db, $copiaId): string { return http_build_query(['error' => 'extension_conflicts', 'loan_id' => $id] - + \App\Support\LoanWindow::renewalRedirectParams((string) $currentDueDate, $blocked)); + + \App\Support\LoanWindow::renewalRedirectParams((string) $currentDueDate, $blocked, null, \App\Support\LibraryCalendar::forDb($db), \App\Support\LibraryCalendar::branchOfCopy($db, $copiaId))); }; if (!$capacity->hasFreeCapacity($libroId, $extensionStart, $extensionEnd, excludePrestitoId: $id)) { $blocked = $capacity->renewalConflictDates($libroId, $id, $copiaId, (string) $currentDueDate, $extensionEnd); diff --git a/app/Controllers/PublicApiController.php b/app/Controllers/PublicApiController.php index 621b09ceb..616648e07 100644 --- a/app/Controllers/PublicApiController.php +++ b/app/Controllers/PublicApiController.php @@ -80,18 +80,20 @@ private function findBooks(mysqli $db, ?string $ean, ?string $isbn13, ?string $i $types .= 's'; } - if ($author !== null && $author !== '') { - $authorLike = '%' . str_replace(['\\', '%', '_'], ['\\\\', '\\%', '\\_'], $author) . '%'; + // Quoted value = exact spelling, accents included (#501). + $authorCond = ($author !== null && $author !== '') + ? \App\Support\ExactSearch::filterCondition(['a.nome', 'a.pseudonimo'], $author) + : null; + if ($authorCond !== null) { $conditions[] = 'EXISTS ( SELECT 1 FROM libri_autori la JOIN autori a ON la.autore_id = a.id WHERE la.libro_id = l.id AND la.ruolo IN (\'principale\', \'co-autore\') - AND (a.nome LIKE ? ESCAPE \'\\\\\' OR a.pseudonimo LIKE ? ESCAPE \'\\\\\') + AND ' . $authorCond['sql'] . ' )'; - $params[] = $authorLike; - $params[] = $authorLike; - $types .= 'ss'; + array_push($params, ...$authorCond['params']); + $types .= $authorCond['types']; } if (empty($conditions)) { diff --git a/app/Controllers/RegistrationController.php b/app/Controllers/RegistrationController.php index e4b6ad529..d3a35a5fe 100644 --- a/app/Controllers/RegistrationController.php +++ b/app/Controllers/RegistrationController.php @@ -181,7 +181,10 @@ public function register(Request $request, Response $response, mysqli $db): Resp // Ensure timezone consistency BEFORE generating dates $db->query("SET SESSION time_zone = '+00:00'"); - $data_scadenza_tessera = gmdate('Y-m-d', strtotime('+5 years')); // Scadenza tessera tra 5 anni in UTC + // Card validity: the default patron type's card duration when it sets + // one (circulation policy), otherwise the historical 5 years. + $data_scadenza_tessera = \App\Support\PatronTypeAssignment::automaticCardExpiry($db, null) + ?? gmdate('Y-m-d', strtotime('+5 years')); // Scadenza tessera tra 5 anni in UTC $data_scadenza_token = gmdate('Y-m-d H:i:s', time() + 24 * 60 * 60); // Token scade tra 24 ore in UTC // GDPR: Record privacy acceptance timestamp diff --git a/app/Controllers/ReservationManager.php b/app/Controllers/ReservationManager.php index edd4349b0..272c33b67 100644 --- a/app/Controllers/ReservationManager.php +++ b/app/Controllers/ReservationManager.php @@ -160,8 +160,8 @@ public function processBookAvailability($bookId) // App-timezone "today" (not PHP UTC) so MySQL-stored dates and the // eligibility comparison agree across the midnight boundary (#157). $today = \App\Support\DateHelper::today(); - $maxLoans = max(0, (int) ((new \App\Models\SettingsRepository($this->db)) - ->get('loans', 'max_active_loans_per_user', '0') ?? 0)); + $circulation = new \App\Services\CirculationPolicy($this->db); + $limitSql = $circulation->totalLoanLimitSql('u'); $ownTransaction = $this->beginTransactionIfNeeded(); try { @@ -202,25 +202,33 @@ public function processBookAvailability($bookId) // irraggiungibile il primo candidato idoneo. Il predicato SQL cerca // direttamente il primo idoneo in FIFO; checkUser() rimane il gate // autorevole sotto la stessa transazione. + // Patrons the per-material rules refuse (not loanable for their + // type, or already at the limit for this material) are skipped + // like those at their total limit; CapacityService leaves the + // same reservations out of the occupied capacity. + $materialRefused = $circulation->materialRefusedReservationUserIds((int) $bookId); + $materialRefusedSql = $materialRefused === [] + ? '' + : ' AND r.utente_id NOT IN (' . implode(',', array_map('intval', $materialRefused)) . ')'; $stmt = $this->db->prepare(" SELECT r.*, u.email, u.nome, u.cognome FROM prenotazioni r JOIN utenti u ON r.utente_id = u.id WHERE r.libro_id = ? AND r.stato = 'attiva' AND " . \App\Support\LoanEligibility::promotableReservationWhere('r') . " - AND " . \App\Support\LoanEligibility::eligibleUserWhere('u') . " - AND (? <= 0 OR ( + AND " . \App\Support\LoanEligibility::eligibleUserWhere('u', $this->db) . "{$materialRefusedSql} + AND ({$limitSql} <= 0 OR ( SELECT COUNT(*) FROM prestiti cap WHERE cap.utente_id = r.utente_id AND cap.attivo = 1 - AND cap.stato IN ('prenotato','da_ritirare','in_corso','in_ritardo') - ) < ?) + AND cap.stato IN (" . \App\Services\CirculationPolicy::ACTIVE_LOAN_STATES_SQL . ") + ) < {$limitSql}) ORDER BY r.queue_position ASC LIMIT 1 FOR UPDATE "); - $stmt->bind_param('isssii', $bookId, $today, $today, $today, $maxLoans, $maxLoans); + $stmt->bind_param('isss', $bookId, $today, $today, $today); $stmt->execute(); $result = $stmt->get_result(); $nextReservation = $result ? $result->fetch_assoc() : null; @@ -244,21 +252,15 @@ public function processBookAvailability($bookId) 'motivo' => $eligibilityError, ]); $nextReservation = null; - } elseif ($maxLoans > 0) { + } else { // The SQL predicate avoids head-of-line blocking; this // second check under the user lock closes the race with - // approvals for other books. - $countStmt = $this->db->prepare(" - SELECT COUNT(*) AS c - FROM prestiti - WHERE utente_id = ? AND attivo = 1 - AND stato IN ('prenotato','da_ritirare','in_corso','in_ritardo') - "); - $countStmt->bind_param('i', $promotedUserId); - $countStmt->execute(); - $activeCount = (int) ($countStmt->get_result()->fetch_assoc()['c'] ?? 0); - $countStmt->close(); - if ($activeCount >= $maxLoans) { + // approvals for other books (CirculationPolicy limits). + $limitError = $circulation->loanLimitError( + $circulation->forLoan($promotedUserId, (int) $bookId), + $promotedUserId + ); + if ($limitError !== null) { $nextReservation = null; } } @@ -458,7 +460,8 @@ private function createLoanFromReservation($reservation) { $bookId = (int) $reservation['libro_id']; $startDate = $reservation['data_inizio_richiesta']; - $endDate = $reservation['data_fine_richiesta']; + // Closures calendar: the loan's due date moves to the next open day. + $endDate = \App\Services\CirculationPolicy::dueDateFor((string) $reservation['data_fine_richiesta'], 0, $this->db); // Always create as 'pendente' (attivo=0) - requires admin confirmation of physical pickup // Origin is 'prenotazione' to distinguish from manual requests @@ -494,6 +497,7 @@ private function createLoanFromReservation($reservation) SELECT c.id FROM copie c WHERE c.libro_id = ? AND c.stato IN ('disponibile', 'prenotato') + AND " . (new \App\Services\CirculationPolicy($this->db))->lendableCopySql('c') . " AND NOT EXISTS ( SELECT 1 FROM prestiti own WHERE own.copia_id = c.id @@ -932,6 +936,7 @@ public function isBookAvailableForImmediateLoan($bookId, ?string $startDate = nu $totalStmt = $this->db->prepare(" SELECT COUNT(*) as total FROM copie WHERE libro_id = ? AND stato NOT IN ('perso', 'danneggiato', 'manutenzione', 'in_restauro', 'in_trasferimento') + AND " . (new \App\Services\CirculationPolicy($this->db))->lendableCopySql('') . " "); $totalStmt->bind_param('i', $bookId); $totalStmt->execute(); @@ -995,6 +1000,11 @@ public function isBookAvailableForImmediateLoan($bookId, ?string $startDate = nu */ public function cancelExpiredReservations(): int { + // Closures calendar: a queue reservation does not lose its place on a + // day the library is closed (same rule as checkExpiredReservations()). + if (!\App\Support\LibraryCalendar::forDb($this->db)->isOpen(\App\Support\DateHelper::today())) { + return 0; + } $ownTransaction = $this->beginTransactionIfNeeded(); $expiryNotifications = []; diff --git a/app/Controllers/ReservationsAdminController.php b/app/Controllers/ReservationsAdminController.php index 9c89c3aba..c6b1bdbd1 100644 --- a/app/Controllers/ReservationsAdminController.php +++ b/app/Controllers/ReservationsAdminController.php @@ -35,15 +35,17 @@ public function index(Request $request, Response $response, mysqli $db): Respons $types .= 'i'; $params[] = $utenteId; } - if ($libroId <= 0 && $qLibro !== '') { - $conds[] = 'l.titolo LIKE ?'; - $types .= 's'; - $params[] = '%' . $qLibro . '%'; - } - if ($utenteId <= 0 && $qUtente !== '') { - $conds[] = "CONCAT(u.nome,' ',u.cognome) LIKE ?"; - $types .= 's'; - $params[] = '%' . $qUtente . '%'; + // Quoted value = exact spelling, accents included (#501). + foreach ([ + ['l.titolo', $libroId <= 0 ? $qLibro : ''], + ["CONCAT(u.nome,' ',u.cognome)", $utenteId <= 0 ? $qUtente : ''], + ] as [$expr, $value]) { + $cond = $value !== '' ? \App\Support\ExactSearch::filterCondition([$expr], $value) : null; + if ($cond !== null) { + $conds[] = $cond['sql']; + $types .= $cond['types']; + array_push($params, ...$cond['params']); + } } if ($conds) { $sql .= ' WHERE ' . implode(' AND ', $conds); @@ -68,6 +70,14 @@ public function index(Request $request, Response $response, mysqli $db): Respons } } + // The view reads these names. They were never set, so the list always + // rendered empty and the filter boxes lost what had been typed. + $items = $rows; + $q_libro = $qLibro; + $q_utente = $qUtente; + $libro_id = $libroId; + $utente_id = $utenteId; + ob_start(); require __DIR__ . '/../Views/prenotazioni/index.php'; $content = ob_get_clean(); @@ -125,8 +135,8 @@ public function update(Request $request, Response $response, mysqli $db, int $id $start = $today; } if ($end === '') { - $loanDays = $this->loanDurationDays($db); - $end = (new \DateTimeImmutable($start))->modify("+{$loanDays} days")->format('Y-m-d'); + $loanDays = (new \App\Services\CirculationPolicy($db))->forReservation($id)->durationDays; + $end = \App\Services\CirculationPolicy::dueDateFor($start, $loanDays, $db); } // Normalize an inverted range on the reservation dates too (form can post // end < start), mirroring the data_*_richiesta clamp below (#252). @@ -444,7 +454,7 @@ public function createForm(Request $request, Response $response, mysqli $db): Re } } - $defaultLoanDays = $this->loanDurationDays($db); + $defaultLoanDays = (new \App\Services\CirculationPolicy($db))->defaults()->durationDays; ob_start(); $title = "Crea Prenotazione - Admin"; @@ -503,12 +513,10 @@ public function store(Request $request, Response $response, mysqli $db): Respons if (empty($dataScadenza)) { // Durata di default allineata all'impostazione admin loan_duration_days // (stesso valore mostrato dalla view), non più 30 giorni hardcoded. - $loanDays = $this->loanDurationDays($db); + $loanDays = (new \App\Services\CirculationPolicy($db))->forLoan($utenteId, $libroId)->durationDays; // The default duration starts from the requested start date, not // from today (critical for future reservations). - $dataScadenzaDate = (new \DateTimeImmutable($dataPrenotazioneDate)) - ->modify("+{$loanDays} days") - ->format('Y-m-d'); + $dataScadenzaDate = \App\Services\CirculationPolicy::dueDateFor($dataPrenotazioneDate, $loanDays, $db); $dataScadenza = $dataScadenzaDate . ' 23:59:59'; } else { $dataScadenzaDate = $dataScadenza; // Keep date only for loan period @@ -593,6 +601,19 @@ public function store(Request $request, Response $response, mysqli $db): Respons return $response->withHeader('Location', url('/admin/reservations/create') . '?error=' . rawurlencode($eligibilityError))->withStatus(302); } + // Circulation rules of the patron type, under the user lock. + // Consultation-only material is never queued; the reservation + // limits can be overridden by the staff with an explicit flag. + $circulation = new \App\Services\CirculationPolicy($db); + $reservationPolicy = $circulation->forLoan($utenteId, $libroId); + $policyError = !$reservationPolicy->loanable + ? 'not_loanable' + : (($data['force_limits'] ?? '') === '1' ? null : $circulation->reservationLimitError($reservationPolicy->user, $utenteId, queue: true)); + if ($policyError !== null) { + $db->rollback(); + return $response->withHeader('Location', url('/admin/reservations/create') . '?error=' . $policyError)->withStatus(302); + } + // Capacity gate (the decision): a waitlist reservation occupies one // capacity unit for its promised period, so reject it when the book is // already at capacity for that window (counting other commitments). @@ -650,17 +671,6 @@ public function store(Request $request, Response $response, mysqli $db): Respons } } - /** - * The configured loan duration in days (loans.loan_duration_days), with the - * canonical fallback to 30 for a missing or non-positive value. Centralizes - * the lookup previously duplicated in update(), store() and createForm(). - */ - private function loanDurationDays(mysqli $db): int - { - $days = (int) ((new \App\Models\SettingsRepository($db))->get('loans', 'loan_duration_days', '30') ?? 30); - return $days > 0 ? $days : 30; - } - private function reorderQueuePositions(mysqli $db, int $libroId): void { $stmt = $db->prepare(" diff --git a/app/Controllers/ReservationsController.php b/app/Controllers/ReservationsController.php index eb30a05e3..d5e75bc6e 100644 --- a/app/Controllers/ReservationsController.php +++ b/app/Controllers/ReservationsController.php @@ -290,6 +290,11 @@ public function createReservation($request, $response, $args) return $response->withHeader('Content-Type', 'application/json')->withStatus(400); } + // A start day the library is closed moves to the next open day, as the + // request calendar proposes it (a crafted request cannot start on a + // closure); identical without closures. + $startDate = \App\Support\LibraryCalendar::forDb($this->db)->nextOpenDay($startDate); + if ($endDate && $endDate < $startDate) { $response->getBody()->write(json_encode(['success' => false, 'error' => 'invalid_range', 'message' => __('La data di fine non può precedere la data di inizio')])); return $response->withHeader('Content-Type', 'application/json')->withStatus(400); @@ -297,22 +302,25 @@ public function createReservation($request, $response, $args) // Configurable duration cap (anti-DoS): applies both to an explicit // end_date and to the default end computed from loan_duration_days. - $settings = new \App\Models\SettingsRepository($this->db); - $maxDurationDays = $settings->maxLoanDurationDays(); + $circulation = new \App\Services\CirculationPolicy($this->db); + $requestPolicy = $circulation->forLoan($userId, (int) $bookId); + $maxDurationDays = $requestPolicy->maxRequestDays; // If no end date specified, set it using the configured loan duration (fallback: 30 days) if (!$endDate) { - $loanDays = $settings->requestLoanDurationDays(); - $endDateTime = new DateTime($startDate); - $endDateTime->modify("+{$loanDays} days"); - $endDate = $endDateTime->format('Y-m-d'); + $endDate = \App\Services\CirculationPolicy::dueDateFor((string) $startDate, $requestPolicy->requestDurationDays()); } - $requestedDurationDays = (int) (new DateTime($startDate))->diff(new DateTime($endDate))->days; - if ($requestedDurationDays > $maxDurationDays) { + // The longest loan ends on the maximum day, moved forward like every + // due date when it falls on a closed day (closed days are not loan + // days); without closures this is start + maximum, as before. + if ($endDate > \App\Services\CirculationPolicy::dueDateFor((string) $startDate, $maxDurationDays, $this->db)) { $response->getBody()->write(json_encode(['success' => false, 'error' => 'max_duration_exceeded', 'message' => __('Il periodo richiesto supera la durata massima consentita di %d giorni', $maxDurationDays)])); return $response->withHeader('Content-Type', 'application/json')->withStatus(400); } + // Closures calendar: a return date on a closed day moves to the next + // open day (after the duration cap: closed days are not loan days). + $endDate = \App\Services\CirculationPolicy::dueDateFor($endDate, 0, $this->db); // Availability decision routes through CapacityService — the single // capacity authority — so this web loan-request gate matches the mobile @@ -410,11 +418,39 @@ public function createReservation($request, $response, $args) // capacity-free pending loan be created. The gate runs inside THIS // transaction (inCallerTransaction: true) under the canonical book // lock taken above. + // Circulation rules of the patron type, under the user lock: + // consultation-only material is neither lent nor queued, and the + // reservation limit counts queue reservations plus requests + // awaiting approval (CirculationPolicy). + $policyError = !$requestPolicy->loanable + ? 'not_loanable' + : $circulation->reservationLimitError($requestPolicy->user, $userId, queue: false); + if ($policyError !== null) { + $this->db->rollback(); + $response->getBody()->write(json_encode([ + 'success' => false, + 'error' => $policyError, + 'message' => \App\Support\LoanEligibility::errorMessage($policyError), + ])); + return $response->withHeader('Content-Type', 'application/json')->withStatus(409); + } + $routing = (new \App\Services\LoanRequestGate($this->db)) ->route($bookId, $userId, $startDate, $endDate, inCallerTransaction: true); $assignableCopyId = $routing->assignableCopyId; if ($routing->isReservation()) { + // The gate queued the request: refused for a patron type + // without reservations (the row is rolled back with the rest). + if (!$requestPolicy->user->reservationsEnabled) { + $this->db->rollback(); + $response->getBody()->write(json_encode([ + 'success' => false, + 'error' => 'reservations_disabled', + 'message' => \App\Support\LoanEligibility::errorMessage('reservations_disabled'), + ])); + return $response->withHeader('Content-Type', 'application/json')->withStatus(409); + } $reservationId = (int) $routing->reservationId; $this->db->commit(); @@ -425,6 +461,9 @@ public function createReservation($request, $response, $args) 'auto_approved' => false, 'status' => 'reserved', 'loan_state' => null, + // The stored window (closed days already skipped). + 'start_date' => $startDate, + 'end_date' => $endDate, ])); return $response->withHeader('Content-Type', 'application/json'); } @@ -433,19 +472,17 @@ public function createReservation($request, $response, $args) // A real waitlist reservation is intentionally handled above: like // UserActionsController::reserve(), it does not consume an active- // loan slot until promotion. - $maxLoans = (int) ((new \App\Models\SettingsRepository($this->db))->get('loans', 'max_active_loans_per_user', '0') ?? 0); - if ($maxLoans > 0) { - $cntStmt = $this->db->prepare("SELECT COUNT(*) FROM prestiti WHERE utente_id = ? AND attivo = 1 AND stato IN ('prenotato','da_ritirare','in_corso','in_ritardo')"); - $cntStmt->bind_param('i', $userId); - $cntStmt->execute(); - $cntResult = $cntStmt->get_result(); - $activeCount = (int) ($cntResult ? $cntResult->fetch_row()[0] : 0); - $cntStmt->close(); - if ($activeCount >= $maxLoans) { - $this->db->rollback(); - $response->getBody()->write(json_encode(['success' => false, 'message' => __('Hai raggiunto il numero massimo di prestiti attivi consentiti')])); - return $response->withHeader('Content-Type', 'application/json')->withStatus(409); - } + $limitError = $circulation->loanLimitError($requestPolicy, $userId); + if ($limitError !== null) { + $this->db->rollback(); + $response->getBody()->write(json_encode([ + 'success' => false, + 'error' => $limitError, + 'message' => $limitError === 'max_loans_reached' + ? __('Hai raggiunto il numero massimo di prestiti attivi consentiti') + : \App\Support\LoanEligibility::errorMessage($limitError), + ])); + return $response->withHeader('Content-Type', 'application/json')->withStatus(409); } // Read the setting while the canonical book lock is still held. With @@ -518,9 +555,16 @@ public function createReservation($request, $response, $args) // ?string: the persisted state ('prenotato' scheduled loan / // 'da_ritirare' immediate pickup) on success, null when the // request stays pending (setting off / approval failed). + $approved = null; $loanState = $autoApproveEnabled - ? $this->autoApproveLoanRequest($request, $loanRequestId, true) + ? $this->autoApproveLoanRequest($request, $loanRequestId, true, $approved) : null; + // The due date the approval wrote (it may have moved over a + // closure of the assigned copy's branch): the fallback below + // when the stored window cannot be read back. + if (is_array($approved) && is_string($approved['data_scadenza'] ?? null) && \App\Support\DateHelper::isISODateFormat($approved['data_scadenza'])) { + $endDate = $approved['data_scadenza']; + } if ($loanState === null) { // Send notification to admins (an auto-approved request no @@ -549,6 +593,32 @@ public function createReservation($request, $response, $args) $status = 'pending_approval'; } + // The window as stored: approval may still move the due date + // over a closure of the assigned copy's branch. + // The request is already committed: a failed read only loses + // the refinement, never the success the reader must see. + $pickupDeadline = null; + $pickupDays = null; + try { + $storedWindow = $this->db->prepare('SELECT data_prestito, data_scadenza, copia_id, pickup_deadline FROM prestiti WHERE id = ?'); + $storedWindow->bind_param('i', $loanRequestId); + $storedWindow->execute(); + $storedRow = $storedWindow->get_result()->fetch_assoc(); + $storedWindow->close(); + if (is_array($storedRow)) { + $startDate = (string) ($storedRow['data_prestito'] ?? $startDate); + $endDate = (string) ($storedRow['data_scadenza'] ?? $endDate); + if ($loanState !== null) { + [$pickupDeadline, $pickupDays] = $this->expectedPickup($startDate, $endDate, $storedRow); + } + } + } catch (\Throwable $windowError) { + \App\Support\SecureLogger::warning('ReservationsController: stored window not readable', ['error' => $windowError->getMessage()]); + if ($loanState !== null) { + [$pickupDeadline, $pickupDays] = $this->fallbackPickup($startDate, $endDate, $approved); + } + } + $response->getBody()->write(json_encode([ 'success' => true, 'message' => $message, @@ -558,6 +628,15 @@ public function createReservation($request, $response, $args) 'auto_approved' => $loanState !== null, 'status' => $status, 'loan_state' => $loanState, + // The stored window (closed days already skipped). + 'start_date' => $startDate, + 'end_date' => $endDate, + // An approved loan's pickup deadline and open pickup days, + // with the closures of the assigned copy's branch (null + // when unknown: the page then computes them itself). For + // a scheduled loan it is the estimate activation applies. + 'pickup_deadline' => $pickupDeadline, + 'pickup_days' => $pickupDays, ])); return $response->withHeader('Content-Type', 'application/json'); } else { @@ -581,13 +660,65 @@ public function createReservation($request, $response, $args) // (UserActionsController::loan) so the two public entry points can never // diverge again. + /** + * The pickup deadline and open pickup days of an approved request, from + * its stored row: the deadline already stored for a loan awaiting pickup, + * otherwise the one activation will set (LibraryCalendar::pickupDeadline + * with the assigned copy's branch, capped at the due date). The days + * follow the pickup emails: never more than the setting, fewer when the + * loan ends first or closed days fall inside the window. + * + * @param array $row prestiti row (copia_id, pickup_deadline) + * @return array{0: ?string, 1: ?int} + */ + private function expectedPickup(string $startDate, string $endDate, array $row): array + { + if (!\App\Support\DateHelper::isISODateFormat($startDate)) { + return [null, null]; + } + $copiaId = !empty($row['copia_id']) ? (int) $row['copia_id'] : null; + $setting = max(1, (int) ((new \App\Models\SettingsRepository($this->db))->get('loans', 'pickup_expiry_days', '3') ?? 3)); + $stored = $row['pickup_deadline'] !== null ? substr((string) $row['pickup_deadline'], 0, 10) : ''; + $deadline = \App\Support\DateHelper::isISODateFormat($stored) + ? $stored + : \App\Support\LibraryCalendar::pickupDeadline($this->db, $startDate, $setting, \App\Support\DateHelper::isISODateFormat($endDate) ? $endDate : null, $copiaId); + $openDays = \App\Support\LibraryCalendar::forDb($this->db)->openDaysBetween($startDate, $deadline, \App\Support\LibraryCalendar::branchOfCopy($this->db, $copiaId)); + return [$deadline, min($setting, $openDays)]; + } + + /** + * The pickup estimate when the stored row cannot be read back: the copy + * the approval assigned (its JSON body carries copia_id) still brings its + * branch closures, so the page does not fall back to the library-wide + * ones. Counted from the requested start, it is an estimate, not the + * deadline the approval stored (that one counts from the approval day). + * The request is already committed: a failure here only loses the + * estimate, never the success. + * + * @param array|null $approved + * @return array{0: ?string, 1: ?int} + */ + private function fallbackPickup(string $startDate, string $endDate, ?array $approved): array + { + try { + $copiaId = is_array($approved) && is_numeric($approved['copia_id'] ?? null) ? (int) $approved['copia_id'] : null; + return $this->expectedPickup($startDate, $endDate, ['copia_id' => $copiaId, 'pickup_deadline' => null]); + } catch (\Throwable $e) { + \App\Support\SecureLogger::warning('ReservationsController: pickup estimate not computed', ['error' => $e->getMessage()]); + return [null, null]; + } + } + /** * Promote a newly-created request through the canonical approval pipeline * when the automatic-approval setting is on (#301). Mirrors * UserActionsController::autoApproveLoanRequest — a failure deliberately * leaves the request pending so an administrator can still process it. + * + * @param array|null $approved set to the approval's JSON + * body (data_scadenza, copia_id…) when the loan was approved */ - private function autoApproveLoanRequest($request, int $loanId, ?bool $knownEnabled = null): ?string + private function autoApproveLoanRequest($request, int $loanId, ?bool $knownEnabled = null, ?array &$approved = null): ?string { // When the caller already captured the setting under the book lock it // passes that decision in $knownEnabled. Legacy/internal callers may omit @@ -617,6 +748,7 @@ private function autoApproveLoanRequest($request, int $loanId, ?bool $knownEnabl // so the response can describe the real outcome instead of // assuming "awaiting pickup". $body = json_decode((string) $result->getBody(), true); + $approved = is_array($body) ? $body : null; return is_array($body) && isset($body['loan_state']) && is_string($body['loan_state']) ? $body['loan_state'] : 'da_ritirare'; @@ -650,7 +782,7 @@ private function autoApproveLoanRequest($request, int $loanId, ?bool $knownEnabl * answer the filter would give, without hiding the record from the * operator or from the reader who already has it reserved. */ - public function getBookAvailabilityData($bookId, ?string $startDate = null, int $days = 730, ?int $excludeUserId = null): ?array + public function getBookAvailabilityData($bookId, ?string $startDate = null, int $days = 730, ?int $excludeUserId = null, bool $coverLoanWindow = false): ?array { $bookStmt = $this->db->prepare("SELECT id FROM libri WHERE id = ? AND deleted_at IS NULL"); $bookStmt->bind_param('i', $bookId); @@ -696,10 +828,32 @@ public function getBookAvailabilityData($bookId, ?string $startDate = null, int // The durations every calendar proposes with (public book page, admin // loan and reservation forms, the app): the configured default, capped // by the maximum the request endpoints enforce, from one source. - $settings = new \App\Models\SettingsRepository($this->db); + $circulation = new \App\Services\CirculationPolicy($this->db); + $policy = $excludeUserId !== null + ? $circulation->forLoan($excludeUserId, (int) $bookId) + : $circulation->defaults(); + // Closures calendar: closed days in the same horizon, so the request + // calendars can refuse them as a start/pickup day, plus enough days + // past it for the browser to move a proposed due date that falls on + // a closed day forward to the next open day (additive field). + $from = $startDate ?? \App\Support\DateHelper::today(); + $closedHorizon = max(0, $days - 1) + max($policy->durationDays, $policy->maxRequestDays) + 31; + // start_until is the last day a request may start on. A start picked + // there still needs occupancy data for its whole loan window, closed + // days included: with $coverLoanWindow the days run on as far as the + // closed days do, or a booked day past them would read as free. + $startUntil = \App\Support\LibraryCalendar::shift($from, max(0, $days - 1)); + if ($coverLoanWindow) { + $days = $closedHorizon + 1; + } + $closedDates = \App\Support\LibraryCalendar::forDb($this->db) + ->closedDatesBetween($from, \App\Support\LibraryCalendar::shift($from, $closedHorizon)); return $this->calculateAvailability($currentLoans, $existingReservations, $totalCopies, $startDate, $days, $excludeUserId) + [ - 'loan_duration_days' => $settings->requestLoanDurationDays(), - 'max_loan_duration_days' => $settings->maxLoanDurationDays(), + 'loan_duration_days' => $policy->requestDurationDays(), + 'max_loan_duration_days' => $policy->maxRequestDays, + 'staff_loan_duration_days' => $policy->durationDays, + 'closed_dates' => $closedDates, + 'start_until' => $startUntil, ]; } @@ -794,6 +948,7 @@ private function getBookTotalCopies(int $bookId): int SELECT COUNT(*) as total FROM copie WHERE libro_id = ? AND stato NOT IN ('perso', 'danneggiato', 'manutenzione', 'in_restauro', 'in_trasferimento') + AND " . (new \App\Services\CirculationPolicy($this->db))->lendableCopySql('') . " "); $stmt->bind_param('i', $bookId); $stmt->execute(); diff --git a/app/Controllers/SearchController.php b/app/Controllers/SearchController.php index 0d4e5059d..470c6ab23 100644 --- a/app/Controllers/SearchController.php +++ b/app/Controllers/SearchController.php @@ -31,19 +31,13 @@ public function authors(Request $request, Response $response, mysqli $db): Respo // SessionRoleRefreshMiddleware) receive them, as on the admin-only /api/autori. $lifeDates = $this->isOperatorSession($request) ? ', data_nascita, data_morte' : ''; $select = "id, nome{$lifeDates}, {$label}"; - if ($q !== '') { - // Split query into words — each word must match (AND logic) - $words = preg_split('/\s+/', $q, -1, PREG_SPLIT_NO_EMPTY); - $conditions = []; - $params = []; - $types = ''; - foreach ($words as $word) { - $conditions[] = '(nome LIKE ? ESCAPE \'\\\\\' OR pseudonimo LIKE ? ESCAPE \'\\\\\')'; - $like = '%' . str_replace(['\\', '%', '_'], ['\\\\', '\\%', '\\_'], $word) . '%'; - $params[] = $like; - $params[] = $like; - $types .= 'ss'; - } + // Every word must match (AND); a quoted group is matched with its + // exact spelling, accents included (#501). + $cond = $q !== '' ? \App\Support\ExactSearch::wordsCondition(['nome', 'pseudonimo'], $q, \App\Support\ExactSearch::MAX_FILTER_TERMS) : null; + if ($cond !== null) { + $conditions = [$cond['sql']]; + $params = $cond['params']; + $types = $cond['types']; $sql = "SELECT {$select} FROM autori WHERE " . implode(' AND ', $conditions) . " ORDER BY " . \App\Support\AuthorName::preferredSql('autori'); $stmt = $db->prepare($sql); @@ -72,17 +66,13 @@ public function publishers(Request $request, Response $response, mysqli $db): Re { $q = trim((string)($request->getQueryParams()['q'] ?? '')); $rows = []; - if ($q !== '') { - // Split query into words — each word must match (AND logic) - $words = preg_split('/\s+/', $q, -1, PREG_SPLIT_NO_EMPTY); - $conditions = []; - $params = []; - $types = ''; - foreach ($words as $word) { - $conditions[] = 'nome LIKE ?'; - $params[] = '%' . $word . '%'; - $types .= 's'; - } + // Every word must match (AND); a quoted group is matched with its + // exact spelling, accents included (#501). + $cond = $q !== '' ? \App\Support\ExactSearch::wordsCondition(['nome'], $q, \App\Support\ExactSearch::MAX_FILTER_TERMS) : null; + if ($cond !== null) { + $conditions = [$cond['sql']]; + $params = $cond['params']; + $types = $cond['types']; $sql = "SELECT id, nome AS label FROM editori WHERE " . implode(' AND ', $conditions) . " ORDER BY nome"; $stmt = $db->prepare($sql); $stmt->bind_param($types, ...$params); @@ -101,8 +91,13 @@ public function users(Request $request, Response $response, mysqli $db): Respons { $q = trim((string)($request->getQueryParams()['q'] ?? '')); $rows=[]; - if ($q !== '') { - $s = '%'.$q.'%'; + // Quoted value = exact spelling, accents included (#501). The full + // name in either order is searched too, so "Mario Rossi" — what the + // label shows — finds Mario Rossi, as the mobile staff picker does. + $search = $q !== '' + ? \App\Support\ExactSearch::filterCondition(['nome', 'cognome', 'telefono', 'email', 'codice_tessera', "CONCAT(nome, ' ', cognome)", "CONCAT(cognome, ' ', nome)"], $q) + : null; + if ($search !== null) { $stmt = $db->prepare(" SELECT id, CONCAT( @@ -113,15 +108,11 @@ public function users(Request $request, Response $response, mysqli $db): Respons END ) AS label FROM utenti - WHERE (nome LIKE ? - OR cognome LIKE ? - OR telefono LIKE ? - OR email LIKE ? - OR codice_tessera LIKE ?)" . \App\Support\Permissions::accountScopeSql('', \App\Support\Permissions::SCOPE_BORROWER) . " + WHERE {$search['sql']}" . \App\Support\Permissions::accountScopeSql('', \App\Support\Permissions::SCOPE_BORROWER) . " ORDER BY cognome, nome LIMIT 20 "); - $stmt->bind_param('sssss', $s, $s, $s, $s, $s); + $stmt->bind_param($search['types'], ...$search['params']); $stmt->execute(); $res = $stmt->get_result(); while ($r = $res->fetch_assoc()) { @@ -399,17 +390,15 @@ private function searchBooks(mysqli $db, string $query, bool $includeRequests = private function searchAuthors(mysqli $db, string $query): array { $results = []; - $words = preg_split('/\s+/', $query, -1, PREG_SPLIT_NO_EMPTY); - $conditions = []; - $params = []; - $types = ''; - foreach ($words as $word) { - $conditions[] = '(nome LIKE ? ESCAPE \'\\\\\' OR pseudonimo LIKE ? ESCAPE \'\\\\\')'; - $like = '%' . str_replace(['\\', '%', '_'], ['\\\\', '\\%', '\\_'], $word) . '%'; - $params[] = $like; - $params[] = $like; - $types .= 'ss'; + // Every word must match (AND); a quoted group is matched with its + // exact spelling, accents included (#501). + $cond = \App\Support\ExactSearch::wordsCondition(['nome', 'pseudonimo'], $query, \App\Support\ExactSearch::MAX_FILTER_TERMS); + if ($cond === null) { + return []; } + $conditions = [$cond['sql']]; + $params = $cond['params']; + $types = $cond['types']; $sql = "SELECT id, " . \App\Support\AuthorName::displaySql('autori') . " AS label FROM autori WHERE " . implode(' AND ', $conditions) . " ORDER BY " . \App\Support\AuthorName::preferredSql('autori') . " LIMIT 5"; @@ -433,15 +422,15 @@ private function searchAuthors(mysqli $db, string $query): array private function searchPublishers(mysqli $db, string $query): array { $results = []; - $words = preg_split('/\s+/', $query, -1, PREG_SPLIT_NO_EMPTY); - $conditions = []; - $params = []; - $types = ''; - foreach ($words as $word) { - $conditions[] = 'nome LIKE ?'; - $params[] = '%' . $word . '%'; - $types .= 's'; + // Every word must match (AND); a quoted group is matched with its + // exact spelling, accents included (#501). + $cond = \App\Support\ExactSearch::wordsCondition(['nome'], $query, \App\Support\ExactSearch::MAX_FILTER_TERMS); + if ($cond === null) { + return []; } + $conditions = [$cond['sql']]; + $params = $cond['params']; + $types = $cond['types']; $sql = "SELECT id, nome AS label FROM editori WHERE " . implode(' AND ', $conditions) . " ORDER BY nome LIMIT 5"; $stmt = $db->prepare($sql); $stmt->bind_param($types, ...$params); @@ -528,17 +517,15 @@ private function searchBooksWithDetails(mysqli $db, string $query): array private function searchAuthorsWithDetails(mysqli $db, string $query): array { $results = []; - $words = preg_split('/\s+/', $query, -1, PREG_SPLIT_NO_EMPTY); - $conditions = []; - $params = []; - $types = ''; - foreach ($words as $word) { - $conditions[] = '(a.nome LIKE ? ESCAPE \'\\\\\' OR a.pseudonimo LIKE ? ESCAPE \'\\\\\')'; - $like = '%' . str_replace(['\\', '%', '_'], ['\\\\', '\\%', '\\_'], $word) . '%'; - $params[] = $like; - $params[] = $like; - $types .= 'ss'; + // Every word must match (AND); a quoted group is matched with its + // exact spelling, accents included (#501). + $cond = \App\Support\ExactSearch::wordsCondition(['a.nome', 'a.pseudonimo'], $query, \App\Support\ExactSearch::MAX_FILTER_TERMS); + if ($cond === null) { + return []; } + $conditions = [$cond['sql']]; + $params = $cond['params']; + $types = $cond['types']; $sql = " SELECT a.id, a.nome, a.pseudonimo, a.biografia, @@ -572,15 +559,15 @@ private function searchAuthorsWithDetails(mysqli $db, string $query): array private function searchPublishersWithDetails(mysqli $db, string $query): array { $results = []; - $words = preg_split('/\s+/', $query, -1, PREG_SPLIT_NO_EMPTY); - $conditions = []; - $params = []; - $types = ''; - foreach ($words as $word) { - $conditions[] = 'e.nome LIKE ?'; - $params[] = '%' . $word . '%'; - $types .= 's'; + // Every word must match (AND); a quoted group is matched with its + // exact spelling, accents included (#501). + $cond = \App\Support\ExactSearch::wordsCondition(['e.nome'], $query, \App\Support\ExactSearch::MAX_FILTER_TERMS); + if ($cond === null) { + return []; } + $conditions = [$cond['sql']]; + $params = $cond['params']; + $types = $cond['types']; // issue #143: count books where the publisher is primary OR a secondary // one in the junction; gate the subquery on table existence so the count @@ -622,10 +609,10 @@ public function genres(Request $request, Response $response, mysqli $db): Respon { $q = trim((string)($request->getQueryParams()['q'] ?? '')); $rows=[]; - if ($q !== '') { - $s = '%'.$q.'%'; - $stmt = $db->prepare("SELECT id, nome AS label FROM generi WHERE nome LIKE ? ORDER BY nome LIMIT 20"); - $stmt->bind_param('s', $s); + $search = \App\Support\ExactSearch::filterCondition(['nome'], $q); + if ($search !== null) { + $stmt = $db->prepare("SELECT id, nome AS label FROM generi WHERE {$search['sql']} ORDER BY nome LIMIT 20"); + $stmt->bind_param($search['types'], ...$search['params']); $stmt->execute(); $res = $stmt->get_result(); while ($r = $res->fetch_assoc()) { @@ -641,8 +628,8 @@ public function locations(Request $request, Response $response, mysqli $db): Res { $q = trim((string)($request->getQueryParams()['q'] ?? '')); $rows = []; - if ($q !== '') { - $s = '%' . $q . '%'; + $search = \App\Support\ExactSearch::filterCondition(['s.nome', 's.codice', 'CAST(m.numero_livello AS CHAR)'], $q); + if ($search !== null) { $stmt = $db->prepare(" SELECT p.id, CONCAT( @@ -653,11 +640,11 @@ public function locations(Request $request, Response $response, mysqli $db): Res FROM posizioni p JOIN scaffali s ON p.scaffale_id = s.id JOIN mensole m ON p.mensola_id = m.id - WHERE s.nome LIKE ? OR s.codice LIKE ? OR CAST(m.numero_livello AS CHAR) LIKE ? + WHERE {$search['sql']} ORDER BY s.ordine, m.numero_livello LIMIT 20 "); - $stmt->bind_param('sss', $s, $s, $s); + $stmt->bind_param($search['types'], ...$search['params']); $stmt->execute(); $res = $stmt->get_result(); while ($r = $res->fetch_assoc()) { diff --git a/app/Controllers/SettingsController.php b/app/Controllers/SettingsController.php index e72cc04e9..bfa996efb 100644 --- a/app/Controllers/SettingsController.php +++ b/app/Controllers/SettingsController.php @@ -68,6 +68,7 @@ public function index(Request $request, Response $response, mysqli $db): Respons $contactSettings = $this->resolveContactSettings($repository); $privacySettings = $this->resolvePrivacySettings($repository); $labelSettings = $this->resolveLabelSettings($repository); + $cardSettings = \App\Support\PatronCardSettings::resolve($repository); $eventSettings = $this->resolveEventSettings($repository); // Catalogue header (Settings → CMS): edit the configured site language. // Historical translations stay stored for later language changes. @@ -79,6 +80,22 @@ public function index(Request $request, Response $response, mysqli $db): Respons $eventsHeaderDefaults = \App\Support\CatalogHeader::defaultsFor(array_map('strval', array_keys($catalogHeaderLocales)), 'events'); $advancedSettings = $this->resolveAdvancedSettings($repository); $loansSettings = $this->resolveLoansSettings($repository); + // Patron types and rules matrix (Settings → Circulation): reserved to + // administrators like every CirculationSettingsController action, so + // staff gets no data and the view renders no panel (the section is + // absent from the settings catalogue for them too). + $circulationData = \App\Support\Permissions::mayConfigure() + ? CirculationSettingsController::viewData($db) + : null; + // Optional modules (Settings → Modules): the saved switches. + $moduleSettings = \App\Support\Features::snapshot(); + $legacyPenaltyImpact = ['count' => 0, 'cents' => 0]; + // Only while loss mirroring is off: the notice describes switching it on. + if (\App\Support\Permissions::mayConfigure() && !\App\Support\Features::stored('loss_penalties') && \App\Services\PatronLedger::schemaReady($db)) { + $row = $db->query("SELECT COUNT(*) AS n, COALESCE(SUM(p.sanzione),0) AS amount FROM prestiti p WHERE p.sanzione > 0 AND NOT EXISTS (SELECT 1 FROM movimenti_lettore m WHERE m.chiave_idempotenza = CONCAT('penalty:',p.id))")->fetch_assoc(); + $legacyPenaltyImpact = ['count' => (int) ($row['n'] ?? 0), 'cents' => \App\Services\FineParameters::decimalToCents($row['amount'] ?? 0)]; + } + $attendanceRetentionDays = \App\Models\AttendanceRepository::retentionDays($repository); $contactMessages = $this->loadContactMessages($db); $cookieBannerTexts = $this->resolveCookieBannerTexts($repository); // Custom registration fields (issue #255) — edited from the email tab's @@ -104,6 +121,7 @@ public function index(Request $request, Response $response, mysqli $db): Respons 'contactSettings', 'privacySettings', 'labelSettings', + 'cardSettings', 'eventSettings', 'catalogHeaderLocales', 'catalogHeaderTexts', @@ -112,6 +130,10 @@ public function index(Request $request, Response $response, mysqli $db): Respons 'eventsHeaderDefaults', 'advancedSettings', 'loansSettings', + 'circulationData', + 'moduleSettings', + 'attendanceRetentionDays', + 'legacyPenaltyImpact', 'contactMessages', 'activeTab', 'db', @@ -1089,9 +1111,11 @@ public function updateAdvancedSettings(Request $request, Response $response, mys // Handle catalogue mode setting (stored in 'system' category) $catalogueMode = isset($data['catalogue_mode']) && $data['catalogue_mode'] === '1' ? '1' : '0'; + $consultationWasOn = \App\Support\Features::enabled('consultation_only'); $repository->set('system', 'catalogue_mode', $catalogueMode); ConfigStore::set('system.catalogue_mode', $catalogueMode === '1'); ConfigStore::clearCache(); + $this->refreshConsultationAvailability($db, $consultationWasOn); $_SESSION['success_message'] = $liteSpeedBlockedByContainer && $requestedLiteSpeed ? __('Impostazioni avanzate aggiornate. LiteSpeed resta disabilitato perché non è disponibile nella versione Docker.') @@ -1301,6 +1325,20 @@ public function updateLabels(Request $request, Response $response, mysqli $db): return $this->redirect($response, '/admin/settings?tab=labels'); } + /** Save the library card settings (Settings → Tessere). */ + public function updateCards(Request $request, Response $response, mysqli $db): Response + { + $data = (array) $request->getParsedBody(); + // CSRF validated by CsrfMiddleware + + $repository = new SettingsRepository($db); + $repository->ensureTables(); + \App\Support\PatronCardSettings::save($repository, \App\Support\PatronCardSettings::sanitize($data)); + + $_SESSION['success_message'] = __('Impostazioni tessere aggiornate correttamente.'); + return $this->redirect($response, '/admin/settings?tab=cards'); + } + public function toggleApi(Request $request, Response $response, mysqli $db): Response { // Exposing or withdrawing the public API changes the application's @@ -1656,7 +1694,7 @@ public function updateCatalogHeader(Request $request, Response $response, mysqli } /** - * @return array{loan_duration_days: int, pickup_expiry_days: int, pickup_reminder_days: int, max_renewals: int, max_active_loans_per_user: int, max_loan_duration_days: int, auto_approve_requests: bool, allow_multiple_loans_same_book: bool, recall_auto_enabled: bool, recall_interval_days: int, recall_max_count: int, app_timezone: string} + * @return array{loan_duration_days: int, pickup_expiry_days: int, pickup_reminder_days: int, card_expiry_warning_days: int, max_renewals: int, max_active_loans_per_user: int, max_loan_duration_days: int, auto_approve_requests: bool, allow_multiple_loans_same_book: bool, recall_auto_enabled: bool, recall_interval_days: int, recall_max_count: int, app_timezone: string, daily_fine: string, fine_grace_days: int, fine_max_per_loan: string, debt_block_threshold: string, fine_charged_email: bool, currency: string} */ private function resolveLoansSettings(SettingsRepository $repository): array { @@ -1664,6 +1702,8 @@ private function resolveLoansSettings(SettingsRepository $repository): array 'loan_duration_days' => (int) ($repository->get('loans', 'loan_duration_days', '30') ?? 30), 'pickup_expiry_days' => (int) ($repository->get('loans', 'pickup_expiry_days', '3') ?? 3), 'pickup_reminder_days' => (int) ($repository->get('loans', 'pickup_reminder_days', '2') ?? 2), + // Card-expiry warning (circulation policy): 0 = off. + 'card_expiry_warning_days' => (int) ($repository->get('loans', 'card_expiry_warning_days', '0') ?? 0), 'max_renewals' => (int) ($repository->get('loans', 'max_renewals', '3') ?? 3), 'max_active_loans_per_user' => (int) ($repository->get('loans', 'max_active_loans_per_user', '0') ?? 0), 'max_loan_duration_days' => (int) ($repository->get('loans', 'max_loan_duration_days', '90') ?? 90), @@ -1675,9 +1715,30 @@ private function resolveLoansSettings(SettingsRepository $repository): array 'recall_max_count' => (int) ($repository->get('loans', 'recall_max_count', '3') ?? 3), // App-wide clock for due dates and automatisms (DateHelper reads it). 'app_timezone' => (string) \App\Support\ConfigStore::get('app.timezone', 'Europe/Rome'), + // Plan §7: fines and debt block. Empty string = not set (disabled). + 'daily_fine' => (string) ($repository->get('loans', 'daily_fine', '') ?? ''), + 'fine_grace_mode' => (string) $repository->get('loans', 'fine_grace_mode', 'threshold'), + 'card_renewal_base' => (string) $repository->get('loans', 'card_renewal_base', 'today'), + 'fine_grace_days' => (int) ($repository->get('loans', 'fine_grace_days', '0') ?? 0), + 'fine_max_per_loan' => (string) ($repository->get('loans', 'fine_max_per_loan', '') ?? ''), + 'debt_block_threshold' => (string) ($repository->get('loans', 'debt_block_threshold', '') ?? ''), + 'fine_charged_email' => (string) ($repository->get('loans', 'fine_charged_email', '0') ?? '0') === '1', + 'currency' => \App\Services\FineParameters::currency(), + // Optional circulation modules switched from this tab (the saved + // switch, not the catalogue-mode-adjusted one). + 'fines_enabled' => \App\Support\Features::stored('fines'), + 'closures_calendar_enabled' => \App\Support\Features::stored('closures_calendar'), + 'circulation_rules_enabled' => \App\Support\Features::stored('circulation_rules'), ]; } + /** Fine amounts of the loans tab → the patron type column with the same label (CirculationSettingsController::fieldLabel()). */ + private const FINE_AMOUNT_FIELDS = [ + 'daily_fine' => 'multa_giornaliera', + 'fine_max_per_loan' => 'multa_massima_prestito', + 'debt_block_threshold' => 'soglia_blocco_debito', + ]; + public function updateLoansSettings(Request $request, Response $response, mysqli $db): Response { $data = (array) $request->getParsedBody(); @@ -1686,6 +1747,23 @@ public function updateLoansSettings(Request $request, Response $response, mysqli $repository = new SettingsRepository($db); $repository->ensureTables(); + // Plan §7: a fine amount above FineParameters::MAX_SETTING_CENTS is a + // typing error, not a policy: the tab is sent back untouched with the + // field named, before anything below is written. + if (\App\Support\Permissions::mayConfigure()) { + foreach (self::FINE_AMOUNT_FIELDS as $amountKey => $column) { + $raw = isset($data[$amountKey]) && is_scalar($data[$amountKey]) ? (string) $data[$amountKey] : ''; + if (\App\Services\FineParameters::exceedsSettingMax($raw)) { + $_SESSION['error_message'] = sprintf( + __('Importo non valido per "%1$s": il massimo consentito è %2$s.'), + \App\Controllers\CirculationSettingsController::fieldLabel($column), + \App\Services\FineParameters::format(\App\Services\FineParameters::MAX_SETTING_CENTS) + ); + return $response->withHeader('Location', url('/admin/settings?tab=loans'))->withStatus(302); + } + } + } + // Clamp to sane bounds on BOTH ends — a hand-crafted POST must not be // able to persist absurd durations/renewals (the UI min/max attributes // are advisory; the server is the authority). Note the two zeros differ: @@ -1696,6 +1774,7 @@ public function updateLoansSettings(Request $request, Response $response, mysqli $loanDurationDays = min(3650, max(1, (int) ($data['loan_duration_days'] ?? 30))); // 1 day … 10 years $pickupExpiryDays = min(30, max(1, (int) ($data['pickup_expiry_days'] ?? 3))); // 1 … 30 days (matches the loans-tab input max) $pickupReminderDays = min(30, max(0, (int) ($data['pickup_reminder_days'] ?? 2))); // 0 (off) … 30 days before the pickup deadline + $cardExpiryWarningDays = min(365, max(0, (int) ($data['card_expiry_warning_days'] ?? 0))); // 0 (off) … 365 days before the card expires $maxRenewals = min(100, max(0, (int) ($data['max_renewals'] ?? 3))); // 0 … 100 $maxActiveLoans = min(1000, max(0, (int) ($data['max_active_loans_per_user'] ?? 0))); // 0 (unlimited) … 1000 $maxLoanDuration = min(3650, max(1, (int) ($data['max_loan_duration_days'] ?? 90))); // 1 day … 10 years (reservation-window cap enforced by ReservationsController) @@ -1716,6 +1795,7 @@ public function updateLoansSettings(Request $request, Response $response, mysqli $repository->set('loans', 'loan_duration_days', (string) $loanDurationDays); $repository->set('loans', 'pickup_expiry_days', (string) $pickupExpiryDays); $repository->set('loans', 'pickup_reminder_days', (string) $pickupReminderDays); + $repository->set('loans', 'card_expiry_warning_days', (string) $cardExpiryWarningDays); $repository->set('loans', 'max_renewals', (string) $maxRenewals); $repository->set('loans', 'max_active_loans_per_user', (string) $maxActiveLoans); $repository->set('loans', 'max_loan_duration_days', (string) $maxLoanDuration); @@ -1725,6 +1805,51 @@ public function updateLoansSettings(Request $request, Response $response, mysqli $repository->set('loans', 'recall_interval_days', (string) $recallInterval); $repository->set('loans', 'recall_max_count', (string) $recallMaxCount); + // Plan §7: fines and debt block. Amounts accept a decimal comma; an + // empty or malformed value is stored as '' (= disabled), never coerced + // into a number the operator did not type. They are money policy: + // staff may save the rest of this tab, but these keys stay as they + // are unless an administrator saves them (AdminAuthMiddleware admits + // staff, so the check is here). + if (\App\Support\Permissions::mayConfigure()) { + foreach (array_keys(self::FINE_AMOUNT_FIELDS) as $amountKey) { + $raw = isset($data[$amountKey]) && is_scalar($data[$amountKey]) ? (string) $data[$amountKey] : ''; + $cents = \App\Services\FineParameters::toCents($raw); + $repository->set('loans', $amountKey, $cents !== null && $cents > 0 ? \App\Services\FineParameters::centsToDecimal($cents) : ''); + } + $repository->set('loans', 'fine_grace_days', (string) min(365, max(0, (int) ($data['fine_grace_days'] ?? 0)))); + $fineEmail = isset($data['fine_charged_email']) && is_scalar($data['fine_charged_email']) + && (string) $data['fine_charged_email'] === '1'; + $repository->set('loans', 'fine_charged_email', $fineEmail ? '1' : '0'); + // The three circulation modules of this tab (patron type rules, + // closures calendar, fines): library policy, administrators only. + \App\Support\Features::preserveInherited($repository); + foreach (['fine_grace_mode' => ['threshold', 'deduct'], 'card_renewal_base' => ['today', 'expiry']] as $key => $allowed) { + if (isset($data[$key]) && is_string($data[$key]) && in_array($data[$key], $allowed, true)) { + $repository->set('loans', $key, $data[$key]); + } + } + $lossWasOn = \App\Support\Features::enabled('loss_penalties'); + $calendarWasOn = \App\Support\Features::enabled('closures_calendar'); + $consultationWasOn = \App\Support\Features::enabled('consultation_only'); + $this->saveFeatureSwitches($repository, $data, \App\Support\Features::LOANS_TAB); + $this->refreshConsultationAvailability($db, $consultationWasOn); + if (!$lossWasOn && \App\Support\Features::enabled('loss_penalties')) { + $repository->set('loans', 'legacy_penalties_backfilled', '0'); + } + // The calendar switched on applies its closures at once: a pending + // pickup whose deadline now falls on a closed day moves to the + // next open day, as when the calendar itself is saved. + if (\App\Support\Features::enabled('closures_calendar') !== $calendarWasOn) { + \App\Support\LibraryCalendar::reset(); + try { + \App\Support\LibraryCalendar::extendPickupDeadlines($db); + } catch (\Throwable $e) { + \App\Support\SecureLogger::warning('Calendar: pickup deadlines not extended: ' . $e->getMessage()); + } + } + } + // App timezone: DateHelper computes the loan clock ("today"/"now") from // this. Validate against the canonical identifier list — an invalid or // missing value leaves the stored setting untouched (the DateHelper @@ -1740,6 +1865,80 @@ public function updateLoansSettings(Request $request, Response $response, mysqli return $response->withHeader('Location', url('/admin/settings?tab=loans'))->withStatus(302); } + /** + * Save Settings → Modules. Switching a module off hides it everywhere, so + * it is library policy: AdminAuthMiddleware admits staff, the check is + * here, as for the other policy settings. + */ + public function updateModules(Request $request, Response $response, mysqli $db): Response + { + if (!\App\Support\Permissions::mayConfigure()) { + $_SESSION['error_message'] = __('Accesso negato. Permessi insufficienti.'); + return $this->redirect($response, '/admin/settings'); + } + $data = (array) $request->getParsedBody(); + // CSRF validated by CsrfMiddleware + + $repository = new SettingsRepository($db); + $repository->ensureTables(); + \App\Support\Features::preserveInherited($repository); + $this->saveFeatureSwitches($repository, $data, \App\Support\Features::MODULES_TAB); + $retention = filter_var($data['attendance_retention_days'] ?? null, FILTER_VALIDATE_INT); + if ($retention !== false && $retention >= 0) { + $days = min(\App\Models\AttendanceRepository::MAX_RETENTION_DAYS, $retention); + $repository->set('attendance', 'retention_days', (string) $days); + } + + $_SESSION['success_message'] = __('Moduli aggiornati correttamente.'); + return $this->redirect($response, '/admin/settings?tab=modules'); + } + + /** + * The consultation-only switch decides which copies count as available + * (CirculationPolicy::lendableCopySql()). When its effective value + * changed, the stored availability of the books that have such a copy, + * the only ones whose count moves, is recalculated at once. + */ + private function refreshConsultationAvailability(mysqli $db, bool $wasOn): void + { + if (\App\Support\Features::enabled('consultation_only') === $wasOn) { + return; + } + try { + $result = $db->query("SELECT DISTINCT c.libro_id FROM copie c JOIN libri l ON l.id = c.libro_id AND l.deleted_at IS NULL WHERE c.solo_consultazione = 1"); + if (!$result instanceof \mysqli_result) { + return; + } + $integrity = new \App\Support\DataIntegrity($db); + foreach ($result->fetch_all(MYSQLI_NUM) as [$bookId]) { + $integrity->recalculateBookAvailability((int) $bookId); + } + } catch (\Throwable $e) { + \App\Support\SecureLogger::warning('Consultation availability not recalculated: ' . $e->getMessage()); + } + } + + /** + * Write features. for each of $keys from the checkbox feature_ + * (absent = off). SettingsRepository::set() drops the ConfigStore cache, + * so the next request already reads the new value. + * + * @param array $data + * @param list $keys + */ + private function saveFeatureSwitches(SettingsRepository $repository, array $data, array $keys): void + { + foreach ($keys as $key) { + $field = 'feature_' . $key; + // Old pages/API clients do not know the new controls: absence preserves them. + if ((isset(\App\Support\Features::INHERITED[$key]) || $key === 'attendance_retention') && !array_key_exists($field, $data)) { + continue; + } + $on = isset($data[$field]) && is_scalar($data[$field]) && (string) $data[$field] === '1'; + $repository->set('features', $key, $on ? '1' : '0'); + } + } + /** A 302 back to an admin path inside the application. */ private function redirect(Response $response, string $location): Response { diff --git a/app/Controllers/SuppliersController.php b/app/Controllers/SuppliersController.php new file mode 100644 index 000000000..953476f69 --- /dev/null +++ b/app/Controllers/SuppliersController.php @@ -0,0 +1,218 @@ +schemaMissing($db, $response)) !== null) { + return $missing; + } + $fornitori = (new SupplierRepository($db))->listWithCounts(); + return $this->render($response, 'index.php', ['fornitori' => $fornitori]); + } + + public function createForm(Request $request, Response $response, mysqli $db): Response + { + if (($off = \App\Support\Features::requireOrRedirect('suppliers', $response)) !== null) { + return $off; + } + if (($missing = $this->schemaMissing($db, $response)) !== null) { + return $missing; + } + return $this->render($response, 'form.php', ['fornitore' => null, 'old' => $this->takeOldInput(null)]); + } + + public function store(Request $request, Response $response, mysqli $db): Response + { + if (($off = \App\Support\Features::requireOrRedirect('suppliers', $response)) !== null) { + return $off; + } + if (($missing = $this->schemaMissing($db, $response)) !== null) { + return $missing; + } + $input = (array) $request->getParsedBody(); + $data = SupplierRepository::normalize($input); + $error = $this->validationError($input, $data); + if ($error !== null) { + $_SESSION['error_message'] = $error; + $this->keepOldInput($input, null); + return $response->withHeader('Location', url('/admin/suppliers/create'))->withStatus(302); + } + try { + (new SupplierRepository($db))->create($data); + $_SESSION['success_message'] = __('Fornitore creato con successo.'); + } catch (\Throwable $e) { + \App\Support\SecureLogger::error('SuppliersController::store error', ['exception' => get_class($e), 'code' => $e->getCode()]); + $_SESSION['error_message'] = __('Impossibile salvare il fornitore. Riprova più tardi.'); + } + return $response->withHeader('Location', url('/admin/suppliers'))->withStatus(302); + } + + public function editForm(Request $request, Response $response, mysqli $db, int $id): Response + { + if (($off = \App\Support\Features::requireOrRedirect('suppliers', $response)) !== null) { + return $off; + } + if (($missing = $this->schemaMissing($db, $response)) !== null) { + return $missing; + } + $fornitore = (new SupplierRepository($db))->getById($id); + if ($fornitore === null) { + return $response->withStatus(404); + } + return $this->render($response, 'form.php', ['fornitore' => $fornitore, 'old' => $this->takeOldInput($id)]); + } + + public function update(Request $request, Response $response, mysqli $db, int $id): Response + { + if (($off = \App\Support\Features::requireOrRedirect('suppliers', $response)) !== null) { + return $off; + } + if (($missing = $this->schemaMissing($db, $response)) !== null) { + return $missing; + } + $repo = new SupplierRepository($db); + if ($repo->getById($id) === null) { + return $response->withStatus(404); + } + $input = (array) $request->getParsedBody(); + $data = SupplierRepository::normalize($input); + $error = $this->validationError($input, $data); + if ($error !== null) { + $_SESSION['error_message'] = $error; + $this->keepOldInput($input, $id); + return $response->withHeader('Location', url('/admin/suppliers/edit/' . $id))->withStatus(302); + } + try { + if ($repo->update($id, $data)) { + $_SESSION['success_message'] = __('Fornitore aggiornato con successo.'); + } else { + $_SESSION['error_message'] = __('Fornitore non trovato.'); + } + } catch (\Throwable $e) { + \App\Support\SecureLogger::error('SuppliersController::update error', ['id' => $id, 'exception' => get_class($e), 'code' => $e->getCode()]); + $_SESSION['error_message'] = __('Impossibile salvare il fornitore. Riprova più tardi.'); + } + return $response->withHeader('Location', url('/admin/suppliers'))->withStatus(302); + } + + public function delete(Request $request, Response $response, mysqli $db, int $id): Response + { + if (($off = \App\Support\Features::requireOrRedirect('suppliers', $response)) !== null) { + return $off; + } + if (($missing = $this->schemaMissing($db, $response)) !== null) { + return $missing; + } + try { + if ((new SupplierRepository($db))->delete($id)) { + $_SESSION['success_message'] = __('Fornitore eliminato. I libri collegati non hanno più un fornitore.'); + } else { + $_SESSION['error_message'] = __('Fornitore non trovato.'); + } + } catch (\Throwable $e) { + \App\Support\SecureLogger::error('SuppliersController::delete error', ['id' => $id, 'exception' => get_class($e), 'code' => $e->getCode()]); + $_SESSION['error_message'] = __('Impossibile eliminare il fornitore. Riprova più tardi.'); + } + return $response->withHeader('Location', url('/admin/suppliers'))->withStatus(302); + } + + /** + * The message that refuses a submission, or NULL when it can be saved: + * the name is required, and an e-mail or website that is not one is + * refused rather than silently dropped by normalize(). + * + * @param array $input raw form data + * @param array $data the same data, normalized + */ + private function validationError(array $input, array $data): ?string + { + $messages = []; + if ($data['nome'] === null) { + $messages[] = __('Il nome del fornitore è obbligatorio.'); + } + foreach (SupplierRepository::invalidFields($input) as $field) { + $messages[] = $field === 'email' + ? __('L\'email del fornitore non è un indirizzo valido.') + : __('Il sito web del fornitore non è un indirizzo valido: indica un URL http(s), ad esempio https://www.esempio.it.'); + } + return $messages === [] ? null : implode(' ', $messages); + } + + /** + * Remember a refused submission (the form's own fields only, as typed) + * for the form it redirects back to; $id tells the create form (NULL) + * from a given supplier's edit form. + * + * @param array $input raw form data + */ + private function keepOldInput(array $input, ?int $id): void + { + $fields = []; + foreach (SupplierRepository::FIELDS as $field) { + $raw = $input[$field] ?? null; + $fields[$field] = is_scalar($raw) ? (string) $raw : ''; + } + $_SESSION[self::OLD_INPUT] = ['id' => $id, 'fields' => $fields]; + } + + /** + * The refused submission for this form, consumed on read; NULL when + * there is none or it belongs to another form. + * + * @return array|null + */ + private function takeOldInput(?int $id): ?array + { + $old = $_SESSION[self::OLD_INPUT] ?? null; + unset($_SESSION[self::OLD_INPUT]); + if (!is_array($old) || ($old['id'] ?? null) !== $id || !is_array($old['fields'] ?? null)) { + return null; + } + return $old['fields']; + } + + /** @param array $data */ + private function render(Response $response, string $view, array $data): Response + { + ob_start(); + require __DIR__ . '/../Views/fornitori/' . $view; + $content = ob_get_clean(); + ob_start(); + require __DIR__ . '/../Views/layout.php'; + $html = ob_get_clean(); + $response->getBody()->write((string) $html); + return $response; + } + + /** + * Files updated before the 0.9.0 migration ran (or a deploy that skipped + * it): the suppliers table is not there yet, so every action answers + * with a message instead of a 500. + */ + private function schemaMissing(mysqli $db, Response $response): ?Response + { + if ((new SupplierRepository($db))->isAvailable()) { + return null; + } + $_SESSION['error_message'] = __('Questa funzione sarà disponibile al termine dell\'aggiornamento del database.'); + return $response->withHeader('Location', url('/admin/dashboard'))->withStatus(302); + } +} diff --git a/app/Controllers/UserActionsController.php b/app/Controllers/UserActionsController.php index ae6b51eae..3634d1ca4 100644 --- a/app/Controllers/UserActionsController.php +++ b/app/Controllers/UserActionsController.php @@ -560,9 +560,9 @@ public function changeReservationDate(Request $request, Response $response, mysq } $uid = (int) $user['id']; + // A closed start day moves to the next open day (see reserve()). + $date = \App\Support\LibraryCalendar::forDb($db)->nextOpenDay($date); $startDate = $date; - $loanDays = (new \App\Models\SettingsRepository($db))->requestLoanDurationDays(); - $endDate = (new \DateTimeImmutable($date))->modify("+{$loanDays} days")->format('Y-m-d'); $db->begin_transaction(); @@ -582,6 +582,8 @@ public function changeReservationDate(Request $request, Response $response, mysq } $libroId = (int) $reservation['libro_id']; + $loanDays = (new \App\Services\CirculationPolicy($db))->forLoan($uid, $libroId)->requestDurationDays(); + $endDate = \App\Services\CirculationPolicy::dueDateFor($date, $loanDays, $db); // Lock book row first to serialize every capacity decision. $lockStmt = $db->prepare("SELECT id FROM libri WHERE id = ? AND deleted_at IS NULL FOR UPDATE"); @@ -710,21 +712,24 @@ public function loan(Request $request, Response $response, mysqli $db): Response // User eligibility gate (M7): stato/tessera are only verified at login, // so a user suspended (or with an expired card) mid-session could // otherwise keep submitting loan requests. - if (\App\Support\LoanEligibility::checkUser($db, $utenteId) !== null) { - return $this->back($response, ['loan_error' => 'not_eligible']); + if (($eligibilityError = \App\Support\LoanEligibility::checkUser($db, $utenteId)) !== null) { + // debt_block keeps its own message; any other refusal is not_eligible. + return $this->back($response, ['loan_error' => $eligibilityError === 'debt_block' ? 'debt_block' : 'not_eligible']); } // "Today" via DateHelper (M9): PHP process tz is often UTC while the // library runs in a local zone, so a raw date('Y-m-d') near midnight // would date the request to the wrong day. - $data_prestito = \App\Support\DateHelper::today(); - $loanDays = (new \App\Models\SettingsRepository($db))->requestLoanDurationDays(); - $data_scadenza = date('Y-m-d', strtotime($data_prestito . " +{$loanDays} days")); + // A day the library is closed starts on the next open day instead. + $data_prestito = \App\Support\LibraryCalendar::forDb($db)->nextOpenDay(\App\Support\DateHelper::today()); + $circulation = new \App\Services\CirculationPolicy($db); + $loanPolicy = $circulation->forLoan($utenteId, $libroId); + $data_scadenza = \App\Services\CirculationPolicy::dueDateFor($data_prestito, $loanPolicy->requestDurationDays(), $db); // Optional return date (Mobile API 1.8.2): the app proposes the day // before a scheduled loan or reservation when the default would cross // it, exactly as the website calendar does. Bounded like the public // endpoint: a real day, not before the start, within the maximum. - $endOverride = $this->requestedEndDate($db, $data, $data_prestito); + $endOverride = $this->requestedEndDate($loanPolicy->maxRequestDays, $data, $data_prestito, $db); if ($endOverride === false) { return $this->back($response, ['loan_error' => 'invalid_date']); } @@ -732,7 +737,8 @@ public function loan(Request $request, Response $response, mysqli $db): Response return $this->back($response, ['loan_error' => 'max_duration_exceeded']); } if ($endOverride !== null) { - $data_scadenza = $endOverride; + // A chosen return date on a closed day moves to the next open day. + $data_scadenza = \App\Services\CirculationPolicy::dueDateFor($endOverride, 0, $db); } // Use transaction + lock to prevent race conditions @@ -799,9 +805,10 @@ public function loan(Request $request, Response $response, mysqli $db): Response $userLockStmt->execute(); $userLockStmt->get_result(); $userLockStmt->close(); - if (\App\Support\LoanEligibility::checkUser($db, $utenteId) !== null) { + if (($eligibilityError = \App\Support\LoanEligibility::checkUser($db, $utenteId)) !== null) { $db->rollback(); - return $this->back($response, ['loan_error' => 'not_eligible']); + // debt_block keeps its own message; any other refusal is not_eligible. + return $this->back($response, ['loan_error' => $eligibilityError === 'debt_block' ? 'debt_block' : 'not_eligible']); } // #384 coherence: the reservation-vs-loan decision is the SAME @@ -819,9 +826,24 @@ public function loan(Request $request, Response $response, mysqli $db): Response // Checked BEFORE the max-active-loans cap, mirroring // createReservation: a waitlist reservation does not consume an // active-loan slot until promotion. + // Circulation rules of the patron type, under the user lock + // (same order as createReservation): consultation-only material, + // then the reservation limit (queue + requests awaiting approval). + $policyError = !$loanPolicy->loanable + ? 'not_loanable' + : $circulation->reservationLimitError($loanPolicy->user, $utenteId, queue: false); + if ($policyError !== null) { + $db->rollback(); + return $this->back($response, ['loan_error' => $policyError]); + } + $routing = (new \App\Services\LoanRequestGate($db)) ->route($libroId, $utenteId, $data_prestito, $data_scadenza, inCallerTransaction: true); if ($routing->isReservation()) { + if (!$loanPolicy->user->reservationsEnabled) { + $db->rollback(); + return $this->back($response, ['loan_error' => 'reservations_disabled']); + } $db->commit(); // Reuse the reserve() success contract: the frontend already // renders reserve_success as "queued in the waitlist". @@ -831,19 +853,12 @@ public function loan(Request $request, Response $response, mysqli $db): Response ]); } - // Enforce max active loans per user (admin setting; 0 = no limit) - $maxLoans = (int) ((new \App\Models\SettingsRepository($db))->get('loans', 'max_active_loans_per_user', '0') ?? 0); - if ($maxLoans > 0) { - $cntStmt = $db->prepare("SELECT COUNT(*) FROM prestiti WHERE utente_id = ? AND attivo = 1 AND stato IN ('prenotato','da_ritirare','in_corso','in_ritardo')"); - $cntStmt->bind_param('i', $utenteId); - $cntStmt->execute(); - $cntResult = $cntStmt->get_result(); - $activeCount = (int) ($cntResult ? $cntResult->fetch_row()[0] : 0); - $cntStmt->close(); - if ($activeCount >= $maxLoans) { - $db->rollback(); - return $this->back($response, ['loan_error' => 'max_loans_reached']); - } + // Circulation limits of this patron and title (CirculationPolicy; + // 0 = no limit), counted under the user lock. + $limitError = $circulation->loanLimitError($loanPolicy, $utenteId); + if ($limitError !== null) { + $db->rollback(); + return $this->back($response, ['loan_error' => $limitError]); } // Capture the setting while the canonical book/copy locks are still @@ -968,16 +983,19 @@ public function reserve(Request $request, Response $response, mysqli $db): Respo // User eligibility gate (M7): stato/tessera are only verified at login, // so a user suspended (or with an expired card) mid-session could // otherwise keep queueing reservations. - if (\App\Support\LoanEligibility::checkUser($db, $utenteId) !== null) { - return $this->back($response, ['reserve_error' => 'not_eligible']); + if (($eligibilityError = \App\Support\LoanEligibility::checkUser($db, $utenteId)) !== null) { + // debt_block keeps its own message; any other refusal is not_eligible. + return $this->back($response, ['reserve_error' => $eligibilityError === 'debt_block' ? 'debt_block' : 'not_eligible']); } - // Calculate date range for availability check - $start = ($desired !== '') ? $desired : $today; - $loanDays = (new \App\Models\SettingsRepository($db))->requestLoanDurationDays(); - $end = (new \DateTimeImmutable($start))->modify("+{$loanDays} days")->format('Y-m-d'); + // Calculate date range for availability check. A start day the library + // is closed moves to the next open day, as the request calendar + // proposes it; identical without closures. + $start = \App\Support\LibraryCalendar::forDb($db)->nextOpenDay(($desired !== '') ? $desired : $today); + $reservePolicy = (new \App\Services\CirculationPolicy($db))->forLoan($utenteId, $libroId); + $end = \App\Services\CirculationPolicy::dueDateFor($start, $reservePolicy->requestDurationDays(), $db); // Optional return date (Mobile API 1.8.2), bounded as in loan(). - $endOverride = $this->requestedEndDate($db, $data, $start); + $endOverride = $this->requestedEndDate($reservePolicy->maxRequestDays, $data, $start, $db); if ($endOverride === false) { return $this->back($response, ['reserve_error' => 'invalid_date']); } @@ -985,7 +1003,8 @@ public function reserve(Request $request, Response $response, mysqli $db): Respo return $this->back($response, ['reserve_error' => 'max_duration_exceeded']); } if ($endOverride !== null) { - $end = $endOverride; + // A chosen end date on a closed day moves to the next open day. + $end = \App\Services\CirculationPolicy::dueDateFor($endOverride, 0, $db); } // Start transaction for concurrency control @@ -1030,9 +1049,19 @@ public function reserve(Request $request, Response $response, mysqli $db): Respo $userLockStmt->execute(); $userLockStmt->get_result(); $userLockStmt->close(); - if (\App\Support\LoanEligibility::checkUser($db, $utenteId) !== null) { + if (($eligibilityError = \App\Support\LoanEligibility::checkUser($db, $utenteId)) !== null) { + $db->rollback(); + // debt_block keeps its own message; any other refusal is not_eligible. + return $this->back($response, ['reserve_error' => $eligibilityError === 'debt_block' ? 'debt_block' : 'not_eligible']); + } + + // Circulation rules of the patron type, under the user lock. + $policyError = !$reservePolicy->loanable + ? 'not_loanable' + : (new \App\Services\CirculationPolicy($db))->reservationLimitError($reservePolicy->user, $utenteId, queue: true); + if ($policyError !== null) { $db->rollback(); - return $this->back($response, ['reserve_error' => 'not_eligible']); + return $this->back($response, ['reserve_error' => $policyError]); } // Canonical peak-capacity decision (same service as admin create, @@ -1095,7 +1124,8 @@ public function reserve(Request $request, Response $response, mysqli $db): Respo $db->commit(); $params = ['reserve_success' => 1]; if ($desired !== '') { - $params['reserve_date'] = $desired; + // The stored start: a closed desired day moved to the next open one. + $params['reserve_date'] = $start; } return $this->back($response, $params); } @@ -1118,7 +1148,7 @@ public function reserve(Request $request, Response $response, mysqli $db): Respo * @param array $data * @return string|false|null */ - private function requestedEndDate(mysqli $db, array $data, string $startDate): string|false|null + private function requestedEndDate(int $maxDays, array $data, string $startDate, ?\mysqli $db = null): string|false|null { $raw = $data['end_date'] ?? null; if (!is_scalar($raw)) { @@ -1131,9 +1161,10 @@ private function requestedEndDate(mysqli $db, array $data, string $startDate): s if (!\App\Support\DateHelper::isISODateFormat($end) || $end < $startDate) { return false; } - $maxDays = (new \App\Models\SettingsRepository($db))->maxLoanDurationDays(); - $days = (int) (new \DateTimeImmutable($startDate))->diff(new \DateTimeImmutable($end))->days; - return $days > $maxDays ? 'max_duration_exceeded' : $end; + // The longest loan ends on the maximum day, moved forward like every + // due date when it falls on a closed day (closed days are not loan + // days); without closures this is start + maximum, as before. + return $end > \App\Services\CirculationPolicy::dueDateFor($startDate, $maxDays, $db) ? 'max_duration_exceeded' : $end; } private function back(Response $response, array $params): Response diff --git a/app/Controllers/UserDashboardController.php b/app/Controllers/UserDashboardController.php index 2feead36e..522e49e37 100644 --- a/app/Controllers/UserDashboardController.php +++ b/app/Controllers/UserDashboardController.php @@ -227,6 +227,23 @@ public function prenotazioni(Request $request, Response $response, mysqli $db, m error_log('Prenotazioni error: ' . $e->getMessage()); } + // Balance and movements (plan §7): the reader's own ledger, plus the + // fines accruing today on overdue loans (not written until the return). + $ledgerBalanceCents = 0; + $ledgerMovements = []; + $ledgerAccrued = []; + try { + // Fines module off: no balance section at all (the rows stay). + if (\App\Services\PatronLedger::active($db)) { + $ledger = \App\Services\PatronLedger::forDb($db); + $ledgerBalanceCents = $ledger->balanceCents($userId); + $ledgerMovements = $ledger->movements($userId, 50); + $ledgerAccrued = $ledger->accruedFines($userId); + } + } catch (\Throwable $e) { + \App\Support\SecureLogger::error('Patron ledger unavailable on the loans page', ['error' => $e->getMessage()]); + } + // Render view ob_start(); $title = 'Le Mie Prenotazioni - Biblioteca'; diff --git a/app/Controllers/UserImportController.php b/app/Controllers/UserImportController.php new file mode 100644 index 000000000..dcc214223 --- /dev/null +++ b/app/Controllers/UserImportController.php @@ -0,0 +1,882 @@ + ';', ',' => ',', 'tab' => "\t"]; + + public function page(Request $request, Response $response, mysqli $db): Response + { + if (($off = \App\Support\Features::requireOrRedirect('user_import', $response)) !== null) { + return $off; + } + if (!\App\Support\Permissions::can('users.manage')) { + return $response->withStatus(403); + } + // Coming back to the page abandons a preview that was never started + // ("Annulla" reloads it); an import in progress is left alone. + $state = $_SESSION[self::SESSION_KEY] ?? null; + if (is_array($state) && empty($state['started'])) { + $this->discardSessionFile(); + unset($_SESSION[self::SESSION_KEY]); + } + // The session's own interrupted run keeps its staged file while its + // page is visited; a file swept meanwhile (the session outlived it) + // closes the run into the import history instead of offering a + // resume that could only fail. + $this->touchSessionFile(); + UserImport::sweepStaleUploads(self::uploadDir()); + $expiredImport = $this->closeVanishedImport($db); + $patronTypes = (new PatronTypeImport($db))->available(); + $resumeImport = self::resumable($_SESSION[self::SESSION_KEY] ?? null); + ob_start(); + require __DIR__ . '/../Views/utenti/import.php'; + $content = ob_get_clean(); + ob_start(); + require __DIR__ . '/../Views/layout.php'; + $html = ob_get_clean(); + $response->getBody()->write((string) $html); + return $response; + } + + /** A sample file with every recognised column. */ + public function example(Request $request, Response $response, mysqli $db): Response + { + if (($off = \App\Support\Features::requireOrRedirect('user_import', $response)) !== null) { + return $off; + } + if (!\App\Support\Permissions::can('users.manage')) { + return $response->withStatus(403); + } + $writer = Csv::writerToString(';', "'"); + $writer->insertOne(['nome', 'cognome', 'email', 'telefono', 'indirizzo', 'data_nascita', 'codice_tessera', 'data_scadenza_tessera', 'note']); + $writer->insertOne(['Maria', 'Rossi', 'maria.rossi@example.org', '+39 333 1234567', 'Via Roma 1, Milano', '15/04/1990', 'T0001', '31/12/2027', '']); + $writer->insertOne(['Luca', 'Bianchi', 'luca.bianchi@example.org', '', '', '1985-09-02', '', '', 'Socio sostenitore']); + $response->getBody()->write("\xEF\xBB\xBF" . $writer->toString()); + return $response + ->withHeader('Content-Type', 'text/csv; charset=UTF-8') + ->withHeader('Content-Disposition', 'attachment; filename="esempio_import_utenti.csv"') + ->withHeader('Cache-Control', 'no-cache, no-store, must-revalidate'); + } + + public function upload(Request $request, Response $response, mysqli $db): Response + { + if (($off = \App\Support\Features::requireOrRedirect('user_import', $response)) !== null) { + return $off; + } + @ini_set('display_errors', '0'); + if (!\App\Support\Permissions::can('users.manage')) { + return $this->json($response, ['success' => false, 'error' => __('Accesso negato')], 403); + } + // A run still under way (another tab) is never replaced: its next + // block would find the session gone and lose its position. + $current = $_SESSION[self::SESSION_KEY] ?? null; + if (is_array($current) && self::isRunning($current)) { + return $this->json($response, [ + 'success' => false, + 'error' => __('Un import è già in corso: attendi la fine o riprendilo da questa pagina.'), + 'processed' => (int) ($current['next'] ?? 0), + 'total_rows' => (int) ($current['total'] ?? 0), + ], 409); + } + // The staged file of a run already in the session is replaced only + // once the new upload proved valid (below): a refused upload leaves + // the import in progress intact. + UserImport::sweepStaleUploads(self::uploadDir()); + + $file = $request->getUploadedFiles()['csv_file'] ?? null; + if (!$file instanceof \Psr\Http\Message\UploadedFileInterface || $file->getError() !== UPLOAD_ERR_OK) { + return $this->json($response, ['success' => false, 'error' => __('Nessun file caricato')], 400); + } + $name = (string) $file->getClientFilename(); + if (!preg_match('/\.(csv|txt|tsv)$/i', $name)) { + return $this->json($response, ['success' => false, 'error' => __('Il file deve avere estensione .tsv, .csv o .txt')], 400); + } + if ((int) $file->getSize() > UserImport::MAX_BYTES) { + return $this->json($response, ['success' => false, 'error' => __('Il file supera la dimensione massima di 5 MB.')], 400); + } + $tmp = $file->getStream()->getMetadata('uri'); + if (!is_string($tmp) || !is_file($tmp)) { + return $this->json($response, ['success' => false, 'error' => __('Impossibile leggere il file caricato')], 400); + } + + $bytes = @file_get_contents($tmp); + if ($bytes === false) { + return $this->json($response, ['success' => false, 'error' => __('Impossibile leggere il file caricato')], 400); + } + // The staged copy is always UTF-8 without BOM: a Windows-1252 export + // is converted here, once, so every later step reads clean text. + ['text' => $text, 'converted' => $converted] = UserImport::toUtf8($bytes); + unset($bytes); + // The separator chosen on the form wins; otherwise it is detected on + // the first logical records (a quoted cell may span several lines). + $chosen = (array) ($request->getParsedBody() ?? []); + $delimiter = self::DELIMITERS[is_string($chosen['delimiter'] ?? null) ? $chosen['delimiter'] : ''] ?? UserImport::detectDelimiter(UserImport::sampleRecords($text, 5)); + if ($delimiter === null) { + return $this->json($response, ['success' => false, 'error' => __('File CSV non valido: usa ";", "," o TAB come separatore.')], 400); + } + + $dir = self::uploadDir(); + if (!is_dir($dir)) { + @mkdir($dir, 0755, true); + } + $saved = $dir . '/user_import_' . bin2hex(random_bytes(12)) . '.csv'; + if (@file_put_contents($saved, $text) === false) { + return $this->json($response, ['success' => false, 'error' => __('Impossibile salvare il file CSV caricato')], 500); + } + unset($text); + + try { + $reader = Csv::readerFromPath($saved, $delimiter); + $headers = array_map(static fn ($h): string => trim((string) $h), (array) $reader->nth(0)); + $total = max(0, count($reader) - 1); + $preview = []; + foreach ((new Statement())->offset(1)->limit(UserImport::PREVIEW_ROWS)->process($reader) as $row) { + $preview[] = array_map(static fn ($v): string => mb_substr(trim((string) $v), 0, 120), array_values((array) $row)); + } + } catch (\Throwable $e) { + @unlink($saved); + return $this->json($response, ['success' => false, 'error' => __('File CSV vuoto o formato non valido')], 400); + } + if ($headers === [] || $total === 0) { + @unlink($saved); + return $this->json($response, ['success' => false, 'error' => __('File CSV vuoto o formato non valido')], 400); + } + + $fields = $this->targetFields($db); + // '' = the run this upload replaces was closed but its history entry + // could not be written: said apart from the conversion notice. + $closedId = $this->closeSessionImport($db); + // Start, every block and "Annulla import" name the preview they come + // from: an upload in another tab replaces this state, and a page + // still showing the earlier preview must not apply its mapping to + // the rows of the new file. + $previewId = bin2hex(random_bytes(12)); + $_SESSION[self::SESSION_KEY] = [ + 'preview_id' => $previewId, + 'file' => $saved, + 'name' => mb_substr($name, 0, 255), + 'delimiter' => $delimiter, + 'total' => $total, + 'columns' => count($headers), + 'started' => false, + ]; + + return $this->json($response, [ + 'success' => true, + 'preview_id' => $previewId, + 'file_name' => $name, + 'total_rows' => $total, + 'headers' => $headers, + 'preview' => $preview, + 'fields' => $fields, + 'mapping' => UserImport::guessMapping($headers, $fields), + 'notice' => $converted ? __('Il file non era in UTF-8 ed è stato convertito da Windows-1252: controlla le lettere accentate nell\'anteprima.') : null, + 'history_warning' => $closedId === '' ? __('L\'import precedente è stato chiuso, ma lo storico import non è stato aggiornato: gli utenti già creati restano.') : null, + ]); + } + + public function start(Request $request, Response $response, mysqli $db): Response + { + if (($off = \App\Support\Features::requireOrRedirect('user_import', $response)) !== null) { + return $off; + } + @ini_set('display_errors', '0'); + if (!\App\Support\Permissions::can('users.manage')) { + return $this->json($response, ['success' => false, 'error' => __('Accesso negato')], 403); + } + $state = $_SESSION[self::SESSION_KEY] ?? null; + $body = json_decode((string) $request->getBody(), true); + if (!is_array($state) || !is_array($body) || !is_file((string) ($state['file'] ?? ''))) { + return $this->json($response, ['success' => false, 'error' => __('Sessione import scaduta o dati non validi')], 400); + } + if (!self::samePreview($state, $body)) { + return $this->stale($response); + } + if (!empty($state['started'])) { + // A replayed start (double click, a second tab, a lost answer) + // would reset the counters and the position of a run already + // under way: the answer carries that position, so the page + // resumes the run instead. + return $this->json($response, [ + 'success' => false, + 'error' => __('Import già avviato: attendi la fine o ricarica il file.'), + 'processed' => (int) ($state['next'] ?? 0), + 'total_rows' => (int) ($state['total'] ?? 0), + ], 409); + } + $check = UserImport::validateMapping($body['mapping'] ?? null, $this->targetFields($db), (int) $state['columns']); + if ($check['error'] !== null) { + return $this->json($response, ['success' => false, 'error' => $check['error']], 422); + } + $state['mapping'] = $check['mapping']; + // A real boolean: the string "false" must not send invitations. + $state['send_invites'] = filter_var($body['send_invites'] ?? false, FILTER_VALIDATE_BOOLEAN); + $state['started'] = true; + $state['created'] = 0; + $state['duplicates'] = 0; + $state['failed'] = 0; + $state['invited'] = 0; + $state['invite_failed'] = 0; + $state['errors'] = []; + $state['next'] = 0; + $state['active_at'] = time(); + $_SESSION[self::SESSION_KEY] = $state; + + return $this->json($response, [ + 'success' => true, + 'total_rows' => (int) $state['total'], + 'chunk_size' => UserImport::CHUNK_SIZE, + ]); + } + + public function chunk(Request $request, Response $response, mysqli $db): Response + { + if (($off = \App\Support\Features::requireOrRedirect('user_import', $response)) !== null) { + return $off; + } + @set_time_limit(300); + @ini_set('display_errors', '0'); + if (!\App\Support\Permissions::can('users.manage')) { + return $this->json($response, ['success' => false, 'error' => __('Accesso negato')], 403); + } + $body = json_decode((string) $request->getBody(), true); + if (is_array($_SESSION[self::SESSION_KEY] ?? null) && is_array($body) && !self::samePreview($_SESSION[self::SESSION_KEY], $body)) { + // A block from a preview another upload has replaced: never + // import the new file under the old mapping. No position in the + // answer, so the page does not "resume" a run that is not its own. + return $this->stale($response); + } + if (is_array($_SESSION[self::SESSION_KEY] ?? null) && !empty($_SESSION[self::SESSION_KEY]['finished']) && is_array($_SESSION[self::SESSION_KEY]['result'] ?? null)) { + // The run is over: this is the retry of a last block whose answer + // was lost (a gateway timeout while the invitations went out). + // Replaying the final answer shows the summary instead of an + // error that would invite uploading the same file again. + return $this->json($response, $_SESSION[self::SESSION_KEY]['result']); + } + if (!is_array($_SESSION[self::SESSION_KEY] ?? null) || empty($_SESSION[self::SESSION_KEY]['started']) || !is_array($body)) { + return $this->json($response, ['success' => false, 'error' => __('Sessione import scaduta o dati non validi')], 400); + } + // Every change below lands in the session at once, row by row: when + // a request dies mid-chunk (timeout, fatal error), PHP still saves + // the session at shutdown, so the position survives and the page + // resumes from it instead of replaying the rows already written. + $state = &$_SESSION[self::SESSION_KEY]; + $start = max(0, (int) ($body['start'] ?? 0)); + // Chunks are processed in order exactly once: a replayed request (a + // retry after a timeout) must not insert the same rows again. The + // answer carries the position to resume from. + if ($start !== (int) $state['next']) { + return $this->json($response, ['success' => false, 'error' => __('Blocco di righe non valido.'), 'processed' => (int) $state['next']], 409); + } + $total = (int) $state['total']; + if (!is_file((string) $state['file'])) { + // Swept or deleted meanwhile: touch() would recreate it empty and + // the run would end as if the remaining rows did not exist. The + // rows already done are written to the import history first. + unset($state); + $importId = $this->closeSessionImport($db); + return $this->json($response, [ + 'success' => false, + 'error' => $importId === '' + ? __('Il file dell\'import non è più disponibile e lo storico import non è stato aggiornato: gli utenti già creati restano. Carica di nuovo il file per completarlo.') + : __('Il file dell\'import non è più disponibile: le righe già elaborate sono state registrate nello storico import. Carica di nuovo il file per completarlo.'), + 'import_id' => $importId, + ], 409); + } + @touch((string) $state['file']); // in use: not stale for sweepStaleUploads() + $state['active_at'] = time(); // driven by a page: not abandoned (isRunning()) + + $handle = @fopen((string) $state['file'], 'rb'); + if ($handle === false) { + return $this->json($response, ['success' => false, 'error' => __('Impossibile aprire il file CSV')], 500); + } + // Each block resumes at the byte where the previous one stopped, so + // a large file is read once instead of again from the top for every + // block. Only the first block (or a run started before the offset + // was kept) skips the header and the rows already done by counting. + $delimiter = (string) $state['delimiter']; + if (is_int($state['offset'] ?? null)) { + fseek($handle, $state['offset']); + } else { + for ($skip = 1 + $start; $skip > 0; $skip--) { + if (self::nextRecord($handle, $delimiter) === null) { + break; + } + } + } + + $customDefinitions = RegistrationFields::definitions($db); + $locales = array_map('strval', array_keys(\App\Support\I18n::getAvailableLocales())); + $visible = [ + 'sesso' => RegistrationFields::isVisible('sesso'), + 'cod_fiscale' => RegistrationFields::isVisible('cod_fiscale'), + ]; + $installLocale = \App\Support\I18n::normalizeLocaleCode(\App\Support\I18n::getInstallationLocale()); + if (!in_array($installLocale, $locales, true)) { + $installLocale = 'it_IT'; + } + $patronTypes = new PatronTypeImport($db); + $notifier = $state['send_invites'] ? new NotificationService($db) : null; + + $line = $start + 2; // header is line 1 + $processed = 0; + while ($processed < UserImport::CHUNK_SIZE && ($row = self::nextRecord($handle, $delimiter)) !== null) { + $offset = (int) ftell($handle); + $processed++; + $label = ''; + try { + $parsed = UserImport::normalizeRow($row, $state['mapping'], $customDefinitions, $locales, $visible); + $record = $parsed['record']; + $label = $this->text(trim((string) ($record['nome'] ?? '') . ' ' . (string) ($record['cognome'] ?? '')) ?: (string) ($record['email'] ?? '')); + if ($parsed['error'] !== null) { + $this->rowError($state, $line, $this->rowLabel($row, $state['mapping']), $parsed['error'], 'validation'); + continue; + } + $email = (string) $record['email']; + $code = (string) ($record['codice_tessera'] ?? ''); + // The rows inserted by earlier blocks are in the table already: + // exists() catches a repeat within the file too, without a + // per-row set in the session that grows with every import. + if ($this->exists($db, 'email', $email)) { + $this->duplicate($state, $line, $label, sprintf(__('Email già registrata: %s'), $email)); + continue; + } + if ($code !== '' && $this->exists($db, 'codice_tessera', $code)) { + $this->duplicate($state, $line, $label, sprintf(__('Codice tessera già assegnato: %s'), $code)); + continue; + } + if ($code === '') { + $record['codice_tessera'] = $this->generateCardCode($db); + } + $record['locale'] ??= $installLocale; + + $userId = $this->insertUser($db, $record); + $state['created']++; + // The account exists from here on: what follows completes it, so + // a failure is a warning on a created row, never a failed row + // (that would count it twice and invite a duplicate re-import). + $this->completeImportedUser($db, $state, $userId, $line, $label, $parsed, $patronTypes, $notifier); + } catch (\Throwable $e) { + $field = $e instanceof \mysqli_sql_exception ? \App\Support\UniqueViolation::fieldFor($e) : 'other'; + if ($field === 'email' || $field === 'codice_tessera' || $field === 'cod_fiscale') { + $this->duplicate($state, $line, $label, match ($field) { + 'email' => __('Email già registrata'), + 'codice_tessera' => __('Codice tessera già assegnato'), + default => __('Codice fiscale già registrato'), + }); + } else { + SecureLogger::error('[UserImport] row failed', ['line' => $line, 'error' => $e->getMessage()]); + $this->rowError($state, $line, $label, __('Errore del database'), 'database'); + } + } finally { + $line++; + // Position and byte offset move together, row by row. + $state['next'] = $start + $processed; + $state['offset'] = $offset; + $state['active_at'] = time(); + } + } + fclose($handle); + + $state['next'] = $start + $processed; + $done = $processed === 0 || $state['next'] >= $total; + $payload = [ + 'success' => true, + 'processed' => (int) $state['next'], + 'total_rows' => $total, + 'created' => (int) $state['created'], + 'duplicates' => (int) $state['duplicates'], + 'failed' => (int) $state['failed'], + 'invited' => (int) $state['invited'], + 'done' => $done, + ]; + + if ($done) { + $payload['import_id'] = $this->finish($db, $state); + if ($payload['import_id'] === '') { + // The accounts are created and counted either way; only the + // history entry (and the downloadable report) is missing. + $payload['log_error'] = __('Import completato, ma lo storico non è stato salvato: il report degli errori non è scaricabile.'); + } + $payload['invite_failed'] = (int) $state['invite_failed']; + $payload['errors'] = array_slice($state['errors'], 0, 50); + $summary = ['created' => $state['created'], 'duplicates' => $state['duplicates'], 'failed' => $state['failed']]; + $this->discardSessionFile(); + unset($state); + // Only the final answer is kept, for a retry of this request; + // the next upload or a visit to the page replaces it. + $_SESSION[self::SESSION_KEY] = ['finished' => true, 'preview_id' => (string) ($_SESSION[self::SESSION_KEY]['preview_id'] ?? ''), 'result' => $payload]; + SecureLogger::info('Users imported from CSV', $summary + ['by' => $_SESSION['user']['id'] ?? null]); + } + return $this->json($response, $payload); + } + + /** + * The next non-blank record of an open CSV, or null at its end. Split + * with the same controls as Csv::readerFromPath() (double-quote + * enclosure, no escape character, blank lines skipped), so the rows match + * the ones the league Reader counted and previewed at upload. + * + * @param resource $handle + * @return list|null + */ + private static function nextRecord($handle, string $delimiter): ?array + { + while (($row = fgetcsv($handle, null, $delimiter, '"', '')) !== false) { + if ($row !== [null]) { + return $row; + } + } + return null; + } + + /** + * "Annulla import" on an interrupted run: the rows already written stay + * (they are real accounts now) and go into the import history as a + * partial run; the staged file and the session state are released. A run + * still active is refused, so a second tab cannot cut it short. + */ + public function cancel(Request $request, Response $response, mysqli $db): Response + { + @ini_set('display_errors', '0'); + if (!\App\Support\Permissions::can('users.manage')) { + return $this->json($response, ['success' => false, 'error' => __('Accesso negato')], 403); + } + $state = $_SESSION[self::SESSION_KEY] ?? null; + $body = json_decode((string) $request->getBody(), true); + if (is_array($state) && !self::samePreview($state, is_array($body) ? $body : [])) { + // A page left over from an earlier preview: the import now in the + // session belongs to another upload and is left alone. + return $this->json($response, ['success' => true, 'stale' => true, 'import_id' => null]); + } + if (!is_array($state) || empty($state['started']) || !empty($state['finished'])) { + unset($_SESSION[self::SESSION_KEY]); + return $this->json($response, ['success' => true, 'import_id' => null]); + } + if (self::isRunning($state)) { + return $this->json($response, [ + 'success' => false, + 'error' => sprintf(__('L\'import è ancora attivo: riprendilo, oppure riprova tra %d minuti.'), (int) ceil(self::IDLE_SECONDS / 60)), + 'processed' => (int) ($state['next'] ?? 0), + ], 409); + } + $importId = $this->closeSessionImport($db); + // The run is closed either way; when its history entry could not be + // written the page says so instead of moving on as if it were. + return $this->json($response, [ + 'success' => true, + 'import_id' => $importId, + 'history_warning' => $importId === '' ? __('L\'import precedente è stato chiuso, ma lo storico import non è stato aggiornato: gli utenti già creati restano.') : null, + ]); + } + + /** + * Position of an interrupted run the page can resume, or null. + * + * @return array{preview_id:string, name:string, processed:int, total:int}|null + */ + public static function resumable(mixed $state): ?array + { + if (!is_array($state) || empty($state['started']) || !empty($state['finished'])) { + return null; + } + return [ + 'preview_id' => (string) ($state['preview_id'] ?? ''), + 'name' => (string) ($state['name'] ?? ''), + 'processed' => (int) ($state['next'] ?? 0), + 'total' => (int) ($state['total'] ?? 0), + ]; + } + + /** + * Whether a request names the preview held in the session. A missing or + * empty id never matches: every page that can start, drive or cancel an + * import received the id with its preview. + * + * @param array $state + * @param array $body + */ + private static function samePreview(array $state, array $body): bool + { + $current = (string) ($state['preview_id'] ?? ''); + $given = $body['preview_id'] ?? null; + return $current !== '' && is_string($given) && $given !== '' && hash_equals($current, $given); + } + + /** 409 for a request from a preview another upload has replaced. */ + private function stale(Response $response): Response + { + return $this->json($response, [ + 'success' => false, + 'stale' => true, + 'error' => __('Questa anteprima è stata sostituita da un altro file caricato: carica di nuovo il file per importarlo.'), + ], 409); + } + + /** A started run whose last request is recent enough to be still driven by a page. */ + private static function isRunning(array $state): bool + { + return !empty($state['started']) && empty($state['finished']) + && (int) ($state['active_at'] ?? 0) > time() - self::IDLE_SECONDS; + } + + /** + * Release the session's import: a started run is written to the import + * history first (as far as it got), so the accounts it created are not + * left without a trace. Returns that run's import_id, '' when its history + * entry could not be written, or null when no started run was closed. + */ + private function closeSessionImport(mysqli $db): ?string + { + $state = $_SESSION[self::SESSION_KEY] ?? null; + $importId = null; + if (is_array($state) && !empty($state['started']) && empty($state['finished'])) { + $importId = $this->finish($db, $state, (int) ($state['next'] ?? 0)); + SecureLogger::info('User import closed before its end', [ + 'processed' => (int) ($state['next'] ?? 0), + 'total' => (int) ($state['total'] ?? 0), + 'created' => (int) ($state['created'] ?? 0), + 'by' => $_SESSION['user']['id'] ?? null, + ]); + } + $this->discardSessionFile(); + unset($_SESSION[self::SESSION_KEY]); + return $importId; + } + + /** Mark the staged file of the session's interrupted run as in use, so the sweep keeps it. */ + private function touchSessionFile(): void + { + $state = $_SESSION[self::SESSION_KEY] ?? null; + if (is_array($state) && !empty($state['started']) && empty($state['finished']) + && is_string($state['file'] ?? null) && is_file($state['file'])) { + @touch($state['file']); + } + } + + /** + * Close the session's interrupted run when its staged file is gone: it + * can no longer be resumed, so it is written to the import history as far + * as it got. Returns what the page tells the user, or null. + * + * @return array{name:string, processed:int, total:int, import_id:?string}|null + */ + public function closeVanishedImport(mysqli $db): ?array + { + $state = $_SESSION[self::SESSION_KEY] ?? null; + if (!is_array($state) || empty($state['started']) || !empty($state['finished']) + || is_file((string) ($state['file'] ?? ''))) { + return null; + } + $importId = $this->closeSessionImport($db); + return [ + 'name' => (string) ($state['name'] ?? ''), + 'processed' => (int) ($state['next'] ?? 0), + 'total' => (int) ($state['total'] ?? 0), + 'import_id' => $importId, + ]; + } + + /** Write the run into import_logs; returns its import_id ('' when logging failed). */ + private function finish(mysqli $db, array $state, ?int $rows = null): string + { + try { + $logger = new ImportLogger($db, 'users', (string) $state['name'], isset($_SESSION['user']['id']) ? (int) $_SESSION['user']['id'] : null); + $logger->setStats(['imported' => (int) $state['created'], 'failed' => (int) $state['failed']]); + foreach ($state['errors'] as $error) { + $logger->addError((int) $error['line'], (string) $error['title'], (string) $error['message'], (string) $error['type']); + } + // complete() reports a failed write by returning false: the id of + // a log that was never completed must not reach the page. + return $logger->complete($rows ?? (int) $state['total']) ? $logger->getImportId() : ''; + } catch (\Throwable $e) { + SecureLogger::error('[UserImport] could not write the import log: ' . $e->getMessage()); + return ''; + } + } + + /** @param array $record */ + private function insertUser(mysqli $db, array $record): int + { + // Never imported: the password is a random secret nobody knows, the + // reader sets a real one through the setup invitation. + $password = password_hash(bin2hex(random_bytes(16)), PASSWORD_DEFAULT); + $stato = 'attivo'; + $tipo = 'standard'; + $verified = 1; + $stmt = $db->prepare('INSERT INTO utenti ( + nome, cognome, email, telefono, password, indirizzo, cod_fiscale, data_nascita, sesso, + codice_tessera, data_scadenza_tessera, stato, tipo_utente, note_utente, locale, email_verificata + ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)'); + if ($stmt === false) { + throw new \RuntimeException($db->error); + } + $stmt->bind_param( + str_repeat('s', 15) . 'i', + $record['nome'], + $record['cognome'], + $record['email'], + $record['telefono'], + $password, + $record['indirizzo'], + $record['cod_fiscale'], + $record['data_nascita'], + $record['sesso'], + $record['codice_tessera'], + $record['data_scadenza_tessera'], + $stato, + $tipo, + $record['note_utente'], + $record['locale'], + $verified + ); + try { + $stmt->execute(); + return (int) $stmt->insert_id; + } finally { + $stmt->close(); + } + } + + private function exists(mysqli $db, string $column, string $value): bool + { + $sql = $column === 'email' + ? 'SELECT 1 FROM utenti WHERE LOWER(email) = LOWER(?) LIMIT 1' + : 'SELECT 1 FROM utenti WHERE codice_tessera = ? LIMIT 1'; + $stmt = $db->prepare($sql); + if ($stmt === false) { + return false; + } + $stmt->bind_param('s', $value); + $stmt->execute(); + $found = $stmt->get_result()->fetch_row() !== null; + $stmt->close(); + return $found; + } + + /** Same shape as the codes UsersController generates for new readers. */ + private function generateCardCode(mysqli $db): string + { + do { + $code = 'T' . strtoupper(substr(bin2hex(random_bytes(6)), 0, 10)); + } while ($this->exists($db, 'codice_tessera', $code)); + return $code; + } + + private function rowError(array &$state, int $line, string $label, string $message, string $type): void + { + $state['failed']++; + self::report($state, ['line' => $line, 'title' => $label, 'message' => $message, 'type' => $type]); + } + + private function duplicate(array &$state, int $line, string $label, string $message): void + { + $state['duplicates']++; + self::report($state, ['line' => $line, 'title' => $label, 'message' => $message, 'type' => 'duplicate']); + } + + /** + * Add an entry to the run's error report, up to MAX_STORED_ERRORS. The + * first entry past the cap becomes one `truncated` notice, so the report + * says it is incomplete instead of silently disagreeing with the counters. + * + * @param array $state + * @param array{line:int, title:string, message:string, type:string} $entry + */ + public static function report(array &$state, array $entry): void + { + $count = count((array) ($state['errors'] ?? [])); + if ($count < self::MAX_STORED_ERRORS) { + $state['errors'][] = $entry; + } elseif ($count === self::MAX_STORED_ERRORS) { + $state['errors'][] = [ + 'line' => $entry['line'], + 'title' => '', + 'message' => sprintf(__('Report limitato a %d voci: le righe successive non sono elencate, ma sono incluse nei conteggi.'), self::MAX_STORED_ERRORS), + 'type' => 'truncated', + ]; + } + } + + /** + * A readable name for a row that failed validation (nome/cognome/email cells). + * + * @param list $row + * @param array $mapping + */ + private function rowLabel(array $row, array $mapping): string + { + $parts = []; + foreach (['nome', 'cognome', 'email'] as $field) { + $index = array_search($field, $mapping, true); + if ($index !== false && trim((string) ($row[$index] ?? '')) !== '') { + $parts[] = trim((string) $row[$index]); + } + } + return $this->text(implode(' ', $parts)); + } + + /** A label for the error report: valid UTF-8 (whatever the cells held) and at most 200 characters. */ + private function text(string $value): string + { + return mb_substr(mb_scrub($value, 'UTF-8'), 0, 200); + } + + /** + * Custom fields, patron type and invitation of a user already inserted. + * + * @param array $state + * @param array $parsed + */ + private function completeImportedUser(mysqli $db, array &$state, int $userId, int $line, string $label, array $parsed, PatronTypeImport $patronTypes, ?NotificationService $notifier): void + { + $warn = static function (string $message, \Throwable $e) use (&$state, $line, $label, $userId): void { + SecureLogger::error('[UserImport] post-insert step failed', ['line' => $line, 'user_id' => $userId, 'error' => $e->getMessage()]); + self::report($state, ['line' => $line, 'title' => $label, 'message' => $message, 'type' => 'warning']); + }; + if ($parsed['custom'] !== []) { + try { + RegistrationFields::saveValues($db, $userId, $parsed['custom']); + } catch (\Throwable $e) { + $warn(__('Utente importato, ma i campi personalizzati non sono stati salvati.'), $e); + } + } + try { + if ($patronTypes->assign($userId, $parsed['tipologia']) === 'unknown') { + self::report($state, ['line' => $line, 'title' => $label, 'message' => sprintf(__('Tipologia lettore sconosciuta: %s (assegnata quella predefinita)'), (string) $parsed['tipologia']), 'type' => 'warning']); + } + } catch (\Throwable $e) { + $warn(__('Utente importato, ma la tipologia lettore non è stata assegnata.'), $e); + } + // Card expiry of the patron type, as the user form computes it with + // "calcola la scadenza" ticked: only when the file gave no expiry (an + // explicit date always wins), and from the default type when the row + // has no type or an unknown one, so a file without a type column gets + // the default type's card duration too. No duration: the expiry stays + // empty, as on the form. + if (($parsed['record']['data_scadenza_tessera'] ?? null) === null) { + try { + $expiry = PatronTypeAssignment::automaticCardExpiry($db, PatronTypeAssignment::of($db, $userId)); + if ($expiry !== null) { + $stmt = $db->prepare('UPDATE utenti SET data_scadenza_tessera = ? WHERE id = ?'); + if ($stmt === false) { + throw new \RuntimeException($db->error); + } + $stmt->bind_param('si', $expiry, $userId); + $stmt->execute(); + $stmt->close(); + } + } catch (\Throwable $e) { + $warn(__('Utente importato, ma la scadenza della tessera non è stata calcolata.'), $e); + } + } + if ($notifier !== null) { + try { + $token = PasswordSetupToken::issue($db, $userId); + $sent = $token !== null && $notifier->sendUserPasswordSetup($userId, $token); + } catch (\Throwable $e) { + SecureLogger::error('[UserImport] invitation failed', ['line' => $line, 'user_id' => $userId, 'error' => $e->getMessage()]); + $sent = false; + } + if ($sent) { + $state['invited']++; + } else { + $state['invite_failed']++; + self::report($state, ['line' => $line, 'title' => $label, 'message' => __('Utente importato, ma l\'invito non è stato inviato.'), 'type' => 'email']); + } + } + } + + /** + * The error report of a user import, for whoever may import users: the + * generic one under /admin/imports belongs to the catalogue permission. + */ + public function downloadErrors(Request $request, Response $response, mysqli $db): Response + { + if (($off = \App\Support\Features::requireOrRedirect('user_import', $response)) !== null) { + return $off; + } + if (!\App\Support\Permissions::can('users.manage')) { + return $response->withStatus(403); + } + return (new ImportHistoryController())->downloadErrors($request, $response, $db, 'users'); + } + + private static function uploadDir(): string + { + return dirname(__DIR__, 2) . '/writable/uploads'; + } + + /** @return array */ + private function targetFields(mysqli $db): array + { + return UserImport::targetFields(RegistrationFields::definitions($db), (new PatronTypeImport($db))->available()); + } + + private function discardSessionFile(): void + { + $file = $_SESSION[self::SESSION_KEY]['file'] ?? null; + if (is_string($file) && str_contains($file, '/writable/uploads/user_import_') && is_file($file)) { + @unlink($file); + } + } + + /** @param array $payload */ + private function json(Response $response, array $payload, int $status = 200): Response + { + // A stray invalid byte becomes U+FFFD rather than an empty body the page + // cannot parse. + $response->getBody()->write((string) json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_INVALID_UTF8_SUBSTITUTE)); + return $response->withHeader('Content-Type', 'application/json')->withStatus($status); + } +} diff --git a/app/Controllers/UsersController.php b/app/Controllers/UsersController.php index cb9de24fa..e4972081e 100644 --- a/app/Controllers/UsersController.php +++ b/app/Controllers/UsersController.php @@ -54,6 +54,9 @@ public function createForm(Request $request, Response $response, ?mysqli $db = n $roleChoices = $db !== null && ($_SESSION['user']['tipo_utente'] ?? '') === 'admin' ? (new \App\Models\CustomRoleRepository($db))->all() : []; $availableLocales = \App\Support\I18n::getAvailableLocales(); $selectedLocale = $this->installationLocale(); + $patronTypes = $db !== null ? \App\Support\PatronTypeAssignment::choices($db) : []; + $patronTypeId = null; + $defaultPatronTypeId = $db !== null ? (new \App\Services\CirculationPolicy($db))->defaultTypeId() : null; ob_start(); require __DIR__ . '/../Views/utenti/crea_utente.php'; $content = ob_get_clean(); @@ -172,6 +175,20 @@ public function store(Request $request, Response $response, mysqli $db): Respons $codiceTessera = $codiceTesseraInput !== '' ? $codiceTesseraInput : $this->generateTessera($db); $dataScadenzaTessera = $dataScadenzaInput !== '' ? $dataScadenzaInput : null; } + // Patron type (circulation rules) and, when asked, the card expiry it + // implies (today + the type's card duration). The checkbox is ticked + // by default on this form, so it fills an empty date only: a date the + // librarian typed wins. (The edit form opts in explicitly and + // recomputes, see update().) + $patronTypeId = \App\Support\PatronTypeAssignment::fromSubmission($db, $data); + // The form pre-fills a 5-year date as a suggestion and flags it + // (card_expiry_prefilled=1) until the librarian edits the field: that + // date is not "typed", the type's card duration replaces it, and it + // stays when the type has no card duration. + $expiryTyped = $dataScadenzaInput !== '' && (string) ($data['card_expiry_prefilled'] ?? '0') !== '1'; + if (!$isAdmin && in_array($role, ['standard', 'premium'], true) && !empty($data['card_auto']) && !$expiryTyped) { + $dataScadenzaTessera = \App\Support\PatronTypeAssignment::automaticCardExpiry($db, $patronTypeId) ?? $dataScadenzaTessera; + } $stato = (string) ($data['stato'] ?? ($isAdmin ? 'attivo' : 'attivo')); if (!in_array($stato, ['attivo', 'sospeso', 'scaduto'], true)) { @@ -243,6 +260,7 @@ public function store(Request $request, Response $response, mysqli $db): Respons ); $stmt->execute(); $userId = (int) $stmt->insert_id; + \App\Support\PatronTypeAssignment::save($db, $userId, $patronTypeId); } catch (\Throwable $e) { $code = ($e instanceof \mysqli_sql_exception) ? \App\Support\UniqueViolation::errorCode($e) : 'db_error'; if ($code === 'db_error') { @@ -325,6 +343,10 @@ public function editForm(Request $request, Response $response, mysqli $db, int $ // admin can see community handles (e.g. Telegram) alongside the account. $customFieldValues = \App\Support\RegistrationFields::labelledValuesForUser($db, $id); + $patronTypeId = isset($utente['tipologia_id']) ? (int) $utente['tipologia_id'] : null; + $patronTypes = \App\Support\PatronTypeAssignment::choices($db, $patronTypeId); + $defaultPatronTypeId = (new \App\Services\CirculationPolicy($db))->defaultTypeId(); + ob_start(); require __DIR__ . '/../Views/utenti/modifica_utente.php'; $content = ob_get_clean(); @@ -460,6 +482,11 @@ public function update(Request $request, Response $response, mysqli $db, int $id $dataScadenzaTessera = $dataScadenzaInput !== '' ? $dataScadenzaInput : null; } } + $originalPatronType = isset($original['tipologia_id']) ? (int) $original['tipologia_id'] : null; + $patronTypeId = \App\Support\PatronTypeAssignment::fromSubmission($db, $data, $originalPatronType); + if (!$isAdmin && in_array($role, ['standard', 'premium'], true) && !empty($data['card_auto'])) { + $dataScadenzaTessera = \App\Support\PatronTypeAssignment::automaticCardExpiry($db, $patronTypeId) ?? $dataScadenzaTessera; + } $stato = (string) ($data['stato'] ?? ($original['stato'] ?? 'attivo')); if (!in_array($stato, ['attivo', 'sospeso', 'scaduto'], true)) { @@ -553,6 +580,9 @@ public function update(Request $request, Response $response, mysqli $db, int $id $id ); $stmt->execute(); + if ($patronTypeId !== $originalPatronType) { + \App\Support\PatronTypeAssignment::save($db, $id, $patronTypeId); + } } catch (\Throwable $e) { // $stmt stays null if prepare() itself threw — guard the close(). if ($stmt instanceof \mysqli_stmt) { @@ -664,48 +694,96 @@ public function delete(Request $request, Response $response, mysqli $db, int $id if (!$userToDelete) { return $response->withStatus(404); } if (!\App\Support\UserRoleAssignment::canEdit($userToDelete)) { return $response->withStatus(403); } - // Check for any loans (active or not) to preserve history - $loanCheck = $db->prepare("SELECT COUNT(*) as count FROM prestiti WHERE utente_id = ?"); - $loanCheck->bind_param("i", $id); - $loanCheck->execute(); - $loanResult = $loanCheck->get_result()->fetch_assoc(); - $loanCheck->close(); - - if ($loanResult['count'] > 0) { - // Cannot delete user with loan history - mark as suspended instead - $updateStmt = $db->prepare("UPDATE utenti SET stato = 'sospeso', note_utente = CONCAT(IFNULL(note_utente, ''), '\n[ELIMINATO IL ', NOW(), ']') WHERE id = ?"); - $updateStmt->bind_param("i", $id); - $updateStmt->execute(); - $updateStmt->close(); - - // Audit logging - \App\Support\SecureLogger::info('User marked as deleted (has loan history)', [ - 'user_id' => $id, - 'user_name' => full_name($userToDelete['nome'] ?? '', $userToDelete['cognome'] ?? ''), - 'user_email' => $userToDelete['email'], - 'loan_count' => $loanResult['count'], - 'deleted_by' => $_SESSION['user']['id'] ?? 'unknown' - ]); - - $_SESSION['success_message'] = 'L\'utente ha uno storico prestiti e non può essere eliminato. È stato contrassegnato come sospeso.'; - return $response->withHeader('Location', url('/admin/users'))->withStatus(302); - } + // Check for any loans (active or not) to preserve history. Ledger rows + // (payments, charges) are history too: a reader with movements is + // suspended like one with loans, never erased with the ledger. The + // check and the delete run under the user row lock that ledger writes + // and loan limits take too, so a payment committed in between cannot + // be erased by the cascade. + $db->begin_transaction(); + try { + $lockUser = $db->prepare('SELECT id FROM utenti WHERE id = ? FOR UPDATE'); + $lockUser->bind_param('i', $id); + $lockUser->execute(); + $lockUser->close(); + // Before the 0.9.0 migration there is no ledger to count. A failed + // probe throws (rollback below) instead of skipping the ledger + // rows and letting the cascade erase them. + $loanCheck = \App\Services\PatronLedger::schemaReadyOrFail($db) + ? $db->prepare("SELECT (SELECT COUNT(*) FROM prestiti WHERE utente_id = ?) + (SELECT COUNT(*) FROM movimenti_lettore WHERE utente_id = ?) AS count") + : $db->prepare("SELECT (SELECT COUNT(*) FROM prestiti WHERE utente_id = ?) + 0 * ? AS count"); + $loanCheck->bind_param("ii", $id, $id); + $loanCheck->execute(); + $loanResult = $loanCheck->get_result()->fetch_assoc(); + $loanCheck->close(); + + if ($loanResult['count'] > 0) { + // Cannot delete user with loan history - mark as suspended instead + $updateStmt = $db->prepare("UPDATE utenti SET stato = 'sospeso', note_utente = CONCAT(IFNULL(note_utente, ''), '\n[ELIMINATO IL ', NOW(), ']') WHERE id = ?"); + $updateStmt->bind_param("i", $id); + $updateStmt->execute(); + $updateStmt->close(); + $db->commit(); + + // The photo is personal data and goes with the account even when + // the record itself is kept for the loan history. delete() unlinks + // the file and clears the reference; its failures are logged, never + // thrown, so the request completes either way and says so. + $photoRemoved = \App\Support\PatronPhoto::delete($db, $id); + + // Audit logging + \App\Support\SecureLogger::info('User marked as deleted (has loan history)', [ + 'user_id' => $id, + 'user_name' => full_name($userToDelete['nome'] ?? '', $userToDelete['cognome'] ?? ''), + 'user_email' => $userToDelete['email'], + 'loan_count' => $loanResult['count'], + 'deleted_by' => $_SESSION['user']['id'] ?? 'unknown' + ]); + + $_SESSION['success_message'] = 'L\'utente ha uno storico prestiti e non può essere eliminato. È stato contrassegnato come sospeso.'; + if (!$photoRemoved) { + $_SESSION['error_message'] = __('La foto del lettore non è stata rimossa: eliminala dalla scheda del lettore.'); + } + return $response->withHeader('Location', url('/admin/users'))->withStatus(302); + } - // No loans - safe to delete - $stmt = $db->prepare("DELETE FROM utenti WHERE id = ?"); - $stmt->bind_param("i", $id); - $success = $stmt->execute(); - $error = $stmt->error; - $stmt->close(); + // No loans - safe to delete. The row and its photo (personal data) + // go together: the DELETE runs first and the file is removed before + // the commit. A DELETE that fails (a reservation still pointing to + // the reader) leaves the photo untouched, and a file that cannot be + // removed rolls the DELETE back instead of being orphaned. The row + // is already locked above. + $stmt = $db->prepare("SELECT foto_profilo_url FROM utenti WHERE id = ?"); + $stmt->bind_param("i", $id); + $stmt->execute(); + $photo = $stmt->get_result()->fetch_assoc()['foto_profilo_url'] ?? null; + $stmt->close(); - if (!$success) { + $stmt = $db->prepare("DELETE FROM utenti WHERE id = ?"); + $stmt->bind_param("i", $id); + $success = $stmt->execute(); + $error = $stmt->error; + $stmt->close(); + if (!$success) { + throw new \RuntimeException($error); + } + if (!\App\Support\PatronPhoto::deleteFile(is_string($photo) ? $photo : null)) { + $db->rollback(); + $_SESSION['error_message'] = __('Utente non eliminato: la sua foto non può essere rimossa dal disco. Riprova o controlla i permessi della cartella storage.'); + return $response->withHeader('Location', url('/admin/users'))->withStatus(302); + } + $db->commit(); + } catch (\Throwable $e) { + $db->rollback(); \App\Support\SecureLogger::error('User deletion failed', [ 'user_id' => $id, - 'mysqli_error' => $error, + 'mysqli_error' => $e->getMessage(), ]); $_SESSION['error_message'] = __('Errore durante l\'eliminazione'); return $response->withHeader('Location', url('/admin/users?error=db_error'))->withStatus(302); } + // Leftovers of earlier photos whose unlink failed go with the account. + \App\Support\PatronPhoto::sweepOrphans($id, null); // Audit logging for user deletion \App\Support\SecureLogger::info('User deleted', [ @@ -759,6 +837,38 @@ public function details(Request $request, Response $response, mysqli $db, int $i } $loanStmt->close(); + // Effective circulation rules of this patron (patron type → globals). + $circulation = new \App\Services\CirculationPolicy($db); + $patronPolicy = $circulation->forType($circulation->patronTypeOf($id)); + $cardRenewalPreview = \App\Support\PatronTypeAssignment::renewalDate( + $patronPolicy->user->cardDurationDays ?? \App\Support\PatronTypeAssignment::DEFAULT_RENEWAL_DAYS, + $utente['data_scadenza_tessera'], + (string) (new \App\Models\SettingsRepository($db))->get('loans', 'card_renewal_base', 'today') + ); + $patronTypeName = $patronPolicy->user->patronTypeName; + $patronTypeIsOwn = isset($utente['tipologia_id']); + // "Movimenti" section (plan §7): balance, ledger rows, fines accruing on + // open overdue loans (shown, not written), and who may act on them. + $ledger = \App\Services\PatronLedger::forDb($db); + $ledgerBalanceCents = $ledger->balanceCents($id); + // 200 rows per page, newest first; ?ledger_page=N reaches older ones. + // The page links carry ?ledger_upto (the newest row on the first + // load): a movement recorded meanwhile does not shift the pages. + $ledgerPerPage = 200; + $ledgerQuery = $request->getQueryParams(); + $ledgerUpTo = is_scalar($ledgerQuery['ledger_upto'] ?? null) && ctype_digit((string) $ledgerQuery['ledger_upto']) + ? (int) $ledgerQuery['ledger_upto'] + : $ledger->latestMovementId($id); + $ledgerTotal = $ledger->movementCount($id, $ledgerUpTo); + $ledgerPages = max(1, (int) ceil($ledgerTotal / $ledgerPerPage)); + $ledgerPage = min($ledgerPages, max(1, (int) ($ledgerQuery['ledger_page'] ?? 1))); + $ledgerMovements = $ledger->movements($id, $ledgerPerPage, ($ledgerPage - 1) * $ledgerPerPage, $ledgerUpTo); + $ledgerAccrued = $ledger->accruedFines($id); + $ledgerParams = \App\Services\FineParameters::forUser($db, $id); + $ledgerCanManage = \App\Support\Permissions::can('loans.manage'); + // Fines module off (Settings -> Loans): no Movimenti section. + $ledgerEnabled = \App\Services\PatronLedger::active($db); + ob_start(); require __DIR__ . '/../Views/utenti/dettagli_utente.php'; $content = ob_get_clean(); @@ -874,6 +984,9 @@ public function approveAndSendActivation(Request $request, Response $response, m } $stmt->close(); + // Card validity from the patron type, when it sets one. + \App\Support\PatronTypeAssignment::applyCardDurationOnApproval($db, $userId); + // Invia email di attivazione con link di verifica $notifier = new NotificationService($db); $notifier->sendUserActivationWithVerification($userId, $token); @@ -932,6 +1045,9 @@ public function activateDirectly(Request $request, Response $response, mysqli $d } $stmt->close(); + // Card validity from the patron type, when it sets one. + \App\Support\PatronTypeAssignment::applyCardDurationOnApproval($db, $userId); + // Invia email di benvenuto $notifier = new NotificationService($db); $notifier->sendUserAccountApproved($userId); @@ -943,6 +1059,41 @@ public function activateDirectly(Request $request, Response $response, mysqli $d )->withStatus(302); } + /** + * "Rinnova tessera": new expiry = today + the patron type's card duration + * (365 days when the type sets none); an expired account is reactivated. + */ + public function renewCard(Request $request, Response $response, mysqli $db, int $id): Response + { + if ($guard = $this->guardAdminStaff($response)) { + return $guard; + } + $stmt = $db->prepare('SELECT * FROM utenti WHERE id = ? LIMIT 1'); + $stmt->bind_param('i', $id); + $stmt->execute(); + $user = $stmt->get_result()->fetch_assoc(); + $stmt->close(); + if (!$user) { + return $response->withHeader('Location', url('/admin/users?error=user_not_found'))->withStatus(302); + } + if (!in_array($_SESSION['user']['tipo_utente'] ?? '', ['admin', 'staff'], true) + && !\App\Support\UserRoleAssignment::canEdit($user)) { + return $response->withStatus(403); + } + try { + $expiry = \App\Support\PatronTypeAssignment::renewCard($db, $id); + } catch (\Throwable $e) { + \App\Support\SecureLogger::error('[Users] card renewal failed for user ' . $id, ['error' => $e->getMessage()]); + $expiry = null; + } + if ($expiry === null) { + $_SESSION['error_message'] = __('Impossibile rinnovare la tessera di questo account.'); + } else { + $_SESSION['success_message'] = sprintf(__('Tessera rinnovata fino al %s.'), format_date($expiry, false, '/')); + } + return $response->withHeader('Location', url('/admin/users/details/' . $id))->withStatus(302); + } + /** * Export utenti to CSV format */ @@ -967,13 +1118,14 @@ public function exportCsv(Request $request, Response $response, mysqli $db): Res $bindValues = []; // Search text filter (nome, cognome, email) - if (!empty($searchText)) { - $whereClauses[] = "(nome LIKE ? OR cognome LIKE ? OR email LIKE ?)"; - $searchParam = "%{$searchText}%"; - for ($i = 0; $i < 3; $i++) { - $bindTypes .= 's'; - $bindValues[] = $searchParam; - } + // Quoted value = exact spelling, accents included (#501), as in the list. + $search = !empty($searchText) + ? \App\Support\ExactSearch::filterCondition(['nome', 'cognome', 'email', 'telefono', 'codice_tessera', "CONCAT(nome, ' ', cognome)", "CONCAT(cognome, ' ', nome)"], (string) $searchText) + : null; + if ($search !== null) { + $whereClauses[] = $search['sql']; + $bindTypes .= $search['types']; + array_push($bindValues, ...$search['params']); } // Role filter @@ -997,12 +1149,22 @@ public function exportCsv(Request $request, Response $response, mysqli $db): Res $bindValues[] = $createdFrom; } + // Patron type filter (users without a type belong to the default one) + $patronType = \App\Support\PatronTypeAssignment::filterCondition($db, $params['patron_type_filter'] ?? ''); + if ($patronType !== null) { + $whereClauses[] = $patronType['sql']; + $bindTypes .= $patronType['types']; + array_push($bindValues, ...$patronType['params']); + } + // Same rule as the import: no patron type column while the module is off. + $patronTypeColumn = (new \App\Services\CirculationPolicy($db))->rulesActive() ? ', tipologia_id' : ''; + // Build the query - export ALL fields except sensitive ones (password, tokens) $query = "SELECT id, codice_tessera, nome, cognome, email, telefono, indirizzo, data_nascita, sesso, data_registrazione, data_scadenza_tessera, stato, tipo_utente, cod_fiscale, note_utente, email_verificata, - data_ultimo_accesso, created_at, updated_at + data_ultimo_accesso, created_at, updated_at{$patronTypeColumn} FROM utenti"; if (!empty($whereClauses)) { @@ -1050,6 +1212,8 @@ public function exportCsv(Request $request, Response $response, mysqli $db): Res __('Data Scadenza Tessera'), __('Stato'), __('Tipo Utente'), + __('Tipologia lettore'), + __('Codice tipologia lettore'), __('Codice Fiscale'), __('Note Utente'), __('Email Verificata'), @@ -1065,6 +1229,8 @@ public function exportCsv(Request $request, Response $response, mysqli $db): Res // (nome, email, note) that start with = + - @. $writer = Csv::writerToStream($stream, ';', "'"); $writer->insertOne($headers); + // One policy for the whole file: the patron types are read once. + $exportPolicy = new \App\Services\CirculationPolicy($db); foreach ($utenti as $utente) { $row = [ @@ -1081,6 +1247,10 @@ public function exportCsv(Request $request, Response $response, mysqli $db): Res isset($utente['data_scadenza_tessera']) ? format_date($utente['data_scadenza_tessera'], false, '/') : '', $utente['stato'] ?? '', $utente['tipo_utente'] ?? '', + \App\Support\PatronTypeAssignment::nameOf($db, $utente['tipologia_id'] ?? null, $exportPolicy), + // The code beside the name: the import resolves by code first, + // so this file re-imports exactly where the same types exist. + \App\Support\PatronTypeAssignment::codeOf($db, $utente['tipologia_id'] ?? null, $exportPolicy), $utente['cod_fiscale'] ?? '', $utente['note_utente'] ?? '', ($utente['email_verificata'] ?? 0) == 1 ? 'Sì' : 'No', diff --git a/app/Controllers/UtentiApiController.php b/app/Controllers/UtentiApiController.php index a635e9595..91711a77d 100644 --- a/app/Controllers/UtentiApiController.php +++ b/app/Controllers/UtentiApiController.php @@ -24,15 +24,14 @@ public function list(Request $request, Response $response, mysqli $db): Response $params = []; $types = ''; - if ($search_value !== '') { - $searchParam = "%$search_value%"; - $where .= " AND (u.nome LIKE ? - OR u.cognome LIKE ? - OR u.email LIKE ? - OR u.telefono LIKE ? - OR u.codice_tessera LIKE ?)"; - $params = array_fill(0, 5, $searchParam); - $types = 'sssss'; + // Quoted value = exact spelling, accents included (#501). + $search = $search_value !== '' + ? \App\Support\ExactSearch::filterCondition(['u.nome', 'u.cognome', 'u.email', 'u.telefono', 'u.codice_tessera', "CONCAT(u.nome, ' ', u.cognome)", "CONCAT(u.cognome, ' ', u.nome)"], $search_value) + : null; + if ($search !== null) { + $where .= ' AND ' . $search['sql']; + $params = $search['params']; + $types = $search['types']; } // Additional filters @@ -54,6 +53,12 @@ public function list(Request $request, Response $response, mysqli $db): Response $params[] = $createdFrom; $types .= 's'; } + $patronType = \App\Support\PatronTypeAssignment::filterCondition($db, $q['patron_type_filter'] ?? '', 'u.tipologia_id'); + if ($patronType !== null) { + $where .= ' AND ' . $patronType['sql'] . ' '; + array_push($params, ...$patronType['params']); + $types .= $patronType['types']; + } // Count total records with prepared statement $total_sql = 'SELECT COUNT(*) AS c FROM utenti WHERE 1=1' . \App\Support\Permissions::accountScopeSql(); @@ -93,12 +98,13 @@ public function list(Request $request, Response $response, mysqli $db): Response // Map column index to database column // 0: nome/cognome, 1: email, 2: telefono, 3: tipo_utente, 4: stato + // Column 0 of the on-screen table is the selection checkbox (not orderable). $columnMap = [ - 0 => 'u.cognome', // Nome (sorts by cognome first) - 1 => 'u.email', // Email - 2 => 'u.telefono', // Telefono - 3 => 'u.tipo_utente', // Ruolo - 4 => 'u.stato', // Stato + 1 => 'u.cognome', // Nome (sorts by cognome first) + 2 => 'u.email', // Email + 3 => 'u.telefono', // Telefono + 4 => 'u.tipo_utente', // Ruolo + 5 => 'u.stato', // Stato ]; // Parse order parameter from DataTables @@ -110,7 +116,7 @@ public function list(Request $request, Response $response, mysqli $db): Response $orderColumn = $columnMap[$colIdx]; $orderDir = $dir; // Only add secondary sort for nome column - $orderExtra = ($colIdx === 0) ? ', u.nome ' . $dir : ''; + $orderExtra = ($colIdx === 1) ? ', u.nome ' . $dir : ''; } } @@ -180,11 +186,14 @@ public function exportPdf(Request $request, Response $response, mysqli $db): Res $types = ''; $search_value = trim((string)($q['search_text'] ?? ($q['search_value'] ?? ''))); - if ($search_value !== '') { - $searchParam = "%$search_value%"; - $where .= ' AND (u.nome LIKE ? OR u.cognome LIKE ? OR u.email LIKE ? OR u.telefono LIKE ? OR u.codice_tessera LIKE ?)'; - $params = array_fill(0, 5, $searchParam); - $types = 'sssss'; + // Quoted value = exact spelling, accents included (#501). + $search = $search_value !== '' + ? \App\Support\ExactSearch::filterCondition(['u.nome', 'u.cognome', 'u.email', 'u.telefono', 'u.codice_tessera', "CONCAT(u.nome, ' ', u.cognome)", "CONCAT(u.cognome, ' ', u.nome)"], $search_value) + : null; + if ($search !== null) { + $where .= ' AND ' . $search['sql']; + $params = $search['params']; + $types = $search['types']; } $role = trim((string)($q['role_filter'] ?? '')); if ($role !== '') { @@ -204,6 +213,12 @@ public function exportPdf(Request $request, Response $response, mysqli $db): Res $params[] = $createdFrom; $types .= 's'; } + $patronType = \App\Support\PatronTypeAssignment::filterCondition($db, $q['patron_type_filter'] ?? '', 'u.tipologia_id'); + if ($patronType !== null) { + $where .= ' AND ' . $patronType['sql'] . ' '; + array_push($params, ...$patronType['params']); + $types .= $patronType['types']; + } // Unfiltered total for the header summary ("N filtered out of M total"). $total = 0; diff --git a/app/Middleware/FeatureGateMiddleware.php b/app/Middleware/FeatureGateMiddleware.php new file mode 100644 index 000000000..329a73c1f --- /dev/null +++ b/app/Middleware/FeatureGateMiddleware.php @@ -0,0 +1,63 @@ +feature; + } + + public function process(Request $request, RequestHandler $handler): Response + { + if (Features::enabled($this->feature) || ($this->configuration && \App\Support\Permissions::mayConfigure())) { + return $handler->handle($request); + } + + $uri = $request->getUri()->getPath(); + $wantsJson = str_starts_with($uri, '/api/') + || str_contains($uri, '/api/') + || strtolower($request->getHeaderLine('X-Requested-With')) === 'xmlhttprequest' + || str_contains($request->getHeaderLine('Accept'), 'application/json'); + + if ($wantsJson) { + $res = new SlimResponse(404); + $res->getBody()->write((string) json_encode([ + 'error' => true, + 'code' => 'feature_disabled', + 'message' => __('Funzione non disponibile su questa istanza.'), + ], JSON_UNESCAPED_UNICODE)); + return $res->withHeader('Content-Type', 'application/json'); + } + + $_SESSION['error_message'] = __('Funzione disattivata nelle impostazioni.'); + return (new SlimResponse(302))->withHeader('Location', url('/admin/dashboard')); + } +} diff --git a/app/Models/AttendanceRepository.php b/app/Models/AttendanceRepository.php new file mode 100644 index 000000000..9c55226a0 --- /dev/null +++ b/app/Models/AttendanceRepository.php @@ -0,0 +1,312 @@ +db->query("SELECT 1 FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'presenze'"); + return $res instanceof \mysqli_result && $res->num_rows > 0; + } catch (\Throwable) { + return false; + } + } + + /** + * Find the member owning a card code (exact match, surrounding blanks ignored). + * + * @return array{id:int, nome:string, cognome:string, stato:string}|null + */ + public function findMemberByCard(string $card): ?array + { + $card = trim($card); + if ($card === '' || mb_strlen($card) > 20) { + return null; + } + // Same reach as the loan borrower search: an operator with a custom + // role only resolves the reader accounts they may lend to. + $scope = Permissions::accountScopeSql('', Permissions::SCOPE_BORROWER); + $stmt = $this->db->prepare('SELECT id, nome, cognome, stato FROM utenti WHERE codice_tessera = ?' . $scope . ' LIMIT 1'); + $stmt->bind_param('s', $card); + $stmt->execute(); + $row = $stmt->get_result()->fetch_assoc(); + $stmt->close(); + if (!$row) { + return null; + } + return [ + 'id' => (int) $row['id'], + 'nome' => (string) $row['nome'], + 'cognome' => (string) $row['cognome'], + 'stato' => (string) ($row['stato'] ?? 'attivo'), + ]; + } + + public function recordMember(int $userId, ?int $sedeId): int + { + return $this->insert('socio', $userId, $sedeId, null, null); + } + + public function recordGuest(?string $name, ?string $reason, ?int $sedeId): int + { + return $this->insert('ospite', null, $sedeId, self::clip($name, 150), self::clip($reason, 255)); + } + + /** + * Active branches (sedi) for the kiosk selector; empty on single-site installs. + * + * @return array + */ + public function branches(): array + { + $out = []; + try { + $res = $this->db->query('SELECT id, nome FROM sedi WHERE is_active = 1 ORDER BY nome ASC'); + if ($res instanceof \mysqli_result) { + while ($row = $res->fetch_assoc()) { + $out[(int) $row['id']] = (string) $row['nome']; + } + } + } catch (\Throwable) { + return []; + } + return $out; + } + + /** A submitted branch id that exists and is active, or NULL. */ + public function validBranchId(mixed $raw): ?int + { + $id = filter_var($raw, FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]); + if ($id === false) { + return null; + } + return array_key_exists($id, $this->branches()) ? $id : null; + } + + /** + * Visits per day over the last $days days (today included), oldest first, + * with zero rows for days without visits. + * + * @return list + */ + public function dailyCounts(int $days): array + { + [$from, $to] = self::window($days); + $stmt = $this->db->prepare( + "SELECT DATE(created_at) AS giorno, + SUM(tipo = 'socio') AS soci, + SUM(tipo = 'ospite') AS ospiti, + COUNT(*) AS totale + FROM presenze + WHERE created_at >= ? AND created_at < ? + GROUP BY DATE(created_at)" + ); + $stmt->bind_param('ss', $from, $to); + $stmt->execute(); + $res = $stmt->get_result(); + $byDay = []; + while ($row = $res->fetch_assoc()) { + $byDay[(string) $row['giorno']] = $row; + } + $stmt->close(); + + $out = []; + $cursor = new \DateTimeImmutable(substr($from, 0, 10)); + for ($i = 0; $i < self::clampDays($days); $i++) { + $day = $cursor->modify('+' . $i . ' day')->format('Y-m-d'); + $row = $byDay[$day] ?? null; + $out[] = [ + 'giorno' => $day, + 'soci' => (int) ($row['soci'] ?? 0), + 'ospiti' => (int) ($row['ospiti'] ?? 0), + 'totale' => (int) ($row['totale'] ?? 0), + ]; + } + return $out; + } + + /** + * Visits per hour slot (0–23) over the last $days days. + * + * @return array + */ + public function hourlyCounts(int $days): array + { + [$from, $to] = self::window($days); + $stmt = $this->db->prepare( + 'SELECT HOUR(created_at) AS ora, COUNT(*) AS totale + FROM presenze + WHERE created_at >= ? AND created_at < ? + GROUP BY HOUR(created_at)' + ); + $stmt->bind_param('ss', $from, $to); + $stmt->execute(); + $res = $stmt->get_result(); + $out = array_fill(0, 24, 0); + while ($row = $res->fetch_assoc()) { + $out[(int) $row['ora']] = (int) $row['totale']; + } + $stmt->close(); + return $out; + } + + /** + * @return array{soci:int, ospiti:int, totale:int} + */ + public function totals(int $days): array + { + $totals = ['soci' => 0, 'ospiti' => 0, 'totale' => 0]; + foreach ($this->dailyCounts($days) as $row) { + $totals['soci'] += $row['soci']; + $totals['ospiti'] += $row['ospiti']; + $totals['totale'] += $row['totale']; + } + return $totals; + } + + /** + * Latest visits for the kiosk feed. Member names are shortened to + * "Name S." so the screen facing the desk shows as little as possible. + * + * @return list + */ + public function recent(int $limit = 10): array + { + $limit = max(1, min(50, $limit)); + // The scope sits in the ON clause: a visit by an account outside the + // operator's reach stays in the feed, only without the name. + $scope = Permissions::accountScopeSql('u', Permissions::SCOPE_BORROWER); + $res = $this->db->query( + 'SELECT p.created_at, p.tipo, p.nome_ospite, u.nome, u.cognome + FROM presenze p + LEFT JOIN utenti u ON u.id = p.utente_id' . $scope . ' + ORDER BY p.created_at DESC, p.id DESC + LIMIT ' . $limit + ); + $out = []; + if ($res instanceof \mysqli_result) { + while ($row = $res->fetch_assoc()) { + $label = $row['tipo'] === 'socio' + ? self::shortName((string) ($row['nome'] ?? ''), (string) ($row['cognome'] ?? '')) + : trim((string) ($row['nome_ospite'] ?? '')); + $out[] = ['created_at' => (string) $row['created_at'], 'tipo' => (string) $row['tipo'], 'etichetta' => $label]; + } + } + return $out; + } + + /** + * Delete visits older than the retention window. 0 days = keep forever. + * The cut-off is midnight (application timezone) $retentionDays days ago, + * so a visit is kept for at least $retentionDays whole days. + */ + public function purge(int $retentionDays): int + { + if ($retentionDays <= 0) { + return 0; + } + $cutoff = (new \DateTimeImmutable(DateHelper::today())) + ->modify('-' . min($retentionDays, self::MAX_RETENTION_DAYS) . ' day') + ->format('Y-m-d 00:00:00'); + $stmt = $this->db->prepare('DELETE FROM presenze WHERE created_at < ?'); + $stmt->bind_param('s', $cutoff); + $stmt->execute(); + $deleted = (int) $stmt->affected_rows; + $stmt->close(); + return max(0, $deleted); + } + + /** Configured retention in days (0 = keep forever). */ + public static function retentionDays(SettingsRepository $settings): int + { + $raw = $settings->get(self::SETTINGS_CATEGORY, self::SETTING_RETENTION, null); + if ($raw === null || trim($raw) === '' || !is_numeric($raw)) { + return self::DEFAULT_RETENTION_DAYS; + } + return self::normalizeRetention($raw); + } + + /** Clamp a submitted retention to 0..MAX_RETENTION_DAYS (non-numeric → default). */ + public static function normalizeRetention(mixed $raw): int + { + $days = filter_var($raw, FILTER_VALIDATE_INT); + // Not a number, or negative: the prudent default, never 0 (= keep + // forever), so a typo cannot switch the GDPR purge off. + if ($days === false || $days < 0) { + return self::DEFAULT_RETENTION_DAYS; + } + return max(0, min(self::MAX_RETENTION_DAYS, $days)); + } + + /** "Mario Rossi" → "Mario R." */ + public static function shortName(string $first, string $last): string + { + $first = trim($first); + $last = trim($last); + if ($last === '') { + return $first; + } + return trim($first . ' ' . mb_strtoupper(mb_substr($last, 0, 1)) . '.'); + } + + public static function clampDays(int $days): int + { + return max(1, min(366, $days)); + } + + /** + * [from, to) bounds of the last $days days in the application timezone. + * + * @return array{0:string, 1:string} + */ + public static function window(int $days): array + { + $today = new \DateTimeImmutable(DateHelper::today()); + $from = $today->modify('-' . (self::clampDays($days) - 1) . ' day')->format('Y-m-d 00:00:00'); + $to = $today->modify('+1 day')->format('Y-m-d 00:00:00'); + return [$from, $to]; + } + + private function insert(string $type, ?int $userId, ?int $sedeId, ?string $guestName, ?string $reason): int + { + $now = DateHelper::now(); + $stmt = $this->db->prepare('INSERT INTO presenze (utente_id, sede_id, tipo, nome_ospite, motivo, created_at) VALUES (?, ?, ?, ?, ?, ?)'); + $stmt->bind_param('iissss', $userId, $sedeId, $type, $guestName, $reason, $now); + $stmt->execute(); + $id = (int) $this->db->insert_id; + $stmt->close(); + return $id; + } + + private static function clip(?string $value, int $max): ?string + { + $value = trim((string) $value); + return $value === '' ? null : mb_substr($value, 0, $max); + } +} diff --git a/app/Models/AuthorRepository.php b/app/Models/AuthorRepository.php index 98fad3646..36f898e06 100644 --- a/app/Models/AuthorRepository.php +++ b/app/Models/AuthorRepository.php @@ -137,6 +137,9 @@ public function create(array $data): int // Decode HTML entities to prevent double encoding $nome = \App\Support\HtmlHelper::decode($data['nome']); + // Quotes typed around a new name mean "exact spelling" (#501); they + // are never part of the stored name. + $nome = \App\Support\ExactSearch::quotedValue($nome) ?? $nome; $pseudonimo = \App\Support\HtmlHelper::decode($data['pseudonimo'] ?? ''); $nazionalita = \App\Support\HtmlHelper::decode($data['nazionalita'] ?? ''); $biografia = \App\Support\HtmlHelper::decode($data['biografia'] ?? ''); @@ -213,7 +216,12 @@ public function update(int $id, array $data): bool $sito_web = \App\Support\HtmlHelper::decode($data['sito_web'] ?? ''); // Normalize author name to canonical format ("Name Surname") - // This ensures consistency when updating existing authors + // This ensures consistency when updating existing authors. Quotes + // around the whole name are never stored, as in create() (#501). + // One record holds one name: normalizeStored() would split a '|' or + // ';' in it as a cache-column list, so the edit form would store + // another name than create() does. + $nome = \App\Support\ExactSearch::quotedValue($nome) ?? $nome; $nome = \App\Support\AuthorNormalizer::normalize($nome); // Base columns always present. @@ -300,6 +308,26 @@ public function findByName(string $name): ?int * @param string $name Author name in any format * @return int|null Author ID if found, null otherwise */ + /** + * Author written exactly like $name (accents distinct, case ignored), as + * typed or in canonical "Name Surname" form; null when there is none. + */ + private function findByExactName(string $name): ?int + { + foreach (array_unique([$name, \App\Support\AuthorNormalizer::normalize($name)]) as $candidate) { + $match = \App\Support\ExactSearch::equalsCondition('nome', $candidate); + $stmt = $this->db->prepare("SELECT id FROM autori WHERE {$match['sql']} ORDER BY id LIMIT 1"); + $stmt->bind_param($match['types'], ...$match['params']); + $stmt->execute(); + $row = $stmt->get_result()->fetch_assoc(); + $stmt->close(); + if ($row) { + return (int) $row['id']; + } + } + return null; + } + public function findByCanonicalName(string $name): ?int { $name = trim($name); @@ -307,12 +335,26 @@ public function findByCanonicalName(string $name): ?int return null; } + // A name typed in quotes asks for that exact spelling (#501): only an + // author written the same way (accents included, case ignored) is + // reused, and the accent-tolerant steps below are skipped, so + // "Häffner" in quotes becomes a new author when only Haffner exists. + $quoted = \App\Support\ExactSearch::quotedValue($name); + if ($quoted !== null) { + return $this->findByExactName($quoted); + } + // Normalize the input name first (this is the key fix!) $normalizedInput = \App\Support\AuthorNormalizer::normalize($name); + // The collation folds accents, so `nome = ?` also admits "Haffner" + // for "Häffner". When both are on file the exact spelling must win + // (#501): order the byte-identical row first in every equality step. + $exactFirst = \App\Support\ExactSearch::exactFirstSql('nome'); + // First try exact match with normalized name - $stmt = $this->db->prepare("SELECT id FROM autori WHERE nome = ? LIMIT 1"); - $stmt->bind_param('s', $normalizedInput); + $stmt = $this->db->prepare("SELECT id FROM autori WHERE nome = ? ORDER BY {$exactFirst}, id LIMIT 1"); + $stmt->bind_param('ss', $normalizedInput, $normalizedInput); $stmt->execute(); $res = $stmt->get_result(); $row = $res->fetch_assoc(); @@ -323,8 +365,8 @@ public function findByCanonicalName(string $name): ?int // Also try the original name (in case DB has unnormalized entries) if ($normalizedInput !== $name) { - $stmt = $this->db->prepare("SELECT id FROM autori WHERE nome = ? LIMIT 1"); - $stmt->bind_param('s', $name); + $stmt = $this->db->prepare("SELECT id FROM autori WHERE nome = ? ORDER BY {$exactFirst}, id LIMIT 1"); + $stmt->bind_param('ss', $name, $name); $stmt->execute(); $res = $stmt->get_result(); $row = $res->fetch_assoc(); @@ -336,8 +378,8 @@ public function findByCanonicalName(string $name): ?int // Try case-insensitive match with normalized name $lowerNormalized = mb_strtolower($normalizedInput, 'UTF-8'); - $stmt = $this->db->prepare("SELECT id FROM autori WHERE LOWER(nome) = ? LIMIT 1"); - $stmt->bind_param('s', $lowerNormalized); + $stmt = $this->db->prepare("SELECT id FROM autori WHERE LOWER(nome) = ? ORDER BY (LOWER(nome) COLLATE utf8mb4_bin = ?) DESC, id LIMIT 1"); + $stmt->bind_param('ss', $lowerNormalized, $lowerNormalized); $stmt->execute(); $res = $stmt->get_result(); $row = $res->fetch_assoc(); diff --git a/app/Models/BookRepository.php b/app/Models/BookRepository.php index 0313350ee..aae1012e0 100644 --- a/app/Models/BookRepository.php +++ b/app/Models/BookRepository.php @@ -517,17 +517,17 @@ public function createBasic(array $data): int if ($this->hasColumn('traduttore') && array_key_exists('traduttore', $data)) { $val = $data['traduttore'] ?? null; $val = is_string($val) ? trim($val) : null; - $addField('traduttore', 's', $val !== null && $val !== '' ? \App\Support\AuthorNormalizer::normalize($val) : null); + $addField('traduttore', 's', $val !== null && $val !== '' ? \App\Support\AuthorNormalizer::normalizeStored($val) : null); } if ($this->hasColumn('illustratore') && array_key_exists('illustratore', $data)) { $val = $data['illustratore'] ?? null; $val = is_string($val) ? trim($val) : null; - $addField('illustratore', 's', $val !== null && $val !== '' ? \App\Support\AuthorNormalizer::normalize($val) : null); + $addField('illustratore', 's', $val !== null && $val !== '' ? \App\Support\AuthorNormalizer::normalizeStored($val) : null); } if ($this->hasColumn('curatore') && array_key_exists('curatore', $data)) { $val = $data['curatore'] ?? null; $val = is_string($val) ? trim($val) : null; - $addField('curatore', 's', $val !== null && $val !== '' ? \App\Support\AuthorNormalizer::normalize($val) : null); + $addField('curatore', 's', $val !== null && $val !== '' ? \App\Support\AuthorNormalizer::normalizeStored($val) : null); } if ($this->hasColumn('numero_pagine')) { $numPagineRaw = $data['numero_pagine'] ?? null; @@ -881,17 +881,17 @@ public function updateBasic(int $id, array $data): bool if ($this->hasColumn('traduttore') && array_key_exists('traduttore', $data)) { $val = $data['traduttore'] ?? null; $val = is_string($val) ? trim($val) : null; - $addSet('traduttore', 's', $val !== null && $val !== '' ? \App\Support\AuthorNormalizer::normalize($val) : null); + $addSet('traduttore', 's', $val !== null && $val !== '' ? \App\Support\AuthorNormalizer::normalizeStored($val) : null); } if ($this->hasColumn('illustratore') && array_key_exists('illustratore', $data)) { $val = $data['illustratore'] ?? null; $val = is_string($val) ? trim($val) : null; - $addSet('illustratore', 's', $val !== null && $val !== '' ? \App\Support\AuthorNormalizer::normalize($val) : null); + $addSet('illustratore', 's', $val !== null && $val !== '' ? \App\Support\AuthorNormalizer::normalizeStored($val) : null); } if ($this->hasColumn('curatore') && array_key_exists('curatore', $data)) { $val = $data['curatore'] ?? null; $val = is_string($val) ? trim($val) : null; - $addSet('curatore', 's', $val !== null && $val !== '' ? \App\Support\AuthorNormalizer::normalize($val) : null); + $addSet('curatore', 's', $val !== null && $val !== '' ? \App\Support\AuthorNormalizer::normalizeStored($val) : null); } if ($this->hasColumn('numero_pagine')) { $numPagineRaw = $data['numero_pagine'] ?? null; @@ -1656,7 +1656,10 @@ public function updateOptionals(int $bookId, array $data): void if (!is_string($data[$c])) { continue; } $cols[$c] = $this->normalizeEnumValue((string) $data[$c], 'tipo_media', 'libro'); } elseif (in_array($c, ['traduttore', 'illustratore', 'curatore'], true)) { - $cols[$c] = \App\Support\AuthorNormalizer::normalize((string) $data[$c]); + // The text column is a display cache: a name typed in quotes + // (exact spelling, #501) was already resolved to its entity + // by the controller; the quotes are not part of the name. + $cols[$c] = \App\Support\AuthorNormalizer::normalizeStored((string) $data[$c]); } elseif ($c === 'luogo_pubblicazione') { // Same rule as create()/update(): trimmed, and a blank // value is not written (it would overwrite their NULL). @@ -1714,13 +1717,13 @@ public function updateOptionals(int $bookId, array $data): void if ($this->hasColumn('traduttore') && !$traduttoreProvided && !array_key_exists('traduttori_ids', $data) && !empty($data['scraped_translator'])) { - $cols['traduttore'] = \App\Support\AuthorNormalizer::normalize((string) $data['scraped_translator']); + $cols['traduttore'] = \App\Support\AuthorNormalizer::normalizeStored((string) $data['scraped_translator']); } $illustratoreProvided = array_key_exists('illustratore', $data) && $data['illustratore'] !== '' && $data['illustratore'] !== null; if ($this->hasColumn('illustratore') && !$illustratoreProvided && !array_key_exists('illustratori_ids', $data) && !empty($data['scraped_illustrator'])) { - $cols['illustratore'] = \App\Support\AuthorNormalizer::normalize((string) $data['scraped_illustrator']); + $cols['illustratore'] = \App\Support\AuthorNormalizer::normalizeStored((string) $data['scraped_illustrator']); } if ($this->hasColumn('tipo_media') && !array_key_exists('tipo_media', $cols)) { $formato = trim((string) ($data['formato'] ?? ($data['scraped_formato'] ?? ''))); diff --git a/app/Models/CopyRepository.php b/app/Models/CopyRepository.php index 8da299766..e58cb2dee 100644 --- a/app/Models/CopyRepository.php +++ b/app/Models/CopyRepository.php @@ -770,6 +770,7 @@ public function getAvailableByBookId(int $bookId): array LEFT JOIN prestiti p ON c.id = p.copia_id AND ((p.attivo = 1 AND p.stato IN ('in_corso', 'in_ritardo', 'da_ritirare', 'prenotato')) OR (p.stato = 'pendente' AND p.copia_id IS NOT NULL)) WHERE c.libro_id = ? AND c.stato = 'disponibile' + AND " . (new \App\Services\CirculationPolicy($this->db))->lendableCopySql('c') . " AND p.id IS NULL ORDER BY c.numero_inventario ASC "); @@ -806,6 +807,7 @@ public function getAvailableByBookIdForDateRange(int $bookId, string $startDate, FROM copie c WHERE c.libro_id = ? AND c.stato NOT IN ('perso', 'danneggiato', 'manutenzione', 'in_restauro', 'in_trasferimento') + AND " . (new \App\Services\CirculationPolicy($this->db))->lendableCopySql('c') . " AND NOT EXISTS ( SELECT 1 FROM prestiti p WHERE p.copia_id = c.id @@ -840,6 +842,7 @@ public function countAvailableByBookId(int $bookId): int LEFT JOIN prestiti p ON c.id = p.copia_id AND ((p.attivo = 1 AND p.stato IN ('in_corso', 'in_ritardo', 'da_ritirare', 'prenotato')) OR (p.stato = 'pendente' AND p.copia_id IS NOT NULL)) WHERE c.libro_id = ? AND c.stato = 'disponibile' + AND " . (new \App\Services\CirculationPolicy($this->db))->lendableCopySql('c') . " AND p.id IS NULL "); $stmt->bind_param('i', $bookId); diff --git a/app/Models/LoanRepository.php b/app/Models/LoanRepository.php index bd6484cbe..7316a572b 100644 --- a/app/Models/LoanRepository.php +++ b/app/Models/LoanRepository.php @@ -99,8 +99,8 @@ public function update(int $id, array $data): bool // Fallback dalla setting di durata prestito: il vecchio +14gg // hardcoded era metà del default seminato (30) e ignorava la // configurazione dell'admin — stesso fix M5b già applicato a renew(). - $loanDays = (new SettingsRepository($this->db))->loanDurationDays(); - $data_scadenza = date('Y-m-d', strtotime($data_prestito . " +{$loanDays} days")); + $policy = (new \App\Services\CirculationPolicy($this->db))->forExistingLoan($id, $utente_id > 0 ? $utente_id : null); + $data_scadenza = \App\Services\CirculationPolicy::dueDateFor((string) $data_prestito, $policy->durationDays, $this->db); } $processed_by = $data['processed_by'] ?? null; $stmt->bind_param('issii', $utente_id, $data_prestito, $data_scadenza, $processed_by, $id); @@ -253,6 +253,10 @@ public function close(int $id, ?int $expectedBookId = null, ?int $expectedUserId return false; } + // Late fine into the patron ledger, atomic with the close (plan §7). + // Idempotent (late:) and 0 while fines are disabled. + $lateFineCents = \App\Services\PatronLedger::forDb($this->db)->chargeLateReturn($id, null); + // Recalculate availability and process reservations INSIDE the transaction // This ensures FOR UPDATE locks in processBookAvailability are effective $integrity = new DataIntegrity($this->db); @@ -318,6 +322,14 @@ public function close(int $id, ?int $expectedBookId = null, ?int $expectedUserId SecureLogger::warning('LoanRepository::close flush notifications warning', ['error' => $e->getMessage()]); } + if ($lateFineCents > 0) { + try { + (new \App\Support\NotificationService($this->db))->sendFineChargedNotification($id); + } catch (\Throwable $e) { + SecureLogger::warning('LoanRepository::close fine notification warning', ['error' => $e->getMessage()]); + } + } + // validateAndUpdateLoan has its own transaction, call it after main transaction completes try { $integrity = new DataIntegrity($this->db); diff --git a/app/Models/PublisherRepository.php b/app/Models/PublisherRepository.php index 9d37cb224..8402bdfb9 100644 --- a/app/Models/PublisherRepository.php +++ b/app/Models/PublisherRepository.php @@ -143,6 +143,8 @@ public function create(array $data): int // Decode HTML entities to prevent double encoding $nome = \App\Support\HtmlHelper::decode($data['nome']); + // Quotes typed around a new name mean "exact spelling" (#501). + $nome = \App\Support\ExactSearch::quotedValue($nome) ?? $nome; $sito_web = \App\Support\HtmlHelper::decode($data['sito_web'] ?? ''); $indirizzo = \App\Support\HtmlHelper::decode($data['indirizzo'] ?? ''); $telefono = \App\Support\HtmlHelper::decode($data['telefono'] ?? ''); @@ -182,6 +184,8 @@ public function update(int $id, array $data): bool // Decode HTML entities to prevent double encoding $nome = \App\Support\HtmlHelper::decode($data['nome']); + // Quotes around the whole name are never stored, as in create() (#501). + $nome = \App\Support\ExactSearch::quotedValue($nome) ?? $nome; $sito_web = \App\Support\HtmlHelper::decode($data['sito_web'] ?? ''); $indirizzo = \App\Support\HtmlHelper::decode($data['indirizzo'] ?? ''); $telefono = \App\Support\HtmlHelper::decode($data['telefono'] ?? ''); @@ -283,8 +287,21 @@ public function findByName(string $name): ?int { $name = trim($name); if ($name === '') return null; - $stmt = $this->db->prepare('SELECT id FROM editori WHERE nome = ? LIMIT 1'); - $stmt->bind_param('s', $name); + // Typed in quotes: only that exact spelling (accents included, case + // ignored) is reused, so a new "Häffner" is not merged into Haffner. + $quoted = \App\Support\ExactSearch::quotedValue($name); + if ($quoted !== null) { + $match = \App\Support\ExactSearch::equalsCondition('nome', $quoted); + $stmt = $this->db->prepare("SELECT id FROM editori WHERE {$match['sql']} ORDER BY id LIMIT 1"); + $stmt->bind_param($match['types'], ...$match['params']); + $stmt->execute(); + $stmt->bind_result($id); + return $stmt->fetch() ? (int) $id : null; + } + // The collation folds accents, so "Häffner" also matches "Haffner"; + // when both are on file the exact spelling wins (#501). + $stmt = $this->db->prepare('SELECT id FROM editori WHERE nome = ? ORDER BY ' . \App\Support\ExactSearch::exactFirstSql('nome') . ', id LIMIT 1'); + $stmt->bind_param('ss', $name, $name); $stmt->execute(); $stmt->bind_result($id); if ($stmt->fetch()) { return (int)$id; } diff --git a/app/Models/SettingsRepository.php b/app/Models/SettingsRepository.php index 20764a0a1..bfc008cad 100644 --- a/app/Models/SettingsRepository.php +++ b/app/Models/SettingsRepository.php @@ -108,26 +108,33 @@ public function allowsMultipleLoansSameBook(): bool } /** - * Configured default loan duration. Invalid or missing values retain the - * historical 30-day fallback in every loan creation/update path. + * Configured default loan duration (global rule, no patron context). + * + * @deprecated read the rules from \App\Services\CirculationPolicy (forLoan()/defaults()). */ public function loanDurationDays(): int { - $days = (int) ($this->get('loans', 'loan_duration_days', '30') ?? 30); - return $days >= 1 ? $days : 30; + return (new \App\Services\CirculationPolicy($this->db, $this))->defaults()->durationDays; } - /** Maximum duration of a reader's request, including an explicit end date. */ + /** + * Maximum duration of a reader's request, including an explicit end date. + * + * @deprecated read the rules from \App\Services\CirculationPolicy (forLoan()/defaults()). + */ public function maxLoanDurationDays(): int { - $days = (int) ($this->get('loans', 'max_loan_duration_days', '90') ?? 90); - return $days >= 1 ? $days : 90; + return (new \App\Services\CirculationPolicy($this->db, $this))->defaults()->maxRequestDays; } - /** Default for reader requests and their calendars; staff loans retain their own duration. */ + /** + * Default for reader requests and their calendars; staff loans retain their own duration. + * + * @deprecated read the rules from \App\Services\CirculationPolicy (forLoan()/defaults()). + */ public function requestLoanDurationDays(): int { - return min($this->loanDurationDays(), $this->maxLoanDurationDays()); + return (new \App\Services\CirculationPolicy($this->db, $this))->defaults()->requestDurationDays(); } /** @@ -189,21 +196,34 @@ public function ensureEmailTemplates(array $templates, ?string $locale = null): if ($existing !== null) { if (isset($italianBase[$name])) { $base = $italianBase[$name]; + // The Italian text as shipped now, or as an earlier + // release shipped it (SettingsMailTemplates::retiredBodies(): + // loan_returned before its {{multa}} line), so a row left + // in Italian by an older install still gets translated. + $storedBody = trim(str_replace("\r\n", "\n", (string) $existing['body'])); + $italianBodies = array_map( + static fn (string $body): string => trim(str_replace("\r\n", "\n", $body)), + array_merge([(string) $base['body']], \App\Support\SettingsMailTemplates::retiredBodies($name, 'it_IT')) + ); $stillItalian = trim((string) $existing['subject']) === trim((string) $base['subject']) - && trim((string) $existing['body']) === trim((string) $base['body']); + && in_array($storedBody, $italianBodies, true); $haveTranslation = trim((string) $template['subject']) !== trim((string) $existing['subject']) || trim((string) $template['body']) !== trim((string) $existing['body']); if ($stillItalian && $haveTranslation) { + // The text is translated; a template the administrator + // switched off stays off. $this->saveEmailTemplate( $name, $template['subject'], $template['body'], $template['description'] ?? null, - true, + (int) ($existing['active'] ?? 1) === 1, $locale ); + continue; } } + $this->healRetiredDefault($name, $existing, $template, $locale); continue; } $this->saveEmailTemplate( @@ -217,6 +237,43 @@ public function ensureEmailTemplates(array $templates, ?string $locale = null): } } + /** + * A stored row that still equals a default this template shipped with + * BEFORE the current one (SettingsMailTemplates::retiredBodies(): the + * 0.9.0 loan_returned without its {{multa}} line) gets the current body, + * so a new placeholder reaches an upgraded install. Any other text is a + * customisation and is left exactly as it is; the subject, the description + * and the active flag of the row are kept. Idempotent: once healed the row + * equals the current default and matches nothing retired. + * + * @param array $existing + * @param array{subject:string, body:string, description?:string} $template + */ + private function healRetiredDefault(string $name, array $existing, array $template, string $locale): bool + { + $canon = static fn (string $html): string => trim(str_replace("\r\n", "\n", $html)); + $stored = $canon((string) $existing['body']); + $current = $canon((string) $template['body']); + if ($stored === '' || $stored === $current) { + return false; + } + foreach (\App\Support\SettingsMailTemplates::retiredBodies($name, $locale) as $retired) { + if ($stored !== $canon($retired) && $stored !== $canon(\App\Support\EmailLayout::normalizeContent($retired))) { + continue; + } + $this->saveEmailTemplate( + $name, + (string) $existing['subject'], + $template['body'], + isset($existing['description']) ? (string) $existing['description'] : ($template['description'] ?? null), + (int) ($existing['active'] ?? 1) === 1, + $locale + ); + return true; + } + return false; + } + /** * #299: repair email templates already stored with links double-prefixed by * the admin base — the WYSIWYG editor used to resolve placeholder URLs (e.g. diff --git a/app/Models/SupplierRepository.php b/app/Models/SupplierRepository.php new file mode 100644 index 000000000..4b37650ed --- /dev/null +++ b/app/Models/SupplierRepository.php @@ -0,0 +1,233 @@ +db->query( + "SELECT (SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'fornitori') + + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'libri' AND COLUMN_NAME = 'fornitore_id') AS c" + ); + return $res instanceof \mysqli_result && (int) ($res->fetch_assoc()['c'] ?? 0) === 2; + } catch (\Throwable) { + return false; + } + } + + /** + * Every supplier with the number of (non-deleted) books linked to it. + * + * @return list> + */ + public function listWithCounts(): array + { + if (!$this->isAvailable()) { + return []; + } + // A correlated count instead of JOIN + GROUP BY f.id: MariaDB's + // ONLY_FULL_GROUP_BY does not accept f.* grouped by the key alone. + $sql = 'SELECT f.*, + (SELECT COUNT(*) FROM libri l WHERE l.fornitore_id = f.id AND l.deleted_at IS NULL) AS libri_count + FROM fornitori f + ORDER BY f.nome ASC, f.id ASC'; + $rows = []; + $res = $this->db->query($sql); + if ($res instanceof \mysqli_result) { + while ($row = $res->fetch_assoc()) { + $rows[] = $row; + } + } + return $rows; + } + + /** + * id => name, for the book form select and the book list filter. + * + * @return array + */ + public function options(): array + { + if (!$this->isAvailable()) { + return []; + } + $out = []; + $res = $this->db->query('SELECT id, nome FROM fornitori ORDER BY nome ASC, id ASC'); + if ($res instanceof \mysqli_result) { + while ($row = $res->fetch_assoc()) { + $out[(int) $row['id']] = (string) $row['nome']; + } + } + return $out; + } + + /** @return array|null */ + public function getById(int $id): ?array + { + $stmt = $this->db->prepare('SELECT * FROM fornitori WHERE id = ?'); + $stmt->bind_param('i', $id); + $stmt->execute(); + $row = $stmt->get_result()->fetch_assoc(); + $stmt->close(); + return $row ?: null; + } + + /** @param array $data normalized by normalize() */ + public function create(array $data): int + { + $values = $this->orderedValues($data); + $columns = implode(', ', self::FIELDS); + $placeholders = implode(', ', array_fill(0, count(self::FIELDS), '?')); + $stmt = $this->db->prepare("INSERT INTO fornitori ({$columns}) VALUES ({$placeholders})"); + $stmt->bind_param(str_repeat('s', count($values)), ...$values); + $stmt->execute(); + $id = (int) $this->db->insert_id; + $stmt->close(); + return $id; + } + + /** + * Save a supplier. FALSE when it no longer exists (deleted meanwhile); + * a save that changes nothing on an existing row is still TRUE. + * + * @param array $data normalized by normalize() + */ + public function update(int $id, array $data): bool + { + $values = $this->orderedValues($data); + $set = implode(', ', array_map(static fn (string $c): string => "{$c} = ?", self::FIELDS)); + $stmt = $this->db->prepare("UPDATE fornitori SET {$set} WHERE id = ?"); + $values[] = $id; + $stmt->bind_param(str_repeat('s', count(self::FIELDS)) . 'i', ...$values); + $ok = $stmt->execute(); + $changed = $stmt->affected_rows > 0; + $stmt->close(); + if (!$ok) { + return false; + } + // affected_rows is 0 both for "no row" and "same values": re-read. + return $changed || $this->getById($id) !== null; + } + + /** Deleting a supplier unlinks its books (FK ON DELETE SET NULL). */ + public function delete(int $id): bool + { + $stmt = $this->db->prepare('DELETE FROM fornitori WHERE id = ?'); + $stmt->bind_param('i', $id); + $ok = $stmt->execute() && $stmt->affected_rows > 0; + $stmt->close(); + return $ok; + } + + /** + * Trim every field, blank → NULL, cap the lengths to the column sizes and + * keep only http(s) websites. The name is required by the caller. + * + * @param array $input + * @return array + */ + public static function normalize(array $input): array + { + // The website is not cut: websiteUrl() refuses one that does not fit + // the column once the scheme is added, and a cut URL is a wrong one. + $limits = ['nome' => 255, 'referente' => 150, 'email' => 255, 'telefono' => 30, 'partita_iva' => 20, 'indirizzo' => 2000, 'note' => 5000]; + $out = []; + foreach (self::FIELDS as $field) { + $raw = $input[$field] ?? null; + $value = is_scalar($raw) ? trim((string) $raw) : ''; + if ($value !== '' && isset($limits[$field])) { + $value = mb_substr($value, 0, $limits[$field]); + } + $out[$field] = $value === '' ? null : $value; + } + if ($out['email'] !== null && !self::isValidEmail($out['email'])) { + $out['email'] = null; + } + if ($out['sito_web'] !== null) { + $out['sito_web'] = self::websiteUrl($out['sito_web']); + } + return $out; + } + + /** + * Fields of a submission that carry a value normalize() would have to + * drop: 'email' when it is not an e-mail address, 'sito_web' when it is + * not an http(s) URL of at most 255 characters once the scheme is added. + * The controller refuses the form on these instead of saving NULL with a + * success message. + * + * @param array $input + * @return list + */ + public static function invalidFields(array $input): array + { + $invalid = []; + $email = is_scalar($input['email'] ?? null) ? trim((string) $input['email']) : ''; + if ($email !== '' && !self::isValidEmail($email)) { + $invalid[] = 'email'; + } + $site = is_scalar($input['sito_web'] ?? null) ? trim((string) $input['sito_web']) : ''; + if ($site !== '' && self::websiteUrl($site) === null) { + $invalid[] = 'sito_web'; + } + return $invalid; + } + + private static function isValidEmail(string $email): bool + { + return filter_var($email, FILTER_VALIDATE_EMAIL) !== false; + } + + /** + * The website as an http(s) URL (a missing scheme becomes https), or NULL + * when it is not a URL or, scheme included, exceeds the 255-character column. + */ + private static function websiteUrl(string $raw): ?string + { + $url = $raw; + if (!preg_match('#^https?://#i', $url)) { + $url = 'https://' . ltrim($url, '/'); + } + if (mb_strlen($url) > self::WEBSITE_MAX) { + return null; + } + return filter_var($url, FILTER_VALIDATE_URL) !== false ? $url : null; + } + + /** + * @param array $data + * @return list + */ + private function orderedValues(array $data): array + { + $values = []; + foreach (self::FIELDS as $field) { + $values[] = $data[$field] ?? null; + } + return $values; + } +} diff --git a/app/Routes/web.php b/app/Routes/web.php index 3d5650d08..ee0ea27b5 100644 --- a/app/Routes/web.php +++ b/app/Routes/web.php @@ -718,6 +718,17 @@ return $controller->updateLabels($request, $response, $db); })->add(new CsrfMiddleware())->add(new AdminAuthMiddleware()); + // Library cards (patron-cards-import): the tab lives in /admin/settings like + // the labels one; the GET form of the address opens that tab. + $app->get('/admin/settings/cards', function ($request, $response) { + return $response->withHeader('Location', url('/admin/settings?tab=cards'))->withStatus(302); + })->add(new \App\Middleware\FeatureGateMiddleware('patron_cards', configuration: true))->add(new AdminAuthMiddleware()); + $app->post('/admin/settings/cards', function ($request, $response) use ($app) { + $db = $app->getContainer()->get('db'); + $controller = new SettingsController(); + return $controller->updateCards($request, $response, $db); + })->add(new \App\Middleware\FeatureGateMiddleware('patron_cards', configuration: true))->add(new CsrfMiddleware())->add(new AdminAuthMiddleware()); + $app->post('/admin/settings/sharing', function ($request, $response) use ($app) { $db = $app->getContainer()->get('db'); $controller = new SettingsController(); @@ -760,12 +771,60 @@ return $controller->flushAllCaches($request, $response, $db); })->add(new CsrfMiddleware())->add(new AdminAuthMiddleware()); + // Optional modules (Settings → Modules). Administrators only: the + // controller re-checks mayConfigure() inline, as for the policy settings. + $app->post('/admin/settings/modules', function ($request, $response) use ($app) { + $db = $app->getContainer()->get('db'); + $controller = new SettingsController(); + return $controller->updateModules($request, $response, $db); + })->add(new CsrfMiddleware())->add(new AdminAuthMiddleware()); + $app->post('/admin/settings/loans', function ($request, $response) use ($app) { $db = $app->getContainer()->get('db'); $controller = new SettingsController(); return $controller->updateLoansSettings($request, $response, $db); })->add(new CsrfMiddleware())->add(new AdminAuthMiddleware()); + // Circulation policy: patron types and rules matrix (Settings → Circulation) + $app->get('/admin/settings/circulation', function ($request, $response) { + return (new \App\Controllers\CirculationSettingsController())->index($request, $response); + })->add(new \App\Middleware\FeatureGateMiddleware('circulation_rules', configuration: true))->add(new AdminAuthMiddleware()); + + $app->post('/admin/settings/circulation/types', function ($request, $response) use ($app) { + $db = $app->getContainer()->get('db'); + return (new \App\Controllers\CirculationSettingsController())->createType($request, $response, $db); + })->add(new \App\Middleware\FeatureGateMiddleware('circulation_rules', configuration: true))->add(new CsrfMiddleware())->add(new AdminAuthMiddleware()); + + $app->get('/admin/settings/circulation/types/{id:\d+}', function ($request, $response, $args) use ($app) { + $db = $app->getContainer()->get('db'); + return (new \App\Controllers\CirculationSettingsController())->editForm($request, $response, $db, (int) $args['id']); + })->add(new \App\Middleware\FeatureGateMiddleware('circulation_rules', configuration: true))->add(new AdminAuthMiddleware()); + + $app->post('/admin/settings/circulation/types/{id:\d+}', function ($request, $response, $args) use ($app) { + $db = $app->getContainer()->get('db'); + return (new \App\Controllers\CirculationSettingsController())->updateType($request, $response, $db, (int) $args['id']); + })->add(new \App\Middleware\FeatureGateMiddleware('circulation_rules', configuration: true))->add(new CsrfMiddleware())->add(new AdminAuthMiddleware()); + + $app->post('/admin/settings/circulation/types/{id:\d+}/delete', function ($request, $response, $args) use ($app) { + $db = $app->getContainer()->get('db'); + return (new \App\Controllers\CirculationSettingsController())->deleteType($request, $response, $db, (int) $args['id']); + })->add(new \App\Middleware\FeatureGateMiddleware('circulation_rules', configuration: true))->add(new CsrfMiddleware())->add(new AdminAuthMiddleware()); + + $app->post('/admin/settings/circulation/types/{id:\d+}/default', function ($request, $response, $args) use ($app) { + $db = $app->getContainer()->get('db'); + return (new \App\Controllers\CirculationSettingsController())->setDefault($request, $response, $db, (int) $args['id']); + })->add(new \App\Middleware\FeatureGateMiddleware('circulation_rules', configuration: true))->add(new CsrfMiddleware())->add(new AdminAuthMiddleware()); + + $app->post('/admin/settings/circulation/types/{id:\d+}/move', function ($request, $response, $args) use ($app) { + $db = $app->getContainer()->get('db'); + return (new \App\Controllers\CirculationSettingsController())->moveType($request, $response, $db, (int) $args['id']); + })->add(new \App\Middleware\FeatureGateMiddleware('circulation_rules', configuration: true))->add(new CsrfMiddleware())->add(new AdminAuthMiddleware()); + + $app->post('/admin/settings/circulation/rules', function ($request, $response) use ($app) { + $db = $app->getContainer()->get('db'); + return (new \App\Controllers\CirculationSettingsController())->saveRules($request, $response, $db); + })->add(new \App\Middleware\FeatureGateMiddleware('circulation_rules', configuration: true))->add(new CsrfMiddleware())->add(new AdminAuthMiddleware()); + // API Settings Routes $app->post('/admin/settings/api/toggle', function ($request, $response) use ($app) { $db = $app->getContainer()->get('db'); @@ -791,6 +850,68 @@ return $controller->deleteApiKey($request, $response, $db, (int) $args['id']); })->add(new CsrfMiddleware())->add(new AdminAuthMiddleware()); + // Closures calendar (Admin Only): weekly closed days, closure intervals, + // holiday presets, yearly closures on/off, ICS import. Every action re-checks mayConfigure() inline. + $app->get('/admin/calendar', function ($request, $response) use ($app) { + $db = $app->getContainer()->get('db'); + return (new \App\Controllers\Admin\LibraryCalendarController())->index($request, $response, $db); + })->add(new \App\Middleware\FeatureGateMiddleware('closures_calendar', configuration: true))->add(new AdminAuthMiddleware()); + + $app->post('/admin/calendar/weekdays', function ($request, $response) use ($app) { + $db = $app->getContainer()->get('db'); + return (new \App\Controllers\Admin\LibraryCalendarController())->saveWeekdays($request, $response, $db); + })->add(new \App\Middleware\FeatureGateMiddleware('closures_calendar', configuration: true))->add(new CsrfMiddleware())->add(new AdminAuthMiddleware()); + + $app->post('/admin/calendar/closures', function ($request, $response) use ($app) { + $db = $app->getContainer()->get('db'); + return (new \App\Controllers\Admin\LibraryCalendarController())->store($request, $response, $db); + })->add(new \App\Middleware\FeatureGateMiddleware('closures_calendar', configuration: true))->add(new CsrfMiddleware())->add(new AdminAuthMiddleware()); + + $app->post('/admin/calendar/closures/{id:\d+}/update', function ($request, $response, $args) use ($app) { + $db = $app->getContainer()->get('db'); + return (new \App\Controllers\Admin\LibraryCalendarController())->update($request, $response, $db, (int) $args['id']); + })->add(new \App\Middleware\FeatureGateMiddleware('closures_calendar', configuration: true))->add(new CsrfMiddleware())->add(new AdminAuthMiddleware()); + + $app->post('/admin/calendar/closures/{id:\d+}/delete', function ($request, $response, $args) use ($app) { + $db = $app->getContainer()->get('db'); + return (new \App\Controllers\Admin\LibraryCalendarController())->delete($request, $response, $db, (int) $args['id']); + })->add(new \App\Middleware\FeatureGateMiddleware('closures_calendar', configuration: true))->add(new CsrfMiddleware())->add(new AdminAuthMiddleware()); + + $app->post('/admin/calendar/preset', function ($request, $response) use ($app) { + $db = $app->getContainer()->get('db'); + return (new \App\Controllers\Admin\LibraryCalendarController())->importPreset($request, $response, $db); + })->add(new \App\Middleware\FeatureGateMiddleware('closures_calendar', configuration: true))->add(new CsrfMiddleware())->add(new AdminAuthMiddleware()); + + $app->post('/admin/calendar/custom-holidays', function ($request, $response) use ($app) { + $db = $app->getContainer()->get('db'); + return (new \App\Controllers\Admin\LibraryCalendarController())->storeCustom($request, $response, $db); + })->add(new \App\Middleware\FeatureGateMiddleware('closures_calendar', configuration: true))->add(new CsrfMiddleware())->add(new AdminAuthMiddleware()); + + $app->post('/admin/calendar/custom-holidays/{id:\d+}/update', function ($request, $response, $args) use ($app) { + $db = $app->getContainer()->get('db'); + return (new \App\Controllers\Admin\LibraryCalendarController())->updateCustom($request, $response, $db, (int) $args['id']); + })->add(new \App\Middleware\FeatureGateMiddleware('closures_calendar', configuration: true))->add(new CsrfMiddleware())->add(new AdminAuthMiddleware()); + + $app->post('/admin/calendar/custom-holidays/{id:\d+}/toggle', function ($request, $response, $args) use ($app) { + $db = $app->getContainer()->get('db'); + return (new \App\Controllers\Admin\LibraryCalendarController())->toggleCustom($request, $response, $db, (int) $args['id']); + })->add(new \App\Middleware\FeatureGateMiddleware('closures_calendar', configuration: true))->add(new CsrfMiddleware())->add(new AdminAuthMiddleware()); + + $app->post('/admin/calendar/custom-holidays/{id:\d+}/delete', function ($request, $response, $args) use ($app) { + $db = $app->getContainer()->get('db'); + return (new \App\Controllers\Admin\LibraryCalendarController())->deleteCustom($request, $response, $db, (int) $args['id']); + })->add(new \App\Middleware\FeatureGateMiddleware('closures_calendar', configuration: true))->add(new CsrfMiddleware())->add(new AdminAuthMiddleware()); + + $app->post('/admin/calendar/yearly', function ($request, $response) use ($app) { + $db = $app->getContainer()->get('db'); + return (new \App\Controllers\Admin\LibraryCalendarController())->saveYearly($request, $response, $db); + })->add(new \App\Middleware\FeatureGateMiddleware('closures_calendar', configuration: true))->add(new CsrfMiddleware())->add(new AdminAuthMiddleware()); + + $app->post('/admin/calendar/import-ics', function ($request, $response) use ($app) { + $db = $app->getContainer()->get('db'); + return (new \App\Controllers\Admin\LibraryCalendarController())->importIcs($request, $response, $db); + })->add(new \App\Middleware\FeatureGateMiddleware('closures_calendar', configuration: true))->add(new CsrfMiddleware())->add(new AdminAuthMiddleware()); + // Language Management Routes (Admin Only) $app->get('/admin/languages', function ($request, $response) use ($app) { $db = $app->getContainer()->get('db'); @@ -1193,6 +1314,13 @@ return $controller->index($request, $response, $db); })->add(new AdminAuthMiddleware()); + // Fines report CSV (plan §7): charged / collected / waived per month. + $app->get('/admin/statistics/fines.csv', function ($request, $response) use ($app) { + $db = $app->getContainer()->get('db'); + $controller = new \App\Controllers\Admin\StatsController(); + return $controller->finesCsv($request, $response, $db); + })->add(new \App\Middleware\FeatureGateMiddleware('fines'))->add(new AdminAuthMiddleware()); + // Simple stats APIs used by header quick stats (require authentication) $app->get('/api/stats/books-count', function ($request, $response) use ($app) { $db = $app->getContainer()->get('db'); @@ -1260,17 +1388,91 @@ $db = $app->getContainer()->get('db'); return $controller->delete($request, $response, $db, (int) $args['id']); })->add(new CsrfMiddleware())->add(new AdminAuthMiddleware()); + // Patron ledger (plan §7): payments, manual charges, waivers, payment receipt. + $app->post('/admin/users/{id:\d+}/ledger/payment', function ($request, $response, $args) use ($app) { + $db = $app->getContainer()->get('db'); + return (new \App\Controllers\PatronLedgerController())->payment($request, $response, $db, (int) $args['id']); + })->add(new \App\Middleware\FeatureGateMiddleware('fines'))->add(new CsrfMiddleware())->add(new AdminAuthMiddleware()); + $app->post('/admin/users/{id:\d+}/ledger/charge', function ($request, $response, $args) use ($app) { + $db = $app->getContainer()->get('db'); + return (new \App\Controllers\PatronLedgerController())->charge($request, $response, $db, (int) $args['id']); + })->add(new \App\Middleware\FeatureGateMiddleware('fines'))->add(new CsrfMiddleware())->add(new AdminAuthMiddleware()); + $app->post('/admin/users/{id:\d+}/ledger/waive', function ($request, $response, $args) use ($app) { + $db = $app->getContainer()->get('db'); + return (new \App\Controllers\PatronLedgerController())->waive($request, $response, $db, (int) $args['id']); + })->add(new \App\Middleware\FeatureGateMiddleware('fines'))->add(new CsrfMiddleware())->add(new AdminAuthMiddleware()); + $app->get('/admin/users/{id:\d+}/ledger/receipt/{movement:\d+}', function ($request, $response, $args) use ($app) { + $db = $app->getContainer()->get('db'); + return (new \App\Controllers\PatronLedgerController())->receipt($request, $response, $db, (int) $args['id'], (int) $args['movement']); + })->add(new \App\Middleware\FeatureGateMiddleware('fines'))->add(new AdminAuthMiddleware()); $app->post('/admin/users/{id:\d+}/approve-and-send-activation', function ($request, $response, $args) use ($app) { $controller = new \App\Controllers\UsersController(); $db = $app->getContainer()->get('db'); return $controller->approveAndSendActivation($request, $response, $db, $args); })->add(new CsrfMiddleware())->add(new AdminAuthMiddleware()); + $app->post('/admin/users/{id:\d+}/renew-card', function ($request, $response, $args) use ($app) { + $db = $app->getContainer()->get('db'); + return (new \App\Controllers\UsersController())->renewCard($request, $response, $db, (int) $args['id']); + })->add(new CsrfMiddleware())->add(new AdminAuthMiddleware()); + $app->post('/admin/users/{id:\d+}/activate-directly', function ($request, $response, $args) use ($app) { $controller = new \App\Controllers\UsersController(); $db = $app->getContainer()->get('db'); return $controller->activateDirectly($request, $response, $db, $args); })->add(new CsrfMiddleware())->add(new AdminAuthMiddleware()); + // Library cards, patron photo and user CSV import (patron-cards-import). + $app->get('/admin/users/{id:\d+}/card-pdf', function ($request, $response, $args) use ($app) { + $controller = new \App\Controllers\PatronCardsController(); + return $controller->cardPdf($request, $response, $app->getContainer()->get('db'), (int) $args['id']); + })->add(new \App\Middleware\FeatureGateMiddleware('patron_cards'))->add(new \App\Middleware\RateLimitMiddleware(30, 60))->add(new AdminAuthMiddleware()); + $app->get('/admin/users/cards-pdf', function ($request, $response) use ($app) { + $controller = new \App\Controllers\PatronCardsController(); + return $controller->bulkPdf($request, $response, $app->getContainer()->get('db')); + })->add(new \App\Middleware\FeatureGateMiddleware('patron_cards'))->add(new \App\Middleware\RateLimitMiddleware(15, 60))->add(new AdminAuthMiddleware()); + $app->get('/admin/users/{id:\d+}/photo', function ($request, $response, $args) use ($app) { + $controller = new \App\Controllers\PatronCardsController(); + return $controller->photo($request, $response, $app->getContainer()->get('db'), (int) $args['id']); + })->add(new \App\Middleware\FeatureGateMiddleware('patron_photos'))->add(new AdminAuthMiddleware()); + $app->post('/admin/users/{id:\d+}/photo', function ($request, $response, $args) use ($app) { + $controller = new \App\Controllers\PatronCardsController(); + return $controller->uploadPhoto($request, $response, $app->getContainer()->get('db'), (int) $args['id']); + })->add(new \App\Middleware\FeatureGateMiddleware('patron_photos'))->add(new CsrfMiddleware())->add(new \App\Middleware\RateLimitMiddleware(30, 60))->add(new AdminAuthMiddleware()); + $app->post('/admin/users/{id:\d+}/photo/delete', function ($request, $response, $args) use ($app) { + $controller = new \App\Controllers\PatronCardsController(); + return $controller->deletePhoto($request, $response, $app->getContainer()->get('db'), (int) $args['id']); + })->add(new \App\Middleware\FeatureGateMiddleware('patron_photos'))->add(new CsrfMiddleware())->add(new AdminAuthMiddleware()); + $app->get('/admin/users/import', function ($request, $response) use ($app) { + $controller = new \App\Controllers\UserImportController(); + return $controller->page($request, $response, $app->getContainer()->get('db')); + })->add(new \App\Middleware\FeatureGateMiddleware('user_import'))->add(new \App\Middleware\RateLimitMiddleware(30, 60))->add(new AdminAuthMiddleware()); + $app->get('/admin/users/import/example', function ($request, $response) use ($app) { + $controller = new \App\Controllers\UserImportController(); + return $controller->example($request, $response, $app->getContainer()->get('db')); + })->add(new \App\Middleware\FeatureGateMiddleware('user_import'))->add(new \App\Middleware\RateLimitMiddleware(30, 60))->add(new AdminAuthMiddleware()); + $app->get('/admin/users/import/errors', function ($request, $response) use ($app) { + $controller = new \App\Controllers\UserImportController(); + return $controller->downloadErrors($request, $response, $app->getContainer()->get('db')); + })->add(new \App\Middleware\FeatureGateMiddleware('user_import'))->add(new \App\Middleware\RateLimitMiddleware(30, 60))->add(new AdminAuthMiddleware()); + $app->post('/admin/users/import/upload', function ($request, $response) use ($app) { + $controller = new \App\Controllers\UserImportController(); + return $controller->upload($request, $response, $app->getContainer()->get('db')); + })->add(new \App\Middleware\FeatureGateMiddleware('user_import'))->add(new CsrfMiddleware())->add(new \App\Middleware\RateLimitMiddleware(30, 60))->add(new AdminAuthMiddleware()); + $app->post('/admin/users/import/start', function ($request, $response) use ($app) { + $controller = new \App\Controllers\UserImportController(); + return $controller->start($request, $response, $app->getContainer()->get('db')); + })->add(new \App\Middleware\FeatureGateMiddleware('user_import'))->add(new CsrfMiddleware())->add(new AdminAuthMiddleware()); + // No RateLimitMiddleware on /chunk: same rationale as the book import (#113), + // one sequential request per chunk, bound to the session's import. + $app->post('/admin/users/import/chunk', function ($request, $response) use ($app) { + $controller = new \App\Controllers\UserImportController(); + return $controller->chunk($request, $response, $app->getContainer()->get('db')); + })->add(new \App\Middleware\FeatureGateMiddleware('user_import'))->add(new CsrfMiddleware())->add(new AdminAuthMiddleware()); + $app->post('/admin/users/import/cancel', function ($request, $response) use ($app) { + $controller = new \App\Controllers\UserImportController(); + return $controller->cancel($request, $response, $app->getContainer()->get('db')); + })->add(new \App\Middleware\FeatureGateMiddleware('user_import'))->add(new CsrfMiddleware())->add(new AdminAuthMiddleware()); + // CSV Export route for users $app->get('/admin/users/export/csv', function ($request, $response) use ($app) { $controller = new \App\Controllers\UsersController(); @@ -1451,9 +1653,10 @@ })->add(new CsrfMiddleware())->add(new \App\Middleware\RateLimitMiddleware(30, 60))->add(new AdminAuthMiddleware()); // 30 requests per minute for bulk operations // CSV Import routes - $app->get('/admin/books/import', function ($request, $response) { + $app->get('/admin/books/import', function ($request, $response) use ($app) { $controller = new \App\Controllers\CsvImportController(); - return $controller->showImportPage($request, $response); + $db = $app->getContainer()->get('db'); + return $controller->showImportPage($request, $response, $db); })->add(new \App\Middleware\RateLimitMiddleware(30, 60))->add(new AdminAuthMiddleware()); // 30 requests per minute $app->get('/admin/books/import/example', function ($request, $response) { @@ -1491,6 +1694,39 @@ // chunk loop is further bounded by the session-scoped import_id. // See issue #113 for the original rate-limit regression. + // MARC bulk import (ISO 2709 / MARCXML; MARC 21, UNIMARC, danMARC2) on + // the CSV import infrastructure: upload + preview, then chunks. No + // RateLimitMiddleware on /chunk for the same reason as the CSV chunk (#113). + $app->get('/admin/books/import/marc', function ($request, $response) use ($app) { + $controller = new \App\Controllers\MarcImportController(); + $db = $app->getContainer()->get('db'); + return $controller->showImportPage($request, $response, $db); + })->add(new \App\Middleware\FeatureGateMiddleware('marc'))->add(new \App\Middleware\RateLimitMiddleware(30, 60))->add(new AdminAuthMiddleware()); + + $app->post('/admin/books/import/marc/upload', function ($request, $response) use ($app) { + $controller = new \App\Controllers\MarcImportController(); + $db = $app->getContainer()->get('db'); + return $controller->upload($request, $response, $db); + })->add(new \App\Middleware\FeatureGateMiddleware('marc'))->add(new CsrfMiddleware())->add(new AdminAuthMiddleware()); + + $app->post('/admin/books/import/marc/chunk', function ($request, $response) use ($app) { + $controller = new \App\Controllers\MarcImportController(); + $db = $app->getContainer()->get('db'); + return $controller->chunk($request, $response, $db); + })->add(new \App\Middleware\FeatureGateMiddleware('marc'))->add(new CsrfMiddleware())->add(new AdminAuthMiddleware()); + + $app->post('/admin/books/import/marc/discard', function ($request, $response) { + $controller = new \App\Controllers\MarcImportController(); + return $controller->discard($request, $response); + })->add(new CsrfMiddleware())->add(new AdminAuthMiddleware()); + + // MARC bulk export: whole catalogue or ?ids= selection, streamed. + $app->get('/admin/books/export/marc', function ($request, $response) use ($app) { + $controller = new \App\Controllers\MarcExportController(); + $db = $app->getContainer()->get('db'); + return $controller->export($request, $response, $db); + })->add(new \App\Middleware\FeatureGateMiddleware('marc'))->add(new \App\Middleware\RateLimitMiddleware(15, 60))->add(new AdminAuthMiddleware()); + // LibraryThing Plugin Routes // Plugin Administration @@ -1646,6 +1882,12 @@ return $controller->updateCopy($request, $response, $db, (int) $args['id']); })->add(new CsrfMiddleware())->add(new AdminAuthMiddleware()); + $app->post('/admin/books/copies/{id:\d+}/consultation', function ($request, $response, $args) use ($app) { + $controller = new \App\Controllers\CopyController(); + $db = $app->getContainer()->get('db'); + return $controller->toggleConsultation($request, $response, $db, (int) $args['id']); + })->add(new \App\Middleware\FeatureGateMiddleware('consultation_only'))->add(new CsrfMiddleware())->add(new AdminAuthMiddleware()); + $app->post('/admin/books/copies/{id:\d+}/delete', function ($request, $response, $args) use ($app) { $controller = new \App\Controllers\CopyController(); $db = $app->getContainer()->get('db'); @@ -1787,6 +2029,29 @@ return $controller->destroy($request, $response, $db, (int) $args['id']); })->add(new CsrfMiddleware())->add(new AdminAuthMiddleware()); + // Inventory stocktake (inventory.manage; closing with "mark lost" re-checks admin inline) + // Slim binds route closures to the container, so they must not be static. + $inventory = function (string $method) use ($app): \Closure { + return function ($request, $response, $args) use ($app, $method) { + $controller = new \App\Controllers\InventoryController(); + $db = $app->getContainer()->get('db'); + return isset($args['id']) + ? $controller->{$method}($request, $response, $db, (int) $args['id']) + : $controller->{$method}($request, $response, $db); + }; + }; + $app->get('/admin/inventory', $inventory('index'))->add(new \App\Middleware\FeatureGateMiddleware('stocktake'))->add(new \App\Middleware\RateLimitMiddleware(30, 60))->add(new AdminAuthMiddleware()); + $app->post('/admin/inventory', $inventory('store'))->add(new \App\Middleware\FeatureGateMiddleware('stocktake'))->add(new CsrfMiddleware())->add(new AdminAuthMiddleware()); + $app->get('/admin/inventory/{id:\d+}', $inventory('show'))->add(new \App\Middleware\FeatureGateMiddleware('stocktake'))->add(new AdminAuthMiddleware()); + $app->get('/admin/inventory/{id:\d+}/state', $inventory('state'))->add(new \App\Middleware\FeatureGateMiddleware('stocktake'))->add(new AdminAuthMiddleware()); + // Scanning is a burst workload (wedge scanners read several codes a second). + $app->post('/admin/inventory/{id:\d+}/scan', $inventory('scan'))->add(new \App\Middleware\FeatureGateMiddleware('stocktake'))->add(new CsrfMiddleware())->add(new \App\Middleware\RateLimitMiddleware(600, 60))->add(new AdminAuthMiddleware()); + $app->post('/admin/inventory/{id:\d+}/upload', $inventory('upload'))->add(new \App\Middleware\FeatureGateMiddleware('stocktake'))->add(new CsrfMiddleware())->add(new \App\Middleware\RateLimitMiddleware(30, 60))->add(new AdminAuthMiddleware()); + $app->post('/admin/inventory/{id:\d+}/close', $inventory('close'))->add(new \App\Middleware\FeatureGateMiddleware('stocktake'))->add(new CsrfMiddleware())->add(new AdminAuthMiddleware()); + $app->post('/admin/inventory/{id:\d+}/cancel', $inventory('cancel'))->add(new \App\Middleware\FeatureGateMiddleware('stocktake'))->add(new CsrfMiddleware())->add(new AdminAuthMiddleware()); + $app->get('/admin/inventory/{id:\d+}/report.csv', $inventory('exportCsv'))->add(new \App\Middleware\FeatureGateMiddleware('stocktake'))->add(new \App\Middleware\RateLimitMiddleware(30, 60))->add(new AdminAuthMiddleware()); + $app->get('/admin/inventory/{id:\d+}/report.pdf', $inventory('exportPdf'))->add(new \App\Middleware\FeatureGateMiddleware('stocktake'))->add(new \App\Middleware\RateLimitMiddleware(30, 60))->add(new AdminAuthMiddleware()); + // Placement (protected admin) $app->get('/admin/placement', function ($request, $response) use ($app) { $controller = new CollocazioneController(); @@ -2563,6 +2828,54 @@ return $controller->delete($request, $response, $db, (int) $args['id']); })->add(new CsrfMiddleware())->add(new AdminAuthMiddleware()); + // Suppliers (0.9.0): the publishers' CRUD shape, catalogue permission. + $app->get('/admin/suppliers', function ($request, $response) use ($app) { + return (new \App\Controllers\SuppliersController())->index($request, $response, $app->getContainer()->get('db')); + })->add(new \App\Middleware\FeatureGateMiddleware('suppliers'))->add(new AdminAuthMiddleware()); + $app->get('/admin/suppliers/create', function ($request, $response) use ($app) { + return (new \App\Controllers\SuppliersController())->createForm($request, $response, $app->getContainer()->get('db')); + })->add(new \App\Middleware\FeatureGateMiddleware('suppliers'))->add(new AdminAuthMiddleware()); + $app->post('/admin/suppliers/create', function ($request, $response) use ($app) { + return (new \App\Controllers\SuppliersController())->store($request, $response, $app->getContainer()->get('db')); + })->add(new \App\Middleware\FeatureGateMiddleware('suppliers'))->add(new CsrfMiddleware())->add(new AdminAuthMiddleware()); + $app->get('/admin/suppliers/edit/{id:\d+}', function ($request, $response, $args) use ($app) { + return (new \App\Controllers\SuppliersController())->editForm($request, $response, $app->getContainer()->get('db'), (int) $args['id']); + })->add(new \App\Middleware\FeatureGateMiddleware('suppliers'))->add(new AdminAuthMiddleware()); + $app->post('/admin/suppliers/update/{id:\d+}', function ($request, $response, $args) use ($app) { + return (new \App\Controllers\SuppliersController())->update($request, $response, $app->getContainer()->get('db'), (int) $args['id']); + })->add(new \App\Middleware\FeatureGateMiddleware('suppliers'))->add(new CsrfMiddleware())->add(new AdminAuthMiddleware()); + $app->post('/admin/suppliers/delete/{id:\d+}', function ($request, $response, $args) use ($app) { + return (new \App\Controllers\SuppliersController())->delete($request, $response, $app->getContainer()->get('db'), (int) $args['id']); + })->add(new \App\Middleware\FeatureGateMiddleware('suppliers'))->add(new CsrfMiddleware())->add(new AdminAuthMiddleware()); + + // ISBD catalogue cards (0.9.0): one book, or the books selected in the list. + $app->get('/admin/books/{id:\d+}/catalogue-card', function ($request, $response, $args) use ($app) { + return (new \App\Controllers\CatalogueCardController())->single($request, $response, $app->getContainer()->get('db'), (int) $args['id']); + })->add(new \App\Middleware\FeatureGateMiddleware('catalogue_cards'))->add(new \App\Middleware\RateLimitMiddleware(30, 60))->add(new AdminAuthMiddleware()); + $app->get('/admin/books/catalogue-cards', function ($request, $response) use ($app) { + return (new \App\Controllers\CatalogueCardController())->bulk($request, $response, $app->getContainer()->get('db')); + })->add(new \App\Middleware\FeatureGateMiddleware('catalogue_cards'))->add(new \App\Middleware\RateLimitMiddleware(30, 60))->add(new AdminAuthMiddleware()); + + // Visitor log kiosk (0.9.0). Front-desk surface: loans permission. + $app->get('/admin/attendance', function ($request, $response) use ($app) { + return (new \App\Controllers\AttendanceController())->index($request, $response, $app->getContainer()->get('db')); + })->add(new \App\Middleware\FeatureGateMiddleware('visitor_log'))->add(new AdminAuthMiddleware()); + $app->get('/admin/attendance/export.csv', function ($request, $response) use ($app) { + return (new \App\Controllers\AttendanceController())->exportCsv($request, $response, $app->getContainer()->get('db')); + })->add(new \App\Middleware\FeatureGateMiddleware('visitor_log'))->add(new AdminAuthMiddleware()); + $app->post('/admin/attendance/scan', function ($request, $response) use ($app) { + return (new \App\Controllers\AttendanceController())->scan($request, $response, $app->getContainer()->get('db')); + })->add(new \App\Middleware\FeatureGateMiddleware('visitor_log'))->add(new CsrfMiddleware())->add(new \App\Middleware\RateLimitMiddleware(120, 60))->add(new AdminAuthMiddleware()); + $app->post('/admin/attendance/guest', function ($request, $response) use ($app) { + return (new \App\Controllers\AttendanceController())->guest($request, $response, $app->getContainer()->get('db')); + })->add(new \App\Middleware\FeatureGateMiddleware('visitor_log'))->add(new CsrfMiddleware())->add(new \App\Middleware\RateLimitMiddleware(120, 60))->add(new AdminAuthMiddleware()); + $app->post('/admin/attendance/settings', function ($request, $response) use ($app) { + return (new \App\Controllers\AttendanceController())->saveSettings($request, $response, $app->getContainer()->get('db')); + })->add(new \App\Middleware\FeatureGateMiddleware('visitor_log'))->add(new CsrfMiddleware())->add(new AdminAuthMiddleware()); + $app->post('/admin/attendance/purge', function ($request, $response) use ($app) { + return (new \App\Controllers\AttendanceController())->purge($request, $response, $app->getContainer()->get('db')); + })->add(new \App\Middleware\FeatureGateMiddleware('visitor_log'))->add(new CsrfMiddleware())->add(new AdminAuthMiddleware()); + // API Libri by Author/Publisher $app->get('/api/libri/author/{id:\d+}', function ($request, $response, $args) use ($app) { $controller = new \App\Controllers\LibriApiController(); @@ -2849,7 +3162,9 @@ if ($forUser !== null && is_numeric($forUser) && \App\Support\Permissions::can('loans.manage')) { $excludeUserId = (int) $forUser; } - $availability = $controller->getBookAvailabilityData($bookId, \App\Support\DateHelper::today(), 180, $excludeUserId); + // 180 days to start in, and occupancy data past them for the + // longest loan a start on the last of them can ask for. + $availability = $controller->getBookAvailabilityData($bookId, \App\Support\DateHelper::today(), 180, $excludeUserId, true); if ($availability === null) { $response->getBody()->write(json_encode(['success' => false, 'message' => __('Libro non trovato')])); return $response->withStatus(404)->withHeader('Content-Type', 'application/json'); @@ -2869,7 +3184,14 @@ // page, admin loan and reservation forms): the configured // default, capped by the maximum the request endpoints enforce. 'loan_duration_days' => $availability['loan_duration_days'] ?? null, - 'max_loan_duration_days' => $availability['max_loan_duration_days'] ?? null + 'max_loan_duration_days' => $availability['max_loan_duration_days'] ?? null, + // Staff loan length for this borrower's patron type and + // this material (CirculationPolicy), for the admin form. + 'staff_loan_duration_days' => $availability['staff_loan_duration_days'] ?? null, + // Closures calendar: days the library is closed (not a start/pickup day). + 'closed_dates' => $availability['closed_dates'] ?? [], + // Last day a request may start on; 'days' run past it. + 'start_until' => $availability['start_until'] ?? null ] ]; $response->getBody()->write(json_encode($data, JSON_UNESCAPED_UNICODE)); @@ -3701,6 +4023,8 @@ 'collocazione' => 'placement', 'recensioni' => 'reviews', 'statistiche' => 'statistics', + 'presenze' => 'attendance', + 'fornitori' => 'suppliers', ]; /** @var array $actionSegmentMap Italian action words → English */ diff --git a/app/Services/BulkEnrichmentService.php b/app/Services/BulkEnrichmentService.php index fede3b7f4..1cce05885 100644 --- a/app/Services/BulkEnrichmentService.php +++ b/app/Services/BulkEnrichmentService.php @@ -695,12 +695,13 @@ private function findOrCreatePublisher(string $name): ?int } // Try to find existing publisher - $stmt = $this->db->prepare("SELECT id FROM editori WHERE nome = ? LIMIT 1"); + // Exact spelling first when the collation also admits "Haffner" for "Häffner" (#501). + $stmt = $this->db->prepare("SELECT id FROM editori WHERE nome = ? ORDER BY " . \App\Support\ExactSearch::exactFirstSql('nome') . ", id LIMIT 1"); if ($stmt === false) { return null; } - $stmt->bind_param('s', $name); + $stmt->bind_param('ss', $name, $name); $stmt->execute(); $result = $stmt->get_result(); $row = $result->fetch_assoc(); @@ -792,12 +793,13 @@ private function findOrCreateAuthor(string $name): ?int } // Try to find existing author - $stmt = $this->db->prepare("SELECT id FROM autori WHERE nome = ? LIMIT 1"); + // Exact spelling first when the collation also admits "Haffner" for "Häffner" (#501). + $stmt = $this->db->prepare("SELECT id FROM autori WHERE nome = ? ORDER BY " . \App\Support\ExactSearch::exactFirstSql('nome') . ", id LIMIT 1"); if ($stmt === false) { return null; } - $stmt->bind_param('s', $name); + $stmt->bind_param('ss', $name, $name); $stmt->execute(); $result = $stmt->get_result(); $row = $result->fetch_assoc(); diff --git a/app/Services/CapacityService.php b/app/Services/CapacityService.php index ae769a7e4..7f0f0b284 100644 --- a/app/Services/CapacityService.php +++ b/app/Services/CapacityService.php @@ -40,7 +40,8 @@ */ final class CapacityService { - private ?int $maxActiveLoansPerUser = null; + /** Circulation rules, resolved once per instance (the settings rows are cached there). */ + private ?CirculationPolicy $circulation = null; public function __construct(private mysqli $db) {} @@ -63,7 +64,8 @@ public function totalCopies(int $libroId): int $stmt = $this->db->prepare( "SELECT COUNT(*) AS total FROM copie WHERE libro_id = ? - AND stato NOT IN ('perso','danneggiato','manutenzione','in_restauro','in_trasferimento')" + AND stato NOT IN ('perso','danneggiato','manutenzione','in_restauro','in_trasferimento') + AND " . ($this->circulation ??= new CirculationPolicy($this->db))->lendableCopySql('') . "" ); $stmt->bind_param('i', $libroId); $stmt->execute(); @@ -441,19 +443,21 @@ private function activeReservationIntervals(int $libroId, string $start, string // loan limit. The reservation stays active and regains its FIFO priority // automatically once the account becomes eligible again. $today = \App\Support\DateHelper::today(); - $maxLoans = $this->maxActiveLoansPerUser(); + $circulation = $this->circulation ??= new CirculationPolicy($this->db); $sql = "SELECT GREATEST($rStart, ?) AS s, LEAST($rEnd, ?) AS e FROM prenotazioni r JOIN utenti u ON u.id = r.utente_id WHERE r.libro_id = ? AND r.stato = 'attiva' - AND " . \App\Support\LoanEligibility::eligibleUserWhere('u') . " + AND " . \App\Support\LoanEligibility::eligibleUserWhere('u', $this->db) . " AND $rStart IS NOT NULL AND $rStart <= ? AND $rEnd >= ?"; $types = 'ssisss'; $params = [$start, $end, $libroId, $today, $end, $start]; - if ($maxLoans > 0) { + if ($circulation->anyTotalLoanLimit()) { + // Per-patron limit (CirculationPolicy; 0 = no limit for that patron). + $limitSql = $circulation->totalLoanLimitSql('u'); // Future reservations reserve capacity against overlapping commitments. // Once they become promotable, promotion and approval enforce the total // active-loan cap (including future scheduled loans). Use the SHARED @@ -463,23 +467,33 @@ private function activeReservationIntervals(int $libroId, string $start, string // today — the two predicates must stay identical by construction, or // an unpromotable-at-cap legacy head would keep occupying capacity // and freeze the queue (the very bug this branch fixes). - $sql .= " AND ( + $sql .= " AND ({$limitSql} <= 0 OR ( SELECT COUNT(*) FROM prestiti cap WHERE cap.utente_id = r.utente_id AND cap.attivo = 1 - AND cap.stato IN ('prenotato','da_ritirare','in_corso','in_ritardo') + AND cap.stato IN (" . CirculationPolicy::ACTIVE_LOAN_STATES_SQL . ") AND (" . \App\Support\LoanEligibility::promotableReservationWhere('r') . " OR (cap.data_prestito <= $rEnd AND (cap.stato = 'in_ritardo' OR (cap.stato = 'in_corso' AND cap.data_scadenza < ?) OR cap.data_scadenza >= $rStart))) - ) < ?"; - $types .= 'sssi'; + ) < {$limitSql})"; + $types .= 'sss'; $params[] = $today; $params[] = $today; $params[] = $today; - $params[] = $maxLoans; + } + // Same pause as queue promotion for the per-material rules: a + // promotable reservation of a patron the matrix refuses for this + // title does not hold capacity (ReservationManager skips it too). + $materialRefused = $circulation->materialRefusedReservationUserIds($libroId); + if ($materialRefused !== []) { + $sql .= ' AND NOT (r.utente_id IN (' . implode(',', array_map('intval', $materialRefused)) . ') + AND ' . \App\Support\LoanEligibility::promotableReservationWhere('r') . ')'; + $types .= 'ss'; + $params[] = $today; + $params[] = $today; } if ($excludeReservationId !== null) { $sql .= ' AND r.id <> ?'; @@ -504,16 +518,6 @@ private function activeReservationIntervals(int $libroId, string $start, string return $this->fetchIntervals($sql, $types, $params); } - private function maxActiveLoansPerUser(): int - { - if ($this->maxActiveLoansPerUser === null) { - $value = (new \App\Models\SettingsRepository($this->db)) - ->get('loans', 'max_active_loans_per_user', '0'); - $this->maxActiveLoansPerUser = max(0, (int) ($value ?? 0)); - } - return $this->maxActiveLoansPerUser; - } - /** * @param string $types * @param list $params diff --git a/app/Services/Circulation/LoanPolicy.php b/app/Services/Circulation/LoanPolicy.php new file mode 100644 index 000000000..e6d3c11cd --- /dev/null +++ b/app/Services/Circulation/LoanPolicy.php @@ -0,0 +1,51 @@ +durationDays, $this->maxRequestDays); + } + + /** Whether any quantity limit has to be counted for this loan. */ + public function hasQuantityLimits(): bool + { + return $this->maxActiveLoans > 0 || $this->maxActiveForMedia > 0; + } +} diff --git a/app/Services/Circulation/PolicyResolver.php b/app/Services/Circulation/PolicyResolver.php new file mode 100644 index 000000000..9493f76c1 --- /dev/null +++ b/app/Services/Circulation/PolicyResolver.php @@ -0,0 +1,141 @@ +|null> $layers + */ + public static function pick(array $layers, string $field): mixed + { + foreach ($layers as $layer) { + if (is_array($layer) && array_key_exists($field, $layer) && $layer[$field] !== null && $layer[$field] !== '') { + return $layer[$field]; + } + } + return null; + } + + /** + * @param list|null> $layers rule/type layers, most specific first, WITHOUT the global one + * @param array $globals the complete global layer (see CirculationPolicy::globalLayer()) + */ + public static function resolve(array $layers, array $globals, UserPolicy $user, string $mediaType = '*'): LoanPolicy + { + $all = $layers; + $all[] = $globals; + + $globalDuration = self::positiveInt($globals['duration_days'] ?? null, 30); + $globalMaxRequest = self::positiveInt($globals['max_request_days'] ?? null, 90); + $globalRenewals = self::nonNegativeInt($globals['max_renewals'] ?? null, 3); + + $duration = self::positiveInt(self::pick($all, 'duration_days'), $globalDuration); + $renewal = self::pick($all, 'renewal_days'); + + return new LoanPolicy( + user: $user, + mediaType: $mediaType, + loanable: (bool) (self::pick($all, 'loanable') ?? true), + durationDays: $duration, + maxRequestDays: self::positiveInt(self::pick($all, 'max_request_days'), $globalMaxRequest), + maxRenewals: self::nonNegativeInt(self::pick($all, 'max_renewals'), $globalRenewals), + renewalDays: self::positiveInt($renewal, $duration), + maxActiveLoans: $user->maxActiveLoans, + maxActiveForMedia: self::nonNegativeInt(self::pick($all, 'max_active_for_media'), 0), + dailyFine: self::nullableFloat(self::pick($all, 'daily_fine')), + graceDays: self::nullableInt(self::pick($all, 'grace_days')), + maxFinePerLoan: self::nullableFloat(self::pick($all, 'max_fine_per_loan')), + debtBlockThreshold: $user->debtBlockThreshold, + ); + } + + /** + * Patron-only limits: patron type first, then the globals. + * + * @param array|null $type patron type row (keys: id, code, name, max_active_loans, + * reservations_enabled, max_reservations, card_duration_days, + * debt_block_threshold) + * @param array $globals + */ + public static function resolveUser(?array $type, array $globals): UserPolicy + { + $layers = [$type, $globals]; + $enabled = self::pick($layers, 'reservations_enabled'); + + return new UserPolicy( + patronTypeId: isset($type['id']) ? (int) $type['id'] : null, + patronTypeCode: isset($type['code']) ? (string) $type['code'] : null, + patronTypeName: isset($type['name']) ? (string) $type['name'] : null, + maxActiveLoans: self::nonNegativeInt(self::pick($layers, 'max_active_loans'), 0), + reservationsEnabled: $enabled === null ? true : (bool) $enabled, + maxReservations: self::nonNegativeInt(self::pick($layers, 'max_reservations'), 0), + cardDurationDays: self::nullablePositiveInt(self::pick($layers, 'card_duration_days')), + debtBlockThreshold: self::nullableFloat(self::pick($layers, 'debt_block_threshold')), + ); + } + + private static function positiveInt(mixed $value, int $fallback): int + { + if ($value === null || !is_numeric($value)) { + return $fallback; + } + $int = (int) $value; + return $int >= 1 ? $int : $fallback; + } + + private static function nonNegativeInt(mixed $value, int $fallback): int + { + if ($value === null || !is_numeric($value)) { + return $fallback; + } + $int = (int) $value; + return $int >= 0 ? $int : $fallback; + } + + private static function nullableInt(mixed $value): ?int + { + return $value === null || !is_numeric($value) ? null : max(0, (int) $value); + } + + private static function nullablePositiveInt(mixed $value): ?int + { + return $value === null || !is_numeric($value) || (int) $value < 1 ? null : (int) $value; + } + + private static function nullableFloat(mixed $value): ?float + { + return $value === null || !is_numeric($value) ? null : (float) $value; + } +} diff --git a/app/Services/Circulation/UserPolicy.php b/app/Services/Circulation/UserPolicy.php new file mode 100644 index 000000000..d7e0c4184 --- /dev/null +++ b/app/Services/Circulation/UserPolicy.php @@ -0,0 +1,32 @@ + schema availability per connection (one information_schema read per request) */ + private static array $schemaReady = []; + + /** @var array|null */ + private ?array $globals = null; + /** @var array>|null */ + private ?array $types = null; + private ?int $defaultTypeId = null; + /** @var array>|null */ + private ?array $rules = null; + /** @var array */ + private array $userTypes = []; + /** @var array */ + private array $media = []; + + private SettingsRepository $settings; + + public function __construct(private mysqli $db, ?SettingsRepository $settings = null, private bool $configuration = false) + { + $this->settings = $settings ?? new SettingsRepository($db); + } + + /** + * The final due date of a loan or renewal that starts (or is extended + * from) $startYmd and lasts $days days. + * + * Every due date in the application is computed here. With a connection + * the closures calendar applies: a date that falls on a closed day moves + * forward to the next open day (plan §13, decision 7), including the + * closures of the copy's branch when $copiaId is known. A due date the + * staff or the reader typed goes through here with $days = 0, so it is + * moved by the same rule. Without closures (or without $db) the result is + * the plain calendar arithmetic of every release before 0.9.0. + */ + public static function dueDateFor(string $startYmd, int $days, ?mysqli $db = null, ?int $copiaId = null): string + { + $start = \DateTimeImmutable::createFromFormat('!Y-m-d', substr($startYmd, 0, 10)); + if ($start === false) { + $start = new \DateTimeImmutable($startYmd); + } + $due = $start->modify('+' . max(0, $days) . ' days')->format('Y-m-d'); + return $db === null ? $due : \App\Support\LibraryCalendar::dueDate($db, $due, $copiaId); + } + + /** Drop the per-request schema probe (tests that migrate a database mid-process). */ + public static function resetSchemaCache(): void + { + self::$schemaReady = []; + } + + /** Rules that depend only on the patron. */ + public function forUser(int $userId): UserPolicy + { + return PolicyResolver::resolveUser($this->userTypeLayer($this->patronTypeOf($userId)), $this->globalLayer()); + } + + /** Rules for $userId borrowing title $libroId (optionally the copy $copiaId). */ + public function forLoan(int $userId, int $libroId, ?int $copiaId = null): LoanPolicy + { + return $this->forTypeAndTitle($this->patronTypeOf($userId), $libroId, $copiaId); + } + + /** + * Rules for whoever views a title: the patron's when logged in, the + * default patron type's for an anonymous visitor (book page buttons). + */ + public function forViewer(?int $userId, int $libroId): LoanPolicy + { + return $userId !== null && $userId > 0 + ? $this->forLoan($userId, $libroId) + : $this->forTypeAndTitle($this->defaultTypeId(), $libroId, null); + } + + private function forTypeAndTitle(?int $typeId, int $libroId, ?int $copiaId): LoanPolicy + { + $user = PolicyResolver::resolveUser($this->userTypeLayer($typeId), $this->globalLayer()); + $media = $this->mediaOf($libroId); + $layers = $this->ruleLayers($typeId, $media); + // The single copy kept for consultation, or a title whose every copy + // still in circulation is a consultation copy, is not lent. + if ($copiaId !== null && $copiaId > 0 + ? $this->isConsultationCopy($copiaId) + : $this->onlyConsultationCopies($libroId)) { + array_unshift($layers, ['loanable' => false]); + } + return PolicyResolver::resolve($layers, $this->globalLayer(), $user, $media); + } + + /** + * Rules for an existing loan row ($userId overrides the borrower, e.g. on + * reassignment). Falls back to the global rules when the row is gone. + */ + public function forExistingLoan(int $loanId, ?int $userId = null): LoanPolicy + { + $stmt = $this->db->prepare('SELECT utente_id, libro_id, copia_id FROM prestiti WHERE id = ?'); + $stmt->bind_param('i', $loanId); + $stmt->execute(); + $result = $stmt->get_result(); + $row = $result ? $result->fetch_assoc() : null; + $stmt->close(); + if (!is_array($row)) { + return $this->defaults(); + } + return $this->forLoan( + $userId ?? (int) $row['utente_id'], + (int) $row['libro_id'], + $row['copia_id'] !== null ? (int) $row['copia_id'] : null + ); + } + + /** Rules for the patron and title of an existing reservation (globals when it is gone). */ + public function forReservation(int $reservationId): LoanPolicy + { + $stmt = $this->db->prepare('SELECT utente_id, libro_id FROM prenotazioni WHERE id = ?'); + $stmt->bind_param('i', $reservationId); + $stmt->execute(); + $result = $stmt->get_result(); + $row = $result ? $result->fetch_assoc() : null; + $stmt->close(); + if (!is_array($row)) { + return $this->defaults(); + } + return $this->forLoan((int) $row['utente_id'], (int) $row['libro_id']); + } + + /** The rules of the default patron type, for contexts with no patron (admin defaults, dashboards). */ + public function defaults(): LoanPolicy + { + $typeId = $this->defaultTypeId(); + $globals = $this->globalLayer(); + return PolicyResolver::resolve( + $this->ruleLayers($typeId, self::ALL_MEDIA), + $globals, + PolicyResolver::resolveUser($this->userTypeLayer($typeId), $globals), + self::ALL_MEDIA + ); + } + + /** + * Rules of a patron type for a material, without a patron (admin matrix, + * effective-rules summaries). $typeId null = the default type. + */ + public function forType(?int $typeId, string $media = self::ALL_MEDIA): LoanPolicy + { + $typeId ??= $this->defaultTypeId(); + $globals = $this->globalLayer(); + return PolicyResolver::resolve( + $this->ruleLayers($typeId, $media), + $globals, + PolicyResolver::resolveUser($this->userTypeLayer($typeId), $globals), + $media + ); + } + + /** + * The global layer, read from the loans settings with the historical + * fallbacks (30-day duration, 90-day request cap, 3 renewals, no limit). + * + * @return array + */ + public function globalLayer(): array + { + if ($this->globals === null) { + $rawRenewals = $this->settings->get('loans', 'max_renewals', '3'); + $renewals = is_numeric($rawRenewals) ? (int) $rawRenewals : 3; + $duration = (int) ($this->settings->get('loans', 'loan_duration_days', '30') ?? 30); + $maxRequest = (int) ($this->settings->get('loans', 'max_loan_duration_days', '90') ?? 90); + $this->globals = [ + 'loanable' => true, + 'duration_days' => $duration >= 1 ? $duration : 30, + 'max_request_days' => $maxRequest >= 1 ? $maxRequest : 90, + 'max_renewals' => $renewals >= 0 ? $renewals : 3, + 'renewal_days' => null, + 'max_active_for_media' => null, + 'max_active_loans' => max(0, (int) ($this->settings->get('loans', 'max_active_loans_per_user', '0') ?? 0)), + 'reservations_enabled' => true, + 'max_reservations' => 0, + 'card_duration_days' => null, + // Fines (Settings → Loans → Fines). Amounts are normalised to + // DECIMAL strings; empty or malformed = null (no fine, no cap, + // no debt block), exactly the pre-0.9.0 behaviour. + 'debt_block_threshold' => self::moneyOrNull($this->settings->get('loans', 'debt_block_threshold', '')), + 'daily_fine' => self::moneyOrNull($this->settings->get('loans', 'daily_fine', '')), + 'grace_days' => self::graceOrNull($this->settings->get('loans', 'fine_grace_days', '')), + 'max_fine_per_loan' => self::moneyOrNull($this->settings->get('loans', 'fine_max_per_loan', '')), + ]; + } + return $this->globals; + } + + private ?string $graceMode = null; + + /** Settings → Loans → Fines: 'threshold' (the default) or 'deduct'; read once per instance. */ + public function fineGraceMode(): string + { + return $this->graceMode ??= ($this->settings->get('loans', 'fine_grace_mode', 'threshold') === 'deduct' ? 'deduct' : 'threshold'); + } + + /** An operator-typed amount ("2", "2,50") as a DECIMAL string, null when empty or malformed. */ + private static function moneyOrNull(?string $raw): ?string + { + $cents = FineParameters::toCents((string) $raw); + return $cents === null ? null : FineParameters::centsToDecimal($cents); + } + + /** Global grace days (0 … 365); null when unset or 0, which is the same rule: no grace. */ + private static function graceOrNull(?string $raw): ?int + { + $days = max(0, min(365, (int) ($raw ?? 0))); + return $days > 0 ? $days : null; + } + + // ─── Counting and refusals ────────────────────────────────────────────── + + /** + * Open loans of $userId that count against the active-loan limit. + * Callers that enforce a limit hold the user row lock first. + */ + public function countActiveLoans(int $userId, ?int $excludeLoanId = null): int + { + $sql = 'SELECT COUNT(*) FROM prestiti WHERE utente_id = ? AND attivo = 1 AND stato IN (' . self::ACTIVE_LOAN_STATES_SQL . ')'; + if ($excludeLoanId !== null) { + $sql .= ' AND id <> ?'; + } + $stmt = $this->db->prepare($sql); + if ($excludeLoanId !== null) { + $stmt->bind_param('ii', $userId, $excludeLoanId); + } else { + $stmt->bind_param('i', $userId); + } + $stmt->execute(); + $result = $stmt->get_result(); + $count = (int) ($result ? ($result->fetch_row()[0] ?? 0) : 0); + $stmt->close(); + return $count; + } + + /** Open loans of $userId on titles of material $media (same states as countActiveLoans()). */ + public function countActiveLoansForMedia(int $userId, string $media, ?int $excludeLoanId = null): int + { + // CI-SOFT-DELETE-EXEMPT: a loan on a title archived after checkout is still out and still counts against the limit. + $sql = 'SELECT COUNT(*) FROM prestiti p JOIN libri l ON l.id = p.libro_id + WHERE p.utente_id = ? AND p.attivo = 1 AND p.stato IN (' . self::ACTIVE_LOAN_STATES_SQL . ') + AND l.tipo_media = ?'; + if ($excludeLoanId !== null) { + $sql .= ' AND p.id <> ?'; + } + $stmt = $this->db->prepare($sql); + if ($excludeLoanId !== null) { + $stmt->bind_param('isi', $userId, $media, $excludeLoanId); + } else { + $stmt->bind_param('is', $userId, $media); + } + $stmt->execute(); + $result = $stmt->get_result(); + $count = (int) ($result ? ($result->fetch_row()[0] ?? 0) : 0); + $stmt->close(); + return $count; + } + + /** + * The quantity/loanability refusal for a new open loan, or null. + * + * Codes: 'not_loanable', 'max_loans_reached', 'media_limit_reached'. + * $excludeLoanId leaves out the loan being approved/reassigned. Both + * limits apply: the total one and the one for the material. + */ + public function loanLimitError(LoanPolicy $policy, int $userId, ?int $excludeLoanId = null): ?string + { + if (!$policy->loanable) { + return 'not_loanable'; + } + if ($policy->maxActiveLoans > 0 && $this->countActiveLoans($userId, $excludeLoanId) >= $policy->maxActiveLoans) { + return 'max_loans_reached'; + } + if ($policy->maxActiveForMedia > 0 && $policy->mediaType !== self::ALL_MEDIA + && $this->countActiveLoansForMedia($userId, $policy->mediaType, $excludeLoanId) >= $policy->maxActiveForMedia) { + return 'media_limit_reached'; + } + return null; + } + + /** + * Patrons holding an active reservation on $libroId whom the per-material + * rules refuse right now: the title's material is not loanable for their + * patron type, or they already hold the limit for that material. Queue + * promotion (ReservationManager) and capacity occupancy (CapacityService) + * both leave their promotable reservations aside, the same pause the + * total-limit SQL predicate applies, so a refused head of the queue does + * not block the patrons behind it. Empty without rules-matrix rows (a + * zero-config install never pauses anyone here). + * + * @return list + */ + public function materialRefusedReservationUserIds(int $libroId): array + { + if ($this->rules() === []) { + return []; + } + $media = $this->mediaOf($libroId); + $stmt = $this->db->prepare("SELECT DISTINCT utente_id FROM prenotazioni WHERE libro_id = ? AND stato = 'attiva'"); + $stmt->bind_param('i', $libroId); + $stmt->execute(); + $result = $stmt->get_result(); + $userIds = []; + while ($result && ($row = $result->fetch_row())) { + $userIds[] = (int) $row[0]; + } + $stmt->close(); + + $refused = []; + foreach ($userIds as $userId) { + $policy = $this->forType($this->patronTypeOf($userId), $media); + if (!$policy->loanable + || ($policy->maxActiveForMedia > 0 && $media !== self::ALL_MEDIA + && $this->countActiveLoansForMedia($userId, $media) >= $policy->maxActiveForMedia)) { + $refused[] = $userId; + } + } + return $refused; + } + + /** + * The debt-block refusal for $userId, or null (plan §7, decision 6). + * + * The threshold is the patron type's (soglia_blocco_debito), else the + * global loans.debt_block_threshold; NULL/0 = no block. Every channel + * reaches it through LoanEligibility::checkUser(), and its SQL twin is + * LoanEligibility::debtBlockSql() (the queue selection). + */ + public function debtBlockError(int $userId): ?string + { + // No ledger table (code deployed before its migration) or fines off: no + // balance, no block. A failed probe throws: an error must not admit the patron. + if (!PatronLedger::activeOrFail($this->db) || !\App\Support\Features::enabled('debt_block')) { + return null; + } + $threshold = FineParameters::moneyToCents($this->forUser($userId)->debtBlockThreshold); + if ($threshold === null || $threshold <= 0) { + return null; + } + // debtBalanceCents(): legacy penalties the upgrade did not convert yet + // count too, until the backfill completes. + return PatronLedger::forDb($this->db)->debtBalanceCents($userId) > $threshold ? 'debt_block' : null; + } + + /** + * Active queue reservations plus loan requests still awaiting approval: + * what the reservation limit counts (plan decision 4). + */ + public function countReservations(int $userId, ?int $excludeReservationId = null): int + { + $sql = "SELECT + (SELECT COUNT(*) FROM prenotazioni WHERE utente_id = ? AND stato = 'attiva'" . ($excludeReservationId !== null ? ' AND id <> ?' : '') . ") + + (SELECT COUNT(*) FROM prestiti WHERE utente_id = ? AND attivo = 0 AND stato = 'pendente')"; + $stmt = $this->db->prepare($sql); + if ($excludeReservationId !== null) { + $stmt->bind_param('iii', $userId, $excludeReservationId, $userId); + } else { + $stmt->bind_param('ii', $userId, $userId); + } + $stmt->execute(); + $result = $stmt->get_result(); + $count = (int) ($result ? ($result->fetch_row()[0] ?? 0) : 0); + $stmt->close(); + return $count; + } + + /** + * Refusal for a new queue reservation ($queue = true) or reader loan + * request ($queue = false), or null. Codes: 'reservations_disabled' + * (queue reservations only), 'reservation_limit_reached'. + */ + public function reservationLimitError(UserPolicy $user, int $userId, bool $queue = true): ?string + { + if ($queue && !$user->reservationsEnabled) { + return 'reservations_disabled'; + } + if ($user->maxReservations > 0 && $this->countReservations($userId) >= $user->maxReservations) { + return 'reservation_limit_reached'; + } + return null; + } + + /** + * SQL expression evaluating to the total active-loan limit of the patron + * row aliased $userAlias (utenti); 0 = no limit. No placeholders: the ids + * and the global value are integers rendered by the application. + * Queue selection uses it to skip patrons already at their limit. + */ + public function totalLoanLimitSql(string $userAlias): string + { + $global = (int) $this->globalLayer()['max_active_loans']; + if (!$this->rulesActive()) { + return (string) $global; + } + $default = $this->defaultTypeId() ?? 0; + return "COALESCE((SELECT tl.max_prestiti FROM tipologie_lettore tl WHERE tl.id = COALESCE({$userAlias}.tipologia_id, {$default})), {$global})"; + } + + /** Whether any patron can be subject to a total active-loan limit. */ + public function anyTotalLoanLimit(): bool + { + if ((int) $this->globalLayer()['max_active_loans'] > 0) { + return true; + } + foreach ($this->types() as $type) { + if ($type['max_prestiti'] !== null && (int) $type['max_prestiti'] > 0) { + return true; + } + } + return false; + } + + // ─── Patron types and matrix ──────────────────────────────────────────── + + /** + * Patron types ordered for display. + * + * @return array> + */ + public function types(): array + { + if ($this->types === null) { + $this->types = []; + if ($this->rulesActive()) { + $result = $this->db->query('SELECT * FROM tipologie_lettore ORDER BY ordinamento ASC, nome ASC, id ASC'); + if ($result instanceof \mysqli_result) { + while ($row = $result->fetch_assoc()) { + $this->types[(int) $row['id']] = $row; + if ((int) $row['predefinita'] === 1 && $this->defaultTypeId === null) { + $this->defaultTypeId = (int) $row['id']; + } + } + $result->free(); + } + } + } + return $this->types; + } + + /** The patron type assigned to users without one (null when none exists). */ + public function defaultTypeId(): ?int + { + $this->types(); + return $this->defaultTypeId; + } + + /** + * Matrix rules keyed "typeId|media". + * + * @return array> + */ + public function rules(): array + { + if ($this->rules === null) { + $this->rules = []; + if ($this->rulesActive()) { + $result = $this->db->query('SELECT * FROM regole_prestito'); + if ($result instanceof \mysqli_result) { + while ($row = $result->fetch_assoc()) { + $this->rules[(int) $row['tipologia_id'] . '|' . (string) $row['tipo_media']] = $row; + } + $result->free(); + } + } + } + return $this->rules; + } + + /** The patron type governing $userId: theirs, else the default one. */ + public function patronTypeOf(int $userId): ?int + { + if (!$this->rulesActive()) { + return null; + } + if (!array_key_exists($userId, $this->userTypes)) { + $stmt = $this->db->prepare('SELECT tipologia_id FROM utenti WHERE id = ?'); + $stmt->bind_param('i', $userId); + $stmt->execute(); + $result = $stmt->get_result(); + $row = $result ? $result->fetch_assoc() : null; + $stmt->close(); + $own = is_array($row) && $row['tipologia_id'] !== null ? (int) $row['tipologia_id'] : null; + $this->userTypes[$userId] = ($own !== null && isset($this->types()[$own])) ? $own : $this->defaultTypeId(); + } + return $this->userTypes[$userId]; + } + + /** + * The layers of $typeId for $media, most specific first (see the class + * docblock), as PolicyResolver field arrays. + * + * @return list|null> + */ + public function ruleLayers(?int $typeId, string $media): array + { + $rules = $this->rules(); + $layers = []; + if ($typeId !== null) { + if ($media !== self::ALL_MEDIA) { + $layers[] = self::ruleLayer($rules[$typeId . '|' . $media] ?? null, true); + } + $layers[] = self::ruleLayer($rules[$typeId . '|' . self::ALL_MEDIA] ?? null, false); + } + if ($media !== self::ALL_MEDIA) { + $layers[] = self::ruleLayer($rules[self::ALL_TYPES . '|' . $media] ?? null, true); + } + $layers[] = $typeId !== null ? self::typeLayer($this->types()[$typeId] ?? null) : null; + return $layers; + } + + /** + * @param array|null $rule + * @return array|null + */ + private static function ruleLayer(?array $rule, bool $concreteMedia): ?array + { + if ($rule === null) { + return null; + } + return [ + 'loanable' => $rule['prestabile'], + 'duration_days' => $rule['durata_prestito_giorni'], + 'max_renewals' => $rule['max_rinnovi'], + 'renewal_days' => $rule['giorni_rinnovo'], + // The per-material limit exists only for a concrete material. + 'max_active_for_media' => $concreteMedia ? $rule['max_prestiti'] : null, + 'daily_fine' => $rule['multa_giornaliera'], + 'grace_days' => $rule['giorni_tolleranza'], + 'max_fine_per_loan' => $rule['multa_massima_prestito'], + ]; + } + + /** + * @param array|null $type + * @return array|null + */ + private static function typeLayer(?array $type): ?array + { + if ($type === null) { + return null; + } + return [ + 'duration_days' => $type['durata_prestito_giorni'], + 'max_request_days' => $type['max_durata_richiesta_giorni'], + 'max_renewals' => $type['max_rinnovi'], + 'renewal_days' => $type['giorni_rinnovo'], + 'daily_fine' => $type['multa_giornaliera'], + 'grace_days' => $type['giorni_tolleranza'], + 'max_fine_per_loan' => $type['multa_massima_prestito'], + ]; + } + + /** @return array|null */ + private function userTypeLayer(?int $typeId): ?array + { + $type = $typeId !== null ? ($this->types()[$typeId] ?? null) : null; + if ($type === null) { + return null; + } + return [ + 'id' => (int) $type['id'], + 'code' => (string) $type['codice'], + 'name' => (string) $type['nome'], + 'max_active_loans' => $type['max_prestiti'], + 'reservations_enabled' => $type['prenotazioni_abilitate'], + 'max_reservations' => $type['max_prenotazioni'], + 'card_duration_days' => $type['durata_tessera_giorni'], + 'debt_block_threshold' => $type['soglia_blocco_debito'], + ]; + } + + /** libri.tipo_media of $libroId ('*' when the title is unknown). */ + public function mediaOf(int $libroId): string + { + if (!array_key_exists($libroId, $this->media)) { + // CI-SOFT-DELETE-EXEMPT: the rules of a running loan stay those of its material after the title is archived. + $stmt = $this->db->prepare('SELECT tipo_media FROM libri WHERE id = ?'); + $stmt->bind_param('i', $libroId); + $stmt->execute(); + $result = $stmt->get_result(); + $row = $result ? $result->fetch_assoc() : null; + $stmt->close(); + $this->media[$libroId] = is_array($row) && is_string($row['tipo_media']) && $row['tipo_media'] !== '' + ? $row['tipo_media'] + : self::ALL_MEDIA; + } + return $this->media[$libroId]; + } + + /** Whether the copy is kept for in-house consultation only. */ + public function isConsultationCopy(int $copiaId): bool + { + if (!\App\Support\Features::enabled('consultation_only') || !$this->schemaReady()) { + return false; + } + $stmt = $this->db->prepare('SELECT solo_consultazione FROM copie WHERE id = ?'); + $stmt->bind_param('i', $copiaId); + $stmt->execute(); + $result = $stmt->get_result(); + $row = $result ? $result->fetch_assoc() : null; + $stmt->close(); + return is_array($row) && (int) $row['solo_consultazione'] === 1; + } + + /** + * Whether $libroId has consultation copies and no other copy in + * circulation (copies lost, damaged or in repair do not count). + */ + public function onlyConsultationCopies(int $libroId): bool + { + if (!\App\Support\Features::enabled('consultation_only') || !$this->schemaReady()) { + return false; + } + $stmt = $this->db->prepare( + "SELECT COALESCE(SUM(solo_consultazione = 1), 0) AS consultation, + COALESCE(SUM(solo_consultazione = 0 AND stato NOT IN ('perso', 'danneggiato', 'manutenzione', 'in_restauro', 'in_trasferimento')), 0) AS lendable + FROM copie WHERE libro_id = ?" + ); + $stmt->bind_param('i', $libroId); + $stmt->execute(); + $result = $stmt->get_result(); + $row = $result ? $result->fetch_assoc() : null; + $stmt->close(); + return is_array($row) && (int) $row['consultation'] > 0 && (int) $row['lendable'] === 0; + } + + /** + * SQL predicate (no placeholders) excluding consultation-only copies from + * lendable capacity and from copy allocation. $alias is the copie alias + * (empty string for an unaliased table). Always true before the 0.9.0 + * migration has run. + */ + public function lendableCopySql(string $alias = 'c'): string + { + if (!\App\Support\Features::enabled('consultation_only') || !$this->schemaReady()) { + return '1 = 1'; + } + return ($alias !== '' ? $alias . '.' : '') . 'solo_consultazione = 0'; + } + + /** + * Whether patron types and the rules matrix + * apply: their tables exist AND the module is on (Settings -> Loans, + * features.circulation_rules; also off in catalogue mode). Off, every + * rule is the global layer, exactly as before 0.9.0, on every channel + * at once (web, Mobile API, NCIP, maintenance), since they all resolve + * through this class. Consultation uses its own switch. The data stays: switching back on restores it. + */ + public function rulesActive(): bool + { + return ($this->configuration || \App\Support\Features::enabled('circulation_rules')) && $this->schemaReady(); + } + + /** + * Whether the 0.9.0 circulation tables and columns exist. Code ships + * before its migration runs during an upgrade: until then every rule is + * the global one, exactly as before. + */ + public function schemaReady(): bool + { + $key = (string) spl_object_id($this->db); + if (!array_key_exists($key, self::$schemaReady)) { + $ready = false; + try { + $result = $this->db->query( + "SELECT + (SELECT COUNT(*) FROM information_schema.TABLES + WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME IN ('tipologie_lettore', 'regole_prestito')) + + (SELECT COUNT(*) FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = DATABASE() + AND ((TABLE_NAME = 'utenti' AND COLUMN_NAME = 'tipologia_id') + OR (TABLE_NAME = 'copie' AND COLUMN_NAME = 'solo_consultazione')))" + ); + $ready = $result instanceof \mysqli_result && (int) ($result->fetch_row()[0] ?? 0) === 4; + } catch (\Throwable) { + $ready = false; + } + self::$schemaReady[$key] = $ready; + } + return self::$schemaReady[$key]; + } +} diff --git a/app/Services/FineCalculator.php b/app/Services/FineCalculator.php new file mode 100644 index 000000000..e3e814cbd --- /dev/null +++ b/app/Services/FineCalculator.php @@ -0,0 +1,96 @@ +isOpenDay = $isOpenDay; + } + + /** Open days the item was kept after $dueDate, up to $returnDate ($branchId = the copy's branch). */ + public function daysLate(string $dueDate, string $returnDate, ?int $branchId = null): int + { + $due = self::parse($dueDate); + $returned = self::parse($returnDate); + if ($due === null || $returned === null || $returned <= $due) { + return 0; + } + + // The bound applies on both branches, so a calendar never changes the + // count of a loan a decade overdue (every day open = plain span). + $span = min((int) $due->diff($returned)->days, self::MAX_DAYS); + if ($this->isOpenDay === null) { + return $span; + } + + $open = 0; + $day = $due; + for ($i = 0; $i < $span; $i++) { + $day = $day->modify('+1 day'); + if (($this->isOpenDay)($day->format('Y-m-d'), $branchId)) { + $open++; + } + } + return $open; + } + + /** Late fine in cents for a loan due on $dueDate and returned on $returnDate. */ + public function lateFineCents(FineParameters $params, string $dueDate, string $returnDate, ?int $branchId = null): int + { + if (!$params->finesEnabled()) { + return 0; + } + $days = $this->daysLate($dueDate, $returnDate, $branchId); + if ($days <= $params->graceDays) { + return 0; + } + $chargedDays = $params->graceMode === 'deduct' ? $days - $params->graceDays : $days; + $amount = $chargedDays * $params->dailyFineCents; + if ($params->maxFinePerLoanCents !== null) { + $amount = min($amount, $params->maxFinePerLoanCents); + } + // Never more than the ledger can store (DECIMAL(10,2)): the preview, + // the accruing figure and the charge at return all read this bound. + return min($amount, PatronLedger::MAX_AMOUNT_CENTS); + } + + private static function parse(string $date): ?\DateTimeImmutable + { + if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $date) !== 1) { + return null; + } + $parsed = \DateTimeImmutable::createFromFormat('!Y-m-d', $date, new \DateTimeZone('UTC')); + return $parsed instanceof \DateTimeImmutable && $parsed->format('Y-m-d') === $date ? $parsed : null; + } +} diff --git a/app/Services/FineParameters.php b/app/Services/FineParameters.php new file mode 100644 index 000000000..a36aa3d92 --- /dev/null +++ b/app/Services/FineParameters.php @@ -0,0 +1,187 @@ +forExistingLoan($loanId), graceMode: $policy->fineGraceMode()); + } + + /** + * Parameters of a patron regardless of the material: the patron type's + * fine fields and debt threshold over the globals (reader record, app). + */ + public static function forUser(mysqli $db, int $userId, ?CirculationPolicy $policy = null): self + { + $policy ??= new CirculationPolicy($db); + return self::fromPolicy($policy->forType($policy->patronTypeOf($userId)), graceMode: $policy->fineGraceMode()); + } + + /** The global settings only (no patron type, no rule). */ + public static function global(mysqli $db): self + { + $policy = new CirculationPolicy($db); + $globals = $policy->globalLayer(); + return self::fromPolicy(PolicyResolver::resolve([], $globals, PolicyResolver::resolveUser(null, $globals)), graceMode: $policy->fineGraceMode()); + } + + /** Cents view of a resolved circulation policy. */ + public static function fromPolicy(LoanPolicy $policy, ?string $currency = null, string $graceMode = 'threshold'): self + { + $daily = self::moneyToCents($policy->dailyFine) ?? 0; + $cap = self::moneyToCents($policy->maxFinePerLoan); + $threshold = self::moneyToCents($policy->debtBlockThreshold); + + return new self( + max(0, $daily), + max(0, min(365, (int) ($policy->graceDays ?? 0))), + $cap !== null && $cap > 0 ? $cap : null, + $threshold !== null && $threshold > 0 ? $threshold : null, + $currency ?? self::currency(), + $graceMode === 'deduct' ? 'deduct' : 'threshold', + ); + } + + /** A resolved amount (float from PolicyResolver) to cents; null stays null. */ + public static function moneyToCents(?float $amount): ?int + { + return $amount === null ? null : (int) round($amount * 100); + } + + /** + * Build from raw setting strings (unit-testable, no database). + * + * @param array $settings + */ + public static function fromSettings(array $settings, string $currency = 'EUR'): self + { + $daily = self::toCents($settings[self::SETTING_DAILY_FINE] ?? '') ?? 0; + $grace = max(0, min(365, (int) ($settings[self::SETTING_GRACE_DAYS] ?? 0))); + $cap = self::toCents($settings[self::SETTING_MAX_PER_LOAN] ?? ''); + $threshold = self::toCents($settings[self::SETTING_DEBT_THRESHOLD] ?? ''); + + return new self( + max(0, $daily), + $grace, + $cap !== null && $cap > 0 ? $cap : null, + $threshold !== null && $threshold > 0 ? $threshold : null, + $currency, + ($settings['fine_grace_mode'] ?? '') === 'deduct' ? 'deduct' : 'threshold', + ); + } + + public function finesEnabled(): bool + { + return $this->dailyFineCents > 0; + } + + public function debtBlockEnabled(): bool + { + return $this->debtBlockThresholdCents !== null; + } + + /** ISO code configured for the library (app.currency, default EUR). */ + public static function currency(): string + { + $code = strtoupper(trim((string) \App\Support\ConfigStore::get('app.currency', 'EUR'))); + return preg_match('/^[A-Z]{3}$/', $code) === 1 ? $code : 'EUR'; + } + + /** Whether an operator-typed amount parses but is above MAX_SETTING_CENTS. */ + public static function exceedsSettingMax(string $raw): bool + { + $cents = self::toCents($raw); + return $cents !== null && $cents > self::MAX_SETTING_CENTS; + } + + /** + * Parse an operator-typed amount ("2", "2.5", "2,50") into cents. + * Returns null for an empty or malformed value (never a negative amount). + */ + public static function toCents(string $raw): ?int + { + $value = str_replace(',', '.', trim($raw)); + if ($value === '' || preg_match('/^\d{1,8}(?:\.\d{1,2})?$/D', $value) !== 1) { + return null; + } + [$units, $decimals] = array_pad(explode('.', $value, 2), 2, ''); + return ((int) $units) * 100 + (int) str_pad($decimals, 2, '0'); + } + + /** Cents to the DECIMAL(10,2) string the ledger stores. */ + public static function centsToDecimal(int $cents): string + { + $sign = $cents < 0 ? '-' : ''; + $abs = abs($cents); + return $sign . intdiv($abs, 100) . '.' . str_pad((string) ($abs % 100), 2, '0', STR_PAD_LEFT); + } + + /** DECIMAL string ("-12.50") to cents. */ + public static function decimalToCents(string|int|float|null $value): int + { + if ($value === null || $value === '') { + return 0; + } + return (int) round(((float) $value) * 100); + } + + /** Human amount with the configured currency, in the given locale. */ + public static function format(int $cents, ?string $locale = null, ?string $currency = null): string + { + $currency ??= self::currency(); + $lang = substr($locale ?? \App\Support\I18n::getLocale(), 0, 2); + $decimalSep = in_array($lang, ['it', 'de', 'fr', 'da'], true) ? ',' : '.'; + $thousandSep = $decimalSep === ',' ? '.' : ','; + $number = number_format(abs($cents) / 100, 2, $decimalSep, $thousandSep); + $symbol = $currency === 'EUR' ? '€' : $currency; + return ($cents < 0 ? '-' : '') . ($currency === 'EUR' ? $number . ' ' . $symbol : $symbol . ' ' . $number); + } +} diff --git a/app/Services/LoanRequestGate.php b/app/Services/LoanRequestGate.php index d7cce1cdf..db7d340fc 100644 --- a/app/Services/LoanRequestGate.php +++ b/app/Services/LoanRequestGate.php @@ -163,6 +163,7 @@ private function findAssignableInLibraryCopyThrough(int $bookId, string $endDate FROM copie c WHERE c.libro_id = ? AND c.stato IN ('disponibile', 'prenotato') + AND " . (new \App\Services\CirculationPolicy($this->db))->lendableCopySql('c') . " AND NOT EXISTS ( SELECT 1 FROM prestiti p diff --git a/app/Services/PatronLedger.php b/app/Services/PatronLedger.php new file mode 100644 index 000000000..9e9a90ff2 --- /dev/null +++ b/app/Services/PatronLedger.php @@ -0,0 +1,933 @@ + late fine written when the loan is returned + * penalty: amount assessed when the copy is closed lost/damaged + * (the same key the 0.9.0 migration uses for prestiti.sanzione) + * form:<32 hex> one submitted desk form (payment, charge, waiver): a + * repeated POST writes nothing more; the same key with + * other values is refused (duplicate_request) + * storno:late:: / storno:waiver:: + * the n-th undo of a return: the late fine and the + * waivers written for it, reversed + * + * Methods that write take part in the caller's transaction when one is open + * (the return paths); otherwise they open and commit their own. + */ +final class PatronLedger +{ + public const TYPES = ['multa_ritardo', 'addebito_perdita', 'addebito_danno', 'addebito_manuale', 'pagamento', 'condono', 'storno']; + public const CHARGE_TYPES = ['multa_ritardo', 'addebito_perdita', 'addebito_danno', 'addebito_manuale']; + public const PAYMENT_METHODS = ['contanti', 'pos', 'bonifico', 'altro']; + + /** Largest single amount an operator may type (DECIMAL(10,2) headroom). */ + public const MAX_AMOUNT_CENTS = 99999999; + + /** + * One probe per live connection object. A WeakMap, not spl_object_id(): + * an id is reused once its connection is destroyed, so a later connection + * of a long-lived worker could inherit a "false" probed before the + * migration and leave the debt block off. + * + * @var \WeakMap|null + */ + private static ?\WeakMap $schemaReady = null; + + private mysqli $db; + private FineCalculator $calculator; + + public function __construct(mysqli $db, ?FineCalculator $calculator = null) + { + $this->db = $db; + $this->calculator = $calculator ?? new FineCalculator(); + } + + /** + * The ledger every application path uses (return paths, reader record, + * notifications, reports, Mobile API). Building it here is the single + * place where the late-fine day count gets its "is the library open on + * this day" rule, so closed days are honoured on every channel at once. + */ + public static function forDb(mysqli $db): self + { + // Closed days (weekly closed days, closures, yearly closures, closures + // of the copy's branch) are not late days (plan §13, decision 5). With + // no calendar configured every day counts, exactly as before. + $calendar = LibraryCalendar::forDb($db); + if (!$calendar->hasRules()) { + return new self($db, new FineCalculator()); + } + return new self($db, new FineCalculator( + static fn (string $ymd, ?int $branchId = null): bool => $calendar->isOpen($ymd, $branchId) + )); + } + + /** + * Whether movimenti_lettore exists. Code ships before its migration runs + * during an upgrade: until then there is no balance, so nothing to show + * and nothing for the maintenance backfill to do. + * + * A probe that fails (connection lost, information_schema unreadable) + * also answers false here: the display paths (reader record, reports, + * notifications) then show no balance instead of a 500. The decisions + * that must not drop a rule on an error use schemaReadyOrFail(). + * + * Only a positive answer is cached: a long-running process (cron) that + * probed before the migration sees the table as soon as it exists. + */ + public static function schemaReady(mysqli $db): bool + { + try { + return self::probeSchema($db); + } catch (\Throwable $e) { + \App\Support\SecureLogger::warning('Ledger schema probe failed', ['error' => $e->getMessage()]); + return false; + } + } + + /** + * schemaReady() for the debt block and the deletion guard: false only + * when the table is really absent (upgrade window). A failed probe + * throws, so a database error refuses the loan or the deletion instead + * of reading as "no ledger" and admitting a patron over the threshold. + */ + public static function schemaReadyOrFail(mysqli $db): bool + { + return self::probeSchema($db); + } + + private static function probeSchema(mysqli $db): bool + { + self::$schemaReady ??= new \WeakMap(); + if (isset(self::$schemaReady[$db])) { + return true; + } + $result = $db->query("SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'movimenti_lettore'"); + if (!$result instanceof \mysqli_result) { + throw new \RuntimeException('Ledger schema probe failed: ' . $db->error); + } + $ready = (int) ($result->fetch_row()[0] ?? 0) === 1; + if ($ready) { + self::$schemaReady[$db] = true; + } + return $ready; + } + + /** + * Whether fines and the ledger apply: the table exists AND the fines + * module is on (Settings -> Loans, features.fines; also off in catalogue + * mode). Off, nothing is charged, nobody is blocked for debt and the + * movements are not shown; the rows already written stay, and + * prestiti.sanzione keeps being written as before 0.9.0. + */ + public static function active(mysqli $db): bool + { + return \App\Support\Features::enabled('fines') && self::schemaReady($db); + } + + /** + * active() for the paths that admit or refuse a patron: a failed schema + * probe throws (schemaReadyOrFail()) instead of switching the debt block off. + */ + public static function activeOrFail(mysqli $db): bool + { + return \App\Support\Features::enabled('fines') && self::schemaReadyOrFail($db); + } + + /** Drop the per-request schema probe (tests that migrate a database mid-process). */ + public static function resetSchemaCache(): void + { + self::$schemaReady = null; + } + + /** SQL expression for a patron's balance; $userIdExpr is a fixed column reference. */ + public static function balanceSql(string $userIdExpr): string + { + return "(SELECT COALESCE(SUM(ml.importo), 0) FROM movimenti_lettore ml WHERE ml.utente_id = {$userIdExpr})"; + } + + /** + * Whether the legacy penalty conversion has not completed yet + * (loans.legacy_penalties_backfilled unset) while loss penalties are + * on: the upgrade's pass was deferred or failed and maintenance has not + * finished it. + */ + public static function legacyPenaltiesPending(mysqli $db): bool + { + // With loss penalties off the library does not mirror prestiti.sanzione + // in the ledger (the backfill never runs), so nothing is pending: the + // debt block reads the ledger alone, the balance staff and patrons see. + if (!\App\Support\Features::enabled('loss_penalties')) { + return false; + } + return (new \App\Models\SettingsRepository($db))->get('loans', 'legacy_penalties_backfilled', '0') !== '1'; + } + + /** + * SQL expression for the prestiti.sanzione amounts of $userIdExpr that + * have no penalty: ledger row yet: exactly what the backfill will + * charge, so adding it gives the balance the patron will have once the + * conversion completes. $userIdExpr is a fixed column reference. + */ + public static function pendingLegacyPenaltiesSql(string $userIdExpr): string + { + return "(SELECT COALESCE(SUM(lp.sanzione), 0) FROM prestiti lp WHERE lp.utente_id = {$userIdExpr} AND lp.sanzione > 0" + . " AND NOT EXISTS (SELECT 1 FROM movimenti_lettore lm WHERE lm.chiave_idempotenza = CONCAT('penalty:', lp.id)))"; + } + + /** + * The balance the debt block compares with the threshold: the ledger, + * plus the legacy penalties not converted yet while the backfill is + * pending, so a deferred or failed conversion does not reopen loans to a + * patron over the threshold until maintenance finishes it. Once the + * marker is set this is balanceSql() and costs nothing more. + */ + public static function debtBalanceSql(string $userIdExpr, mysqli $db): string + { + if (!self::legacyPenaltiesPending($db)) { + return self::balanceSql($userIdExpr); + } + return '(' . self::balanceSql($userIdExpr) . ' + ' . self::pendingLegacyPenaltiesSql($userIdExpr) . ')'; + } + + /** PHP twin of debtBalanceSql() for one patron. */ + public function debtBalanceCents(int $userId): int + { + if (!self::legacyPenaltiesPending($this->db)) { + return $this->balanceCents($userId); + } + $stmt = $this->db->prepare('SELECT ' . self::debtBalanceSql('?', $this->db)); + $stmt->bind_param('ii', $userId, $userId); + $stmt->execute(); + $value = $stmt->get_result()->fetch_row()[0] ?? 0; + $stmt->close(); + return FineParameters::decimalToCents($value); + } + + public function balanceCents(int $userId): int + { + if (!self::schemaReady($this->db)) { + return 0; + } + $stmt = $this->db->prepare('SELECT COALESCE(SUM(importo), 0) FROM movimenti_lettore WHERE utente_id = ?'); + $stmt->bind_param('i', $userId); + $stmt->execute(); + $value = $stmt->get_result()->fetch_row()[0] ?? 0; + $stmt->close(); + return FineParameters::decimalToCents($value); + } + + /** Balance right after movement $movementId (receipts show the balance at that moment). */ + public function balanceAfterCents(int $userId, int $movementId): int + { + if (!self::schemaReady($this->db)) { + return 0; + } + $stmt = $this->db->prepare('SELECT COALESCE(SUM(importo), 0) FROM movimenti_lettore WHERE utente_id = ? AND id <= ?'); + $stmt->bind_param('ii', $userId, $movementId); + $stmt->execute(); + $value = $stmt->get_result()->fetch_row()[0] ?? 0; + $stmt->close(); + return FineParameters::decimalToCents($value); + } + + /** + * Debt block (plan §7): the patron owes more than the threshold of their + * patron type (else the global one). See CirculationPolicy::debtBlockError(); + * the SQL twin is LoanEligibility::debtBlockSql(). + */ + public static function isDebtBlocked(mysqli $db, int $userId): bool + { + return (new CirculationPolicy($db))->debtBlockError($userId) !== null; + } + + /** + * Movements of a patron, newest first, with the loan's title and the operator. + * + * $upToId pins a paged listing to the ledger as it was on its first page + * (latestMovementId()): the ledger is append-only, so rows written while + * the operator or the app pages through it do not shift the offsets. + * + * @return list> + */ + public function movements(int $userId, int $limit = 500, int $offset = 0, ?int $upToId = null): array + { + if (!self::schemaReady($this->db)) { + return []; + } + $limit = max(1, min(5000, $limit)); + $offset = max(0, $offset); + $upTo = $upToId ?? PHP_INT_MAX; + $stmt = $this->db->prepare( + "SELECT m.id, m.utente_id, m.prestito_id, m.tipo, m.importo, m.descrizione, m.metodo_pagamento, + m.creato_da, m.created_at, m.chiave_idempotenza, + l.titolo AS libro_titolo, + TRIM(CONCAT(COALESCE(op.nome, ''), ' ', COALESCE(op.cognome, ''))) AS operatore + FROM movimenti_lettore m + LEFT JOIN prestiti p ON p.id = m.prestito_id + LEFT JOIN libri l ON l.id = p.libro_id AND l.deleted_at IS NULL + LEFT JOIN utenti op ON op.id = m.creato_da + WHERE m.utente_id = ? AND m.id <= ? + ORDER BY m.created_at DESC, m.id DESC + LIMIT {$limit} OFFSET {$offset}" + ); + $stmt->bind_param('ii', $userId, $upTo); + $stmt->execute(); + $rows = $stmt->get_result()->fetch_all(MYSQLI_ASSOC); + $stmt->close(); + return $rows; + } + + /** Number of ledger rows of $userId up to $upToId (the reader record pages through them). */ + public function movementCount(int $userId, ?int $upToId = null): int + { + if (!self::schemaReady($this->db)) { + return 0; + } + $upTo = $upToId ?? PHP_INT_MAX; + $stmt = $this->db->prepare('SELECT COUNT(*) FROM movimenti_lettore WHERE utente_id = ? AND id <= ?'); + $stmt->bind_param('ii', $userId, $upTo); + $stmt->execute(); + $count = (int) ($stmt->get_result()->fetch_row()[0] ?? 0); + $stmt->close(); + return $count; + } + + /** Id of the newest ledger row of $userId (0 when none): the snapshot a paged listing is pinned to. */ + public function latestMovementId(int $userId): int + { + if (!self::schemaReady($this->db)) { + return 0; + } + $stmt = $this->db->prepare('SELECT COALESCE(MAX(id), 0) FROM movimenti_lettore WHERE utente_id = ?'); + $stmt->bind_param('i', $userId); + $stmt->execute(); + $id = (int) ($stmt->get_result()->fetch_row()[0] ?? 0); + $stmt->close(); + return $id; + } + + /** @return array|null */ + public function movement(int $movementId): ?array + { + if (!self::schemaReady($this->db)) { + return null; + } + $stmt = $this->db->prepare( + "SELECT m.*, TRIM(CONCAT(COALESCE(op.nome, ''), ' ', COALESCE(op.cognome, ''))) AS operatore + FROM movimenti_lettore m + LEFT JOIN utenti op ON op.id = m.creato_da + WHERE m.id = ?" + ); + $stmt->bind_param('i', $movementId); + $stmt->execute(); + $row = $stmt->get_result()->fetch_assoc(); + $stmt->close(); + return $row ?: null; + } + + /** + * Write the late fine of a returned loan (idempotent). + * + * Called inside the return transaction, right after the loan row is closed. + * Only an actual return ('restituito') is fined: a copy closed lost or + * damaged carries the assessed penalty instead. + * + * @return int cents charged by THIS call (0 when nothing is due or the + * fine was already written) + */ + public function chargeLateReturn(int $loanId, ?int $staffId = null): int + { + if (!self::active($this->db) || !\App\Support\Features::enabled('automatic_fines')) { + return 0; + } + $loan = $this->loanRow($loanId); + if ($loan === null || (string) $loan['stato'] !== 'restituito' || empty($loan['data_restituzione'])) { + return 0; + } + $params = FineParameters::forLoan($this->db, $loanId); + $branch = self::branchOf($loan); + $cents = $this->calculator->lateFineCents($params, (string) $loan['data_scadenza'], (string) $loan['data_restituzione'], $branch); + if ($cents <= 0) { + return 0; + } + $days = $this->calculator->daysLate((string) $loan['data_scadenza'], (string) $loan['data_restituzione'], $branch); + $id = $this->insert( + (int) $loan['utente_id'], + $loanId, + 'multa_ritardo', + $cents, + sprintf('%d|%s|%s', $days, (string) $loan['data_scadenza'], (string) $loan['data_restituzione']), + null, + $staffId, + $this->lateKey($loanId) + ); + return $id === null ? 0 : $cents; + } + + /** + * Mirror the amount assessed for a lost/damaged copy (prestiti.sanzione) + * into the ledger (idempotent, key penalty:). + */ + public function chargeLossPenalty(int $loanId, ?int $staffId = null): int + { + // Fines off: prestiti.sanzione alone, as before 0.9.0. The legacy + // backfill mirrors it (same key) once the module is back on; its + // marker is re-armed here, because a library that already had fines + // on has the scan recorded as done. Same transaction as the close. + if (!self::active($this->db) || !\App\Support\Features::enabled('loss_penalties')) { + $loan = $this->loanRow($loanId); + if ($loan !== null && FineParameters::decimalToCents($loan['sanzione'] ?? 0) > 0) { + (new \App\Models\SettingsRepository($this->db))->set('loans', 'legacy_penalties_backfilled', '0'); + } + return 0; + } + $loan = $this->loanRow($loanId); + if ($loan === null) { + return 0; + } + $cents = FineParameters::decimalToCents($loan['sanzione'] ?? 0); + if ($cents <= 0) { + return 0; + } + $type = (string) $loan['stato'] === 'perso' ? 'addebito_perdita' : 'addebito_danno'; + $id = $this->insert((int) $loan['utente_id'], $loanId, $type, $cents, null, null, $staffId, 'penalty:' . $loanId); + return $id === null ? 0 : $cents; + } + + /** + * A return was undone: reverse the late fine with a storno row, and the + * waivers written for it with a second one, so the balance is back where + * it was before the return. A later return of the same loan writes a new + * late fine (the key carries the reversal count, so the history stays one + * row per event). + * + * @return int cents of late fine reversed (0 when no late fine is outstanding) + */ + public function reverseLateFine(int $loanId, string $reason, ?int $staffId = null): int + { + if (!self::schemaReady($this->db)) { + return 0; + } + $charged = $this->loanSum($loanId, "tipo = 'multa_ritardo' OR (tipo = 'storno' AND chiave_idempotenza LIKE 'storno:late:%')"); + if ($charged <= 0) { + return 0; + } + $loan = $this->loanRow($loanId); + if ($loan === null) { + return 0; + } + $waived = -$this->loanSum($loanId, "tipo = 'condono' OR (tipo = 'storno' AND chiave_idempotenza LIKE 'storno:waiver:%')"); + $generation = $this->reversalCount($loanId) + 1; + $id = null; + $this->inTransaction(function () use ($loan, $loanId, $charged, $waived, $reason, $staffId, $generation, &$id): void { + $id = $this->insert( + (int) $loan['utente_id'], + $loanId, + 'storno', + -$charged, + self::clip($reason), + null, + $staffId, + 'storno:late:' . $loanId . ':' . $generation + ); + if ($id !== null && $waived > 0) { + $this->insert( + (int) $loan['utente_id'], + $loanId, + 'storno', + $waived, + self::clip($reason), + null, + $staffId, + 'storno:waiver:' . $loanId . ':' . $generation + ); + } + }); + return $id === null ? 0 : $charged; + } + + /** + * Late fine still standing for a loan: late charges minus their reversals + * and minus the waivers written for that loan (the desk waiver at return + * carries the loan id), never below zero. The return email, the fine + * email and the loan page all read this figure. + */ + public function lateFineNetCents(int $loanId): int + { + if (!self::schemaReady($this->db)) { + return 0; + } + return max(0, $this->loanSum( + $loanId, + "tipo = 'multa_ritardo' OR tipo = 'condono' + OR (tipo = 'storno' AND (chiave_idempotenza LIKE 'storno:late:%' OR chiave_idempotenza LIKE 'storno:waiver:%'))" + )); + } + + /** SUM(importo) in cents of the movements of a loan matching $where (fixed SQL from this class). */ + private function loanSum(int $loanId, string $where): int + { + $stmt = $this->db->prepare("SELECT COALESCE(SUM(importo), 0) FROM movimenti_lettore WHERE prestito_id = ? AND ({$where})"); + $stmt->bind_param('i', $loanId); + $stmt->execute(); + $value = $stmt->get_result()->fetch_row()[0] ?? 0; + $stmt->close(); + return FineParameters::decimalToCents($value); + } + + /** Days late recorded with the most recent late fine of a loan (0 if none). */ + public function lateFineDays(int $loanId): int + { + if (!self::schemaReady($this->db)) { + return 0; + } + $stmt = $this->db->prepare("SELECT descrizione FROM movimenti_lettore WHERE prestito_id = ? AND tipo = 'multa_ritardo' ORDER BY id DESC LIMIT 1"); + $stmt->bind_param('i', $loanId); + $stmt->execute(); + $text = (string) ($stmt->get_result()->fetch_row()[0] ?? ''); + $stmt->close(); + return preg_match('/^(\d+)\|/', $text, $m) === 1 ? (int) $m[1] : 0; + } + + /** + * Payment received at the desk (negative movement). $requestKey (one per + * submitted form) makes a repeated submission return the movement it + * already wrote instead of recording a second payment. + */ + public function recordPayment(int $userId, int $cents, string $method, ?string $note, ?int $staffId, ?string $requestKey = null): int + { + if (!in_array($method, self::PAYMENT_METHODS, true)) { + throw new \InvalidArgumentException('invalid_method'); + } + return $this->credit($userId, 'pagamento', $cents, $note, $method, $staffId, null, $requestKey); + } + + /** Waiver (condono) of part or all of the debt; the reason is mandatory. */ + public function waive(int $userId, int $cents, string $reason, ?int $staffId, ?int $loanId = null, ?string $requestKey = null): int + { + if (trim($reason) === '') { + throw new \InvalidArgumentException('reason_required'); + } + return $this->credit($userId, 'condono', $cents, $reason, null, $staffId, $loanId, $requestKey); + } + + /** Manual charge with a mandatory description ($requestKey: see recordPayment()). */ + public function manualCharge(int $userId, int $cents, string $description, ?int $staffId, ?string $requestKey = null): int + { + if ($cents <= 0 || $cents > self::MAX_AMOUNT_CENTS) { + throw new \InvalidArgumentException('invalid_amount'); + } + if (trim($description) === '') { + throw new \InvalidArgumentException('description_required'); + } + $id = null; + $this->inTransaction(function () use ($userId, $cents, $description, $staffId, $requestKey, &$id): void { + $this->lockUser($userId); + $id = $this->replayedMovement($userId, $requestKey, 'addebito_manuale', $cents, null, self::clip($description)) + ?? $this->insert($userId, null, 'addebito_manuale', $cents, self::clip($description), null, $staffId, $requestKey); + if ($id === null) { + throw new \InvalidArgumentException('duplicate_request'); + } + }); + return (int) $id; + } + + /** + * Fine a loan would carry if it were returned on $returnDate (default today). + * + * @return array{days: int, cents: int, enabled: bool} + */ + public function previewLateFine(int $loanId, ?string $returnDate = null): array + { + if (!self::active($this->db) || !\App\Support\Features::enabled('automatic_fines')) { + return ['days' => 0, 'cents' => 0, 'enabled' => false]; + } + $loan = $this->loanRow($loanId); + $params = FineParameters::forLoan($this->db, $loanId); + if ($loan === null) { + return ['days' => 0, 'cents' => 0, 'enabled' => $params->finesEnabled()]; + } + $date = $returnDate ?? DateHelper::today(); + $branch = self::branchOf($loan); + return [ + 'days' => $this->calculator->daysLate((string) $loan['data_scadenza'], $date, $branch), + 'cents' => $this->calculator->lateFineCents($params, (string) $loan['data_scadenza'], $date, $branch), + 'enabled' => $params->finesEnabled(), + ]; + } + + /** + * Fines accruing on the patron's open overdue loans, computed for today and + * never written (plan §7: shown, charged only at return). + * + * @return list + */ + public function accruedFines(int $userId): array + { + if (!self::active($this->db) || !\App\Support\Features::enabled('automatic_fines')) { + return []; + } + $today = DateHelper::today(); + $stmt = $this->db->prepare( + "SELECT p.id, p.data_scadenza, l.titolo, c.sede_id + FROM prestiti p + LEFT JOIN libri l ON l.id = p.libro_id AND l.deleted_at IS NULL + LEFT JOIN copie c ON c.id = p.copia_id + WHERE p.utente_id = ? AND p.attivo = 1 AND p.stato IN ('in_corso','in_ritardo') + AND p.data_scadenza < ? + ORDER BY p.data_scadenza ASC" + ); + $stmt->bind_param('is', $userId, $today); + $stmt->execute(); + $rows = $stmt->get_result()->fetch_all(MYSQLI_ASSOC); + $stmt->close(); + + $out = []; + $policy = new CirculationPolicy($this->db); + foreach ($rows as $row) { + $loanId = (int) $row['id']; + $params = FineParameters::forLoan($this->db, $loanId, $policy); + $branch = self::branchOf($row); + $cents = $this->calculator->lateFineCents($params, (string) $row['data_scadenza'], $today, $branch); + if ($cents <= 0) { + continue; + } + $out[] = [ + 'loan_id' => $loanId, + 'titolo' => (string) ($row['titolo'] ?? ''), + 'data_scadenza' => (string) $row['data_scadenza'], + 'days' => $this->calculator->daysLate((string) $row['data_scadenza'], $today, $branch), + 'cents' => $cents, + ]; + } + return $out; + } + + /** + * Run backfillLegacyPenalties() until one scan completes, then record it + * (loans.legacy_penalties_backfilled) so later passes skip the scan. Called + * by the upgrade right after the migrations, so a debt over the threshold + * blocks loans from the first request, and by maintenance as the retry. + * + * @return bool true when the scan is complete (now or on an earlier pass) + */ + public static function completeLegacyPenaltyBackfill(mysqli $db): bool + { + $settings = new \App\Models\SettingsRepository($db); + if ($settings->get('loans', 'legacy_penalties_backfilled', '0') === '1') { + return true; + } + if (self::forDb($db)->backfillLegacyPenalties() === null) { + return false; + } + $settings->set('loans', 'legacy_penalties_backfilled', '1'); + return true; + } + + /** + * Convert prestiti.sanzione amounts the migration did not reach (archives + * past its batch limit) into ledger rows, $batch loans per transaction. + * Same key as the migration, so it never doubles an amount. + * + * @return int|null movements written; null when the scan is not complete: + * the ledger table does not exist yet (the 0.9.0 + * migration has not run), or $maxBatches full batches ran + * without reaching an empty one (rows may remain), so the + * caller does not record the scan as completed and the + * next pass continues + */ + public function backfillLegacyPenalties(int $batch = 500, int $maxBatches = 1000): ?int + { + // null also while the fines module is off: the scan is not recorded + // as done and runs when the module is switched back on. + if (!self::active($this->db) || !\App\Support\Features::enabled('loss_penalties')) { + return null; + } + $written = 0; + $batch = max(1, min(5000, $batch)); + $sql = "INSERT INTO movimenti_lettore (utente_id, prestito_id, tipo, importo, descrizione, creato_da, created_at, chiave_idempotenza) + SELECT p.utente_id, p.id, IF(p.stato = 'perso', 'addebito_perdita', 'addebito_danno'), p.sanzione, + 'Importo registrato alla chiusura del prestito', p.processed_by, + COALESCE(CAST(p.data_restituzione AS DATETIME), p.updated_at, p.created_at, NOW()), CONCAT('penalty:', p.id) + FROM prestiti p + WHERE p.sanzione > 0 + AND NOT EXISTS (SELECT 1 FROM movimenti_lettore m WHERE m.chiave_idempotenza = CONCAT('penalty:', p.id)) + ORDER BY p.id + LIMIT {$batch} + ON DUPLICATE KEY UPDATE movimenti_lettore.id = movimenti_lettore.id"; + $drained = false; + for ($i = 0; $i < $maxBatches; $i++) { + if (!$this->db->query($sql)) { + // Not "done": the caller must not record the scan as completed. + throw new \RuntimeException('Legacy penalty backfill failed: ' . $this->db->error); + } + $affected = $this->db->affected_rows; + if ($affected <= 0) { + $drained = true; + break; + } + $written += $affected; + } + // Stopped by the batch limit: rows may remain, the scan is not done. + return $drained ? $written : null; + } + + /** + * Charged / collected / waived / reversed per month in [$from, $to]. + * + * @return list + */ + public function periodTotals(string $from, string $to): array + { + if (!self::active($this->db)) { + return []; + } + $fromTs = $from . ' 00:00:00'; + $toTs = $to . ' 23:59:59'; + $stmt = $this->db->prepare( + "SELECT DATE_FORMAT(created_at, '%Y-%m') AS mese, + SUM(CASE WHEN tipo IN ('multa_ritardo','addebito_perdita','addebito_danno','addebito_manuale') THEN importo ELSE 0 END) AS addebitato, + SUM(CASE WHEN tipo = 'pagamento' THEN -importo ELSE 0 END) AS incassato, + SUM(CASE WHEN tipo = 'condono' THEN -importo ELSE 0 END) AS condonato, + SUM(CASE WHEN tipo = 'storno' THEN -importo ELSE 0 END) AS stornato + FROM movimenti_lettore + WHERE created_at BETWEEN ? AND ? + GROUP BY DATE_FORMAT(created_at, '%Y-%m') + ORDER BY mese ASC" + ); + $stmt->bind_param('ss', $fromTs, $toTs); + $stmt->execute(); + $rows = $stmt->get_result()->fetch_all(MYSQLI_ASSOC); + $stmt->close(); + return array_map(static fn (array $r): array => [ + 'mese' => (string) $r['mese'], + 'addebitato' => FineParameters::decimalToCents($r['addebitato']), + 'incassato' => FineParameters::decimalToCents($r['incassato']), + 'condonato' => FineParameters::decimalToCents($r['condonato']), + 'stornato' => FineParameters::decimalToCents($r['stornato']), + ], $rows); + } + + /** Payment or waiver: never more than the patron currently owes. */ + private function credit(int $userId, string $type, int $cents, ?string $text, ?string $method, ?int $staffId, ?int $loanId, ?string $requestKey = null): int + { + if ($cents <= 0 || $cents > self::MAX_AMOUNT_CENTS) { + throw new \InvalidArgumentException('invalid_amount'); + } + $id = null; + $this->inTransaction(function () use ($userId, $type, $cents, $text, $method, $staffId, $loanId, $requestKey, &$id): void { + $this->lockUser($userId); + // A repeated form submission: the movement is already there (and the + // balance check would now refuse it), so hand back the same row. + $id = $this->replayedMovement($userId, $requestKey, $type, -$cents, $method, $text !== null ? self::clip($text) : null); + if ($id !== null) { + return; + } + if ($cents > $this->balanceCents($userId)) { + throw new \InvalidArgumentException('amount_exceeds_balance'); + } + $id = $this->insert($userId, $loanId, $type, -$cents, $text !== null ? self::clip($text) : null, $method, $staffId, $requestKey); + if ($id === null) { + // The key was taken in between: report it, never a fake success. + throw new \InvalidArgumentException('duplicate_request'); + } + }); + return (int) $id; + } + + /** + * The movement of $userId already written under $requestKey, if any. + * + * The key identifies one rendered form, so a replay carries the same + * type, signed amount, payment method and text as the stored row. A key + * sent again with different values (the page restored from the cache + * and submitted with another amount) is not the same event: it is + * refused as 'duplicate_request' instead of being answered with the + * old movement as if the new one had been recorded. + */ + private function replayedMovement(int $userId, ?string $requestKey, string $type, int $signedCents, ?string $method, ?string $text): ?int + { + if ($requestKey === null) { + return null; + } + // Looked up by key alone: a key is unique across the whole ledger, so a + // key already used for another patron is a duplicate too, never a + // fresh movement whose insert would silently do nothing. + $stmt = $this->db->prepare('SELECT id, utente_id, tipo, importo, metodo_pagamento, descrizione FROM movimenti_lettore WHERE chiave_idempotenza = ?'); + $stmt->bind_param('s', $requestKey); + $stmt->execute(); + $row = $stmt->get_result()->fetch_assoc(); + $stmt->close(); + if (!is_array($row)) { + return null; + } + $same = (int) $row['utente_id'] === $userId + && (string) $row['tipo'] === $type + && FineParameters::decimalToCents($row['importo']) === $signedCents + && (string) ($row['metodo_pagamento'] ?? '') === (string) ($method ?? '') + && (string) ($row['descrizione'] ?? '') === (string) ($text ?? ''); + if (!$same) { + throw new \InvalidArgumentException('duplicate_request'); + } + return (int) $row['id']; + } + + /** Serialise balance-dependent writes of one patron on the utenti row. */ + private function lockUser(int $userId): void + { + $stmt = $this->db->prepare('SELECT id FROM utenti WHERE id = ? FOR UPDATE'); + $stmt->bind_param('i', $userId); + $stmt->execute(); + $found = $stmt->get_result()->fetch_row(); + $stmt->close(); + if ($found === null) { + throw new \InvalidArgumentException('user_not_found'); + } + } + + /** + * Run $work in the caller's transaction, or in a new one when none is open. + * An explicit begin_transaction() leaves @@autocommit at 1, so the check is + * DbAtomic's (autocommit flag plus a SAVEPOINT probe): a waiver written + * inside the return transaction must never commit that return halfway. + * + * @param callable():void $work + */ + private function inTransaction(callable $work): void + { + DbAtomic::run($this->db, $work, 'PatronLedger'); + } + + /** + * Insert one movement. Returns the new id, or null when the idempotency key + * already exists (the event was recorded before). + */ + private function insert(int $userId, ?int $loanId, string $type, int $cents, ?string $description, ?string $method, ?int $staffId, ?string $key): ?int + { + if (!in_array($type, self::TYPES, true)) { + throw new \InvalidArgumentException('invalid_type'); + } + $amount = FineParameters::centsToDecimal($cents); + $now = DateHelper::now(); + $stmt = $this->db->prepare( + 'INSERT INTO movimenti_lettore (utente_id, prestito_id, tipo, importo, descrizione, metodo_pagamento, creato_da, created_at, chiave_idempotenza) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) + ON DUPLICATE KEY UPDATE id = id' + ); + $stmt->bind_param('iissssiss', $userId, $loanId, $type, $amount, $description, $method, $staffId, $now, $key); + $stmt->execute(); + $affected = $stmt->affected_rows; + $newId = (int) $stmt->insert_id; + $stmt->close(); + return $affected === 1 ? $newId : null; + } + + /** + * Branch of the loaned copy (its branch closures do not count as late days). + * + * @param array $loan + */ + private static function branchOf(array $loan): ?int + { + return isset($loan['sede_id']) ? (int) $loan['sede_id'] : null; + } + + /** @return array|null */ + private function loanRow(int $loanId): ?array + { + $stmt = $this->db->prepare( + 'SELECT p.id, p.utente_id, p.stato, p.data_scadenza, p.data_restituzione, p.sanzione, c.sede_id + FROM prestiti p LEFT JOIN copie c ON c.id = p.copia_id + WHERE p.id = ?' + ); + $stmt->bind_param('i', $loanId); + $stmt->execute(); + $row = $stmt->get_result()->fetch_assoc(); + $stmt->close(); + return $row ?: null; + } + + private function reversalCount(int $loanId): int + { + $stmt = $this->db->prepare("SELECT COUNT(*) FROM movimenti_lettore WHERE prestito_id = ? AND tipo = 'storno' AND chiave_idempotenza LIKE 'storno:late:%'"); + $stmt->bind_param('i', $loanId); + $stmt->execute(); + $count = (int) ($stmt->get_result()->fetch_row()[0] ?? 0); + $stmt->close(); + return $count; + } + + /** late: for the first return, late:: after n reversals. */ + private function lateKey(int $loanId): string + { + $reversals = $this->reversalCount($loanId); + return 'late:' . $loanId . ($reversals > 0 ? ':' . ($reversals + 1) : ''); + } + + private static function clip(string $text): string + { + $text = trim(preg_replace('/\s+/u', ' ', $text) ?? $text); + return mb_substr($text, 0, 255); + } + + /** + * Readable description of a movement for lists, receipts and the API. + * + * @param array $row + */ + public static function describe(array $row): string + { + $type = (string) ($row['tipo'] ?? ''); + $text = (string) ($row['descrizione'] ?? ''); + if ($type === 'multa_ritardo' && preg_match('/^(\d+)\|/', $text, $m) === 1) { + return sprintf(__('Multa per %d giorni di ritardo'), (int) $m[1]); + } + return $text; + } + + /** Localised label of a movement type. */ + public static function typeLabel(string $type): string + { + return match ($type) { + 'multa_ritardo' => __('Multa per ritardo'), + 'addebito_perdita' => __('Addebito copia persa'), + 'addebito_danno' => __('Addebito copia danneggiata'), + 'addebito_manuale' => __('Addebito manuale'), + 'pagamento' => __('Pagamento'), + 'condono' => __('Condono'), + 'storno' => __('Storno'), + default => $type, + }; + } + + /** Localised label of a payment method. */ + public static function methodLabel(?string $method): string + { + return match ((string) $method) { + 'contanti' => __('Contanti'), + 'pos' => __('POS'), + 'bonifico' => __('Bonifico'), + 'altro' => __('Altro'), + default => '', + }; + } +} diff --git a/app/Services/ReservationReassignmentService.php b/app/Services/ReservationReassignmentService.php index 2c60d58f8..f95039119 100644 --- a/app/Services/ReservationReassignmentService.php +++ b/app/Services/ReservationReassignmentService.php @@ -640,6 +640,7 @@ private function findAvailableCopyExcluding( FROM copie c WHERE c.libro_id = ? AND c.stato NOT IN ('perso', 'danneggiato', 'manutenzione', 'in_restauro', 'in_trasferimento') + AND " . (new \App\Services\CirculationPolicy($this->db))->lendableCopySql('c') . " AND NOT EXISTS ( SELECT 1 FROM prestiti own diff --git a/app/Services/StocktakeService.php b/app/Services/StocktakeService.php new file mode 100644 index 000000000..2e9b2e732 --- /dev/null +++ b/app/Services/StocktakeService.php @@ -0,0 +1,1438 @@ + 0 && $expectedShelf !== $scanShelf) { + return 'fuori_posto'; + } + return 'trovata'; + } + + /** + * Keep only the filter keys the snapshot understands. An absent or empty + * value means "no restriction"; a value that is present but invalid is + * refused, never dropped: dropping it would widen the snapshot to the whole + * catalogue and a mark-lost close would then reach copies of other shelves. + * + * @param array $input + * @return array{scaffale_id?:int,mensola_id?:int,genere_id?:int,tipo_media?:string,collocazione_da?:string,collocazione_a?:string} + * @throws \InvalidArgumentException on a present but invalid id or media type + */ + public static function normalizeFilter(array $input): array + { + $filter = []; + foreach (['scaffale_id', 'mensola_id', 'genere_id'] as $key) { + $value = $input[$key] ?? null; + if ($value === null || $value === '') { + continue; + } + if (!is_scalar($value) || !ctype_digit((string) $value) || (int) $value <= 0) { + throw new \InvalidArgumentException(__('Filtri non validi.')); + } + $filter[$key] = (int) $value; + } + $media = $input['tipo_media'] ?? null; + if ($media !== null && $media !== '') { + if (!is_string($media) || !in_array($media, self::MEDIA_TYPES, true)) { + throw new \InvalidArgumentException(__('Filtri non validi.')); + } + $filter['tipo_media'] = $media; + } + foreach (['collocazione_da', 'collocazione_a'] as $key) { + $value = $input[$key] ?? null; + if (is_string($value)) { + $value = mb_substr(trim($value), 0, 50); + if ($value !== '') { + $filter[$key] = $value; + } + } + } + return $filter; + } + + /** + * A scanned or typed code, without the control characters some wedge + * scanners append. Never shortened: a code longer than MAX_CODE_LENGTH is + * refused by assertCodeLength(), since its first characters could be the + * whole code of another copy. + */ + public static function normalizeCode(string $code): string + { + $code = preg_replace('/[\x00-\x1F\x7F]+/u', '', $code) ?? ''; + return trim($code); + } + + /** @throws \InvalidArgumentException when the code does not fit the column */ + private static function assertCodeLength(string $code): void + { + if (mb_strlen($code) > self::MAX_CODE_LENGTH) { + throw new \InvalidArgumentException(sprintf(__('Codice troppo lungo: al massimo %d caratteri.'), self::MAX_CODE_LENGTH)); + } + } + + /** + * Codes from a .txt (one per line) or .csv (first column) export. Order and + * repetitions are kept: a code read twice by an offline scanner is a duplicate. + * + * A .txt line is the code itself: only a tab (which no code can hold, see + * normalizeCode()) separates it from trailing scanner columns, so a code + * such as "INV,001" survives. A .csv is read record by record with a real + * CSV parser, using the delimiter of its first record, so a quoted "INV,001" + * survives too, and so does a quoted cell that spans several lines. Only + * its first column is the code: a row whose first cell is empty has none. + * + * @return list + */ + public static function parseCodeList(string $text, bool $csv = false): array + { + if (str_starts_with($text, "\xEF\xBB\xBF")) { + $text = substr($text, 3); + } + if (!mb_check_encoding($text, 'UTF-8')) { + $text = mb_convert_encoding($text, 'UTF-8', 'ISO-8859-1'); + } + $codes = []; + $first = true; + foreach ($csv ? self::csvFirstCells($text) : self::txtFirstCells($text) as $cell) { + $cell = trim($cell); + // fgetcsv() already removed the CSV quoting, so a quote left in a + // .csv cell is part of the code. A .txt line has no quoting rules: + // only one pair of quotes wrapping the whole line is dropped. + if (!$csv && preg_match('/^(["\'])(.*)\1$/su', $cell, $m) === 1) { + $cell = $m[2]; + } + $cell = self::normalizeCode($cell); + if ($cell === '') { + continue; + } + if ($first && preg_match('/^(codice|code|codes|barcode|numero_inventario|numero inventario|inventario|inventory|stregkode|code-barres)$/iu', $cell) === 1) { + $first = false; + continue; + } + $first = false; + $codes[] = $cell; + } + return $codes; + } + + /** + * The first cell of every .txt line: what comes before its first tab. A + * line that starts with a tab has no code, as a .csv row whose first cell + * is empty: the next cell is a scanner column (a title, a date), never a code. + * + * @return list + */ + private static function txtFirstCells(string $text): array + { + $cells = []; + foreach (preg_split('/\r\n|\r|\n/', $text) ?: [] as $line) { + $cells[] = explode("\t", $line, 2)[0]; + } + return $cells; + } + + /** + * The first cell of every .csv record. Records are read whole, so a line + * break inside a quoted cell does not start a new record. + * + * @return list + */ + private static function csvFirstCells(string $text): array + { + // fgetcsv() does not split on a bare CR (old Mac exports). + $text = str_replace(["\r\n", "\r"], "\n", $text); + $stream = fopen('php://memory', 'r+'); + if ($stream === false) { + throw new \RuntimeException('Cannot open a memory stream for the CSV file'); + } + try { + fwrite($stream, $text); + rewind($stream); + $delimiter = self::csvDelimiter($text); + $cells = []; + while (($record = fgetcsv($stream, null, $delimiter, '"', '')) !== false) { + $cells[] = (string) ($record[0] ?? ''); + } + return $cells; + } finally { + fclose($stream); + } + } + + /** + * The delimiter of a CSV export: the most frequent of , ; and tab in its + * first non-blank record, counting only those outside quoted cells (comma + * when the record has none, a one-column file). The record ends at the + * first line break outside quotes, so a quoted first cell that spans + * several lines does not hide the delimiter that follows it. + */ + private static function csvDelimiter(string $text): string + { + $counts = [',' => 0, ';' => 0, "\t" => 0]; + $quoted = false; + $blank = true; + for ($i = 0, $n = strlen($text); $i < $n; $i++) { + $char = $text[$i]; + if ($char === '"') { + $quoted = !$quoted; // an escaped "" flips twice + $blank = false; + } elseif (!$quoted && $char === "\n") { + if (!$blank) { + break; + } + $counts = [',' => 0, ';' => 0, "\t" => 0]; // a blank line: keep looking + } elseif (!$quoted && isset($counts[$char])) { + $counts[$char]++; + $blank = false; + } elseif (!ctype_space($char)) { + $blank = false; + } + } + $best = ','; + $bestCount = 0; + foreach ($counts as $candidate => $count) { + if ($count > $bestCount) { + $best = (string) $candidate; + $bestCount = $count; + } + } + return $best; + } + + /** + * Open a session and freeze the copies it expects to find: those matching + * the filter, of books still in the catalogue, that are not already out of + * circulation (OFF_SHELF_STATES). + * + * @param array $filter + */ + public function open(string $name, ?int $sedeId, array $filter, ?int $operatorId, string $note = ''): int + { + $name = mb_substr(trim($name), 0, 255); + if ($name === '') { + throw new \InvalidArgumentException(__('Indica un nome per la revisione.')); + } + $filter = self::normalizeFilter($filter); + $sedeId = ($sedeId !== null && $sedeId > 0) ? $sedeId : null; + $note = trim($note); + [$where, $types, $params] = $this->snapshotWhere($sedeId, $filter); + $filterJson = json_encode((object) $filter, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR); + + $this->db->begin_transaction(); + try { + $stmt = $this->db->prepare( + "INSERT INTO inventario_sessioni (nome, sede_id, filtro, stato, aperta_da, aperta_il, note) VALUES (?, ?, ?, 'aperta', ?, NOW(), ?)" + ); + if ($stmt === false) { + throw new \RuntimeException($this->db->error); + } + $noteValue = $note !== '' ? $note : null; + $stmt->bind_param('sisis', $name, $sedeId, $filterJson, $operatorId, $noteValue); + $stmt->execute(); + $sessionId = (int) $this->db->insert_id; + $stmt->close(); + + // The code and title are frozen too: a copy deleted during the + // stocktake must still be identifiable on the shelf from the report. + $sql = 'INSERT INTO inventario_attese (sessione_id, copia_id, scaffale_atteso, stato_all_apertura, codice_atteso, titolo_atteso) ' + . 'SELECT ?, c.id, l.scaffale_id, c.stato, c.numero_inventario, l.titolo FROM copie c ' + . 'JOIN libri l ON l.id = c.libro_id AND l.deleted_at IS NULL ' + . 'WHERE c.stato NOT IN (' . self::sqlList(self::OFF_SHELF_STATES) . ')' . $where; + $stmt = $this->db->prepare($sql); + if ($stmt === false) { + throw new \RuntimeException($this->db->error); + } + $stmt->bind_param('i' . $types, $sessionId, ...$params); + $stmt->execute(); + $stmt->close(); + + $stmt = $this->db->prepare('UPDATE inventario_sessioni SET totale_attese = (SELECT COUNT(*) FROM inventario_attese WHERE sessione_id = ?) WHERE id = ?'); + if ($stmt === false) { + throw new \RuntimeException($this->db->error); + } + $stmt->bind_param('ii', $sessionId, $sessionId); + $stmt->execute(); + $stmt->close(); + + $this->db->commit(); + } catch (\Throwable $e) { + $this->db->rollback(); + throw $e; + } + + ActivityLog::recordEntityEvent($this->db, 'inventario_sessioni', $sessionId, 'inventory.opened', [], [ + 'nome' => $name, + 'filtro' => $filter, + 'totale_attese' => $this->expectedCount($sessionId), + ], 'inserimento', 'inventory', $operatorId); + + return $sessionId; + } + + /** + * @param array $filter + * @return array{0:string,1:string,2:list} + */ + private function snapshotWhere(?int $sedeId, array $filter): array + { + $where = ''; + $types = ''; + $params = []; + if ($sedeId !== null) { + $where .= ' AND c.sede_id = ?'; + $types .= 'i'; + $params[] = $sedeId; + } + if (isset($filter['scaffale_id'])) { + $where .= ' AND l.scaffale_id = ?'; + $types .= 'i'; + $params[] = (int) $filter['scaffale_id']; + } + if (isset($filter['mensola_id'])) { + $where .= ' AND l.mensola_id = ?'; + $types .= 'i'; + $params[] = (int) $filter['mensola_id']; + } + if (isset($filter['genere_id'])) { + $family = GenreTree::withDescendants($this->db, (int) $filter['genere_id']); + $marks = implode(',', array_fill(0, count($family), '?')); + $where .= " AND (l.genere_id IN ($marks) OR l.sottogenere_id IN ($marks))"; + $types .= str_repeat('i', count($family) * 2); + array_push($params, ...$family, ...$family); + } + if (isset($filter['tipo_media'])) { + $where .= ' AND l.tipo_media = ?'; + $types .= 's'; + $params[] = (string) $filter['tipo_media']; + } + if (isset($filter['collocazione_da'])) { + $where .= ' AND l.collocazione >= ?'; + $types .= 's'; + $params[] = (string) $filter['collocazione_da']; + } + if (isset($filter['collocazione_a'])) { + // The upper bound includes everything that starts with it: up to "B" takes "B.9.99" too. + $upper = (string) $filter['collocazione_a']; + $where .= " AND (l.collocazione <= ? OR l.collocazione LIKE ?)"; + $types .= 'ss'; + $params[] = $upper; + $params[] = addcslashes($upper, '%_\\') . '%'; + } + return [$where, $types, $params]; + } + + /** @return array|null */ + public function find(int $sessionId): ?array + { + $stmt = $this->db->prepare( + 'SELECT s.*, se.nome AS sede_nome, ' + . "TRIM(CONCAT(COALESCE(ua.nome, ''), ' ', COALESCE(ua.cognome, ''))) AS aperta_da_nome, " + . "TRIM(CONCAT(COALESCE(uc.nome, ''), ' ', COALESCE(uc.cognome, ''))) AS chiusa_da_nome " + . 'FROM inventario_sessioni s ' + . 'LEFT JOIN sedi se ON se.id = s.sede_id ' + . 'LEFT JOIN utenti ua ON ua.id = s.aperta_da ' + . 'LEFT JOIN utenti uc ON uc.id = s.chiusa_da ' + . 'WHERE s.id = ?' + ); + if ($stmt === false) { + return null; + } + $stmt->bind_param('i', $sessionId); + $stmt->execute(); + $row = $stmt->get_result()->fetch_assoc(); + $stmt->close(); + if (!is_array($row)) { + return null; + } + $decoded = json_decode((string) ($row['filtro'] ?? '{}'), true); + $row['filtro'] = is_array($decoded) ? $decoded : []; + return $row; + } + + /** @return list> */ + public function listSessions(int $limit = 100): array + { + $limit = max(1, min(500, $limit)); + $select = 'SELECT s.id, s.nome, s.stato, s.totale_attese, s.aperta_il, s.chiusa_il, se.nome AS sede_nome, ' + . '(SELECT COUNT(*) FROM inventario_scansioni sc WHERE sc.sessione_id = s.id) AS scansioni, ' + . "TRIM(CONCAT(COALESCE(ua.nome, ''), ' ', COALESCE(ua.cognome, ''))) AS aperta_da_nome " + . 'FROM inventario_sessioni s ' + . 'LEFT JOIN sedi se ON se.id = s.sede_id ' + . 'LEFT JOIN utenti ua ON ua.id = s.aperta_da '; + // Every open session is listed, however many: the limit only trims the + // history of closed and cancelled ones, so an open one never drops off. + $result = $this->db->query( + '(' . $select . "WHERE s.stato = 'aperta') " + . 'UNION ALL ' + . '(' . $select . "WHERE s.stato <> 'aperta' ORDER BY s.aperta_il DESC, s.id DESC LIMIT " . $limit . ') ' + . "ORDER BY FIELD(stato, 'aperta', 'chiusa', 'annullata'), aperta_il DESC, id DESC" + ); + if ($result === false) { + return []; + } + /** @var list> $rows */ + $rows = $result->fetch_all(MYSQLI_ASSOC); + return $rows; + } + + private function expectedCount(int $sessionId): int + { + $stmt = $this->db->prepare('SELECT COUNT(*) FROM inventario_attese WHERE sessione_id = ?'); + if ($stmt === false) { + return 0; + } + $stmt->bind_param('i', $sessionId); + $stmt->execute(); + $row = $stmt->get_result()->fetch_row(); + $stmt->close(); + return (int) ($row[0] ?? 0); + } + + private function assertOpen(int $sessionId): void + { + $stmt = $this->db->prepare('SELECT stato FROM inventario_sessioni WHERE id = ?'); + if ($stmt === false) { + throw new \RuntimeException($this->db->error); + } + $stmt->bind_param('i', $sessionId); + $stmt->execute(); + $row = $stmt->get_result()->fetch_row(); + $stmt->close(); + if ($row === null) { + throw new \DomainException(__('Revisione non trovata.')); + } + if ($row[0] !== 'aperta') { + throw new \DomainException(__('La revisione non è più aperta: non si possono registrare altre scansioni.')); + } + } + + /** + * Record one scanned or typed code. + * + * @return array{esito:string,codice:string,copia_id:int|null,libro_id:int|null,titolo:string,collocazione:string,scaffale_atteso:string,stato_copia:string,fuori_circolazione:bool} + */ + public function scan(int $sessionId, string $rawCode, ?int $operatorId, ?int $scanShelf = null): array + { + $code = self::normalizeCode($rawCode); + if ($code === '') { + throw new \InvalidArgumentException(__('Codice mancante.')); + } + self::assertCodeLength($code); + $this->assertOpen($sessionId); + return $this->record($sessionId, $code, $operatorId, $scanShelf); + } + + /** + * Record a list of codes, one scan each, in file order. + * + * @param list $codes + * @return array{total:int,outcomes:array} + */ + public function importCodes(int $sessionId, array $codes, ?int $operatorId, ?int $scanShelf = null): array + { + if (count($codes) > self::MAX_UPLOAD_CODES) { + throw new \InvalidArgumentException(sprintf(__('Il file contiene troppi codici: al massimo %d per caricamento.'), self::MAX_UPLOAD_CODES)); + } + // Checked before anything is recorded: a file refused halfway would + // leave its first codes behind, and the retry would find them duplicate. + foreach ($codes as $code) { + self::assertCodeLength(self::normalizeCode($code)); + } + $this->assertOpen($sessionId); + $outcomes = array_fill_keys(self::OUTCOMES, 0); + $total = 0; + // One transaction for the whole file: a file that fails halfway records + // nothing, so uploading it again does not turn its first codes into + // duplicates. The session lock is taken once, as record() would. + $this->db->begin_transaction(); + try { + $this->lockOpenSession($sessionId); + foreach ($codes as $code) { + $code = self::normalizeCode($code); + if ($code === '') { + continue; + } + $result = $this->record($sessionId, $code, $operatorId, $scanShelf, true); + $outcomes[$result['esito']]++; + $total++; + } + $this->db->commit(); + } catch (\Throwable $e) { + $this->db->rollback(); + throw $e; + } + return ['total' => $total, 'outcomes' => $outcomes]; + } + + /** + * `fuori_circolazione` flags a copy recorded as lost, damaged or away + * (OFF_SHELF_STATES): such a copy is left out of the snapshot, so a scan of + * it is out of scope, but the operator must be told it turned up rather + * than that it is outside the filter. An expected copy that got one of + * those states after the opening is found as usual, and flagged too. + * + * With $insideTransaction the caller already holds a transaction and the + * session lock (importCodes()): the scan is written inside it, never in a + * nested one. + * + * @return array{esito:string,codice:string,copia_id:int|null,libro_id:int|null,titolo:string,collocazione:string,scaffale_atteso:string,stato_copia:string,fuori_circolazione:bool} + */ + private function record(int $sessionId, string $code, ?int $operatorId, ?int $scanShelf, bool $insideTransaction = false): array + { + // The scan is written under a shared lock on the session row. close() + // and cancel() flip the state with an UPDATE that waits for that lock, + // so a scan either lands before the session leaves 'aperta' (and close() + // sees it before marking copies lost) or is refused afterwards. + if (!$insideTransaction) { + $this->db->begin_transaction(); + } + try { + if (!$insideTransaction) { + $this->lockOpenSession($sessionId); + } + // `copie` has no deleted_at: a copy of a book removed from the catalogue is unknown. + $stmt = $this->db->prepare( + 'SELECT c.id, c.libro_id, c.stato, l.titolo, l.collocazione FROM copie c ' + . 'JOIN libri l ON l.id = c.libro_id AND l.deleted_at IS NULL ' + . 'WHERE c.numero_inventario = ? LIMIT 1' + ); + if ($stmt === false) { + throw new \RuntimeException($this->db->error); + } + $stmt->bind_param('s', $code); + $stmt->execute(); + $copy = $stmt->get_result()->fetch_assoc(); + $stmt->close(); + + $copyId = is_array($copy) ? (int) $copy['id'] : null; + $expected = null; + $alreadyScanned = false; + $onLoan = false; + if ($copyId !== null) { + $stmt = $this->db->prepare('SELECT scaffale_atteso FROM inventario_attese WHERE sessione_id = ? AND copia_id = ?'); + if ($stmt === false) { + throw new \RuntimeException($this->db->error); + } + $stmt->bind_param('ii', $sessionId, $copyId); + $stmt->execute(); + $expected = $stmt->get_result()->fetch_assoc(); + $stmt->close(); + + $stmt = $this->db->prepare('SELECT 1 FROM inventario_scansioni WHERE sessione_id = ? AND copia_id = ? LIMIT 1'); + if ($stmt === false) { + throw new \RuntimeException($this->db->error); + } + $stmt->bind_param('ii', $sessionId, $copyId); + $stmt->execute(); + $alreadyScanned = $stmt->get_result()->fetch_row() !== null; + $stmt->close(); + + if ($insideTransaction) { + // A file upload holds one transaction for the whole file: + // locking reads would keep loans and returns of every copy + // already recorded waiting until the last code, and deadlock + // with a loan of a copy further down the file. It reads the + // committed state, as the upload found it. + $onLoan = $this->isOnLoan($copyId); + } else { + // A single scan reads the loan status inside its short + // transaction with locking reads, in the order a return + // takes its locks (the loan, then the copy): a return in + // progress is waited for, so the scan never records + // 'in_prestito' for a copy already back on the shelf. + $onLoan = $this->isOnLoan($copyId, true); + $copy['stato'] = $this->lockedCopyState($copyId) ?? (string) ($copy['stato'] ?? ''); + } + $onLoan = $onLoan || in_array($copy['stato'], self::ON_LOAN_COPY_STATES, true); + } + + $expectedShelf = is_array($expected) && $expected['scaffale_atteso'] !== null ? (int) $expected['scaffale_atteso'] : null; + $outcome = self::classify($copyId !== null, $alreadyScanned, is_array($expected), $onLoan, $expectedShelf, $scanShelf); + + $storedCopyId = in_array($outcome, ['sconosciuta', 'duplicata'], true) ? null : $copyId; + try { + $this->insertScan($sessionId, $storedCopyId, $code, $outcome, $operatorId); + } catch (\mysqli_sql_exception $e) { + // Two operators scanned the same copy at the same instant: the UNIQUE + // (sessione_id, copia_id) let one win, the other is a duplicate. + // A failed INSERT undoes only itself, not the surrounding transaction. + if ($e->getCode() !== 1062 || $storedCopyId === null) { + throw $e; + } + $outcome = 'duplicata'; + $this->insertScan($sessionId, null, $code, $outcome, $operatorId); + } + if (!$insideTransaction) { + $this->db->commit(); + } + } catch (\Throwable $e) { + if (!$insideTransaction) { + $this->db->rollback(); + } + throw $e; + } + + return [ + 'esito' => $outcome, + 'codice' => $code, + 'copia_id' => $copyId, + 'libro_id' => is_array($copy) ? (int) $copy['libro_id'] : null, + 'titolo' => is_array($copy) ? (string) $copy['titolo'] : '', + 'collocazione' => is_array($copy) ? (string) ($copy['collocazione'] ?? '') : '', + 'scaffale_atteso' => $expectedShelf !== null ? $this->shelfLabel($expectedShelf) : '', + 'stato_copia' => is_array($copy) ? (string) $copy['stato'] : '', + 'fuori_circolazione' => is_array($copy) && in_array((string) $copy['stato'], self::OFF_SHELF_STATES, true), + ]; + } + + private function lockOpenSession(int $sessionId): void + { + $stmt = $this->db->prepare('SELECT stato FROM inventario_sessioni WHERE id = ? LOCK IN SHARE MODE'); + if ($stmt === false) { + throw new \RuntimeException($this->db->error); + } + $stmt->bind_param('i', $sessionId); + $stmt->execute(); + $row = $stmt->get_result()->fetch_row(); + $stmt->close(); + if ($row === null || $row[0] !== 'aperta') { + throw new \DomainException(__('La revisione non è più aperta: non si possono registrare altre scansioni.')); + } + } + + /** + * Move an open session to $state in one conditional UPDATE. Only the caller + * whose UPDATE changes the row may go on: a concurrent close or cancel that + * lost the race gets a DomainException and must not touch anything. + */ + private function claimTransition(int $sessionId, string $state, ?int $operatorId, ?string $missingAction = null): void + { + $stmt = $this->db->prepare("UPDATE inventario_sessioni SET stato = ?, chiusa_da = ?, chiusa_il = NOW(), azione_mancanti = ? WHERE id = ? AND stato = 'aperta'"); + if ($stmt === false) { + throw new \RuntimeException($this->db->error); + } + $stmt->bind_param('sisi', $state, $operatorId, $missingAction, $sessionId); + $stmt->execute(); + $claimed = $stmt->affected_rows === 1; + $stmt->close(); + if (!$claimed) { + $this->assertOpen($sessionId); // "not found" when the row is gone + throw new \DomainException(__('La revisione non è più aperta: non si possono registrare altre scansioni.')); + } + } + + private function insertScan(int $sessionId, ?int $copyId, string $code, string $outcome, ?int $operatorId): void + { + $stmt = $this->db->prepare( + 'INSERT INTO inventario_scansioni (sessione_id, copia_id, codice_letto, esito, scansionato_da, scansionato_il) VALUES (?, ?, ?, ?, ?, NOW())' + ); + if ($stmt === false) { + throw new \RuntimeException($this->db->error); + } + try { + $stmt->bind_param('iissi', $sessionId, $copyId, $code, $outcome, $operatorId); + $stmt->execute(); + } finally { + $stmt->close(); + } + } + + /** @param list $values */ + private static function sqlList(array $values): string + { + return "'" . implode("','", $values) . "'"; + } + + /** + * SQL predicate: the expected copy `a` (joined as `c`) is in the "on loan" + * bucket. NULL when the copy no longer exists. + */ + private static function onLoanSql(): string + { + return '(c.stato IN (' . self::sqlList(self::ON_LOAN_COPY_STATES) . ') OR EXISTS (' + . 'SELECT 1 FROM prestiti p WHERE p.copia_id = a.copia_id AND p.attivo = 1 AND p.stato IN (' . self::sqlList(self::ON_LOAN_LOAN_STATES) . ')))'; + } + + /** + * Lent, or set aside on the pickup shelf: see ON_LOAN_LOAN_STATES. With + * $lock the read is a locking one (inside a transaction), which waits for + * a return of the copy still in progress and reads what it committed. + */ + private function isOnLoan(int $copyId, bool $lock = false): bool + { + $stmt = $this->db->prepare('SELECT 1 FROM prestiti WHERE copia_id = ? AND attivo = 1 AND stato IN (' . self::sqlList(self::ON_LOAN_LOAN_STATES) . ') LIMIT 1' . ($lock ? ' LOCK IN SHARE MODE' : '')); + if ($stmt === false) { + return false; + } + $stmt->bind_param('i', $copyId); + $stmt->execute(); + $found = $stmt->get_result()->fetch_row() !== null; + $stmt->close(); + return $found; + } + + /** The copy's state, read under a shared lock (inside a transaction); null when it is gone. */ + private function lockedCopyState(int $copyId): ?string + { + $stmt = $this->db->prepare('SELECT stato FROM copie WHERE id = ? LOCK IN SHARE MODE'); + if ($stmt === false) { + throw new \RuntimeException($this->db->error); + } + $stmt->bind_param('i', $copyId); + $stmt->execute(); + $row = $stmt->get_result()->fetch_row(); + $stmt->close(); + return $row === null ? null : (string) $row[0]; + } + + /** @var array */ + private array $shelfLabels = []; + + public function shelfLabel(int $shelfId): string + { + if (!isset($this->shelfLabels[$shelfId])) { + $stmt = $this->db->prepare('SELECT codice, nome FROM scaffali WHERE id = ?'); + $label = '#' . $shelfId; + if ($stmt !== false) { + $stmt->bind_param('i', $shelfId); + $stmt->execute(); + $row = $stmt->get_result()->fetch_assoc(); + $stmt->close(); + if (is_array($row)) { + $label = trim('[' . (string) ($row['codice'] ?? '') . '] ' . (string) ($row['nome'] ?? '')); + } + } + $this->shelfLabels[$shelfId] = $label; + } + return $this->shelfLabels[$shelfId]; + } + + /** + * A closed session reports the buckets frozen at close. A session closed + * before esito_chiusura existed still has NULL rows and keeps the live + * computation, as does an open or cancelled one. + */ + private function isFrozen(int $sessionId): bool + { + $stmt = $this->db->prepare( + "SELECT 1 FROM inventario_sessioni s WHERE s.id = ? AND s.stato <> 'aperta' " + . 'AND NOT EXISTS (SELECT 1 FROM inventario_attese a WHERE a.sessione_id = s.id AND a.esito_chiusura IS NULL)' + ); + if ($stmt === false) { + throw new \RuntimeException($this->db->error); + } + $stmt->bind_param('i', $sessionId); + $stmt->execute(); + $frozen = $stmt->get_result()->fetch_row() !== null; + $stmt->close(); + return $frozen; + } + + /** + * Counters. The four buckets of the expected copies always add up to the + * snapshot size: found + misplaced + on loan + missing = expected. They + * are live while the session is open and frozen once it is closed; `perse` + * is the part of `mancanti` the close marked lost (0 while open). + * + * @return array{attese:int,trovate:int,fuori_posto:int,in_prestito:int,mancanti:int,perse:int,sconosciute:int,fuori_ambito:int,duplicate:int,scansioni:int,mie:int} + */ + public function summary(int $sessionId, ?int $operatorId = null): array + { + $counts = array_fill_keys(self::OUTCOMES, 0); + $mine = 0; + $stmt = $this->db->prepare('SELECT esito, COUNT(*) AS n, SUM(scansionato_da <=> ?) AS mine FROM inventario_scansioni WHERE sessione_id = ? GROUP BY esito'); + if ($stmt === false) { + throw new \RuntimeException($this->db->error); + } + $mineOf = $operatorId ?? 0; + $stmt->bind_param('ii', $mineOf, $sessionId); + $stmt->execute(); + foreach ($stmt->get_result()->fetch_all(MYSQLI_ASSOC) as $row) { + $counts[(string) $row['esito']] = (int) $row['n']; + $mine += (int) $row['mine']; + } + $stmt->close(); + + if ($this->isFrozen($sessionId)) { + $frozen = array_fill_keys(self::CLOSE_OUTCOMES, 0); + $stmt = $this->db->prepare('SELECT esito_chiusura, COUNT(*) AS n FROM inventario_attese WHERE sessione_id = ? GROUP BY esito_chiusura'); + if ($stmt === false) { + throw new \RuntimeException($this->db->error); + } + $stmt->bind_param('i', $sessionId); + $stmt->execute(); + foreach ($stmt->get_result()->fetch_all(MYSQLI_ASSOC) as $row) { + $outcome = (string) $row['esito_chiusura']; + if (isset($frozen[$outcome])) { + $frozen[$outcome] = (int) $row['n']; + } + } + $stmt->close(); + $found = $frozen['trovata']; + $misplaced = $frozen['fuori_posto']; + $onLoan = $frozen['in_prestito']; + $lost = $frozen['persa']; + $missing = $frozen['mancante'] + $lost; + } else { + // Counted in SQL: the scan page asks for this on every scan and every + // poll, so it must not pull the unscanned rows into PHP. A deleted copy + // (c.stato NULL) is not on loan and stays among the missing ones. + $stmt = $this->db->prepare( + 'SELECT COUNT(*) AS n, COALESCE(SUM(t.in_prestito), 0) AS lent FROM (' + . 'SELECT COALESCE(' . self::onLoanSql() . ', 0) AS in_prestito ' + . 'FROM inventario_attese a ' + . 'LEFT JOIN inventario_scansioni s ON s.sessione_id = a.sessione_id AND s.copia_id = a.copia_id ' + . 'LEFT JOIN copie c ON c.id = a.copia_id ' + . 'WHERE a.sessione_id = ? AND s.id IS NULL) t' + ); + if ($stmt === false) { + throw new \RuntimeException($this->db->error); + } + $stmt->bind_param('i', $sessionId); + $stmt->execute(); + $row = $stmt->get_result()->fetch_assoc(); + $stmt->close(); + $unscannedOnLoan = (int) ($row['lent'] ?? 0); + $found = $counts['trovata']; + $misplaced = $counts['fuori_posto']; + $onLoan = $counts['in_prestito'] + $unscannedOnLoan; + $lost = 0; + $missing = (int) ($row['n'] ?? 0) - $unscannedOnLoan; + } + + return [ + 'attese' => $this->expectedCount($sessionId), + 'trovate' => $found, + 'fuori_posto' => $misplaced, + 'in_prestito' => $onLoan, + 'mancanti' => $missing, + 'perse' => $lost, + 'sconosciute' => $counts['sconosciuta'], + 'fuori_ambito' => $counts['fuori_ambito'], + 'duplicate' => $counts['duplicata'], + 'scansioni' => array_sum($counts), + 'mie' => $mine, + ]; + } + + /** + * Expected copies nobody scanned. `in_prestito` is their loan status right + * now, or with $frozen the one recorded at close (esito_chiusura); every + * other column is the copy as it is today. + * + * @return list> + */ + private function unscannedExpected(int $sessionId, bool $frozen = false): array + { + /** @var list> $rows */ + $rows = $this->unscannedExpectedResult($sessionId, $frozen)->fetch_all(MYSQLI_ASSOC); + return $rows; + } + + /** The unscanned expected copies as a buffered result, read one row at a time by the report. */ + private function unscannedExpectedResult(int $sessionId, bool $frozen): \mysqli_result + { + $stmt = $this->db->prepare( + 'SELECT a.copia_id, a.scaffale_atteso, a.stato_all_apertura, a.esito_chiusura, ' + . 'COALESCE(c.numero_inventario, a.codice_atteso) AS numero_inventario, c.stato AS stato_attuale, c.libro_id, c.updated_at AS copia_aggiornata_il, ' + . 'COALESCE(l.titolo, a.titolo_atteso) AS titolo, l.id AS libro_attivo, l.collocazione, l.tipo_media, ' + . ($frozen ? "(a.esito_chiusura = 'in_prestito')" : self::onLoanSql()) . ' AS in_prestito ' + . 'FROM inventario_attese a ' + . 'LEFT JOIN inventario_scansioni s ON s.sessione_id = a.sessione_id AND s.copia_id = a.copia_id ' + . 'LEFT JOIN copie c ON c.id = a.copia_id ' + . 'LEFT JOIN libri l ON l.id = c.libro_id AND l.deleted_at IS NULL ' + . 'WHERE a.sessione_id = ? AND s.id IS NULL ' + . 'ORDER BY l.collocazione, l.titolo, c.numero_inventario' + ); + if ($stmt === false) { + throw new \RuntimeException($this->db->error); + } + $stmt->bind_param('i', $sessionId); + $stmt->execute(); + $result = $stmt->get_result(); + $stmt->close(); + if ($result === false) { + throw new \RuntimeException($this->db->error); + } + return $result; + } + + /** + * Full report rows: every expected copy with its bucket, plus the scans + * that are not about an expected copy (unknown, out of scope, duplicate). + * On a closed session the bucket is the one frozen at close; a copy the + * close marked lost stays a `mancante` row, with `stato_copia` saying + * 'perso'. `stato_copia` is always the copy's state today. + * + * With $limit at most that many rows are read: the PDF export asks for one + * more than it can hold, to tell a report that grew past its limit. + * + * @return list + */ + public function reportRows(int $sessionId, ?int $limit = null): array + { + $rows = []; + foreach ($this->eachReportRow($sessionId) as $row) { + if ($limit !== null && count($rows) >= $limit) { + break; + } + $rows[] = $row; + } + return $rows; + } + + /** + * The rows of reportRows() one at a time, for an export that writes them + * as they come (the CSV) instead of holding the whole report: the database + * results are buffered by the driver, but never turned into one PHP array. + * + * @return \Generator + */ + public function eachReportRow(int $sessionId): \Generator + { + $result = $this->unscannedExpectedResult($sessionId, $this->isFrozen($sessionId)); + try { + while (is_array($row = $result->fetch_assoc())) { + yield [ + 'categoria' => (int) $row['in_prestito'] === 1 ? 'in_prestito' : 'mancante', + 'codice' => (string) ($row['numero_inventario'] ?? ('#' . (int) $row['copia_id'])), + 'titolo' => (string) ($row['titolo'] ?? ''), + 'tipo_media' => (string) ($row['tipo_media'] ?? ''), + 'collocazione' => (string) ($row['collocazione'] ?? ''), + 'scaffale_atteso' => $row['scaffale_atteso'] !== null ? $this->shelfLabel((int) $row['scaffale_atteso']) : '', + 'stato_copia' => (string) ($row['stato_attuale'] ?? ''), + 'operatore' => '', + 'quando' => '', + ]; + } + } finally { + $result->free(); + } + // Every scan, not the latest N: the summary counts them all, so must the report. + $result = $this->scansResult($sessionId, null, null); + try { + while (is_array($scan = $result->fetch_assoc())) { + yield [ + 'categoria' => (string) $scan['esito'], + 'codice' => (string) $scan['codice_letto'], + 'titolo' => (string) ($scan['titolo'] ?? ''), + 'tipo_media' => (string) ($scan['tipo_media'] ?? ''), + 'collocazione' => (string) ($scan['collocazione'] ?? ''), + 'scaffale_atteso' => $scan['scaffale_atteso'] !== null ? $this->shelfLabel((int) $scan['scaffale_atteso']) : '', + 'stato_copia' => (string) ($scan['stato_copia'] ?? ''), + 'operatore' => (string) ($scan['operatore'] ?? ''), + 'quando' => (string) ($scan['scansionato_il'] ?? ''), + ]; + } + } finally { + $result->free(); + } + } + + /** How many rows reportRows() returns, counted without loading them. */ + public function reportRowCount(int $sessionId): int + { + $stmt = $this->db->prepare( + 'SELECT (SELECT COUNT(*) FROM inventario_attese a ' + . 'LEFT JOIN inventario_scansioni s ON s.sessione_id = a.sessione_id AND s.copia_id = a.copia_id ' + . 'WHERE a.sessione_id = ? AND s.id IS NULL) ' + . '+ (SELECT COUNT(*) FROM inventario_scansioni WHERE sessione_id = ?)' + ); + if ($stmt === false) { + throw new \RuntimeException($this->db->error); + } + $stmt->bind_param('ii', $sessionId, $sessionId); + $stmt->execute(); + $row = $stmt->get_result()->fetch_row(); + $stmt->close(); + return (int) ($row[0] ?? 0); + } + + /** + * Most recent scans first; with $operatorId only that operator's. A null + * $limit returns them all (the report); the live list asks for a page. + * + * @return list> + */ + public function scans(int $sessionId, ?int $operatorId = null, ?int $limit = 200): array + { + /** @var list> $rows */ + $rows = $this->scansResult($sessionId, $operatorId, $limit)->fetch_all(MYSQLI_ASSOC); + return $rows; + } + + private function scansResult(int $sessionId, ?int $operatorId, ?int $limit): \mysqli_result + { + $limit = $limit === null ? null : max(1, min(100000, $limit)); + $sql = 'SELECT s.id, s.codice_letto, s.esito, s.scansionato_il, s.scansionato_da, s.copia_id, ' + . 'COALESCE(c.numero_inventario, a.codice_atteso, s.codice_letto) AS codice, COALESCE(l.titolo, a.titolo_atteso) AS titolo, ' + . 'l.collocazione, l.tipo_media, c.stato AS stato_copia, a.scaffale_atteso, ' + . "TRIM(CONCAT(COALESCE(u.nome, ''), ' ', COALESCE(u.cognome, ''))) AS operatore " + . 'FROM inventario_scansioni s ' + . 'LEFT JOIN copie c ON c.id = s.copia_id ' + . 'LEFT JOIN libri l ON l.id = c.libro_id AND l.deleted_at IS NULL ' + . 'LEFT JOIN inventario_attese a ON a.sessione_id = s.sessione_id AND a.copia_id = s.copia_id ' + . 'LEFT JOIN utenti u ON u.id = s.scansionato_da ' + . 'WHERE s.sessione_id = ?' + . ($operatorId !== null ? ' AND s.scansionato_da = ?' : '') + . ' ORDER BY s.id DESC' . ($limit !== null ? ' LIMIT ' . $limit : ''); + $stmt = $this->db->prepare($sql); + if ($stmt === false) { + throw new \RuntimeException($this->db->error); + } + if ($operatorId !== null) { + $stmt->bind_param('ii', $sessionId, $operatorId); + } else { + $stmt->bind_param('i', $sessionId); + } + $stmt->execute(); + $result = $stmt->get_result(); + $stmt->close(); + if ($result === false) { + throw new \RuntimeException($this->db->error); + } + return $result; + } + + /** + * Close the session: claim it, freeze the bucket of every expected copy + * in inventario_attese.esito_chiusura, then with $markLost turn every + * missing copy that is not already explained by its state (lent or waiting + * for pickup, damaged, lost, at the restorer's, in transit, in maintenance) + * into 'perso', one book per transaction, with the same hold reassignment + * and availability recalculation as a manual change of a copy's state and + * an ActivityLog entry per copy. Those copies are frozen as 'persa'. + * + * @return array{marked:int,failed:int} + */ + public function close(int $sessionId, bool $markLost, ?int $operatorId): array + { + // Publish the closed state and its frozen report together. Scans and + // competing transitions wait on the session lock until commit; if the + // snapshot fails, rollback leaves the session open for a retry. + $this->db->begin_transaction(); + try { + $this->claimTransition($sessionId, 'chiusa', $operatorId, $markLost ? 'perso' : 'nessuna'); + $this->freezeOutcomes($sessionId); + $this->db->commit(); + } catch (\Throwable $e) { + $this->db->rollback(); + throw $e; + } + ['marked' => $marked, 'failed' => $failed] = $markLost + ? $this->markMissingLost($sessionId, $operatorId) + : ['marked' => 0, 'failed' => 0]; + + $summary = $this->summary($sessionId); + + ActivityLog::recordEntityEvent($this->db, 'inventario_sessioni', $sessionId, 'inventory.closed', [], [ + 'azione_mancanti' => $markLost ? 'perso' : 'nessuna', + 'copie_perse' => $marked, + 'copie_non_aggiornate' => $failed, + ] + $summary, 'aggiornamento', 'inventory', $operatorId); + + return ['marked' => $marked, 'failed' => $failed]; + } + + /** + * Finish a mark-lost close that did not reach every copy (the request died + * or some books failed): the session is already closed, so close() cannot + * be repeated. Only a session closed with "mark as lost" qualifies, and the + * marking skips every copy already lost, away, lent or found meanwhile, so + * running it again is harmless. + * + * @return array{marked:int,failed:int} + */ + public function resumeMarkLost(int $sessionId, ?int $operatorId): array + { + $session = $this->find($sessionId); + if ($session === null) { + throw new \DomainException(__('Revisione non trovata.')); + } + if ($session['stato'] !== 'chiusa' || ($session['azione_mancanti'] ?? null) !== 'perso') { + throw new \DomainException(__('Azione non valida.')); + } + $result = $this->markMissingLost($sessionId, $operatorId); + ActivityLog::recordEntityEvent($this->db, 'inventario_sessioni', $sessionId, 'inventory.lost_resumed', [], [ + 'copie_perse' => $result['marked'], + 'copie_non_aggiornate' => $result['failed'], + ], 'aggiornamento', 'inventory', $operatorId); + return $result; + } + + /** + * Missing copies a mark-lost close still has to mark: the unscanned, not + * lent copies whose book is still in the catalogue and whose state is not + * already lost or away. Meaningful for a session closed with "mark as + * lost" (azione_mancanti = 'perso'), which the caller checks. + */ + public function pendingLostCount(int $sessionId): int + { + return array_sum(array_map('count', $this->missingToMark($sessionId))); + } + + /** + * The unscanned expected copies a mark-lost close applies to, by book. + * Only rows the close froze as missing ('mancante') qualify: a row it + * already marked ('persa') is never marked again. A copy changed after + * the close (found and put back, repaired, lent) is someone's later + * decision, not a copy the close failed to reach, so it is left alone + * (changedSinceClose()). + * + * @return array>> + */ + private function missingToMark(int $sessionId): array + { + $closedAt = $this->closedAt($sessionId); + $byBook = []; + foreach ($this->unscannedExpected($sessionId, true) as $row) { + if (($row['esito_chiusura'] ?? null) !== 'mancante') { + continue; // already marked lost by the close, or not missing at close + } + if ((int) $row['in_prestito'] === 1 || $row['libro_id'] === null || $row['libro_attivo'] === null) { + continue; // lent or held for pickup, or the copy/book left the catalogue meanwhile + } + if (self::changedSinceClose($row['copia_aggiornata_il'] ?? null, $closedAt)) { + continue; // handled by someone after the close + } + if (in_array((string) $row['stato_attuale'], self::AWAY_STATES, true)) { + continue; + } + $byBook[(int) $row['libro_id']][] = $row; + } + return $byBook; + } + + /** + * The copy was changed at or after the close. Both columns hold whole + * seconds, so a change in the very second of the close cannot be told + * from one just before it: it counts as later, and the copy is left + * alone rather than declared lost over someone's edit. The close itself + * never touches a copy it has not marked, so it cannot trip this. + */ + private static function changedSinceClose(mixed $changedAt, ?string $closedAt): bool + { + return $closedAt !== null && is_string($changedAt) && $changedAt >= $closedAt; + } + + /** When the session was closed (MySQL DATETIME text), or NULL. */ + private function closedAt(int $sessionId): ?string + { + $stmt = $this->db->prepare('SELECT chiusa_il FROM inventario_sessioni WHERE id = ?'); + if ($stmt === false) { + throw new \RuntimeException($this->db->error); + } + $stmt->bind_param('i', $sessionId); + $stmt->execute(); + $value = $stmt->get_result()->fetch_column(); + $stmt->close(); + return is_string($value) ? $value : null; + } + + /** @return array{marked:int,failed:int} */ + private function markMissingLost(int $sessionId, ?int $operatorId): array + { + $marked = 0; + $failed = 0; + foreach ($this->missingToMark($sessionId) as $bookId => $copies) { + try { + $lostInBook = $this->markBookCopiesLost($bookId, $copies, $sessionId, $operatorId); + $marked += count($lostInBook); + } catch (\Throwable $e) { + $failed += count($copies); + SecureLogger::error('[Stocktake] marking missing copies as lost failed', [ + 'sessione_id' => $sessionId, + 'libro_id' => $bookId, + 'error' => $e->getMessage(), + ]); + } + } + return ['marked' => $marked, 'failed' => $failed]; + } + + /** + * Write each expected copy's bucket into esito_chiusura in one statement: + * the scan outcome for a scanned copy (one scan per copy: UNIQUE + * sessione_id, copia_id), else on loan or missing as of this instant. A + * copy that no longer exists (c.stato NULL) is missing. close() marks the + * copies it declares lost afterwards. + */ + private function freezeOutcomes(int $sessionId): void + { + $stmt = $this->db->prepare( + 'UPDATE inventario_attese a ' + . 'LEFT JOIN inventario_scansioni s ON s.sessione_id = a.sessione_id AND s.copia_id = a.copia_id ' + . 'LEFT JOIN copie c ON c.id = a.copia_id ' + . 'SET a.esito_chiusura = CASE ' + . "WHEN s.esito IN ('trovata','fuori_posto','in_prestito') THEN s.esito " + . 'WHEN COALESCE(' . self::onLoanSql() . ", 0) = 1 THEN 'in_prestito' " + . "ELSE 'mancante' END " + . 'WHERE a.sessione_id = ?' + ); + if ($stmt === false) { + throw new \RuntimeException($this->db->error); + } + $stmt->bind_param('i', $sessionId); + $stmt->execute(); + $stmt->close(); + } + + /** + * The copies this close turned into 'perso' leave the frozen 'mancante' + * bucket for 'persa'; the report keeps counting them among the missing. + * + * @param list $copyIds + */ + private function markFrozenLost(int $sessionId, array $copyIds): void + { + foreach (array_chunk($copyIds, 500) as $chunk) { + $ids = implode(',', array_map('intval', $chunk)); + $stmt = $this->db->prepare("UPDATE inventario_attese SET esito_chiusura = 'persa' WHERE sessione_id = ? AND copia_id IN ({$ids})"); + if ($stmt === false) { + throw new \RuntimeException($this->db->error); + } + $stmt->bind_param('i', $sessionId); + $stmt->execute(); + $stmt->close(); + } + } + + /** + * @param list> $copies + * @return list ids of the copies now marked lost + */ + private function markBookCopiesLost(int $bookId, array $copies, int $sessionId, ?int $operatorId): array + { + $reassignment = new ReservationReassignmentService($this->db); + $reassignment->setExternalTransaction(true); + $changed = []; + + $this->db->begin_transaction(); + try { + $lock = $this->db->prepare('SELECT id FROM libri WHERE id = ? AND deleted_at IS NULL FOR UPDATE'); + if ($lock === false) { + throw new \RuntimeException($this->db->error); + } + $lock->bind_param('i', $bookId); + $lock->execute(); + $bookLocked = $lock->get_result()->fetch_row() !== null; + $lock->close(); + if (!$bookLocked) { + $this->db->rollback(); + return []; + } + $closedAt = $this->closedAt($sessionId); + + foreach ($copies as $copy) { + $copyId = (int) $copy['copia_id']; + // Re-read under the book lock: a return, a loan or any other + // edit of the copy may have happened since missingToMark(). + $stmt = $this->db->prepare('SELECT stato, numero_inventario, note, updated_at FROM copie WHERE id = ? AND libro_id = ? FOR UPDATE'); + if ($stmt === false) { + throw new \RuntimeException($this->db->error); + } + $stmt->bind_param('ii', $copyId, $bookId); + $stmt->execute(); + $current = $stmt->get_result()->fetch_assoc(); + $stmt->close(); + if (!is_array($current) || in_array((string) $current['stato'], self::AWAY_STATES, true) || $this->isOnLoan($copyId)) { + continue; + } + if (self::changedSinceClose($current['updated_at'] ?? null, $closedAt)) { + continue; // edited after the close: someone's later decision + } + unset($current['updated_at']); + $scanned = $this->db->prepare('SELECT 1 FROM inventario_scansioni WHERE sessione_id = ? AND copia_id = ? LIMIT 1'); + if ($scanned === false) { + throw new \RuntimeException($this->db->error); + } + $scanned->bind_param('ii', $sessionId, $copyId); + $scanned->execute(); + $foundMeanwhile = $scanned->get_result()->fetch_row() !== null; + $scanned->close(); + if ($foundMeanwhile) { + continue; + } + + $stmt = $this->db->prepare("UPDATE copie SET stato = 'perso', updated_at = NOW() WHERE id = ?"); + if ($stmt === false) { + throw new \RuntimeException($this->db->error); + } + $stmt->bind_param('i', $copyId); + $stmt->execute(); + $stmt->close(); + + // A copy may carry several future holds: move every one of them elsewhere. + // Same predicate as reassignOnCopyLost(), window still valid included: + // an expired hold is left to the expiry cleanup, and counting it here + // would spin the guard without the reassignment ever taking it. + // A hold still there after the guard rolls the whole book back: + // committing would leave a reader's hold on a lost copy. + $today = DateHelper::today(); + for ($guard = 0; ; $guard++) { + $held = $this->db->prepare( + "SELECT 1 FROM prestiti WHERE copia_id = ? AND data_scadenza >= ? AND ((attivo = 1 AND stato IN ('prenotato','da_ritirare')) OR (attivo = 0 AND stato = 'pendente' AND origine = 'prenotazione')) LIMIT 1" + ); + if ($held === false) { + throw new \RuntimeException($this->db->error); + } + $held->bind_param('is', $copyId, $today); + $held->execute(); + $stillHeld = $held->get_result()->fetch_row() !== null; + $held->close(); + if (!$stillHeld) { + break; + } + if ($guard >= 1000) { + throw new \RuntimeException('Holds still on copy ' . $copyId . ' after reassignment'); + } + $reassignment->reassignOnCopyLost($copyId); + } + $changed[] = ['id' => $copyId] + $current; + } + + if ($changed !== [] && !(new DataIntegrity($this->db))->recalculateBookAvailability($bookId, insideTransaction: true)) { + throw new \RuntimeException('Book availability recalculation failed'); + } + // Keep the historical outcome in the same transaction as the copy + // state, hold reassignment and availability it describes. + $this->markFrozenLost($sessionId, array_map(static fn (array $copy): int => (int) $copy['id'], $changed)); + // The audit entry too: written after the commit, a request cut off + // in between left a 'persa' copy that a resume skips, so its + // copy.inventory_lost event was never written. + foreach ($changed as $copy) { + $logged = ActivityLog::recordBookEvent( + $this->db, + $bookId, + 'aggiornamento', + 'copy', + 'copy.inventory_lost', + ['numero_inventario' => $copy['numero_inventario'] ?? null, 'stato' => $copy['stato'] ?? null], + ['numero_inventario' => $copy['numero_inventario'] ?? null, 'stato' => 'perso', 'inventario_sessione' => $sessionId], + $operatorId, + (int) $copy['id'], + null, + 'inventory' + ); + if (!$logged) { + throw new \RuntimeException('Audit entry for the lost copy ' . (int) $copy['id'] . ' could not be written'); + } + } + $this->db->commit(); + } catch (\Throwable $e) { + $this->db->rollback(); + throw $e; + } + + try { + $reassignment->flushDeferredNotifications(); + } catch (\Throwable $e) { + SecureLogger::warning('[Stocktake] reassignment notification failed', ['error' => $e->getMessage()]); + } + return array_map(static fn (array $copy): int => (int) $copy['id'], $changed); + } + + public function cancel(int $sessionId, ?int $operatorId): void + { + $this->claimTransition($sessionId, 'annullata', $operatorId); + ActivityLog::recordEntityEvent($this->db, 'inventario_sessioni', $sessionId, 'inventory.cancelled', [], [], 'aggiornamento', 'inventory', $operatorId); + } +} diff --git a/app/Services/UnifiedCatalogService.php b/app/Services/UnifiedCatalogService.php index 6f74bbb57..8c60c4bd9 100644 --- a/app/Services/UnifiedCatalogService.php +++ b/app/Services/UnifiedCatalogService.php @@ -180,13 +180,39 @@ private function articleQuery(array $filters): ?array if ($term !== '') { // Same word-wise semantics as the book index; punctuation in an // inverted personal name must not prevent a natural-order search. - $words = preg_split('/[^\p{L}\p{N}_%]+/u', mb_substr($term, 0, 200), -1, PREG_SPLIT_NO_EMPTY) ?: []; - foreach (array_slice($words, 0, 20) as $word) { - $textMatch = "CONCAT_WS(' ', c.titolo, c.sottotitolo, c.autori, c.contenitore_titolo, c.keywords, c.abstract, c.issn) LIKE ? ESCAPE '='"; - $pattern = '%' . strtr($word, ['=' => '==', '%' => '=%', '_' => '=_']) . '%'; + // A quoted group is one exact term (#501), like in the book search, + // so articles by "Häffner" do not answer "haffner" in quotes. + $terms = []; + $tokens = \App\Support\ExactSearch::tokenize(mb_substr($term, 0, 200)); + foreach ($tokens as $token) { + if ($token['exact']) { + $terms[] = $token; + continue; + } + foreach (preg_split('/[^\p{L}\p{N}_%]+/u', $token['term'], -1, PREG_SPLIT_NO_EMPTY) ?: [] as $word) { + $terms[] = ['term' => $word, 'exact' => false]; + } + } + if ($tokens !== [] && $terms === []) { + // Only punctuation was typed ("!!!", a garbled "??"): the book + // search looks for it literally and finds nothing, so the + // articles must not answer with the whole collection. + $where[] = '1=0'; + } + $articleText = "CONCAT_WS(' ', c.titolo, c.sottotitolo, c.autori, c.contenitore_titolo, c.keywords, c.abstract, c.issn)"; + foreach (array_slice($terms, 0, 20) as ['term' => $word, 'exact' => $exact]) { + if ($exact) { + $textMatch = \App\Support\ExactSearch::likeSql($articleText, true); + $authorMatch = \App\Support\ExactSearch::likeSql("CONCAT_WS(' ',a.nome,a.pseudonimo)", true); + $pattern = \App\Support\ExactSearch::likePattern($word); + } else { + $textMatch = "$articleText LIKE ? ESCAPE '='"; + $authorMatch = "CONCAT_WS(' ',a.nome,a.pseudonimo) LIKE ? ESCAPE '='"; + $pattern = '%' . strtr($word, ['=' => '==', '%' => '=%', '_' => '=_']) . '%'; + } $params[] = $pattern; if ($linkedAuthors) { - $textMatch = "($textMatch OR EXISTS (SELECT 1 FROM emeroteca_contributi_autori ca JOIN autori a ON a.id=ca.autore_id WHERE ca.contributo_id=c.id AND CONCAT_WS(' ',a.nome,a.pseudonimo) LIKE ? ESCAPE '='))"; + $textMatch = "($textMatch OR EXISTS (SELECT 1 FROM emeroteca_contributi_autori ca JOIN autori a ON a.id=ca.autore_id WHERE ca.contributo_id=c.id AND $authorMatch))"; $params[] = $pattern; } $where[] = $textMatch; diff --git a/app/Support/ActivityLog.php b/app/Support/ActivityLog.php index 9707d51d4..48bd931af 100644 --- a/app/Support/ActivityLog.php +++ b/app/Support/ActivityLog.php @@ -64,6 +64,7 @@ final class ActivityLog 'copy.created' => 'Copia fisica aggiunta', 'copy.updated' => 'Copia fisica aggiornata', 'copy.deleted' => 'Copia fisica eliminata', + 'copy.inventory_lost' => 'Copia dichiarata persa in revisione inventariale', 'import.created' => 'Libro creato tramite importazione', 'import.updated' => 'Libro aggiornato tramite importazione', 'enrich.updated' => 'Metadati arricchiti', @@ -96,6 +97,11 @@ final class ActivityLog 'subscription.created' => 'Abbonamento creato', 'subscription.updated' => 'Abbonamento aggiornato', 'subscription.deleted' => 'Abbonamento eliminato', + // Inventory stocktake sessions (recordEntityEvent on inventario_sessioni) + 'inventory.opened' => 'Revisione inventariale aperta', + 'inventory.closed' => 'Revisione inventariale chiusa', + 'inventory.cancelled' => 'Revisione inventariale annullata', + 'inventory.lost_resumed' => 'Revisione inventariale: copie mancanti segnate come perse dopo la chiusura', ]; /** @var array */ @@ -350,6 +356,46 @@ public static function recent( return self::fetch($db, $page, $perPage, null, $type, $operatorId, $q); } + /** + * The events recordEntityEvent() wrote for one non-book record, newest + * first, at most $limit of them. The book feeds (recent(), forBook()) + * read tabella = 'libri' only, so this is the reader for entity events, + * such as the open/close/cancel history of a stocktake session. + * + * @return list> + */ + public static function forEntity(mysqli $db, string $tabella, int $recordId, int $limit = 50): array + { + if ($recordId <= 0 || $tabella === '' || $tabella === 'libri') { + return []; + } + $limit = max(1, min(200, $limit)); + try { + $stmt = $db->prepare( + "SELECT lm.*, TRIM(CONCAT(u.nome, ' ', u.cognome)) AS operator_name " + . 'FROM log_modifiche lm ' + . 'LEFT JOIN utenti u ON u.id = lm.utente_id ' + . 'WHERE lm.tabella = ? AND lm.record_id = ? ' + . 'ORDER BY lm.data_modifica DESC, lm.id DESC LIMIT ?' + ); + if ($stmt === false) { + throw new \RuntimeException($db->error); + } + $stmt->bind_param('sii', $tabella, $recordId, $limit); + $stmt->execute(); + $result = $stmt->get_result(); + $items = []; + while ($row = $result->fetch_assoc()) { + $items[] = self::decodeRow($row); + } + $stmt->close(); + return $items; + } catch (\Throwable $e) { + SecureLogger::warning('ActivityLog entity read failed', ['tabella' => $tabella, 'error' => $e->getMessage()]); + return []; + } + } + /** @return list */ public static function operators(mysqli $db): array { diff --git a/app/Support/AuthorNormalizer.php b/app/Support/AuthorNormalizer.php index 8ec86d8ba..85b74cf38 100644 --- a/app/Support/AuthorNormalizer.php +++ b/app/Support/AuthorNormalizer.php @@ -15,6 +15,38 @@ */ class AuthorNormalizer { + /** + * The form of a contributor name that is WRITTEN to a text column + * (traduttore, illustratore, curatore, a renamed author or publisher). + * Quotes typed around the whole name mean "exact spelling" (#501) and are + * a lookup instruction, never part of the stored name, exactly as when an + * author record is created. + */ + public static function normalizeStored(string $name): string + { + // A cache column holds a '; '-joined list (the separator ContributorSync + // and the importers use, '|' accepted on input). Each name is + // normalised on its own: run as one string, the comma rule turned + // "Rossi, Mario; Bianchi, Luca" into "Mario; Bianchi, Luca Rossi" and + // the quotes of one listed name were never removed. + // Decoded first, as ContributorSync::splitNames() does: the ';' of an + // HTML entity (O'Brien) is not a list separator. + if (preg_match('/[;|]/u', HtmlHelper::decode($name)) === 1) { + $names = []; + foreach (ContributorSync::splitNames($name) as $part) { + $part = self::normalize(ExactSearch::quotedValue($part) ?? $part); + if ($part !== '' && !in_array($part, $names, true)) { + $names[] = $part; + } + } + return implode('; ', $names); + } + // A single name is decoded too, so it is cached the way the same + // name inside a list is (O'Brien → O'Brien). + $name = HtmlHelper::decode($name); + return self::normalize(ExactSearch::quotedValue($name) ?? $name); + } + /** * Normalize an author name to a canonical format: "Name Surname" * @@ -24,11 +56,24 @@ class AuthorNormalizer * - "Marx, Karl Heinrich" → "Karl Heinrich Marx" * - "De Filippo, Eduardo" → "Eduardo De Filippo" * + * A name typed wholly in quotes (`"Häffner, Rolf"`) means "this exact + * spelling" (#501): the quotes are kept, around the canonical form + * (`"Rolf Häffner"`), so the save and lookup paths still see a whole-value + * quoted string (ExactSearch::quotedValue()) and the person is the one + * written that way. Without this the quotes ended up inside the swapped + * name (`Rolf" "Häffner`) and were stored as part of it. + * * @param string $name The author name in any format * @return string Normalized name in "Name Surname" format */ public static function normalize(string $name): string { + $quoted = ExactSearch::quotedValue($name); + if ($quoted !== null) { + $inner = self::normalize($quoted); + return $inner === '' ? '' : '"' . $inner . '"'; + } + $name = trim($name); if ($name === '') { @@ -78,8 +123,9 @@ public static function normalize(string $name): string */ public static function toSearchForm(string $name): string { - // First normalize the name - $normalized = self::normalize($name); + // First normalize the name. Quotes are a lookup instruction, never + // part of the name: a quoted candidate compares on the name inside. + $normalized = self::normalize(ExactSearch::quotedValue($name) ?? $name); if ($normalized === '') { return ''; @@ -108,7 +154,7 @@ public static function toSearchForm(string $name): string */ public static function getSearchVariants(string $name): array { - $normalized = self::normalize($name); + $normalized = self::normalize(ExactSearch::quotedValue($name) ?? $name); if ($normalized === '') { return []; diff --git a/app/Support/CatalogueCard.php b/app/Support/CatalogueCard.php new file mode 100644 index 000000000..a3747e60b --- /dev/null +++ b/app/Support/CatalogueCard.php @@ -0,0 +1,295 @@ + $book row from BookRepository::getById() + * @return array{call:string, heading:string, body:string, isbn:string, tracings:string, inventory:string} + */ + public static function lines(array $book): array + { + $authors = self::names($book['autori'] ?? []); + $title = self::clean($book['titolo'] ?? ''); + $subtitle = self::clean($book['sottotitolo'] ?? ''); + + $heading = ''; + $principal = is_array($book['autori'] ?? null) ? ($book['autori'][0] ?? null) : null; + if (is_array($principal)) { + $heading = AuthorName::citation($principal); + } + + // Area 1: title and statement of responsibility + $area1 = $title; + if ($subtitle !== '') { + $area1 .= ' : ' . $subtitle; + } + $responsibility = []; + if ($authors !== []) { + $responsibility[] = implode(', ', $authors); + } + $roles = [ + 'traduttori' => __('traduzione di'), + 'illustratori' => __('illustrazioni di'), + 'curatori' => __('a cura di'), + ]; + foreach ($roles as $key => $phrase) { + $names = self::names($book[$key] ?? []); + if ($names !== []) { + $responsibility[] = $phrase . ' ' . implode(', ', $names); + } + } + if ($responsibility !== []) { + $area1 .= ' / ' . implode(' ; ', $responsibility); + } + $areas = [$area1]; + + // Area 2: edition + $edition = self::clean($book['edizione'] ?? ''); + if ($edition !== '') { + $areas[] = $edition; + } + + // Area 4: publication + $place = self::clean($book['luogo_pubblicazione'] ?? ''); + $publishers = []; + foreach ((array) ($book['editori'] ?? []) as $pub) { + $name = is_array($pub) ? self::clean($pub['nome'] ?? '') : ''; + if ($name !== '') { + $publishers[] = $name; + } + } + if ($publishers === [] && self::clean($book['editore_nome'] ?? '') !== '') { + $publishers[] = self::clean($book['editore_nome']); + } + $year = self::clean($book['anno_pubblicazione'] ?? ''); + $publication = ''; + if ($place !== '') { + $publication = $place; + } + if ($publishers !== []) { + $publication .= ($publication !== '' ? ' : ' : '') . implode(' : ', $publishers); + } + if ($year !== '') { + $publication .= ($publication !== '' ? ', ' : '') . $year; + } + if ($publication !== '') { + $areas[] = $publication; + } + + // Area 5: physical description + $pages = (int) ($book['numero_pagine'] ?? 0); + $physical = $pages > 0 ? $pages . ' p.' : ''; + $dimensions = self::clean($book['dimensioni'] ?? ''); + if ($dimensions !== '') { + $physical .= ($physical !== '' ? ' ; ' : '') . $dimensions; + } + if ($physical !== '') { + $areas[] = $physical; + } + + // Area 6: series + $series = self::clean($book['collana'] ?? ''); + if ($series !== '') { + $number = self::clean($book['numero_serie'] ?? ''); + $areas[] = '(' . $series . ($number !== '' ? ' ; ' . $number : '') . ')'; + } + + // ". – " between areas; an area already ending with a full stop + // (an abbreviation such as "ed.") does not get a second one. + $body = ''; + foreach ($areas as $i => $area) { + if ($i > 0) { + $body .= str_ends_with($body, '.') ? ' – ' : self::AREA; + } + $body .= $area; + } + $body = rtrim($body, '.') . '.'; + + $isbn = self::clean($book['isbn13'] ?? '') ?: self::clean($book['isbn10'] ?? ''); + $isbnLine = $isbn !== '' ? 'ISBN ' . $isbn : ''; + $ean = self::clean($book['ean'] ?? ''); + if ($ean !== '' && $ean !== $isbn) { + $isbnLine .= ($isbnLine !== '' ? self::AREA : '') . 'EAN ' . $ean; + } + + // Tracings: subjects (genre, sub-genre, keywords) then the title added entry + $subjects = []; + foreach (['genere_nome', 'sottogenere_nome'] as $key) { + $value = self::clean($book[$key] ?? ''); + if ($value !== '') { + $subjects[mb_strtolower($value)] ??= $value; + } + } + foreach (explode(',', (string) ($book['parole_chiave'] ?? '')) as $keyword) { + $keyword = self::clean($keyword); + if ($keyword !== '') { + $subjects[mb_strtolower($keyword)] ??= $keyword; + } + } + $tracings = []; + $n = 1; + foreach ($subjects as $subject) { + $tracings[] = $n++ . '. ' . rtrim($subject, '.') . '.'; + } + if ($heading !== '' && $title !== '') { + $tracings[] = 'I. ' . __('Titolo') . '.'; + } + + $call = self::clean($book['collocazione'] ?? ''); + $dewey = self::clean($book['classificazione_dewey'] ?? ''); + if ($call === '') { + $call = $dewey; + } elseif ($dewey !== '') { + $call .= "\n" . $dewey; + } + + return [ + 'call' => $call, + 'heading' => $heading !== '' ? $heading : $title, + 'body' => $body, + 'isbn' => $isbnLine, + 'tracings' => implode(' ', $tracings), + 'inventory' => self::clean($book['numero_inventario'] ?? ''), + ]; + } + + /** + * One card per page, in the order given. + * + * @param list> $books rows from BookRepository::getById() + */ + public static function render(array $books, string $libraryName = ''): string + { + $pdf = new TCPDF('L', 'mm', [self::WIDTH_MM, self::HEIGHT_MM], true, 'UTF-8', false); + $pdf->SetCreator($libraryName !== '' ? $libraryName : 'Pinakes'); + $pdf->SetAuthor($libraryName !== '' ? $libraryName : 'Pinakes'); + $pdf->SetTitle(__('Schede catalografiche')); + $pdf->setPrintHeader(false); + $pdf->setPrintFooter(false); + $pdf->SetMargins(8, 7, 8); + // One card, one page: nothing may flow onto a second page. Text that + // does not fit the card shrinks, then the tracings are shortened. + $pdf->SetAutoPageBreak(false); + + foreach ($books as $book) { + $card = self::lines($book); + $pdf->AddPage(); + $bottom = self::HEIGHT_MM - ($card['inventory'] !== '' ? 13.0 : 6.0); + + // Call number, top left + $pdf->SetFont('dejavusans', 'B', 8); + $pdf->MultiCell(30, 0, $card['call'], 0, 'L', false, 0, 8, 7); + + // Main entry heading + self::fitBlock($pdf, $card['heading'], 40, 7, $bottom, 9.0, 'B'); + $pdf->Ln(1.5); + + // Body, indented as on a traditional card + self::fitBlock($pdf, $card['body'], 44, $pdf->GetY(), $bottom, 8.0); + if ($card['isbn'] !== '' && $pdf->GetY() + 4 < $bottom) { + $pdf->Ln(1); + self::fitBlock($pdf, $card['isbn'], 44, $pdf->GetY(), $bottom, 8.0); + } + if ($card['tracings'] !== '' && $pdf->GetY() + 5 < $bottom) { + $pdf->Ln(2); + self::fitBlock($pdf, $card['tracings'], 44, $pdf->GetY(), $bottom, 8.0); + } + if ($card['inventory'] !== '') { + $pdf->SetFont('dejavusans', '', 7); + $pdf->MultiCell(40, 0, __('Inv.') . ' ' . $card['inventory'], 0, 'L', false, 1, 8, self::HEIGHT_MM - 12); + } + } + + return $pdf->Output('', 'S'); + } + + /** + * Write $text from ($x, $y) to the right margin without passing $bottom: + * the font shrinks down to 5.5 pt, then trailing words give way to "…". + */ + private static function fitBlock(TCPDF $pdf, string $text, float $x, float $y, float $bottom, float $size, string $style = ''): void + { + if ($text === '') { + return; + } + $width = self::WIDTH_MM - 8.0 - $x; + $room = $bottom - $y; + if ($room <= 0) { + return; + } + $fits = static function (string $candidate, float $pt) use ($pdf, $style, $width, $room): bool { + $pdf->SetFont('dejavusans', $style, $pt); + return $pdf->getStringHeight($width, $candidate) <= $room; + }; + for ($pt = $size; $pt > 5.5 && !$fits($text, $pt); $pt -= 0.5) { + } + $pt = max(5.5, $pt); + if (!$fits($text, $pt)) { + $words = preg_split('/\s+/u', $text) ?: []; + while (count($words) > 1) { + array_pop($words); + $candidate = rtrim(implode(' ', $words), " ,;.") . ' …'; + if ($fits($candidate, $pt)) { + $text = $candidate; + break; + } + } + if (count($words) <= 1) { + $text = '…'; + } + } + $pdf->SetFont('dejavusans', $style, $pt); + $pdf->MultiCell($width, 0, $text, 0, 'L', false, 1, $x, $y); + } + + /** + * @param mixed $authors list of author rows (nome, pseudonimo) + * @return list + */ + private static function names(mixed $authors): array + { + $out = []; + foreach ((array) $authors as $author) { + if (is_array($author)) { + $name = AuthorName::display($author); + if ($name !== '') { + $out[] = $name; + } + } + } + return $out; + } + + private static function clean(mixed $value): string + { + if (!is_scalar($value)) { + return ''; + } + return trim(preg_replace('/\s+/u', ' ', HtmlHelper::decode((string) $value)) ?? ''); + } +} diff --git a/app/Support/CataloguingExtras.php b/app/Support/CataloguingExtras.php new file mode 100644 index 000000000..f6d8753a0 --- /dev/null +++ b/app/Support/CataloguingExtras.php @@ -0,0 +1,148 @@ + */ + private static array $columnCache = []; + + /** + * Persist the supplier and RDA overrides present in a form submission. + * + * @param array $data raw form data + */ + public static function save(mysqli $db, int $bookId, array $data): void + { + $set = []; + $types = ''; + $values = []; + + // Only the empty choice ("no supplier", "Automatic") clears a value. + // A malformed id, a supplier deleted meanwhile or an unknown RDA code + // leaves the stored value as it is instead of wiping it. A module + // switched off (Settings → Modules) never writes its field: the stored + // value survives a save made while it is off. + if (array_key_exists('fornitore_id', $data) && Features::enabled('suppliers') && self::hasColumn($db, 'fornitore_id')) { + if (self::isEmptyChoice($data['fornitore_id'])) { + $set[] = 'fornitore_id = NULL'; + } elseif (($supplierId = self::existingSupplierId($db, $data['fornitore_id'])) !== null) { + $set[] = 'fornitore_id = ?'; + $types .= 'i'; + $values[] = $supplierId; + } + } + + foreach (RdaTypes::COLUMNS as $kind => $column) { + if (!array_key_exists($column, $data) || !Features::enabled('rda_types') || !self::hasColumn($db, $column)) { + continue; + } + if (self::isEmptyChoice($data[$column])) { + $set[] = "{$column} = NULL"; + } elseif (($code = RdaTypes::normalize($kind, $data[$column])) !== null) { + $set[] = "{$column} = ?"; + $types .= 's'; + $values[] = $code; + } + } + + if ($set === []) { + return; + } + + $sql = 'UPDATE libri SET ' . implode(', ', $set) . ' WHERE id = ? AND deleted_at IS NULL'; + $types .= 'i'; + $values[] = $bookId; + $stmt = $db->prepare($sql); + if ($stmt === false) { + throw new \RuntimeException('Unable to prepare the cataloguing extras update'); + } + try { + $stmt->bind_param($types, ...$values); + $stmt->execute(); + } finally { + $stmt->close(); + } + } + + /** + * Link a book to a supplier chosen for an import batch. Nothing is + * written when the supplier was deleted meanwhile or the column does not + * exist yet (the import must never clear a supplier on its own): FALSE + * then, so the import can report the book as left without it. + */ + public static function assignSupplier(mysqli $db, int $bookId, int $supplierId): bool + { + if (!Features::enabled('suppliers') || !self::hasColumn($db, 'fornitore_id') || self::existingSupplierId($db, $supplierId) === null) { + return false; + } + $stmt = $db->prepare('UPDATE libri SET fornitore_id = ? WHERE id = ? AND deleted_at IS NULL'); + if ($stmt === false) { + throw new \RuntimeException('Unable to prepare the supplier update'); + } + try { + $stmt->bind_param('ii', $supplierId, $bookId); + $stmt->execute(); + } finally { + $stmt->close(); + } + return true; + } + + /** The form's empty option: no value, or a blank string. */ + private static function isEmptyChoice(mixed $raw): bool + { + return $raw === null || (is_string($raw) && trim($raw) === ''); + } + + /** A submitted supplier id that exists, or NULL ("no supplier"). */ + public static function existingSupplierId(mysqli $db, mixed $raw): ?int + { + $id = filter_var($raw, FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]); + if ($id === false) { + return null; + } + $stmt = $db->prepare('SELECT id FROM fornitori WHERE id = ?'); + if ($stmt === false) { + return null; + } + try { + $stmt->bind_param('i', $id); + $stmt->execute(); + $row = $stmt->get_result()->fetch_assoc(); + } finally { + $stmt->close(); + } + return $row ? (int) $row['id'] : null; + } + + private static function hasColumn(mysqli $db, string $column): bool + { + if (!array_key_exists($column, self::$columnCache)) { + $stmt = $db->prepare("SELECT COUNT(*) AS c FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'libri' AND COLUMN_NAME = ?"); + $exists = false; + if ($stmt !== false) { + $stmt->bind_param('s', $column); + $stmt->execute(); + $exists = (int) ($stmt->get_result()->fetch_assoc()['c'] ?? 0) > 0; + $stmt->close(); + } + self::$columnCache[$column] = $exists; + } + return self::$columnCache[$column]; + } +} diff --git a/app/Support/ConfigStore.php b/app/Support/ConfigStore.php index 178461e82..6fcb041b6 100644 --- a/app/Support/ConfigStore.php +++ b/app/Support/ConfigStore.php @@ -153,8 +153,16 @@ public static function all(): array 'litespeed_catalog_ttl' => 120, 'litespeed_book_ttl' => 300, ], + // Optional 0.9.0 modules (Settings → Modules / Loans). The default + // is the upgrade one: an install without the row behaves as an + // upgrade (Features::DEFAULTS is the single source). + 'features' => array_map(static fn (string $v): bool => $v === '1', Features::DEFAULTS), ]; + // NULL marks an absent granular preference, distinct from an explicit OFF. + foreach (Features::INHERITED as $key => $parent) { + $defaults['features'][$key] = null; + } $localizedDefaults = self::getLocaleDefaultTexts(); if (!empty($localizedDefaults)) { $defaults = self::mergeRecursiveDistinct($defaults, $localizedDefaults); @@ -468,6 +476,12 @@ private static function loadDatabaseSettings(): array if (isset($raw['app']['timezone']) && $raw['app']['timezone'] !== '') { self::$dbSettingsCache['app']['timezone'] = (string) $raw['app']['timezone']; } + // app.currency (prices, sanzione, patron ledger): without this + // mapping a stored value would be inert (allowlist trap) and + // every reader would keep seeing the EUR default. + if (isset($raw['app']['currency']) && preg_match('/^[A-Za-z]{3}$/', (string) $raw['app']['currency']) === 1) { + self::$dbSettingsCache['app']['currency'] = strtoupper((string) $raw['app']['currency']); + } // Load social links $socialKeys = ['social_facebook', 'social_twitter', 'social_instagram', 'social_linkedin', 'social_bluesky', 'social_telegram']; foreach ($socialKeys as $socialKey) { @@ -667,6 +681,23 @@ private static function loadDatabaseSettings(): array } } + // features.* (optional modules): without this mapping a saved + // switch would be inert (allowlist trap). Only the known keys, + // as booleans. + if (!empty($raw['features'])) { + self::$dbSettingsCache['features'] = []; + foreach (array_keys(Features::DEFAULTS) as $featureKey) { + if (!isset($raw['features'][$featureKey])) { + continue; + } + $value = filter_var($raw['features'][$featureKey], FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE); + if ($value === null) { + $value = in_array((string) $raw['features'][$featureKey], ['1', 'true', 'yes'], true); + } + self::$dbSettingsCache['features'][$featureKey] = $value; + } + } + if (!empty($raw['system'])) { self::$dbSettingsCache['system'] = []; foreach ($raw['system'] as $key => $value) { diff --git a/app/Support/ContentSecurityPolicy.php b/app/Support/ContentSecurityPolicy.php index 50d0b48f4..385a1a40b 100644 --- a/app/Support/ContentSecurityPolicy.php +++ b/app/Support/ContentSecurityPolicy.php @@ -28,6 +28,24 @@ public static function createNonce(): string return implode('-', str_split(bin2hex(random_bytes(16)), 8)); } + /** + * The Permissions-Policy header sent with every response. + * + * camera=(self): two same-origin features call getUserMedia — the copy + * barcode scanner (zxing-wasm) on the loan/return pages and the patron + * photo taken from the webcam in the user form. Denying it (camera=()) + * blocks both; no other origin (no embedded frame) gets the camera. + * geolocation and microphone stay denied: nothing in the app uses them. + * The CSP needs nothing for the photo: the live MediaStream is not a URL + * that media-src governs, the captured frame is drawn on a canvas and sent + * to the same origin (connect-src 'self'), and the stored photo is an + * ordinary same-origin image (img-src 'self'). + */ + public static function permissionsPolicy(): string + { + return 'geolocation=(), microphone=(), camera=(self)'; + } + public static function header(string $nonce, bool $upgradeInsecureRequests = false): string { if (!preg_match(self::NONCE_PATTERN, $nonce)) { diff --git a/app/Support/ContributorSync.php b/app/Support/ContributorSync.php index 768f72978..b970400eb 100644 --- a/app/Support/ContributorSync.php +++ b/app/Support/ContributorSync.php @@ -20,7 +20,7 @@ final class ContributorSync private const ROLES = ['co-autore', 'traduttore', 'illustratore', 'curatore', 'colorista']; /** @var list */ - private const IMPORT_SOURCES = ['csv', 'librarything', 'legacy-backfill']; + private const IMPORT_SOURCES = ['csv', 'librarything', 'marc', 'legacy-backfill']; /** * Split a legacy contributor value into individual names. diff --git a/app/Support/DataIntegrity.php b/app/Support/DataIntegrity.php index d8c5dec3e..094b206b0 100644 --- a/app/Support/DataIntegrity.php +++ b/app/Support/DataIntegrity.php @@ -26,6 +26,9 @@ public function recalculateAllBookAvailability(bool $insideTransaction = false): // stored dates are app-tz, so CURDATE() (DB session tz) drifts by a day in the window // around local midnight. Validated Y-m-d string → safe to interpolate. $today = \App\Support\DateHelper::today(); + // Consultation-only copies are never lendable capacity: the public + // counter excludes them like CapacityService and the allocators do. + $lendable = (new \App\Services\CirculationPolicy($this->db))->lendableCopySql('c'); try { if (!$insideTransaction) { @@ -74,6 +77,10 @@ public function recalculateAllBookAvailability(bool $insideTransaction = false): // - Include copie 'disponibile' e 'prenotato' (fisicamente in biblioteca) // - Esclude quelle con prestiti attivi già iniziati (in_corso, in_ritardo, da_ritirare, prenotato con data <= oggi) // - Sottrae le prenotazioni attive che coprono la data odierna (slot-level) + // - Esclude le copie di sola consultazione (copie.solo_consultazione): non sono + // capacità prestabile (CapacityService, allocatori), quindi non contano come + // disponibili e non rendono 'disponibile' lo stato del libro. copie_totali + // resta il numero di copie fisiche in circolazione, consultazione compresa. // Note: 'da_ritirare' conta come slot occupato anche se la copia fisica è ancora in biblioteca $stmt = $this->db->prepare(" UPDATE libri l @@ -91,6 +98,7 @@ public function recalculateAllBookAvailability(bool $insideTransaction = false): ) WHERE c.libro_id = l.id AND c.stato IN ('disponibile', 'prenotato') + AND {$lendable} AND p.id IS NULL ) - ( SELECT COUNT(*) @@ -124,6 +132,7 @@ public function recalculateAllBookAvailability(bool $insideTransaction = false): ) WHERE c.libro_id = l.id AND c.stato IN ('disponibile', 'prenotato') + AND {$lendable} AND p.id IS NULL ) - ( SELECT COUNT(*) @@ -139,7 +148,7 @@ public function recalculateAllBookAvailability(bool $insideTransaction = false): WHEN (SELECT COUNT(*) FROM copie c WHERE c.libro_id = l.id AND c.stato = 'prestato') > 0 THEN 'prestato' WHEN (SELECT COUNT(*) FROM copie c WHERE c.libro_id = l.id AND c.stato = 'prenotato') > 0 THEN 'prenotato' -- Copia in biblioteca ma assorbita da una prenotazione attiva → riservata (A1) - WHEN (SELECT COUNT(*) FROM copie c WHERE c.libro_id = l.id AND c.stato IN ('disponibile', 'prenotato')) > 0 THEN 'prenotato' + WHEN (SELECT COUNT(*) FROM copie c WHERE c.libro_id = l.id AND c.stato IN ('disponibile', 'prenotato') AND {$lendable}) > 0 THEN 'prenotato' -- Il libro HA copie ma nessuna è circolante (tutte in manutenzione/ -- in_restauro/perso/danneggiato/in_trasferimento) e nessuna è -- prestata/prenotata → non è disponibile. Senza questo ramo lo stato @@ -306,6 +315,7 @@ public function recalculateBookAvailability(int $bookId, bool $insideTransaction // App-timezone "today" (see recalculateAllBookAvailability) — interpolated in place of // the DB CURDATE() so the availability comparisons don't drift around local midnight. $today = \App\Support\DateHelper::today(); + $lendable = (new \App\Services\CirculationPolicy($this->db))->lendableCopySql('c'); try { if (!$insideTransaction) { $this->db->begin_transaction(); @@ -363,6 +373,10 @@ public function recalculateBookAvailability(int $bookId, bool $insideTransaction // - Include copie 'disponibile' e 'prenotato' (fisicamente in biblioteca) // - Esclude quelle con prestiti attivi già iniziati (in_corso, in_ritardo, da_ritirare, prenotato con data <= oggi) // - Sottrae le prenotazioni attive che coprono la data odierna (slot-level) + // - Esclude le copie di sola consultazione (copie.solo_consultazione): non sono + // capacità prestabile (CapacityService, allocatori), quindi non contano come + // disponibili e non rendono 'disponibile' lo stato del libro. copie_totali + // resta il numero di copie fisiche in circolazione, consultazione compresa. // Note: 'da_ritirare' conta come slot occupato anche se la copia fisica è ancora in biblioteca $stmt = $this->db->prepare(" UPDATE libri l @@ -380,6 +394,7 @@ public function recalculateBookAvailability(int $bookId, bool $insideTransaction ) WHERE c.libro_id = ? AND c.stato IN ('disponibile', 'prenotato') + AND {$lendable} AND p.id IS NULL ) - ( SELECT COUNT(*) @@ -413,6 +428,7 @@ public function recalculateBookAvailability(int $bookId, bool $insideTransaction ) WHERE c.libro_id = ? AND c.stato IN ('disponibile', 'prenotato') + AND {$lendable} AND p.id IS NULL ) - ( SELECT COUNT(*) @@ -430,7 +446,7 @@ public function recalculateBookAvailability(int $bookId, bool $insideTransaction -- Copia fisicamente in biblioteca ma assorbita da una prenotazione attiva: -- è riservata, non 'disponibile' né stantia (A1). Evita di lasciare il libro -- bloccato su uno stato precedente (es. 'prestato') dopo la restituzione. - WHEN (SELECT COUNT(*) FROM copie c WHERE c.libro_id = ? AND c.stato IN ('disponibile', 'prenotato')) > 0 THEN 'prenotato' + WHEN (SELECT COUNT(*) FROM copie c WHERE c.libro_id = ? AND c.stato IN ('disponibile', 'prenotato') AND {$lendable}) > 0 THEN 'prenotato' -- Come nel ricalcolo globale: copie presenti ma tutte fuori -- circolazione e nessuna prestata/prenotata → 'non_disponibile'. WHEN (SELECT COUNT(*) FROM copie c WHERE c.libro_id = ?) > 0 THEN 'non_disponibile' diff --git a/app/Support/ExactSearch.php b/app/Support/ExactSearch.php new file mode 100644 index 000000000..d788c3d2b --- /dev/null +++ b/app/Support/ExactSearch.php @@ -0,0 +1,332 @@ + + */ + public static function tokenize(string $query): array + { + // One pass over the characters: an inch mark is appended to its word + // as it is met, so no stand-in character is substituted and later + // swapped back (a query that itself holds that character would come + // back altered). + $chars = preg_split('//u', self::scrub($query), -1, PREG_SPLIT_NO_EMPTY) ?: []; + $terms = []; + $open = false; + $word = ''; + $phrase = ''; + $afterDigit = false; + foreach ($chars as $char) { + if (str_contains(self::QUOTES, $char)) { + if ($open) { + $inner = self::collapse($phrase); + if ($inner !== '') { + $terms[] = ['term' => $inner, 'exact' => true]; + } + $phrase = ''; + $open = false; + } elseif (($char === '"' || $char === '”') && $afterDigit) { + $word .= '"'; + } else { + if ($word !== '') { + $terms[] = ['term' => $word, 'exact' => false]; + } + $word = ''; + $open = true; + } + $afterDigit = false; + continue; + } + if ($open) { + $phrase .= $char; + } elseif (preg_match('/^\s$/u', $char) === 1) { + if ($word !== '') { + $terms[] = ['term' => $word, 'exact' => false]; + } + $word = ''; + } else { + $word .= $char; + } + $afterDigit = preg_match('/^\d$/u', $char) === 1; + } + if ($open) { + $inner = self::collapse($phrase); + if ($inner !== '') { + $terms[] = ['term' => $inner, 'exact' => true]; + } + } elseif ($word !== '') { + $terms[] = ['term' => $word, 'exact' => false]; + } + return $terms; + } + + /** + * Parse a single-value column filter (admin list "Name" box) into ONE + * term, for callers that can only bind one pattern (prefix ordering, + * in-PHP matching). The value is exact only when it is quoted as a + * whole: every token is a quoted group (`"Häffner"`, `"Thomas" "Mann"`, + * the groups joined into one phrase). A value that mixes quoted and + * unquoted words (`"häffner" rolf`) is not one exact phrase: it comes + * back tolerant, with the quotes dropped so they are never searched as + * literal characters. Callers that can bind one condition per word use + * filterCondition(), which keeps each word's own rule. + * + * @return array{term: string, exact: bool} + */ + public static function parseFilter(string $value): array + { + $value = trim(self::scrub($value)); + $tokens = self::tokenize($value); + if ($tokens === []) { + // Only quotes (or nothing): no term, so no filter. + return ['term' => self::hasQuote($value) ? '' : $value, 'exact' => self::hasQuote($value)]; + } + if (!self::hasQuote($value)) { + return ['term' => $value, 'exact' => false]; + } + $words = self::collapse(implode(' ', array_column($tokens, 'term'))); + $wholeQuoted = !in_array(false, array_column($tokens, 'exact'), true); + return ['term' => $words, 'exact' => $wholeQuoted]; + } + + /** + * The name inside a value typed wholly in quotes (`"Rolf Häffner"`), or + * null otherwise. Used where a name is SAVED rather than searched: quotes + * there mean "this exact spelling is a different person/publisher", and + * they are never part of the stored name. A name that only contains a + * quoted part (`"Il Mulino", Bologna`, `"Pippo" Inzaghi`) is not quoted + * as a whole and is kept exactly as typed. + */ + public static function quotedValue(string $value): ?string + { + ['term' => $term, 'exact' => $exact] = self::parseFilter($value); + return $exact && $term !== '' ? $term : null; + } + + /** + * WHERE fragment for "$expr is exactly $value": accents distinct, case + * ignored. For lookups by a name taken from a link (author page, publisher + * filter), where the collation would otherwise treat `Haffner` and + * `Häffner` as the same name. The plain `=` stays first so an index on + * $expr still narrows the rows; the binary comparison then drops the + * accent-folded matches. + * + * @return array{sql: string, params: list, types: string} + */ + public static function equalsCondition(string $expr, string $value): array + { + return [ + 'sql' => "({$expr} = ? AND LOWER({$expr}) COLLATE utf8mb4_bin = LOWER(?))", + 'params' => [$value, $value], + 'types' => 'ss', + ]; + } + + /** + * Same as equalsCondition() for a list of accepted values (IN). + * + * @param list $values + * @return array{sql: string, params: list, types: string}|null + */ + public static function inCondition(string $expr, array $values): ?array + { + if ($values === []) { + return null; + } + $plain = implode(',', array_fill(0, count($values), '?')); + $lowered = implode(',', array_fill(0, count($values), 'LOWER(?)')); + return [ + 'sql' => "({$expr} IN ({$plain}) AND LOWER({$expr}) COLLATE utf8mb4_bin IN ({$lowered}))", + 'params' => array_merge($values, $values), + 'types' => str_repeat('s', 2 * count($values)), + ]; + } + + /** + * ORDER BY fragment that puts the exact spelling (accents included, case + * ignored) first among rows an accent-folding `$expr = ?` admitted. Bind + * the same value again. Used by find-by-name lookups so an existing + * `Häffner` wins over `Haffner` when both are on file, also when the + * incoming name differs only in case. + */ + public static function exactFirstSql(string $expr): string + { + return "(LOWER({$expr}) COLLATE utf8mb4_bin = LOWER(?)) DESC"; + } + + /** + * WHERE fragment for a single-value filter matched against any of $exprs + * (OR), honouring quotes via parseFilter(). Null when nothing is left to + * match (e.g. the value was only a quote). + * + * A box with more than one term is not one phrase: every term must + * match, each with its own rule, as in wordsCondition() and the main + * search box. That covers mixed words (`archivio "haffner"`) and several + * quoted groups alike: `"Thomas" "Mann"` is two exact words in any + * order, exactly as in the book search, and `"Thomas Mann"` is the + * phrase. parseFilter() joins the groups only for callers that can bind + * a single pattern. + * + * @param list $exprs + * @return array{sql: string, params: list, types: string}|null + */ + public static function filterCondition(array $exprs, string $value): ?array + { + $tokens = self::tokenize($value); + if (count($tokens) > 1 && in_array(true, array_column($tokens, 'exact'), true)) { + // Same budget as the other word searches: a pasted wall of words + // must not turn into thousands of placeholders. + return self::wordsCondition($exprs, $value, self::MAX_FILTER_TERMS); + } + ['term' => $term, 'exact' => $exact] = self::parseFilter($value); + if ($term === '' || $exprs === []) { + return null; + } + $sql = []; + $params = []; + foreach ($exprs as $expr) { + $sql[] = self::likeSql($expr, $exact); + $params[] = self::likePattern($term); + } + return [ + 'sql' => '(' . implode(' OR ', $sql) . ')', + 'params' => $params, + 'types' => str_repeat('s', count($params)), + ]; + } + + /** + * WHERE fragment for a multi-word query: every term (see tokenize()) must + * match at least one of $exprs. Null when the query has no usable term. + * + * @param list $exprs + * @return array{sql: string, params: list, types: string}|null + */ + public static function wordsCondition(array $exprs, string $query, ?int $maxTerms = null): ?array + { + $sql = []; + $params = []; + $terms = self::tokenize($query); + if ($maxTerms !== null) { + $terms = array_slice($terms, 0, max(0, $maxTerms)); + } + foreach ($terms as ['term' => $term, 'exact' => $exact]) { + $or = []; + foreach ($exprs as $expr) { + $or[] = self::likeSql($expr, $exact); + $params[] = self::likePattern($term); + } + $sql[] = '(' . implode(' OR ', $or) . ')'; + } + if ($sql === [] || $exprs === []) { + return null; + } + return [ + 'sql' => '(' . implode(' AND ', $sql) . ')', + 'params' => $params, + 'types' => str_repeat('s', count($params)), + ]; + } + + /** + * SQL for "$expr contains the bound pattern". Bind likePattern($term). + */ + public static function likeSql(string $expr, bool $exact): string + { + return $exact + ? "LOWER({$expr}) COLLATE utf8mb4_bin LIKE LOWER(?) ESCAPE '\\\\'" + : "{$expr} LIKE ? ESCAPE '\\\\'"; + } + + /** `%term%` with LIKE metacharacters escaped (backslash is the ESCAPE char). */ + public static function likePattern(string $term): string + { + return '%' . str_replace(['\\', '%', '_'], ['\\\\', '\\%', '\\_'], $term) . '%'; + } + + /** + * Replace invalid UTF-8 so the /u regexes cannot fail. A failed split + * would yield no term at all, and "no term" means "no filter": a garbled + * query would then list the whole catalogue instead of nothing. + */ + private static function scrub(string $value): string + { + return mb_check_encoding($value, 'UTF-8') ? $value : mb_scrub($value, 'UTF-8'); + } + + private static function hasQuote(string $value): bool + { + return preg_match('/[' . preg_quote(self::QUOTES, '/') . ']/u', self::protectInchMarks($value)) === 1; + } + + /** + * A straight or closing double quote right after a digit, outside any + * quoted group, is an inch mark (`5" floppy`, `24" monitor`), not the start + * of an exact group: it is swapped for a private-use placeholder so the + * tokenizer keeps it as a literal character. Inside a group the same + * character still closes it (`"1984"`). + */ + private static function protectInchMarks(string $value): string + { + if (!str_contains($value, '"') && !str_contains($value, '”')) { + return $value; + } + $chars = preg_split('//u', $value, -1, PREG_SPLIT_NO_EMPTY) ?: []; + $open = false; + $prev = ''; + foreach ($chars as $i => $char) { + if (str_contains(self::QUOTES, $char)) { + if (!$open && ($char === '"' || $char === '”') && preg_match('/^\d$/u', $prev) === 1) { + $chars[$i] = self::INCH_PLACEHOLDER; + } else { + $open = !$open; + } + } + $prev = $chars[$i]; + } + return implode('', $chars); + } + + private static function collapse(string $value): string + { + return trim((string) preg_replace('/\s+/u', ' ', $value)); + } +} diff --git a/app/Support/Features.php b/app/Support/Features.php new file mode 100644 index 000000000..23acde353 --- /dev/null +++ b/app/Support/Features.php @@ -0,0 +1,257 @@ +` row in system_settings. + * + * DEFAULTS provides the static upgrade defaults. INHERITED controls instead + * preserve their former parent when the row is absent. FRESH is what + * a new installation starts with (installer/database/data_*.sql). Split controls + * inherit their former parent until the migration or a settings save freezes + * that choice; explicit OFF always wins. Both lists + * are checked against the seeds, the migration and ConfigStore by + * tests/feature-toggles-registry.unit.php. + * + * Circulation modules and their controls are also off in catalogue mode: that + * switch stays the master switch of circulation. + */ +final class Features +{ + /** @var array Static defaults; INHERITED controls preserve the former parent. */ + public const DEFAULTS = [ + 'stocktake' => '0', + 'marc' => '1', + 'visitor_log' => '0', + 'catalogue_cards' => '1', + 'suppliers' => '0', + 'rda_types' => '0', + 'patron_cards' => '1', + 'user_import' => '1', + 'circulation_rules' => '1', + 'closures_calendar' => '1', + 'fines' => '0', + 'patron_photos' => '1', + 'consultation_only' => '1', + 'automatic_card_expiry' => '1', + 'automatic_fines' => '0', + 'debt_block' => '0', + 'loss_penalties' => '0', + 'attendance_retention' => '0', + ]; + + /** @var array fresh-install default (data_*.sql seeds) */ + public const FRESH = [ + 'stocktake' => '1', + 'marc' => '1', + 'visitor_log' => '0', + 'catalogue_cards' => '1', + 'suppliers' => '1', + 'rda_types' => '1', + 'patron_cards' => '1', + 'user_import' => '1', + 'circulation_rules' => '1', + 'closures_calendar' => '1', + 'fines' => '0', + 'patron_photos' => '1', + 'consultation_only' => '1', + 'automatic_card_expiry' => '1', + 'automatic_fines' => '0', + 'debt_block' => '0', + 'loss_penalties' => '0', + 'attendance_retention' => '0', + ]; + + /** Modules that belong to circulation: off whenever catalogue mode is on. */ + public const CIRCULATION = ['circulation_rules', 'closures_calendar', 'fines', 'visitor_log', 'consultation_only', 'automatic_card_expiry', 'automatic_fines', 'debt_block', 'loss_penalties']; + + /** Controls saved from Settings → Modules. */ + public const MODULES_TAB = ['stocktake', 'marc', 'catalogue_cards', 'suppliers', 'rda_types', 'patron_cards', 'user_import', 'visitor_log', 'patron_photos', 'attendance_retention']; + + /** Modules saved from Settings → Loans. */ + public const LOANS_TAB = ['circulation_rules', 'closures_calendar', 'fines', 'consultation_only', 'automatic_card_expiry', 'automatic_fines', 'debt_block', 'loss_penalties']; + + /** Missing granular choices reproduce the old parent setting until saved. */ + public const INHERITED = [ + 'patron_photos' => 'patron_cards', + 'consultation_only' => 'circulation_rules', + 'automatic_card_expiry' => 'circulation_rules', + 'automatic_fines' => 'fines', + 'debt_block' => 'fines', + 'loss_penalties' => 'fines', + ]; + + /** Dependent automations require their parent module as well as their own switch. */ + private const PARENTS = ['automatic_fines' => 'fines', 'debt_block' => 'fines', 'loss_penalties' => 'fines', 'automatic_card_expiry' => 'circulation_rules']; + + /** @var array test seam: forced values, ahead of the stored setting */ + private static array $overrides = []; + + /** @return list */ + public static function keys(): array + { + return array_keys(self::DEFAULTS); + } + + /** + * Whether $feature is on. An unknown key is a programming error, never a + * module that is silently always on or always off. + */ + public static function enabled(string $feature): bool + { + if (!array_key_exists($feature, self::DEFAULTS)) { + throw new \InvalidArgumentException('Unknown feature: ' . $feature); + } + if (array_key_exists($feature, self::$overrides)) { + return self::$overrides[$feature]; + } + if (in_array($feature, self::CIRCULATION, true) && ConfigStore::isCatalogueMode()) { + return false; + } + if (isset(self::PARENTS[$feature]) && !self::enabled(self::PARENTS[$feature])) { + return false; + } + return self::stored($feature); + } + + /** The saved switch, ignoring catalogue mode (what the settings form shows). */ + public static function stored(string $feature): bool + { + if (!array_key_exists($feature, self::DEFAULTS)) { + throw new \InvalidArgumentException('Unknown feature: ' . $feature); + } + if (array_key_exists($feature, self::$overrides)) { + return self::$overrides[$feature]; + } + $value = ConfigStore::get('features.' . $feature, self::DEFAULTS[$feature] === '1'); + if ($value === null && isset(self::INHERITED[$feature])) { + return self::stored(self::INHERITED[$feature]); + } + if (is_bool($value)) { + return $value; + } + return in_array(strtolower(trim((string) $value)), ['1', 'true', 'yes', 'on'], true); + } + + /** Freeze inherited choices BEFORE changing their former parent (also for old forms). */ + public static function preserveInherited(\App\Models\SettingsRepository $repository): void + { + $values = self::snapshot(); + foreach (self::INHERITED as $key => $parent) { + if ($repository->get('features', $key) === null) { + $repository->set('features', $key, $values[$key] ? '1' : '0'); + } + } + } + + /** + * The saved switches keyed by feature (Settings → Modules, Loans tab). + * + * @return array + */ + public static function snapshot(): array + { + $out = []; + foreach (self::keys() as $key) { + $out[$key] = self::stored($key); + } + return $out; + } + + /** + * The standard refusal of an admin page whose module is off: a 302 to the + * dashboard with the message, or null when the module is on. + */ + public static function requireOrRedirect(string $feature, Response $response): ?Response + { + if (self::enabled($feature)) { + return null; + } + $_SESSION['error_message'] = __('Funzione disattivata nelle impostazioni.'); + return $response->withHeader('Location', url('/admin/dashboard'))->withStatus(302); + } + + /** Label shown in the settings (Italian base string, translated by __()). */ + public static function label(string $feature): string + { + return match ($feature) { + 'stocktake' => __('Revisione inventariale'), + 'marc' => __('Import ed export MARC'), + 'catalogue_cards' => __('Schede catalografiche'), + 'suppliers' => __('Fornitori'), + 'rda_types' => __('Tipi RDA'), + 'patron_cards' => __('Stampa delle tessere'), + 'user_import' => __('Importazione utenti da CSV'), + 'visitor_log' => __('Registro presenze'), + 'circulation_rules' => __('Regole per tipologia di lettore'), + 'closures_calendar' => __('Calendario chiusure'), + 'fines' => __('Registro movimenti'), + 'patron_photos' => __('Foto dei lettori'), + 'consultation_only' => __('Copie in sola consultazione'), + 'automatic_card_expiry' => __('Validità automatica delle tessere'), + 'automatic_fines' => __('Multe automatiche per ritardo'), + 'debt_block' => __('Blocco dei prestiti per debito'), + 'loss_penalties' => __('Riporta penalità per perdita e danno nel registro'), + 'attendance_retention' => __('Conservazione a registro presenze sospeso'), + default => throw new \InvalidArgumentException('Unknown feature: ' . $feature), + }; + } + + /** Help text shown under the label. */ + public static function help(string $feature): string + { + return match ($feature) { + 'stocktake' => __('Sessioni di revisione con scansione delle copie, esiti e report. Spenta: voce di menu e pagine non disponibili, il ruolo "Revisore inventario" resta ma non apre nulla.'), + 'marc' => __('Importazione massiva di record MARC21/UNIMARC/danMARC2, esportazione del catalogo e catalogazione derivata via SRU.'), + 'catalogue_cards' => __('Stampa della scheda catalografica di un libro o di una selezione.'), + 'suppliers' => __('Anagrafica fornitori e campo "Fornitore" nella scheda libro e nell\'import CSV.'), + 'rda_types' => __('Campi tipo di contenuto, media e supporto RDA nella scheda libro. Spenti, i valori restano salvati e gli export li derivano dal tipo di materiale.'), + 'patron_cards' => __('Stampa delle tessere in PDF. Foto e validità automatica hanno controlli separati.'), + 'user_import' => __('Caricamento massivo dei lettori con invito via email.'), + 'visitor_log' => __('Registro degli ingressi in biblioteca con conservazione a tempo. Contiene dati personali: attivalo solo se la biblioteca lo usa davvero.'), + 'circulation_rules' => __('Tipologie di lettore, matrice delle regole per tipo di materiale, limiti alle prenotazioni e durata automatica della tessera. Spente, valgono solo le regole globali di questa scheda.'), + 'closures_calendar' => __('Giorni di chiusura, festività e import ICS: le scadenze saltano i giorni chiusi. Spento, il calendario non viene applicato.'), + 'fines' => __('Addebiti manuali, pagamenti, ricevute e condoni. Multe automatiche, penalità e blocco per debito hanno controlli separati. Spento, conserva i movimenti.'), + 'patron_photos' => __('Acquisizione e visualizzazione delle foto, indipendenti dalla stampa delle tessere. Spegnendo il controllo le foto restano conservate.'), + 'consultation_only' => __('Rispetta il vincolo di consultazione delle copie anche senza regole per tipologia, su banco, sito, app e integrazioni.'), + 'automatic_card_expiry' => __('Applica la durata della tipologia alle nuove iscrizioni e alle approvazioni. Non modifica le scadenze esistenti; il rinnovo manuale resta disponibile.'), + 'automatic_fines' => __('Richiede il registro attivo e una tariffa positiva. Spento, nessuna nuova multa per ritardo viene calcolata o addebitata; tariffe e movimenti restano salvati.'), + 'debt_block' => __('Richiede il registro attivo e una soglia positiva. Può bloccare i prestiti anche senza generare nuove multe automatiche.'), + 'loss_penalties' => __('Riporta gli importi già assegnati ai prestiti nel registro contabile, comprese le penalità precedenti. Non determina nuovi importi.'), + 'attendance_retention' => __('Continua la cancellazione secondo i giorni di conservazione anche quando il registro è spento o in modalità catalogo. Con 0 giorni non cancella nulla.'), + default => throw new \InvalidArgumentException('Unknown feature: ' . $feature), + }; + } + + /** + * Force $feature on/off for the rest of the process (unit tests), or drop + * the forced value with null. + */ + public static function override(string $feature, ?bool $value): void + { + if (!array_key_exists($feature, self::DEFAULTS)) { + throw new \InvalidArgumentException('Unknown feature: ' . $feature); + } + if ($value === null) { + unset(self::$overrides[$feature]); + return; + } + self::$overrides[$feature] = $value; + } + + /** Force every module on (unit tests of the modules themselves). */ + public static function overrideAll(bool $value): void + { + foreach (self::keys() as $key) { + self::$overrides[$key] = $value; + } + } + + public static function resetOverrides(): void + { + self::$overrides = []; + } +} diff --git a/app/Support/Holidays/ChineseLunarCalendar.php b/app/Support/Holidays/ChineseLunarCalendar.php new file mode 100644 index 000000000..f7fe304c5 --- /dev/null +++ b/app/Support/Holidays/ChineseLunarCalendar.php @@ -0,0 +1,124 @@ +> $month)) !== 0 ? 30 : 29; + } + + /** Days in lunar $year. */ + public static function yearDays(int $year): int + { + $days = 0; + for ($m = 1; $m <= 12; $m++) { + $days += self::monthDays($year, $m); + } + $leap = self::leapMonth($year); + return $days + ($leap > 0 ? self::monthDays($year, $leap, true) : 0); + } + + /** + * The Gregorian date of lunar $year-$month-$day; null outside the table + * or for a day the month does not have (the 30th of a 29-day month). + */ + public static function toGregorian(int $year, int $month, int $day, bool $leap = false): ?string + { + if ($year < self::FIRST_YEAR || $year > self::LAST_YEAR || $month < 1 || $month > 12 || $day < 1 + || $day > self::monthDays($year, $month, $leap)) { + return null; + } + $offset = 0; + for ($y = self::FIRST_YEAR; $y < $year; $y++) { + $offset += self::yearDays($y); + } + $leapMonth = self::leapMonth($year); + for ($m = 1; $m < $month; $m++) { + $offset += self::monthDays($year, $m); + if ($m === $leapMonth) { + $offset += self::monthDays($year, $m, true); + } + } + if ($leap) { + $offset += self::monthDays($year, $month); + } + $offset += $day - 1; + return (new \DateTimeImmutable(self::EPOCH, new \DateTimeZone('UTC')))->modify('+' . $offset . ' days')->format('Y-m-d'); + } + + /** + * The days of Gregorian $year that are lunar $month/$day (regular month, + * not the leap one): the lunar year starting in $year and the one before, + * whose last months reach into January and February. + * + * @return list + */ + public static function datesIn(int $month, int $day, int $year): array + { + $out = []; + foreach ([$year - 1, $year] as $lunarYear) { + $date = self::toGregorian($lunarYear, $month, $day); + if ($date !== null && (int) substr($date, 0, 4) === $year) { + $out[] = $date; + } + } + return $out; + } +} diff --git a/app/Support/Holidays/CustomHolidays.php b/app/Support/Holidays/CustomHolidays.php new file mode 100644 index 000000000..fe86c6130 --- /dev/null +++ b/app/Support/Holidays/CustomHolidays.php @@ -0,0 +1,432 @@ + per connection */ + private static array $schema = []; + + public static function schemaReady(\mysqli $db): bool + { + $key = spl_object_id($db); + if (!isset(self::$schema[$key])) { + try { + $result = $db->query("SHOW TABLES LIKE '" . self::TABLE . "'"); + self::$schema[$key] = $result instanceof \mysqli_result && $result->num_rows > 0; + } catch (\Throwable $e) { + self::$schema[$key] = false; + } + } + return self::$schema[$key]; + } + + public static function resetSchemaCache(): void + { + self::$schema = []; + } + + /** @return list> every custom holiday, by name */ + public static function all(\mysqli $db): array + { + if (!self::schemaReady($db)) { + return []; + } + $result = $db->query('SELECT f.*, s.nome AS sede_nome FROM ' . self::TABLE . ' f LEFT JOIN sedi s ON s.id = f.sede_id ORDER BY f.nome, f.id'); + return $result instanceof \mysqli_result ? $result->fetch_all(MYSQLI_ASSOC) : []; + } + + /** @return array|null */ + public static function find(\mysqli $db, int $id): ?array + { + if (!self::schemaReady($db)) { + return null; + } + $stmt = $db->prepare('SELECT * FROM ' . self::TABLE . ' WHERE id = ?'); + $stmt->bind_param('i', $id); + $stmt->execute(); + $row = $stmt->get_result()->fetch_assoc(); + $stmt->close(); + return is_array($row) ? $row : null; + } + + /** + * The active holidays as calendar rules (LibraryCalendar). + * + * @return list> + */ + public static function calendarRules(\mysqli $db): array + { + $out = []; + foreach (self::all($db) as $row) { + if ((int) $row['attiva'] === 1) { + $out[] = self::rule($row); + } + } + return $out; + } + + /** + * One stored row as a HolidayCalculator rule. + * + * @param array $row + * @return array + */ + public static function rule(array $row): array + { + $month = (int) ($row['mese'] ?? 0); + $day = (int) ($row['giorno'] ?? 0); + $spec = match ((string) $row['tipo']) { + 'fixed' => ['fixed', sprintf('%02d-%02d', $month, $day)], + 'nth_weekday' => ['nth_weekday', $month, self::WEEKDAY_CODES[(int) ($row['giorno_settimana'] ?? 1)] ?? 'mon', (int) ($row['occorrenza'] ?? 1)], + 'easter' => ['easter', (int) ($row['scarto_giorni'] ?? 0)], + 'orthodox_easter' => ['orthodox_easter', (int) ($row['scarto_giorni'] ?? 0)], + 'calendar' => ['calendar', (string) $row['calendario'], $month, $day], + default => ['dates', self::parseDates((string) ($row['date_specifiche'] ?? ''))], + }; + $rule = [ + 'id' => 'custom:' . (int) ($row['id'] ?? 0), + 'name' => (string) $row['nome'], + 'rule' => $spec, + 'days' => max(1, (int) ($row['durata_giorni'] ?? 1)), + 'sede' => isset($row['sede_id']) && (int) $row['sede_id'] > 0 ? (int) $row['sede_id'] : null, + ]; + if (in_array($row['fine_settimana'] ?? null, self::WEEKEND_MODES, true)) { + $rule['observed'] = (string) $row['fine_settimana']; + } + if (($row['anno_inizio'] ?? null) !== null && (int) $row['anno_inizio'] > 0) { + $rule['from'] = (int) $row['anno_inizio']; + } + if (($row['anno_fine'] ?? null) !== null && (int) $row['anno_fine'] > 0) { + $rule['until'] = (int) $row['anno_fine']; + } + return $rule; + } + + /** @return list valid Y-m-d dates of a comma, space or line separated list, sorted */ + public static function parseDates(string $raw): array + { + $out = []; + foreach (preg_split('/[\s,;]+/', $raw) ?: [] as $token) { + if ($token !== '' && DateHelper::isISODateFormat($token)) { + $out[$token] = true; + } + } + $dates = array_keys($out); + sort($dates); + return array_map('strval', $dates); + } + + /** + * The next $count closed days of $row from $from on (the list shows them). + * + * @param array $row + * @return list + */ + public static function nextDates(array $row, string $from, int $count = 3): array + { + $rule = self::rule($row); + $year = (int) substr($from, 0, 4); + $out = []; + for ($y = $year; $y <= $year + 60 && count($out) < $count; $y++) { + foreach (HolidayCalculator::datesOf($rule, $y) as $date) { + if ($date >= $from) { + $out[$date] = true; + } + } + } + $dates = array_keys($out); + sort($dates); + return array_map('strval', array_slice($dates, 0, $count)); + } + + /** + * Validate the admin form. Field names: nome, tipo, fixed_mese, + * fixed_giorno, nth_occorrenza, nth_giorno_settimana, nth_mese, scarto, + * calendario, cal_mese_, cal_giorno, date_specifiche, + * durata_giorni, fine_settimana, anno_inizio, anno_fine, sede_id, attiva. + * + * @param array $data + * @param list $branchIds + * @return array|string the row to store, or the error message + */ + public static function validate(array $data, array $branchIds): array|string + { + $str = static fn (string $k): string => is_scalar($data[$k] ?? null) ? trim((string) $data[$k]) : ''; + $int = static function (string $k) use ($str): ?int { + $v = filter_var($str($k), FILTER_VALIDATE_INT); + return $v === false ? null : (int) $v; + }; + $name = mb_substr($str('nome'), 0, 255); + if ($name === '') { + return __('Indica il nome della festività.'); + } + $kind = $str('tipo'); + if (!in_array($kind, self::KINDS, true)) { + return __('Scegli come si calcola la festività.'); + } + $row = [ + 'nome' => $name, 'tipo' => $kind, 'mese' => null, 'giorno' => null, 'giorno_settimana' => null, + 'occorrenza' => null, 'scarto_giorni' => null, 'calendario' => null, 'date_specifiche' => null, + ]; + switch ($kind) { + case 'fixed': + $month = $int('fixed_mese'); + $day = $int('fixed_giorno'); + // 2024 is a leap year: 29 February is accepted and skipped in other years. + if ($month === null || $day === null || !checkdate($month, $day, 2024)) { + return __('La data indicata non esiste.'); + } + $row['mese'] = $month; + $row['giorno'] = $day; + break; + case 'nth_weekday': + $month = $int('nth_mese'); + $weekday = $int('nth_giorno_settimana'); + $nth = $int('nth_occorrenza'); + if ($month === null || $month < 1 || $month > 12 || $weekday === null || $weekday < 0 || $weekday > 6 + || $nth === null || !in_array($nth, [1, 2, 3, 4, 5, -1], true)) { + return __('Indica mese, giorno della settimana e quale occorrenza.'); + } + $row['mese'] = $month; + $row['giorno_settimana'] = $weekday; + $row['occorrenza'] = $nth; + break; + case 'easter': + case 'orthodox_easter': + $offset = $int('scarto'); + if ($offset === null || $offset < -200 || $offset > 200) { + return __('I giorni rispetto alla Pasqua vanno da -200 a 200.'); + } + $row['scarto_giorni'] = $offset; + break; + case 'calendar': + $calendar = $str('calendario'); + if (!WorldCalendars::exists($calendar)) { + return __('Scegli il calendario.'); + } + if (!WorldCalendars::available($calendar)) { + return __('Questo calendario richiede l’estensione PHP intl sul server.'); + } + $month = $int('cal_mese_' . $calendar); + $day = $int('cal_giorno'); + if ($month === null || $month < 1 || $month > count(WorldCalendars::months($calendar)) || $day === null || $day < 1 || $day > 30) { + return __('Indica mese e giorno del calendario scelto.'); + } + $row['calendario'] = $calendar; + $row['mese'] = $month; + $row['giorno'] = $day; + break; + case 'dates': + $dates = self::parseDates($str('date_specifiche')); + if ($dates === []) { + return __('Indica almeno una data nel formato AAAA-MM-GG.'); + } + if (count($dates) > self::MAX_DATES) { + return sprintf(__('Al massimo %d date.'), self::MAX_DATES); + } + $row['date_specifiche'] = implode(',', $dates); + break; + } + $days = $str('durata_giorni') === '' ? 1 : $int('durata_giorni'); + if ($days === null || $days < 1 || $days > HolidayCalculator::MAX_DAYS) { + return sprintf(__('La durata va da 1 a %d giorni.'), HolidayCalculator::MAX_DAYS); + } + $row['durata_giorni'] = $days; + $weekend = $str('fine_settimana'); + $row['fine_settimana'] = in_array($weekend, self::WEEKEND_MODES, true) && $days === 1 ? $weekend : null; + $from = $str('anno_inizio') === '' ? null : $int('anno_inizio'); + $until = $str('anno_fine') === '' ? null : $int('anno_fine'); + foreach ([$from, $until] as $year) { + if ($year !== null && ($year < 1900 || $year > 2199)) { + return __('Gli anni vanno dal 1900 al 2199.'); + } + } + if (($str('anno_inizio') !== '' && $from === null) || ($str('anno_fine') !== '' && $until === null)) { + return __('Gli anni vanno dal 1900 al 2199.'); + } + if ($from !== null && $until !== null && $until < $from) { + return __('L’ultimo anno non può precedere il primo.'); + } + $row['anno_inizio'] = $from; + $row['anno_fine'] = $until; + // Empty or 0 is every branch; any other value must be an existing branch, + // never silently widened to the whole library. + $branch = $str('sede_id') === '' ? 0 : $int('sede_id'); + if ($branch === null || ($branch !== 0 && !in_array($branch, $branchIds, true))) { + return __('Sede non trovata.'); + } + $row['sede_id'] = $branch === 0 ? null : $branch; + $row['attiva'] = ($data['attiva'] ?? null) === '1' ? 1 : 0; + return $row; + } + + /** + * The submitted form as a stored row, unvalidated, so that a refused form + * comes back with what the administrator typed instead of empty or with + * the old values. Only for refilling the form, never for saving. + * + * @param array $data the fields read by validate() + * @return array + */ + public static function draft(array $data, int $id = 0): array + { + $str = static fn (string $k): string => is_scalar($data[$k] ?? null) ? mb_substr(trim((string) $data[$k]), 0, 2000) : ''; + $kind = in_array($str('tipo'), self::KINDS, true) ? $str('tipo') : 'fixed'; + $calendar = WorldCalendars::exists($str('calendario')) ? $str('calendario') : null; + $month = match ($kind) { + 'fixed' => $str('fixed_mese'), + 'nth_weekday' => $str('nth_mese'), + 'calendar' => $calendar !== null ? $str('cal_mese_' . $calendar) : '', + default => '', + }; + $day = match ($kind) { + 'fixed' => $str('fixed_giorno'), + 'calendar' => $str('cal_giorno'), + default => '', + }; + $blank = static fn (string $v): ?string => $v === '' ? null : $v; + return [ + 'id' => $id, 'nome' => $str('nome'), 'tipo' => $kind, 'mese' => $blank($month), 'giorno' => $blank($day), + 'giorno_settimana' => $blank($str('nth_giorno_settimana')), 'occorrenza' => $blank($str('nth_occorrenza')), + 'scarto_giorni' => $blank($str('scarto')), 'calendario' => $calendar, + 'date_specifiche' => $blank(str_replace(["\r\n", "\n", "\r"], ',', $str('date_specifiche'))), + 'durata_giorni' => $blank($str('durata_giorni')), 'fine_settimana' => $blank($str('fine_settimana')), + 'sede_id' => $blank($str('sede_id')), 'anno_inizio' => $blank($str('anno_inizio')), 'anno_fine' => $blank($str('anno_fine')), + 'attiva' => ($data['attiva'] ?? null) === '1' ? 1 : 0, + ]; + } + + /** @param array $row the output of validate() */ + public static function save(\mysqli $db, array $row, ?int $id = null): int + { + $values = [ + $row['nome'], $row['tipo'], $row['mese'], $row['giorno'], $row['giorno_settimana'], $row['occorrenza'], + $row['scarto_giorni'], $row['calendario'], $row['date_specifiche'], $row['durata_giorni'], + $row['fine_settimana'], $row['anno_inizio'], $row['anno_fine'], $row['sede_id'], $row['attiva'], + ]; + $types = 'ssiiiiissisiiii'; + if ($id === null) { + $stmt = $db->prepare('INSERT INTO ' . self::TABLE . ' (nome, tipo, mese, giorno, giorno_settimana, occorrenza, scarto_giorni, calendario, date_specifiche, durata_giorni, fine_settimana, anno_inizio, anno_fine, sede_id, attiva) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'); + $stmt->bind_param($types, ...$values); + $stmt->execute(); + $newId = (int) $db->insert_id; + $stmt->close(); + return $newId; + } + $values[] = $id; + $stmt = $db->prepare('UPDATE ' . self::TABLE . ' SET nome = ?, tipo = ?, mese = ?, giorno = ?, giorno_settimana = ?, occorrenza = ?, scarto_giorni = ?, calendario = ?, + date_specifiche = ?, durata_giorni = ?, fine_settimana = ?, anno_inizio = ?, anno_fine = ?, sede_id = ?, attiva = ? WHERE id = ?'); + $stmt->bind_param($types . 'i', ...$values); + $stmt->execute(); + $stmt->close(); + return $id; + } + + public static function delete(\mysqli $db, int $id): bool + { + $stmt = $db->prepare('DELETE FROM ' . self::TABLE . ' WHERE id = ?'); + $stmt->bind_param('i', $id); + $stmt->execute(); + $deleted = $stmt->affected_rows > 0; + $stmt->close(); + return $deleted; + } + + public static function setActive(\mysqli $db, int $id, bool $active): void + { + $flag = $active ? 1 : 0; + $stmt = $db->prepare('UPDATE ' . self::TABLE . ' SET attiva = ? WHERE id = ?'); + $stmt->bind_param('ii', $flag, $id); + $stmt->execute(); + $stmt->close(); + } + + /** + * How the holiday is computed, in the interface language. + * + * @param array $row + * @param callable(string, string): string $format IntlDateFormatter pattern, Y-m-d + */ + public static function describe(array $row, callable $format): string + { + $month = (int) ($row['mese'] ?? 1); + $day = (int) ($row['giorno'] ?? 1); + $text = match ((string) $row['tipo']) { + 'fixed' => sprintf(__('Ogni anno il %s'), $format('d MMMM', sprintf('2024-%02d-%02d', $month, $day))), + 'nth_weekday' => sprintf( + __('%1$s %2$s di %3$s'), + self::ordinal((int) ($row['occorrenza'] ?? 1), (int) ($row['giorno_settimana'] ?? 1)), + // 2026-11-01 is a Sunday: + weekday gives that weekday. + $format('EEEE', \App\Support\LibraryCalendar::shift('2026-11-01', (int) ($row['giorno_settimana'] ?? 1))), + $format('LLLL', sprintf('2024-%02d-01', $month)) + ), + 'easter' => self::offsetText((int) ($row['scarto_giorni'] ?? 0), __('Pasqua')), + 'orthodox_easter' => self::offsetText((int) ($row['scarto_giorni'] ?? 0), __('Pasqua ortodossa')), + 'calendar' => sprintf(__('Calendario %1$s: %2$d %3$s'), WorldCalendars::label((string) $row['calendario']), $day, WorldCalendars::months((string) $row['calendario'])[$month - 1] ?? (string) $month), + default => sprintf(__('Date specifiche (%d)'), count(self::parseDates((string) ($row['date_specifiche'] ?? '')))), + }; + $days = (int) ($row['durata_giorni'] ?? 1); + if ($days > 1) { + $text .= ' · ' . sprintf(__('%d giorni'), $days); + } + return $text; + } + + /** "Il primo lunedì", but "la prima domenica": in the Italian base Sunday is feminine. */ + private static function ordinal(int $nth, int $weekday): string + { + if ($weekday === 0) { + return match ($nth) { + 1 => __('La prima'), + 2 => __('La seconda'), + 3 => __('La terza'), + 4 => __('La quarta'), + 5 => __('La quinta'), + default => __('L’ultima'), + }; + } + return match ($nth) { + 1 => __('Il primo'), + 2 => __('Il secondo'), + 3 => __('Il terzo'), + 4 => __('Il quarto'), + 5 => __('Il quinto'), + default => __('L’ultimo'), + }; + } + + private static function offsetText(int $offset, string $feast): string + { + if ($offset === 0) { + return $feast; + } + return $offset > 0 + ? sprintf(__('%1$d giorni dopo %2$s'), $offset, $feast) + : sprintf(__('%1$d giorni prima di %2$s'), -$offset, $feast); + } +} diff --git a/app/Support/Holidays/HolidayCalculator.php b/app/Support/Holidays/HolidayCalculator.php new file mode 100644 index 000000000..6e886dfc7 --- /dev/null +++ b/app/Support/Holidays/HolidayCalculator.php @@ -0,0 +1,402 @@ + 0, 'mon' => 1, 'tue' => 2, 'wed' => 3, 'thu' => 4, 'fri' => 5, 'sat' => 6]; + + /** @return array{name: string, regions?: array, region_required?: bool, rules: list>, dataset?: true}|null */ + public static function country(string $code): ?array + { + return HolidayRules::countries()[strtoupper($code)] ?? HolidayDataset::country($code); + } + + /** Every country with holidays: the hand-written tables and the dataset. @return list */ + public static function codes(): array + { + $codes = array_values(array_unique(array_merge(array_keys(HolidayRules::countries()), HolidayDataset::codes()))); + sort($codes); + return $codes; + } + + /** The country's name in the interface language (ext-intl), its own name otherwise. */ + public static function countryName(string $code): string + { + $code = strtoupper($code); + if (class_exists(\Locale::class)) { + $name = \Locale::getDisplayRegion('und-' . $code, \App\Support\I18n::getLocale()); + if (is_string($name) && $name !== '' && $name !== $code) { + return $name; + } + } + return HolidayRules::countries()[$code]['name'] ?? $code; + } + + /** + * Country code => display name, sorted by name. + * + * @return array + */ + public static function countryNames(): array + { + $names = []; + foreach (self::codes() as $code) { + $names[$code] = self::countryName($code); + } + uasort($names, static fn (string $a, string $b): int => strcmp(self::sortKey($a), self::sortKey($b))); + return $names; + } + + /** @return array region code => name (empty when the country has none) */ + public static function regions(string $country): array + { + return self::country($country)['regions'] ?? []; + } + + /** + * The region to use: $region when the country has it, the first region + * when the country needs one (United Kingdom), '' otherwise. + */ + public static function normaliseRegion(string $country, string $region): string + { + $regions = self::regions($country); + if ($region !== '' && isset($regions[$region])) { + return $region; + } + if (!empty(self::country($country)['region_required']) && $regions !== []) { + return (string) array_key_first($regions); + } + return ''; + } + + /** + * The rules that apply to $country / $region, in table order: the + * national ones plus, with a region, those of that region. + * + * @return list> + */ + public static function rulesFor(string $country, string $region = ''): array + { + // A dataset rule carries the region: its dates are the national ones + // plus those of that subdivision. + $data = HolidayRules::countries()[strtoupper($country)] ?? HolidayDataset::country($country, $region); + if ($data === null) { + return []; + } + $out = []; + foreach ($data['rules'] as $rule) { + $regions = $rule['regions'] ?? null; + if (is_array($regions) && !in_array($region, $regions, true)) { + continue; + } + $out[] = $rule; + } + return $out; + } + + /** + * Each applicable rule with its date in $year (null when the holiday + * does not exist that year). + * + * @return list, yearly: bool, regional: bool, optional: bool, name_en: string, category: string, estimated: bool}> + */ + public static function forYear(string $country, string $region, int $year): array + { + $country = strtoupper($country); + $out = []; + foreach (self::rulesFor($country, $region) as $rule) { + $out[] = [ + 'id' => $country . ':' . (string) $rule['id'], + 'name' => (string) $rule['name'], + 'date' => self::dateOf($rule, $year), + 'dates' => self::datesOf($rule, $year), + 'yearly' => self::isYearly($rule), + 'regional' => isset($rule['regions']), + 'optional' => !empty($rule['optional']), + 'name_en' => (string) ($rule['name_en'] ?? $rule['name']), + 'category' => (string) ($rule['category'] ?? 'public'), + 'estimated' => ($rule['rule'][0] ?? null) === 'dataset' + && HolidayDataset::isEstimated((string) $rule['rule'][1], (string) $rule['rule'][2], $year), + ]; + } + return $out; + } + + /** + * The same date every year: a fixed day that never moves and always + * exists. It is stored once as a yearly closure; any other rule is + * stored as one row per year. + * + * @param array $rule + */ + public static function isYearly(array $rule): bool + { + $kind = $rule['rule'][0] ?? null; + return $kind === 'fixed' + && empty($rule['observed']) + && !isset($rule['from']) + && !isset($rule['until']) + && empty($rule['overrides']); + } + + /** + * The date of one rule in $year, after the weekend rule and the yearly + * overrides; null when the year is outside from/until. + * + * @param array $rule + */ + public static function dateOf(array $rule, int $year): ?string + { + if (isset($rule['from']) && $year < (int) $rule['from']) { + return null; + } + if (isset($rule['until']) && $year > (int) $rule['until']) { + return null; + } + $overrides = $rule['overrides'] ?? []; + if (is_array($overrides) && isset($overrides[$year]) && is_string($overrides[$year])) { + return $overrides[$year]; + } + $spec = $rule['rule'] ?? null; + if (!is_array($spec)) { + return null; + } + $date = self::evaluate($spec, $year); + if ($date === null) { + return null; + } + $observed = $rule['observed'] ?? null; + return is_string($observed) && $observed !== '' ? self::observe($date, $observed) : $date; + } + + /** Weekend rules that add a day in lieu: the holiday itself keeps its date. */ + private const IN_LIEU_MODES = ['us', 'next_monday', 'pair']; + + /** Longest holiday a library can define as consecutive days. */ + public const MAX_DAYS = 14; + + /** + * Every closed day of one rule in $year: the observed date, plus the + * actual date when a weekend rule adds a day in lieu (us, next_monday, + * pair) instead of moving the holiday. Koningsdag (sunday_back) and the + * Argentine movable holidays (ar_movable) move: only the new date is a + * holiday. A yearly override is the one date of that year. A rule with + * 'days' > 1 closes that many consecutive days from each start date. + * + * @param array $rule + * @return list + */ + public static function datesOf(array $rule, int $year): array + { + if (isset($rule['from']) && $year < (int) $rule['from']) { + return []; + } + if (isset($rule['until']) && $year > (int) $rule['until']) { + return []; + } + $overrides = $rule['overrides'] ?? []; + if (is_array($overrides) && isset($overrides[$year]) && is_string($overrides[$year])) { + return [$overrides[$year]]; + } + $spec = $rule['rule'] ?? null; + if (!is_array($spec)) { + return []; + } + $days = max(1, min(self::MAX_DAYS, (int) ($rule['days'] ?? 1))); + $mode = $rule['observed'] ?? null; + $out = []; + foreach (self::occurrences($spec, $year) as $date) { + if ($days > 1) { + for ($i = 0; $i < $days; $i++) { + $out[] = LibraryCalendar::shift($date, $i); + } + continue; + } + if (!is_string($mode) || $mode === '') { + $out[] = $date; + continue; + } + $observed = self::observe($date, $mode); + if ($observed !== $date && in_array($mode, self::IN_LIEU_MODES, true)) { + $out[] = $date; + } + $out[] = $observed; + } + return array_values(array_unique($out)); + } + + /** + * The start dates of $spec in $year before any weekend rule: one for a + * Gregorian or Easter rule, any number for a dataset holiday, another + * calendar (an Islamic date can fall twice in one year) or a list. + * + * @param array $spec + * @return list + */ + public static function occurrences(array $spec, int $year): array + { + switch ($spec[0] ?? null) { + case 'dataset': + return HolidayDataset::dates((string) $spec[1], (string) $spec[2], $year, (string) ($spec[3] ?? '')); + case 'calendar': + return WorldCalendars::datesIn((string) $spec[1], (int) $spec[2], (int) $spec[3], $year); + case 'dates': + $prefix = sprintf('%04d-', $year); + return array_values(array_filter(array_map('strval', (array) ($spec[1] ?? [])), static fn (string $d): bool => str_starts_with($d, $prefix))); + } + $date = self::evaluate($spec, $year); + return $date === null ? [] : [$date]; + } + + /** Orthodox Easter Sunday (Julian computus) as a Gregorian date. */ + public static function orthodoxEaster(int $year): string + { + $a = $year % 4; + $b = $year % 7; + $c = $year % 19; + $d = (19 * $c + 15) % 30; + $e = (2 * $a + 4 * $b - $d + 34) % 7; + $month = intdiv($d + $e + 114, 31); + $day = (($d + $e + 114) % 31) + 1; + // Julian to Gregorian: 13 days in 1900-2099, one more each century not divisible by 400. + $gap = intdiv($year, 100) - intdiv($year, 400) - 2; + return LibraryCalendar::shift(sprintf('%04d-%02d-%02d', $year, $month, $day), $gap); + } + + /** + * @param array $spec + */ + public static function evaluate(array $spec, int $year): ?string + { + $kind = $spec[0] ?? null; + switch ($kind) { + case 'fixed': + return self::ymd($year, (string) $spec[1]); + case 'easter': + return LibraryCalendar::shift(LibraryCalendar::easterSunday($year), (int) $spec[1]); + case 'orthodox_easter': + return LibraryCalendar::shift(self::orthodoxEaster($year), (int) $spec[1]); + case 'dataset': + case 'calendar': + case 'dates': + return self::occurrences($spec, $year)[0] ?? null; + case 'nth_weekday': + $date = self::nthWeekday($year, (int) $spec[1], self::weekdayIndex((string) $spec[2]), (int) $spec[3]); + return $date === null ? null : LibraryCalendar::shift($date, (int) ($spec[4] ?? 0)); + case 'weekday_on_or_before': + $anchor = self::ymd($year, (string) $spec[1]); + if ($anchor === null) { + return null; + } + $back = (self::weekday($anchor) - self::weekdayIndex((string) $spec[2]) + 7) % 7; + return LibraryCalendar::shift($anchor, -$back); + case 'weekday_on_or_after': + $anchor = self::ymd($year, (string) $spec[1]); + if ($anchor === null) { + return null; + } + $forward = (self::weekdayIndex((string) $spec[2]) - self::weekday($anchor) + 7) % 7; + return LibraryCalendar::shift($anchor, $forward); + case 'fixed_if': + $date = self::ymd($year, (string) $spec[1]); + if ($date === null) { + return null; + } + $days = array_map(static fn ($d): int => self::weekdayIndex((string) $d), (array) $spec[2]); + if (in_array(self::weekday($date), $days, true)) { + return $date; + } + return is_array($spec[3] ?? null) ? self::evaluate($spec[3], $year) : null; + } + return null; + } + + /** + * The day off for a holiday that falls on a weekend: + * - us Saturday → Friday before, Sunday → Monday after; + * - next_monday Saturday or Sunday → the Monday after; + * - pair Saturday or Sunday → two days later: for two holidays + * in a row (Christmas and Boxing Day, 1 and 2 January in + * Scotland) the pair lands on Monday and Tuesday; + * - sunday_back Sunday → the Saturday before (Koningsdag); + * - ar_movable Tuesday/Wednesday → the Monday before, Thursday/Friday + * → the Monday after (Argentina, law 27.399). + */ + public static function observe(string $date, string $mode): string + { + $weekday = self::weekday($date); + switch ($mode) { + case 'us': + return $weekday === 6 ? LibraryCalendar::shift($date, -1) : ($weekday === 0 ? LibraryCalendar::shift($date, 1) : $date); + case 'next_monday': + return $weekday === 6 ? LibraryCalendar::shift($date, 2) : ($weekday === 0 ? LibraryCalendar::shift($date, 1) : $date); + case 'pair': + return ($weekday === 6 || $weekday === 0) ? LibraryCalendar::shift($date, 2) : $date; + case 'sunday_back': + return $weekday === 0 ? LibraryCalendar::shift($date, -1) : $date; + case 'ar_movable': + return match ($weekday) { + 2 => LibraryCalendar::shift($date, -1), + 3 => LibraryCalendar::shift($date, -2), + 4 => LibraryCalendar::shift($date, 4), + 5 => LibraryCalendar::shift($date, 3), + default => $date, + }; + } + return $date; + } + + /** The $n-th $weekday (0 = Sunday) of $month; $n = -1 is the last one. */ + public static function nthWeekday(int $year, int $month, int $weekday, int $n): ?string + { + if ($month < 1 || $month > 12 || $weekday < 0 || $n === 0 || $n > 5 || $n < -1) { + return null; + } + if ($n > 0) { + $first = sprintf('%04d-%02d-01', $year, $month); + $date = LibraryCalendar::shift($first, (($weekday - self::weekday($first) + 7) % 7) + 7 * ($n - 1)); + return substr($date, 0, 7) === substr($first, 0, 7) ? $date : null; + } + $last = (new \DateTimeImmutable(sprintf('%04d-%02d-01', $year, $month), new \DateTimeZone('UTC')))->modify('last day of this month')->format('Y-m-d'); + return LibraryCalendar::shift($last, -((self::weekday($last) - $weekday + 7) % 7)); + } + + private static function ymd(int $year, string $monthDay): ?string + { + $date = sprintf('%04d-%s', $year, $monthDay); + $parsed = \DateTimeImmutable::createFromFormat('!Y-m-d', $date, new \DateTimeZone('UTC')); + return $parsed !== false && $parsed->format('Y-m-d') === $date ? $date : null; + } + + private static function weekday(string $ymd): int + { + $date = \DateTimeImmutable::createFromFormat('!Y-m-d', $ymd, new \DateTimeZone('UTC')); + return $date === false ? -1 : (int) $date->format('w'); + } + + private static function weekdayIndex(string $name): int + { + return self::WEEKDAYS[strtolower($name)] ?? -1; + } + + /** Sort key without accents, so "Éire" sorts with the E. */ + private static function sortKey(string $name): string + { + if (class_exists(\Normalizer::class)) { + $decomposed = \Normalizer::normalize($name, \Normalizer::FORM_D); + if (is_string($decomposed)) { + $name = (string) preg_replace('/\p{Mn}+/u', '', $decomposed); + } + } + return mb_strtolower($name, 'UTF-8'); + } +} diff --git a/app/Support/Holidays/HolidayDataset.php b/app/Support/Holidays/HolidayDataset.php new file mode 100644 index 000000000..65754c904 --- /dev/null +++ b/app/Support/Holidays/HolidayDataset.php @@ -0,0 +1,226 @@ +.json for + * the years the files declare (2010-2060). + * + * The countries with a hand-written rule table (HolidayRules) are read from + * that table, which works for any year; every other country comes from here, + * with the same selection, preview and closure machinery. Dates that depend + * on the sighting of the moon are marked as estimated in the data. + * + * Date strings: "d|MMDD:2010-2060,2063|…" (by day of the year) or + * "y|2026:0319-0324,1225|…" (by year, with runs of days). + */ +final class HolidayDataset +{ + /** @var array|false> */ + private static array $files = []; + + /** @var array>> decoded date strings */ + private static array $decoded = []; + + private static function dir(): string + { + return __DIR__ . '/data'; + } + + /** @return list country codes with a data file */ + public static function codes(): array + { + static $codes = null; + if ($codes === null) { + $codes = []; + foreach (glob(self::dir() . '/*.json') ?: [] as $file) { + $code = basename($file, '.json'); + if (preg_match('/^[A-Z]{2}$/', $code) === 1) { + $codes[] = $code; + } + } + sort($codes); + } + return $codes; + } + + /** @return array|null the decoded file of $code */ + private static function file(string $code): ?array + { + $code = strtoupper($code); + if (preg_match('/^[A-Z]{2}$/', $code) !== 1) { + return null; + } + if (!array_key_exists($code, self::$files)) { + $path = self::dir() . '/' . $code . '.json'; + $data = is_file($path) ? json_decode((string) file_get_contents($path), true) : null; + self::$files[$code] = is_array($data) ? $data : false; + } + return self::$files[$code] === false ? null : self::$files[$code]; + } + + /** @return array{int, int}|null first and last year of the data */ + public static function years(string $code): ?array + { + $years = self::file($code)['years'] ?? null; + return is_array($years) && count($years) === 2 ? [(int) $years[0], (int) $years[1]] : null; + } + + /** + * The country in the shape of HolidayRules::countries(): the regions + * are its subdivisions, each holiday one rule. A rule reads its dates + * through ['dataset', code, id, region]. + * + * @return array{name: string, regions?: array, rules: list>, dataset: true}|null + */ + public static function country(string $code, string $region = ''): ?array + { + $data = self::file($code); + if ($data === null) { + return null; + } + $code = strtoupper($code); + $rules = []; + foreach ((array) ($data['holidays'] ?? []) as $holiday) { + if (!is_array($holiday) || !isset($holiday['id'], $holiday['name'])) { + continue; + } + $rule = [ + 'id' => (string) $holiday['id'], + 'name' => (string) $holiday['name'], + 'name_en' => (string) ($holiday['en'] ?? $holiday['name']), + 'rule' => ['dataset', $code, (string) $holiday['id'], $region], + 'category' => (string) ($holiday['category'] ?? 'public'), + ]; + if (($holiday['category'] ?? 'public') !== 'public') { + $rule['optional'] = true; + } + if (!isset($holiday['dates']) && is_array($holiday['regional'] ?? null)) { + $rule['regions'] = array_map('strval', array_keys($holiday['regional'])); + } + $rules[] = $rule; + } + $out = ['name' => $code, 'rules' => $rules, 'dataset' => true]; + $subdivisions = is_array($data['subdivisions'] ?? null) ? $data['subdivisions'] : []; + if ($subdivisions !== []) { + $out['regions'] = array_map('strval', $subdivisions); + } + return $out; + } + + /** Badge of a holiday category other than "public", in the interface language. */ + public static function categoryLabel(string $category): string + { + return match ($category) { + 'optional' => __('facoltativa'), + 'optional_women' => __('facoltativa per le donne'), + 'government' => __('uffici pubblici'), + 'unofficial' => __('non ufficiale'), + 'bank' => __('banche'), + 'half_day' => __('mezza giornata'), + 'school' => __('scuole'), + 'de_facto' => __('di fatto'), + 'armed_forces' => __('forze armate'), + 'catholic' => __('comunità cattolica'), + 'orthodox' => __('comunità ortodossa'), + 'christian' => __('comunità cristiana'), + 'hebrew' => __('comunità ebraica'), + 'islamic' => __('comunità musulmana'), + 'hindu' => __('comunità induista'), + 'sabian' => __('comunità mandea'), + 'yazidi' => __('comunità yazida'), + 'serbian' => __('comunità serba'), + 'roma' => __('comunità rom'), + 'vlach' => __('comunità valacca'), + 'bosnian' => __('comunità bosgnacca'), + 'albanian' => __('comunità albanese'), + 'turkish' => __('comunità turca'), + 'armenian' => __('comunità armena'), + default => $category, + }; + } + + /** The holiday $id of $code, raw. @return array|null */ + private static function holiday(string $code, string $id): ?array + { + foreach ((array) (self::file($code)['holidays'] ?? []) as $holiday) { + if (is_array($holiday) && ($holiday['id'] ?? null) === $id) { + return $holiday; + } + } + return null; + } + + /** + * The dates of holiday $id in $year (national, plus those of $region). + * None outside the years of the file (years()): that day counts as open. + * + * @return list + */ + public static function dates(string $code, string $id, int $year, string $region = ''): array + { + $holiday = self::holiday($code, $id); + if ($holiday === null) { + return []; + } + $dates = isset($holiday['dates']) ? self::decode((string) $holiday['dates'])[$year] ?? [] : []; + if ($region !== '' && isset($holiday['regional'][$region])) { + $dates = array_merge($dates, self::decode((string) $holiday['regional'][$region])[$year] ?? []); + } + $dates = array_values(array_unique($dates)); + sort($dates); + return $dates; + } + + /** True when a date of holiday $id in $year is an estimate (lunar sighting). */ + public static function isEstimated(string $code, string $id, int $year): bool + { + $holiday = self::holiday($code, $id); + return $holiday !== null && isset($holiday['estimated']) + && (self::decode((string) $holiday['estimated'])[$year] ?? []) !== []; + } + + /** + * @return array> year => Y-m-d dates + */ + public static function decode(string $encoded): array + { + if (isset(self::$decoded[$encoded])) { + return self::$decoded[$encoded]; + } + $out = []; + $form = substr($encoded, 0, 2); + foreach (explode('|', substr($encoded, 2)) as $part) { + [$head, $tail] = array_pad(explode(':', $part, 2), 2, ''); + if ($form === 'd|') { + foreach (explode(',', $tail) as $run) { + [$from, $to] = array_pad(explode('-', $run, 2), 2, null); + for ($y = (int) $from; $y <= (int) ($to ?? $from); $y++) { + $date = sprintf('%04d-%s-%s', $y, substr($head, 0, 2), substr($head, 2, 2)); + if (checkdate((int) substr($head, 0, 2), (int) substr($head, 2, 2), $y)) { + $out[$y][] = $date; + } + } + } + } elseif ($form === 'y|') { + $y = (int) $head; + foreach (explode(',', $tail) as $run) { + [$from, $to] = array_pad(explode('-', $run, 2), 2, null); + $day = new \DateTimeImmutable(sprintf('%04d-%s-%s', $y, substr($from, 0, 2), substr($from, 2, 2)), new \DateTimeZone('UTC')); + $last = $to === null ? $day : new \DateTimeImmutable(sprintf('%04d-%s-%s', $y, substr($to, 0, 2), substr($to, 2, 2)), new \DateTimeZone('UTC')); + for (; $day <= $last; $day = $day->modify('+1 day')) { + $out[$y][] = $day->format('Y-m-d'); + } + } + } + } + foreach ($out as &$dates) { + sort($dates); + } + unset($dates); + return self::$decoded[$encoded] = $out; + } +} diff --git a/app/Support/Holidays/HolidayPresets.php b/app/Support/Holidays/HolidayPresets.php new file mode 100644 index 000000000..d5aea38a6 --- /dev/null +++ b/app/Support/Holidays/HolidayPresets.php @@ -0,0 +1,521 @@ + 'Capodanno', + 'IT:epiphany' => 'Epifania', + 'IT:easter' => 'Pasqua', + 'IT:easter_monday' => "Lunedì dell'Angelo (Pasquetta)", + 'IT:liberation' => 'Festa della Liberazione', + 'IT:labour' => 'Festa del Lavoro', + 'IT:republic' => 'Festa della Repubblica', + 'IT:ferragosto' => 'Ferragosto', + 'IT:all_saints' => 'Ognissanti', + 'IT:immaculate' => 'Immacolata Concezione', + 'IT:christmas' => 'Natale', + 'IT:st_stephen' => 'Santo Stefano', + ]; + + /** @var array>|null */ + private static ?array $legacyLabelCache = null; + + /** The country preselected before any choice: the one of the installation language. */ + public static function defaultCountry(): string + { + $locale = ''; + try { + $locale = I18n::getInstallationLocale(); + } catch (\Throwable $e) { + $locale = ''; + } + return match (substr($locale, 0, 2)) { + 'en' => 'US', + 'de' => 'DE', + 'fr' => 'FR', + 'da' => 'DK', + default => 'IT', + }; + } + + /** + * What the library chose. 'saved' is false while nothing was ever applied. + * 'missing' lists the chosen ids of that country that the rule table or + * the dataset no longer knows (a regeneration run with --accept-removed, + * a hand edit): they close nothing any more, and the calendar page says so. + * 'unknown_country' is the stored country code when it is set but no + * longer a known country (a hand edit, a country dropped from the data): + * 'saved' is then false as well, so the calendar page says why the + * library's holidays are no longer kept up to date. + * + * @return array{country: string, region: string, rules: list, saved: bool, missing: list, unknown_country: string} + */ + public static function selection(\mysqli $db): array + { + return self::readSelection($db); + } + + /** + * Read all three values in one statement: a concurrent apply cannot mix + * one country's code with another selection's rules. Writers use a + * locking current read, including inside an existing REPEATABLE READ + * transaction whose ordinary reads may still see an older snapshot. + * + * @return array{country: string, region: string, rules: list, saved: bool, missing: list, unknown_country: string} + */ + private static function readSelection(\mysqli $db, bool $forUpdate = false): array + { + $result = $db->query("SELECT setting_key, setting_value FROM system_settings WHERE category = 'calendar' AND setting_key IN ('holiday_country', 'holiday_region', 'holiday_rules')" . ($forUpdate ? ' FOR UPDATE' : '')); + if (!$result instanceof \mysqli_result) { + throw new \RuntimeException('Holiday selection could not be read'); + } + $values = []; + while ($row = $result->fetch_assoc()) { + $values[(string) $row['setting_key']] = (string) $row['setting_value']; + } + $result->free(); + $country = strtoupper($values['holiday_country'] ?? ''); + $saved = $country !== '' && HolidayCalculator::country($country) !== null; + $unknownCountry = $country !== '' && !$saved ? $country : ''; + if (!$saved) { + $country = self::defaultCountry(); + } + $region = HolidayCalculator::normaliseRegion($country, $values['holiday_region'] ?? ''); + $rules = []; + $decoded = json_decode($values['holiday_rules'] ?? '[]', true); + foreach (is_array($decoded) ? $decoded : [] as $id) { + if (is_string($id)) { + $rules[] = $id; + } + } + return [ + 'country' => $country, + 'region' => $region, + 'rules' => $saved ? self::filterIds($country, $region, $rules) : [], + 'saved' => $saved, + 'missing' => $saved ? self::missingIds($country, $region, $rules) : [], + 'unknown_country' => $unknownCountry, + ]; + } + + /** + * The stored ids of $country that no rule of $country / $region carries + * any more: a holiday removed from the dataset, or an id that changed in + * a regeneration accepted with --accept-removed. Ids of other countries + * are not reported (apply() never stores them). Order kept, no duplicates. + * + * @param list $ids + * @return list + */ + public static function missingIds(string $country, string $region, array $ids): array + { + $country = strtoupper($country); + $known = []; + foreach (HolidayCalculator::rulesFor($country, $region) as $rule) { + $known[$country . ':' . (string) $rule['id']] = true; + } + $out = []; + foreach ($ids as $id) { + if (str_starts_with($id, $country . ':') && !isset($known[$id]) && !in_array($id, $out, true)) { + $out[] = $id; + } + } + return $out; + } + + /** + * The rules the due-date engine evaluates for any requested year. null + * means there is no saved selection (or no migration yet): retain the + * dated closures, including those of the former Italian preset. + * + * @return list>|null + */ + public static function calendarRules(\mysqli $db): ?array + { + if (!self::schemaReady($db)) { + return null; + } + $selection = self::selection($db); + if (!$selection['saved']) { + return null; + } + $wanted = array_flip($selection['rules']); + return array_values(array_filter( + HolidayCalculator::rulesFor($selection['country'], $selection['region']), + static fn (array $rule): bool => isset($wanted[$selection['country'] . ':' . (string) $rule['id']]) + )); + } + + /** + * Shared transaction lock for apply and upkeep. The row exists even + * before the first selection; a no-op upsert serialises that first save + * too. The lock lasts until the owning transaction commits or rolls back. + */ + private static function lockSelection(\mysqli $db): void + { + if (!$db->query("INSERT INTO system_settings (category, setting_key, setting_value) VALUES ('calendar', 'holiday_rules', '[]') ON DUPLICATE KEY UPDATE setting_key = setting_key")) { + throw new \RuntimeException('Holiday selection could not be locked'); + } + } + + /** + * Keep the ids that belong to $country / $region, in table order. + * + * @param list $ids + * @return list + */ + public static function filterIds(string $country, string $region, array $ids): array + { + $wanted = array_flip($ids); + $out = []; + foreach (HolidayCalculator::rulesFor($country, $region) as $rule) { + $id = strtoupper($country) . ':' . (string) $rule['id']; + if (isset($wanted[$id])) { + $out[] = $id; + } + } + return $out; + } + + /** + * The closures the chosen rules stand for, from $firstYear on. + * + * @param list $ids + * @return array> rule id => rows + */ + public static function expectedRows(string $country, string $region, array $ids, int $firstYear): array + { + $country = strtoupper($country); + $wanted = array_flip($ids); + $out = []; + foreach (HolidayCalculator::rulesFor($country, $region) as $rule) { + $id = $country . ':' . (string) $rule['id']; + if (!isset($wanted[$id])) { + continue; + } + $rows = []; + if (HolidayCalculator::isYearly($rule)) { + $date = HolidayCalculator::dateOf($rule, $firstYear); + if ($date !== null) { + $rows[] = ['start' => $date, 'end' => $date, 'description' => (string) $rule['name'], 'yearly' => true]; + } + } else { + for ($year = $firstYear; $year <= $firstYear + self::YEARS_AHEAD; $year++) { + // A day in lieu closes both days: the holiday and its substitute. + foreach (HolidayCalculator::datesOf($rule, $year) as $date) { + $rows[] = ['start' => $date, 'end' => $date, 'description' => (string) $rule['name'], 'yearly' => false]; + } + } + } + $out[$id] = $rows; + } + return $out; + } + + /** The migration that adds calendario_chiusure.regola_festivita has run. */ + public static function schemaReady(\mysqli $db): bool + { + try { + $result = $db->query("SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'calendario_chiusure' AND COLUMN_NAME IN ('regola_festivita', 'attiva')"); + if (!$result instanceof \mysqli_result) { + return false; + } + $row = $result->fetch_row(); + $result->free(); + return (int) ($row[0] ?? 0) === 2; + } catch (\Throwable $e) { + return false; + } + } + + /** + * Save the choice and bring calendario_chiusure in line with it: rows of + * rules no longer chosen are removed, missing rows are added (or, for a + * former Italian preset row, adopted), rows already there stay. Applying + * the same choice twice changes nothing. + * + * @param list $ids + * @return array{added: int, removed: int, adopted: int} + */ + public static function apply(\mysqli $db, string $country, string $region, array $ids, ?string $today = null): array + { + $country = strtoupper($country); + if (HolidayCalculator::country($country) === null) { + throw new \InvalidArgumentException('Unknown country: ' . $country); + } + $region = HolidayCalculator::normaliseRegion($country, $region); + $ids = self::filterIds($country, $region, $ids); + $firstYear = (int) substr($today ?? DateHelper::today(), 0, 4); + $counts = ['added' => 0, 'removed' => 0, 'adopted' => 0]; + + DbAtomic::run($db, static function () use ($db, $country, $region, $ids, $firstYear, &$counts): void { + self::lockSelection($db); + $repo = new SettingsRepository($db); + $repo->set(self::SETTINGS_CATEGORY, 'holiday_country', $country); + $repo->set(self::SETTINGS_CATEGORY, 'holiday_region', $region); + $repo->set(self::SETTINGS_CATEGORY, 'holiday_rules', (string) json_encode($ids, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)); + + // Unticked (or of another country): every row of that rule goes. + if ($ids === []) { + $db->query('DELETE FROM calendario_chiusure WHERE regola_festivita IS NOT NULL'); + $counts['removed'] += max(0, $db->affected_rows); + } else { + $placeholders = implode(',', array_fill(0, count($ids), '?')); + $stmt = $db->prepare("DELETE FROM calendario_chiusure WHERE regola_festivita IS NOT NULL AND regola_festivita NOT IN ({$placeholders})"); + $stmt->bind_param(str_repeat('s', count($ids)), ...$ids); + $stmt->execute(); + $counts['removed'] += max(0, $stmt->affected_rows); + $stmt->close(); + } + + foreach (self::expectedRows($country, $region, $ids, $firstYear) as $ruleId => $rows) { + $counts['removed'] += self::pruneStale($db, $ruleId, $rows, $firstYear); + foreach ($rows as $row) { + $outcome = self::ensureRow($db, $ruleId, $row); + if ($outcome !== null) { + $counts[$outcome]++; + } + } + } + }, 'HolidayPresets::apply'); + + LibraryCalendar::reset(); + return $counts; + } + + /** + * Yearly upkeep (maintenance): the chosen movable holidays get their rows + * up to YEARS_AHEAD years after the current one. Adds only; the rows of + * past years stay as history. Returns the number of rows added. + */ + public static function ensureYears(\mysqli $db, ?string $today = null): int + { + if (!self::schemaReady($db)) { + return 0; + } + // Nothing chosen yet (every upgraded library until its administrator + // picks holidays): leave system_settings alone. The lock below upserts + // calendar.holiday_rules, which would otherwise appear on the first + // maintenance run without anyone having saved anything. A selection + // committed meanwhile is picked up by the next run. + if (!self::readSelection($db)['saved']) { + return 0; + } + $firstYear = (int) substr($today ?? DateHelper::today(), 0, 4); + $added = 0; + DbAtomic::run($db, static function () use ($db, $firstYear, &$added): void { + self::lockSelection($db); + $selection = self::readSelection($db, true); + if (!$selection['saved'] || $selection['rules'] === []) { + return; + } + foreach (self::expectedRows($selection['country'], $selection['region'], $selection['rules'], $firstYear) as $ruleId => $rows) { + foreach ($rows as $row) { + if (self::ensureRow($db, $ruleId, $row) !== null) { + $added++; + } + } + } + }, 'HolidayPresets::ensureYears'); + // Even a no-op upkeep may have read a newer selection than a calendar + // cached earlier in this process, before another writer committed. + LibraryCalendar::reset(); + return $added; + } + + /** + * Rows of a still-chosen rule that no longer match its dates from + * $firstYear on (a rule whose shape changed); earlier years stay. + * + * @param list $rows + */ + private static function pruneStale(\mysqli $db, string $ruleId, array $rows, int $firstYear): int + { + $keepYearly = []; + $keepDates = []; + foreach ($rows as $row) { + if ($row['yearly']) { + $keepYearly[substr($row['start'], 5)] = true; + } else { + $keepDates[$row['start']] = true; + } + } + $from = $keepDates !== [] ? min(array_keys($keepDates)) : sprintf('%04d-01-01', $firstYear); + $stmt = $db->prepare('SELECT id, data_inizio, ricorrente_annuale FROM calendario_chiusure WHERE regola_festivita = ? FOR UPDATE'); + $stmt->bind_param('s', $ruleId); + $stmt->execute(); + $existing = $stmt->get_result()->fetch_all(MYSQLI_ASSOC); + $stmt->close(); + $removed = 0; + foreach ($existing as $row) { + $start = substr((string) $row['data_inizio'], 0, 10); + $stale = (int) $row['ricorrente_annuale'] === 1 + ? !isset($keepYearly[substr($start, 5)]) + : ($start >= $from && !isset($keepDates[$start])); + if ($stale) { + $id = (int) $row['id']; + $delete = $db->prepare('DELETE FROM calendario_chiusure WHERE id = ?'); + $delete->bind_param('i', $id); + $delete->execute(); + $removed += max(0, $delete->affected_rows); + $delete->close(); + } + } + return $removed; + } + + /** + * One expected row: already there (null), adopted from a former preset + * row ('adopted') or inserted ('added'). + * + * @param array{start: string, end: string, description: string, yearly: bool} $row + * @return 'added'|'adopted'|null + */ + private static function ensureRow(\mysqli $db, string $ruleId, array $row): ?string + { + $yearly = $row['yearly'] ? 1 : 0; + if ($yearly === 1) { + $md = substr($row['start'], 5); + $stmt = $db->prepare("SELECT 1 FROM calendario_chiusure WHERE regola_festivita = ? AND ricorrente_annuale = 1 AND DATE_FORMAT(data_inizio, '%m-%d') = ? LIMIT 1 FOR UPDATE"); + $stmt->bind_param('ss', $ruleId, $md); + } else { + $stmt = $db->prepare('SELECT 1 FROM calendario_chiusure WHERE regola_festivita = ? AND ricorrente_annuale = 0 AND data_inizio = ? LIMIT 1 FOR UPDATE'); + $stmt->bind_param('ss', $ruleId, $row['start']); + } + $stmt->execute(); + $found = $stmt->get_result()->fetch_row() !== null; + $stmt->close(); + if ($found) { + return null; + } + + $legacy = self::legacyLabels($ruleId); + if ($legacy !== []) { + $placeholders = implode(',', array_fill(0, count($legacy), '?')); + if ($yearly === 1) { + $md = substr($row['start'], 5); + $sql = "SELECT id FROM calendario_chiusure WHERE regola_festivita IS NULL AND sede_id IS NULL AND ricorrente_annuale = 1 + AND DATE_FORMAT(data_inizio, '%m-%d') = ? AND DATE_FORMAT(data_fine, '%m-%d') = ? AND descrizione IN ({$placeholders}) ORDER BY id LIMIT 1 FOR UPDATE"; + $params = array_merge([$md, $md], $legacy); + } else { + $sql = "SELECT id FROM calendario_chiusure WHERE regola_festivita IS NULL AND sede_id IS NULL AND ricorrente_annuale = 0 + AND data_inizio = ? AND data_fine = ? AND descrizione IN ({$placeholders}) ORDER BY id LIMIT 1 FOR UPDATE"; + $params = array_merge([$row['start'], $row['end']], $legacy); + } + $stmt = $db->prepare($sql); + $stmt->bind_param(str_repeat('s', count($params)), ...$params); + $stmt->execute(); + $match = $stmt->get_result()->fetch_row(); + $stmt->close(); + if ($match !== null) { + $id = (int) $match[0]; + $update = $db->prepare('UPDATE calendario_chiusure SET regola_festivita = ?, descrizione = ?, attiva = 1 WHERE id = ?'); + $update->bind_param('ssi', $ruleId, $row['description'], $id); + $update->execute(); + $update->close(); + return 'adopted'; + } + } + + $stmt = $db->prepare('INSERT INTO calendario_chiusure (data_inizio, data_fine, descrizione, ricorrente_annuale, sede_id, regola_festivita, attiva) VALUES (?, ?, ?, ?, NULL, ?, 1)'); + $stmt->bind_param('sssis', $row['start'], $row['end'], $row['description'], $yearly, $ruleId); + $stmt->execute(); + $stmt->close(); + return 'added'; + } + + /** + * Former Italian preset rules the librarian suspended: every legacy row + * of the rule is switched off (attiva = 0). Before the first selection + * they start unticked, so applying the defaults does not bring them back. + * + * @return list rule ids + */ + public static function suspendedLegacyRules(\mysqli $db): array + { + if (!self::schemaReady($db)) { + return []; + } + $out = []; + foreach (array_keys(self::LEGACY_IT_LABELS) as $ruleId) { + $labels = self::legacyLabels($ruleId); + $placeholders = implode(',', array_fill(0, count($labels), '?')); + $stmt = $db->prepare("SELECT COUNT(*), COALESCE(SUM(attiva), 0) FROM calendario_chiusure + WHERE regola_festivita IS NULL AND sede_id IS NULL AND descrizione IN ({$placeholders})"); + $stmt->bind_param(str_repeat('s', count($labels)), ...$labels); + $stmt->execute(); + [$rows, $active] = $stmt->get_result()->fetch_row() ?? [0, 0]; + $stmt->close(); + if ((int) $rows > 0 && (int) $active === 0) { + $out[] = $ruleId; + } + } + return $out; + } + + /** + * The descriptions the former Italian preset could have stored for a + * rule: the Italian label and its translation in every locale file. + * + * @return list + */ + public static function legacyLabels(string $ruleId): array + { + if (!isset(self::LEGACY_IT_LABELS[$ruleId])) { + return []; + } + if (self::$legacyLabelCache === null) { + self::$legacyLabelCache = []; + $catalogues = []; + foreach (glob(dirname(__DIR__, 3) . '/locale/??_??.json') ?: [] as $file) { + $decoded = json_decode((string) file_get_contents($file), true); + if (is_array($decoded)) { + $catalogues[] = $decoded; + } + } + foreach (self::LEGACY_IT_LABELS as $id => $label) { + $labels = [$label => true]; + foreach ($catalogues as $catalogue) { + if (isset($catalogue[$label]) && is_string($catalogue[$label]) && $catalogue[$label] !== '') { + $labels[$catalogue[$label]] = true; + } + } + self::$legacyLabelCache[$id] = array_map('strval', array_keys($labels)); + } + } + return self::$legacyLabelCache[$ruleId] ?? []; + } +} diff --git a/app/Support/Holidays/HolidayRules.php b/app/Support/Holidays/HolidayRules.php new file mode 100644 index 000000000..669589ba5 --- /dev/null +++ b/app/Support/Holidays/HolidayRules.php @@ -0,0 +1,518 @@ + Y-m-d, a one-year move decided by law (VE Day 2020). + * + * Rule kinds (all evaluated by HolidayCalculator::dateOf()): + * ['fixed', 'MM-DD'] + * ['easter', offsetDays] Gregorian Easter Sunday ± n + * ['nth_weekday', month, 'mon', n, offset = 0] n = 1..5, -1 = last + * ['weekday_on_or_before', 'MM-DD', 'mon'] + * ['weekday_on_or_after', 'MM-DD', 'sat'] + * ['fixed_if', 'MM-DD', ['fri'], ] that date on those weekdays, + * otherwise the fallback rule + * + * The names are stored as written here (calendario_chiusure.descrizione) and + * never go through __(): a German library wants "Fronleichnam" in German. + * The lists follow the official rules known when they were written; the + * library ticks the ones it closes for and can always add closures by hand. + * Regional lists are not exhaustive where noted (CH: six cantons; AU: the + * Western Australia King's Birthday is set by proclamation and is left out). + */ +final class HolidayRules +{ + /** + * @return array, region_required?: bool, rules: list>}> + */ + public static function countries(): array + { + return [ + 'IT' => [ + 'name' => 'Italia', + 'rules' => [ + ['id' => 'new_year', 'name' => 'Capodanno', 'rule' => ['fixed', '01-01']], + ['id' => 'epiphany', 'name' => 'Epifania', 'rule' => ['fixed', '01-06']], + ['id' => 'easter', 'name' => 'Pasqua', 'rule' => ['easter', 0]], + ['id' => 'easter_monday', 'name' => "Lunedì dell'Angelo (Pasquetta)", 'rule' => ['easter', 1]], + ['id' => 'liberation', 'name' => 'Festa della Liberazione', 'rule' => ['fixed', '04-25']], + ['id' => 'labour', 'name' => 'Festa del Lavoro', 'rule' => ['fixed', '05-01']], + ['id' => 'republic', 'name' => 'Festa della Repubblica', 'rule' => ['fixed', '06-02']], + ['id' => 'ferragosto', 'name' => 'Ferragosto', 'rule' => ['fixed', '08-15']], + // Restored as a national holiday by the 2025 law, from 2026. + ['id' => 'san_francesco', 'name' => "San Francesco d'Assisi, patrono d'Italia", 'rule' => ['fixed', '10-04'], 'from' => 2026], + ['id' => 'all_saints', 'name' => 'Ognissanti', 'rule' => ['fixed', '11-01']], + ['id' => 'immaculate', 'name' => 'Immacolata Concezione', 'rule' => ['fixed', '12-08']], + ['id' => 'christmas', 'name' => 'Natale', 'rule' => ['fixed', '12-25']], + ['id' => 'st_stephen', 'name' => 'Santo Stefano', 'rule' => ['fixed', '12-26']], + ], + ], + 'US' => [ + 'name' => 'United States', + 'rules' => [ + ['id' => 'new_year', 'name' => "New Year's Day", 'rule' => ['fixed', '01-01'], 'observed' => 'us'], + ['id' => 'mlk_day', 'name' => 'Birthday of Martin Luther King, Jr.', 'rule' => ['nth_weekday', 1, 'mon', 3]], + ['id' => 'presidents_day', 'name' => "Washington's Birthday (Presidents' Day)", 'rule' => ['nth_weekday', 2, 'mon', 3]], + ['id' => 'memorial_day', 'name' => 'Memorial Day', 'rule' => ['nth_weekday', 5, 'mon', -1]], + ['id' => 'juneteenth', 'name' => 'Juneteenth National Independence Day', 'rule' => ['fixed', '06-19'], 'observed' => 'us', 'from' => 2021], + ['id' => 'independence_day', 'name' => 'Independence Day', 'rule' => ['fixed', '07-04'], 'observed' => 'us'], + ['id' => 'labor_day', 'name' => 'Labor Day', 'rule' => ['nth_weekday', 9, 'mon', 1]], + ['id' => 'columbus_day', 'name' => 'Columbus Day', 'rule' => ['nth_weekday', 10, 'mon', 2]], + ['id' => 'veterans_day', 'name' => 'Veterans Day', 'rule' => ['fixed', '11-11'], 'observed' => 'us'], + ['id' => 'thanksgiving', 'name' => 'Thanksgiving Day', 'rule' => ['nth_weekday', 11, 'thu', 4]], + ['id' => 'christmas', 'name' => 'Christmas Day', 'rule' => ['fixed', '12-25'], 'observed' => 'us'], + ], + ], + 'GB' => [ + 'name' => 'United Kingdom', + 'regions' => ['ENG' => 'England and Wales', 'SCT' => 'Scotland', 'NIR' => 'Northern Ireland'], + 'region_required' => true, + 'rules' => [ + ['id' => 'new_year', 'name' => "New Year's Day", 'rule' => ['fixed', '01-01'], 'observed' => 'next_monday', 'regions' => ['ENG', 'NIR']], + ['id' => 'new_year_sct', 'name' => "New Year's Day", 'rule' => ['fixed', '01-01'], 'observed' => 'pair', 'regions' => ['SCT']], + ['id' => 'second_january', 'name' => '2nd January', 'rule' => ['fixed', '01-02'], 'observed' => 'pair', 'regions' => ['SCT']], + ['id' => 'st_patrick', 'name' => "St Patrick's Day", 'rule' => ['fixed', '03-17'], 'observed' => 'next_monday', 'regions' => ['NIR']], + ['id' => 'good_friday', 'name' => 'Good Friday', 'rule' => ['easter', -2]], + ['id' => 'easter_monday', 'name' => 'Easter Monday', 'rule' => ['easter', 1], 'regions' => ['ENG', 'NIR']], + // Moved to Friday 8 May for the VE Day anniversaries. + ['id' => 'early_may', 'name' => 'Early May bank holiday', 'rule' => ['nth_weekday', 5, 'mon', 1], 'overrides' => [1995 => '1995-05-08', 2020 => '2020-05-08']], + // Moved for the Golden, Diamond and Platinum Jubilees. + ['id' => 'spring', 'name' => 'Spring bank holiday', 'rule' => ['nth_weekday', 5, 'mon', -1], 'overrides' => [2002 => '2002-06-04', 2012 => '2012-06-04', 2022 => '2022-06-02']], + ['id' => 'battle_of_the_boyne', 'name' => "Battle of the Boyne (Orangemen's Day)", 'rule' => ['fixed', '07-12'], 'observed' => 'next_monday', 'regions' => ['NIR']], + ['id' => 'summer', 'name' => 'Summer bank holiday', 'rule' => ['nth_weekday', 8, 'mon', -1], 'regions' => ['ENG', 'NIR']], + ['id' => 'summer_sct', 'name' => 'Summer bank holiday', 'rule' => ['nth_weekday', 8, 'mon', 1], 'regions' => ['SCT']], + ['id' => 'st_andrew', 'name' => "St Andrew's Day", 'rule' => ['fixed', '11-30'], 'observed' => 'next_monday', 'regions' => ['SCT']], + ['id' => 'christmas', 'name' => 'Christmas Day', 'rule' => ['fixed', '12-25'], 'observed' => 'pair'], + ['id' => 'boxing_day', 'name' => 'Boxing Day', 'rule' => ['fixed', '12-26'], 'observed' => 'pair'], + ], + ], + 'IE' => [ + 'name' => 'Éire / Ireland', + 'rules' => [ + ['id' => 'new_year', 'name' => "New Year's Day", 'rule' => ['fixed', '01-01'], 'observed' => 'next_monday'], + // First Monday of February, or 1 February when that is a Friday. + ['id' => 'st_brigid', 'name' => "St Brigid's Day", 'rule' => ['fixed_if', '02-01', ['fri'], ['nth_weekday', 2, 'mon', 1]], 'from' => 2023], + ['id' => 'st_patrick', 'name' => "St Patrick's Day", 'rule' => ['fixed', '03-17'], 'observed' => 'next_monday'], + ['id' => 'easter_monday', 'name' => 'Easter Monday', 'rule' => ['easter', 1]], + ['id' => 'may_day', 'name' => 'May Day', 'rule' => ['nth_weekday', 5, 'mon', 1]], + ['id' => 'june_holiday', 'name' => 'June Holiday', 'rule' => ['nth_weekday', 6, 'mon', 1]], + ['id' => 'august_holiday', 'name' => 'August Holiday', 'rule' => ['nth_weekday', 8, 'mon', 1]], + ['id' => 'october_holiday', 'name' => 'October Holiday', 'rule' => ['nth_weekday', 10, 'mon', -1]], + ['id' => 'christmas', 'name' => 'Christmas Day', 'rule' => ['fixed', '12-25'], 'observed' => 'pair'], + ['id' => 'st_stephen', 'name' => "St Stephen's Day", 'rule' => ['fixed', '12-26'], 'observed' => 'pair'], + ], + ], + 'DE' => [ + 'name' => 'Deutschland', + 'regions' => [ + 'BW' => 'Baden-Württemberg', 'BY' => 'Bayern', 'BE' => 'Berlin', 'BB' => 'Brandenburg', + 'HB' => 'Bremen', 'HH' => 'Hamburg', 'HE' => 'Hessen', 'MV' => 'Mecklenburg-Vorpommern', + 'NI' => 'Niedersachsen', 'NW' => 'Nordrhein-Westfalen', 'RP' => 'Rheinland-Pfalz', 'SL' => 'Saarland', + 'SN' => 'Sachsen', 'ST' => 'Sachsen-Anhalt', 'SH' => 'Schleswig-Holstein', 'TH' => 'Thüringen', + ], + 'rules' => [ + ['id' => 'neujahr', 'name' => 'Neujahr', 'rule' => ['fixed', '01-01']], + ['id' => 'heilige_drei_koenige', 'name' => 'Heilige Drei Könige', 'rule' => ['fixed', '01-06'], 'regions' => ['BW', 'BY', 'ST']], + ['id' => 'frauentag', 'name' => 'Internationaler Frauentag', 'rule' => ['fixed', '03-08'], 'regions' => ['BE'], 'from' => 2019], + ['id' => 'frauentag_mv', 'name' => 'Internationaler Frauentag', 'rule' => ['fixed', '03-08'], 'regions' => ['MV'], 'from' => 2023], + ['id' => 'karfreitag', 'name' => 'Karfreitag', 'rule' => ['easter', -2]], + ['id' => 'ostersonntag', 'name' => 'Ostersonntag', 'rule' => ['easter', 0], 'regions' => ['BB']], + ['id' => 'ostermontag', 'name' => 'Ostermontag', 'rule' => ['easter', 1]], + ['id' => 'tag_der_arbeit', 'name' => 'Tag der Arbeit', 'rule' => ['fixed', '05-01']], + ['id' => 'christi_himmelfahrt', 'name' => 'Christi Himmelfahrt', 'rule' => ['easter', 39]], + ['id' => 'pfingstsonntag', 'name' => 'Pfingstsonntag', 'rule' => ['easter', 49], 'regions' => ['BB']], + ['id' => 'pfingstmontag', 'name' => 'Pfingstmontag', 'rule' => ['easter', 50]], + ['id' => 'fronleichnam', 'name' => 'Fronleichnam', 'rule' => ['easter', 60], 'regions' => ['BW', 'BY', 'HE', 'NW', 'RP', 'SL']], + ['id' => 'mariae_himmelfahrt', 'name' => 'Mariä Himmelfahrt', 'rule' => ['fixed', '08-15'], 'regions' => ['BY', 'SL']], + ['id' => 'weltkindertag', 'name' => 'Weltkindertag', 'rule' => ['fixed', '09-20'], 'regions' => ['TH'], 'from' => 2019], + ['id' => 'tag_der_deutschen_einheit', 'name' => 'Tag der Deutschen Einheit', 'rule' => ['fixed', '10-03']], + ['id' => 'reformationstag', 'name' => 'Reformationstag', 'rule' => ['fixed', '10-31'], 'regions' => ['BB', 'MV', 'SN', 'ST', 'TH']], + ['id' => 'reformationstag_nord', 'name' => 'Reformationstag', 'rule' => ['fixed', '10-31'], 'regions' => ['HB', 'HH', 'NI', 'SH'], 'from' => 2018], + ['id' => 'allerheiligen', 'name' => 'Allerheiligen', 'rule' => ['fixed', '11-01'], 'regions' => ['BW', 'BY', 'NW', 'RP', 'SL']], + // The Wednesday before 23 November. + ['id' => 'buss_und_bettag', 'name' => 'Buß- und Bettag', 'rule' => ['weekday_on_or_before', '11-22', 'wed'], 'regions' => ['SN']], + ['id' => 'erster_weihnachtstag', 'name' => '1. Weihnachtstag', 'rule' => ['fixed', '12-25']], + ['id' => 'zweiter_weihnachtstag', 'name' => '2. Weihnachtstag', 'rule' => ['fixed', '12-26']], + ], + ], + 'FR' => [ + 'name' => 'France', + 'regions' => ['ALM' => 'Alsace-Moselle'], + 'rules' => [ + ['id' => 'jour_de_l_an', 'name' => "Jour de l'an", 'rule' => ['fixed', '01-01']], + ['id' => 'vendredi_saint', 'name' => 'Vendredi saint', 'rule' => ['easter', -2], 'regions' => ['ALM']], + ['id' => 'lundi_de_paques', 'name' => 'Lundi de Pâques', 'rule' => ['easter', 1]], + ['id' => 'fete_du_travail', 'name' => 'Fête du Travail', 'rule' => ['fixed', '05-01']], + ['id' => 'victoire_1945', 'name' => 'Victoire 1945', 'rule' => ['fixed', '05-08']], + ['id' => 'ascension', 'name' => 'Ascension', 'rule' => ['easter', 39]], + ['id' => 'lundi_de_pentecote', 'name' => 'Lundi de Pentecôte', 'rule' => ['easter', 50]], + ['id' => 'fete_nationale', 'name' => 'Fête nationale', 'rule' => ['fixed', '07-14']], + ['id' => 'assomption', 'name' => 'Assomption', 'rule' => ['fixed', '08-15']], + ['id' => 'toussaint', 'name' => 'Toussaint', 'rule' => ['fixed', '11-01']], + ['id' => 'armistice', 'name' => 'Armistice 1918', 'rule' => ['fixed', '11-11']], + ['id' => 'noel', 'name' => 'Noël', 'rule' => ['fixed', '12-25']], + ['id' => 'saint_etienne', 'name' => 'Saint-Étienne', 'rule' => ['fixed', '12-26'], 'regions' => ['ALM']], + ], + ], + 'DK' => [ + 'name' => 'Danmark', + 'rules' => [ + ['id' => 'nytaarsdag', 'name' => 'Nytårsdag', 'rule' => ['fixed', '01-01']], + ['id' => 'skaertorsdag', 'name' => 'Skærtorsdag', 'rule' => ['easter', -3]], + ['id' => 'langfredag', 'name' => 'Langfredag', 'rule' => ['easter', -2]], + ['id' => 'paaskedag', 'name' => 'Påskedag', 'rule' => ['easter', 0]], + ['id' => 'anden_paaskedag', 'name' => '2. påskedag', 'rule' => ['easter', 1]], + // Abolished as a public holiday from 2024. + ['id' => 'store_bededag', 'name' => 'Store bededag', 'rule' => ['easter', 26], 'until' => 2023], + ['id' => 'kristi_himmelfartsdag', 'name' => 'Kristi himmelfartsdag', 'rule' => ['easter', 39]], + ['id' => 'pinsedag', 'name' => 'Pinsedag', 'rule' => ['easter', 49]], + ['id' => 'anden_pinsedag', 'name' => '2. pinsedag', 'rule' => ['easter', 50]], + ['id' => 'grundlovsdag', 'name' => 'Grundlovsdag', 'rule' => ['fixed', '06-05']], + ['id' => 'juleaftensdag', 'name' => 'Juleaftensdag', 'rule' => ['fixed', '12-24'], 'optional' => true], + ['id' => 'juledag', 'name' => 'Juledag', 'rule' => ['fixed', '12-25']], + ['id' => 'anden_juledag', 'name' => '2. juledag', 'rule' => ['fixed', '12-26']], + ['id' => 'nytaarsaftensdag', 'name' => 'Nytårsaftensdag', 'rule' => ['fixed', '12-31'], 'optional' => true], + ], + ], + 'ES' => [ + 'name' => 'España', + 'rules' => [ + ['id' => 'ano_nuevo', 'name' => 'Año Nuevo', 'rule' => ['fixed', '01-01']], + ['id' => 'epifania', 'name' => 'Epifanía del Señor', 'rule' => ['fixed', '01-06']], + // Regional in most autonomous communities: picked by the library. + ['id' => 'jueves_santo', 'name' => 'Jueves Santo', 'rule' => ['easter', -3], 'optional' => true], + ['id' => 'viernes_santo', 'name' => 'Viernes Santo', 'rule' => ['easter', -2]], + ['id' => 'lunes_de_pascua', 'name' => 'Lunes de Pascua', 'rule' => ['easter', 1], 'optional' => true], + ['id' => 'fiesta_del_trabajo', 'name' => 'Fiesta del Trabajo', 'rule' => ['fixed', '05-01']], + ['id' => 'asuncion', 'name' => 'Asunción de la Virgen', 'rule' => ['fixed', '08-15']], + ['id' => 'fiesta_nacional', 'name' => 'Fiesta Nacional de España', 'rule' => ['fixed', '10-12']], + ['id' => 'todos_los_santos', 'name' => 'Todos los Santos', 'rule' => ['fixed', '11-01']], + ['id' => 'constitucion', 'name' => 'Día de la Constitución Española', 'rule' => ['fixed', '12-06']], + ['id' => 'inmaculada', 'name' => 'Inmaculada Concepción', 'rule' => ['fixed', '12-08']], + ['id' => 'navidad', 'name' => 'Natividad del Señor', 'rule' => ['fixed', '12-25']], + ], + ], + 'PT' => [ + 'name' => 'Portugal', + 'rules' => [ + ['id' => 'ano_novo', 'name' => 'Ano Novo', 'rule' => ['fixed', '01-01']], + ['id' => 'carnaval', 'name' => 'Carnaval', 'rule' => ['easter', -47], 'optional' => true], + ['id' => 'sexta_feira_santa', 'name' => 'Sexta-feira Santa', 'rule' => ['easter', -2]], + ['id' => 'pascoa', 'name' => 'Páscoa', 'rule' => ['easter', 0]], + ['id' => 'dia_da_liberdade', 'name' => 'Dia da Liberdade', 'rule' => ['fixed', '04-25']], + ['id' => 'dia_do_trabalhador', 'name' => 'Dia do Trabalhador', 'rule' => ['fixed', '05-01']], + ['id' => 'corpo_de_deus', 'name' => 'Corpo de Deus', 'rule' => ['easter', 60]], + ['id' => 'dia_de_portugal', 'name' => 'Dia de Portugal, de Camões e das Comunidades Portuguesas', 'rule' => ['fixed', '06-10']], + ['id' => 'assuncao', 'name' => 'Assunção de Nossa Senhora', 'rule' => ['fixed', '08-15']], + ['id' => 'implantacao_da_republica', 'name' => 'Implantação da República', 'rule' => ['fixed', '10-05']], + ['id' => 'todos_os_santos', 'name' => 'Dia de Todos-os-Santos', 'rule' => ['fixed', '11-01']], + ['id' => 'restauracao', 'name' => 'Restauração da Independência', 'rule' => ['fixed', '12-01']], + ['id' => 'imaculada_conceicao', 'name' => 'Imaculada Conceição', 'rule' => ['fixed', '12-08']], + ['id' => 'natal', 'name' => 'Natal', 'rule' => ['fixed', '12-25']], + ], + ], + 'NL' => [ + 'name' => 'Nederland', + 'rules' => [ + ['id' => 'nieuwjaarsdag', 'name' => 'Nieuwjaarsdag', 'rule' => ['fixed', '01-01']], + ['id' => 'goede_vrijdag', 'name' => 'Goede Vrijdag', 'rule' => ['easter', -2], 'optional' => true], + ['id' => 'eerste_paasdag', 'name' => 'Eerste Paasdag', 'rule' => ['easter', 0]], + ['id' => 'tweede_paasdag', 'name' => 'Tweede Paasdag', 'rule' => ['easter', 1]], + // 27 April, the Saturday before when the 27th is a Sunday. + ['id' => 'koningsdag', 'name' => 'Koningsdag', 'rule' => ['fixed', '04-27'], 'observed' => 'sunday_back', 'from' => 2014], + ['id' => 'bevrijdingsdag', 'name' => 'Bevrijdingsdag', 'rule' => ['fixed', '05-05'], 'optional' => true], + ['id' => 'hemelvaartsdag', 'name' => 'Hemelvaartsdag', 'rule' => ['easter', 39]], + ['id' => 'eerste_pinksterdag', 'name' => 'Eerste Pinksterdag', 'rule' => ['easter', 49]], + ['id' => 'tweede_pinksterdag', 'name' => 'Tweede Pinksterdag', 'rule' => ['easter', 50]], + ['id' => 'eerste_kerstdag', 'name' => 'Eerste Kerstdag', 'rule' => ['fixed', '12-25']], + ['id' => 'tweede_kerstdag', 'name' => 'Tweede Kerstdag', 'rule' => ['fixed', '12-26']], + ], + ], + 'BE' => [ + 'name' => 'België / Belgique', + 'rules' => [ + ['id' => 'nieuwjaar', 'name' => "Nieuwjaar / Jour de l'An", 'rule' => ['fixed', '01-01']], + ['id' => 'pasen', 'name' => 'Pasen / Pâques', 'rule' => ['easter', 0]], + ['id' => 'paasmaandag', 'name' => 'Paasmaandag / Lundi de Pâques', 'rule' => ['easter', 1]], + ['id' => 'dag_van_de_arbeid', 'name' => 'Dag van de Arbeid / Fête du Travail', 'rule' => ['fixed', '05-01']], + ['id' => 'hemelvaart', 'name' => 'O.L.H. Hemelvaart / Ascension', 'rule' => ['easter', 39]], + ['id' => 'pinksteren', 'name' => 'Pinksteren / Pentecôte', 'rule' => ['easter', 49]], + ['id' => 'pinkstermaandag', 'name' => 'Pinkstermaandag / Lundi de Pentecôte', 'rule' => ['easter', 50]], + ['id' => 'nationale_feestdag', 'name' => 'Nationale feestdag / Fête nationale', 'rule' => ['fixed', '07-21']], + ['id' => 'olv_hemelvaart', 'name' => 'O.L.V. Hemelvaart / Assomption', 'rule' => ['fixed', '08-15']], + ['id' => 'allerheiligen', 'name' => 'Allerheiligen / Toussaint', 'rule' => ['fixed', '11-01']], + ['id' => 'wapenstilstand', 'name' => 'Wapenstilstand / Armistice', 'rule' => ['fixed', '11-11']], + ['id' => 'kerstmis', 'name' => 'Kerstmis / Noël', 'rule' => ['fixed', '12-25']], + ], + ], + 'AT' => [ + 'name' => 'Österreich', + 'rules' => [ + ['id' => 'neujahr', 'name' => 'Neujahr', 'rule' => ['fixed', '01-01']], + ['id' => 'heilige_drei_koenige', 'name' => 'Heilige Drei Könige', 'rule' => ['fixed', '01-06']], + ['id' => 'ostermontag', 'name' => 'Ostermontag', 'rule' => ['easter', 1]], + ['id' => 'staatsfeiertag', 'name' => 'Staatsfeiertag', 'rule' => ['fixed', '05-01']], + ['id' => 'christi_himmelfahrt', 'name' => 'Christi Himmelfahrt', 'rule' => ['easter', 39]], + ['id' => 'pfingstmontag', 'name' => 'Pfingstmontag', 'rule' => ['easter', 50]], + ['id' => 'fronleichnam', 'name' => 'Fronleichnam', 'rule' => ['easter', 60]], + ['id' => 'mariae_himmelfahrt', 'name' => 'Mariä Himmelfahrt', 'rule' => ['fixed', '08-15']], + ['id' => 'nationalfeiertag', 'name' => 'Nationalfeiertag', 'rule' => ['fixed', '10-26']], + ['id' => 'allerheiligen', 'name' => 'Allerheiligen', 'rule' => ['fixed', '11-01']], + ['id' => 'mariae_empfaengnis', 'name' => 'Mariä Empfängnis', 'rule' => ['fixed', '12-08']], + ['id' => 'christtag', 'name' => 'Christtag', 'rule' => ['fixed', '12-25']], + ['id' => 'stefanitag', 'name' => 'Stefanitag', 'rule' => ['fixed', '12-26']], + ], + ], + 'CH' => [ + 'name' => 'Schweiz / Suisse / Svizzera', + // Not exhaustive: six cantons in this first list. + 'regions' => ['BE' => 'Bern', 'BS' => 'Basel-Stadt', 'GE' => 'Genève', 'TI' => 'Ticino', 'VD' => 'Vaud', 'ZH' => 'Zürich'], + 'rules' => [ + ['id' => 'neujahr', 'name' => 'Neujahr / Nouvel An / Capodanno', 'rule' => ['fixed', '01-01']], + ['id' => 'berchtoldstag', 'name' => 'Berchtoldstag / Saint-Berthold', 'rule' => ['fixed', '01-02'], 'regions' => ['BE', 'VD', 'ZH']], + ['id' => 'epifania', 'name' => 'Epifania', 'rule' => ['fixed', '01-06'], 'regions' => ['TI']], + ['id' => 'san_giuseppe', 'name' => 'San Giuseppe', 'rule' => ['fixed', '03-19'], 'regions' => ['TI']], + ['id' => 'karfreitag', 'name' => 'Karfreitag / Vendredi saint', 'rule' => ['easter', -2], 'regions' => ['BE', 'BS', 'GE', 'VD', 'ZH']], + ['id' => 'ostermontag', 'name' => 'Ostermontag / Lundi de Pâques / Lunedì di Pasqua', 'rule' => ['easter', 1], 'regions' => ['BE', 'BS', 'GE', 'TI', 'VD', 'ZH']], + ['id' => 'tag_der_arbeit', 'name' => 'Tag der Arbeit / Festa del lavoro', 'rule' => ['fixed', '05-01'], 'regions' => ['BS', 'TI', 'ZH']], + ['id' => 'auffahrt', 'name' => 'Auffahrt / Ascension / Ascensione', 'rule' => ['easter', 39]], + ['id' => 'pfingstmontag', 'name' => 'Pfingstmontag / Lundi de Pentecôte / Lunedì di Pentecoste', 'rule' => ['easter', 50], 'regions' => ['BE', 'BS', 'GE', 'TI', 'VD', 'ZH']], + ['id' => 'corpus_domini', 'name' => 'Corpus Domini', 'rule' => ['easter', 60], 'regions' => ['TI']], + ['id' => 'santi_pietro_e_paolo', 'name' => 'Santi Pietro e Paolo', 'rule' => ['fixed', '06-29'], 'regions' => ['TI']], + ['id' => 'bundesfeier', 'name' => 'Bundesfeier / Fête nationale / Festa nazionale', 'rule' => ['fixed', '08-01']], + ['id' => 'assunzione', 'name' => 'Assunzione', 'rule' => ['fixed', '08-15'], 'regions' => ['TI']], + // The Thursday after the first Sunday of September. + ['id' => 'jeune_genevois', 'name' => 'Jeûne genevois', 'rule' => ['nth_weekday', 9, 'sun', 1, 4], 'regions' => ['GE']], + // The Monday after the third Sunday of September. + ['id' => 'lundi_du_jeune', 'name' => 'Lundi du Jeûne fédéral', 'rule' => ['nth_weekday', 9, 'sun', 3, 1], 'regions' => ['VD']], + ['id' => 'ognissanti', 'name' => 'Ognissanti', 'rule' => ['fixed', '11-01'], 'regions' => ['TI']], + ['id' => 'immacolata', 'name' => 'Immacolata', 'rule' => ['fixed', '12-08'], 'regions' => ['TI']], + ['id' => 'weihnachten', 'name' => 'Weihnachten / Noël / Natale', 'rule' => ['fixed', '12-25']], + ['id' => 'stephanstag', 'name' => 'Stephanstag / Santo Stefano', 'rule' => ['fixed', '12-26'], 'regions' => ['BE', 'BS', 'TI', 'ZH']], + ['id' => 'restauration', 'name' => 'Restauration de la République', 'rule' => ['fixed', '12-31'], 'regions' => ['GE']], + ], + ], + 'SE' => [ + 'name' => 'Sverige', + 'rules' => [ + ['id' => 'nyarsdagen', 'name' => 'Nyårsdagen', 'rule' => ['fixed', '01-01']], + ['id' => 'trettondedag_jul', 'name' => 'Trettondedag jul', 'rule' => ['fixed', '01-06']], + ['id' => 'langfredagen', 'name' => 'Långfredagen', 'rule' => ['easter', -2]], + ['id' => 'paskdagen', 'name' => 'Påskdagen', 'rule' => ['easter', 0]], + ['id' => 'annandag_pask', 'name' => 'Annandag påsk', 'rule' => ['easter', 1]], + ['id' => 'forsta_maj', 'name' => 'Första maj', 'rule' => ['fixed', '05-01']], + ['id' => 'kristi_himmelsfardsdag', 'name' => 'Kristi himmelsfärdsdag', 'rule' => ['easter', 39]], + ['id' => 'pingstdagen', 'name' => 'Pingstdagen', 'rule' => ['easter', 49]], + ['id' => 'nationaldagen', 'name' => 'Sveriges nationaldag', 'rule' => ['fixed', '06-06']], + ['id' => 'midsommarafton', 'name' => 'Midsommarafton', 'rule' => ['weekday_on_or_after', '06-19', 'fri'], 'optional' => true], + ['id' => 'midsommardagen', 'name' => 'Midsommardagen', 'rule' => ['weekday_on_or_after', '06-20', 'sat']], + ['id' => 'alla_helgons_dag', 'name' => 'Alla helgons dag', 'rule' => ['weekday_on_or_after', '10-31', 'sat']], + ['id' => 'julafton', 'name' => 'Julafton', 'rule' => ['fixed', '12-24'], 'optional' => true], + ['id' => 'juldagen', 'name' => 'Juldagen', 'rule' => ['fixed', '12-25']], + ['id' => 'annandag_jul', 'name' => 'Annandag jul', 'rule' => ['fixed', '12-26']], + ['id' => 'nyarsafton', 'name' => 'Nyårsafton', 'rule' => ['fixed', '12-31'], 'optional' => true], + ], + ], + 'NO' => [ + 'name' => 'Norge', + 'rules' => [ + ['id' => 'forste_nyttarsdag', 'name' => '1. nyttårsdag', 'rule' => ['fixed', '01-01']], + ['id' => 'skjaertorsdag', 'name' => 'Skjærtorsdag', 'rule' => ['easter', -3]], + ['id' => 'langfredag', 'name' => 'Langfredag', 'rule' => ['easter', -2]], + ['id' => 'forste_paskedag', 'name' => '1. påskedag', 'rule' => ['easter', 0]], + ['id' => 'andre_paskedag', 'name' => '2. påskedag', 'rule' => ['easter', 1]], + ['id' => 'arbeidernes_dag', 'name' => 'Arbeidernes dag', 'rule' => ['fixed', '05-01']], + ['id' => 'grunnlovsdag', 'name' => 'Grunnlovsdag', 'rule' => ['fixed', '05-17']], + ['id' => 'kristi_himmelfartsdag', 'name' => 'Kristi himmelfartsdag', 'rule' => ['easter', 39]], + ['id' => 'forste_pinsedag', 'name' => '1. pinsedag', 'rule' => ['easter', 49]], + ['id' => 'andre_pinsedag', 'name' => '2. pinsedag', 'rule' => ['easter', 50]], + ['id' => 'forste_juledag', 'name' => '1. juledag', 'rule' => ['fixed', '12-25']], + ['id' => 'andre_juledag', 'name' => '2. juledag', 'rule' => ['fixed', '12-26']], + ], + ], + 'FI' => [ + 'name' => 'Suomi', + 'rules' => [ + ['id' => 'uudenvuodenpaiva', 'name' => 'Uudenvuodenpäivä', 'rule' => ['fixed', '01-01']], + ['id' => 'loppiainen', 'name' => 'Loppiainen', 'rule' => ['fixed', '01-06']], + ['id' => 'pitkaperjantai', 'name' => 'Pitkäperjantai', 'rule' => ['easter', -2]], + ['id' => 'paasiaispaiva', 'name' => 'Pääsiäispäivä', 'rule' => ['easter', 0]], + ['id' => 'toinen_paasiaispaiva', 'name' => '2. pääsiäispäivä', 'rule' => ['easter', 1]], + ['id' => 'vappu', 'name' => 'Vappu', 'rule' => ['fixed', '05-01']], + ['id' => 'helatorstai', 'name' => 'Helatorstai', 'rule' => ['easter', 39]], + ['id' => 'helluntaipaiva', 'name' => 'Helluntaipäivä', 'rule' => ['easter', 49]], + ['id' => 'juhannusaatto', 'name' => 'Juhannusaatto', 'rule' => ['weekday_on_or_after', '06-19', 'fri'], 'optional' => true], + ['id' => 'juhannuspaiva', 'name' => 'Juhannuspäivä', 'rule' => ['weekday_on_or_after', '06-20', 'sat']], + ['id' => 'pyhainpaiva', 'name' => 'Pyhäinpäivä', 'rule' => ['weekday_on_or_after', '10-31', 'sat']], + ['id' => 'itsenaisyyspaiva', 'name' => 'Itsenäisyyspäivä', 'rule' => ['fixed', '12-06']], + ['id' => 'jouluaatto', 'name' => 'Jouluaatto', 'rule' => ['fixed', '12-24'], 'optional' => true], + ['id' => 'joulupaiva', 'name' => 'Joulupäivä', 'rule' => ['fixed', '12-25']], + ['id' => 'tapaninpaiva', 'name' => 'Tapaninpäivä', 'rule' => ['fixed', '12-26']], + ], + ], + 'PL' => [ + 'name' => 'Polska', + 'rules' => [ + ['id' => 'nowy_rok', 'name' => 'Nowy Rok', 'rule' => ['fixed', '01-01']], + ['id' => 'trzech_kroli', 'name' => 'Święto Trzech Króli', 'rule' => ['fixed', '01-06'], 'from' => 2011], + ['id' => 'wielkanoc', 'name' => 'Wielkanoc', 'rule' => ['easter', 0]], + ['id' => 'poniedzialek_wielkanocny', 'name' => 'Poniedziałek Wielkanocny', 'rule' => ['easter', 1]], + ['id' => 'swieto_pracy', 'name' => 'Święto Pracy', 'rule' => ['fixed', '05-01']], + ['id' => 'konstytucja_3_maja', 'name' => 'Święto Konstytucji 3 Maja', 'rule' => ['fixed', '05-03']], + ['id' => 'zielone_swiatki', 'name' => 'Zielone Świątki', 'rule' => ['easter', 49]], + ['id' => 'boze_cialo', 'name' => 'Boże Ciało', 'rule' => ['easter', 60]], + ['id' => 'wniebowziecie', 'name' => 'Wniebowzięcie Najświętszej Maryi Panny', 'rule' => ['fixed', '08-15']], + ['id' => 'wszystkich_swietych', 'name' => 'Wszystkich Świętych', 'rule' => ['fixed', '11-01']], + ['id' => 'niepodleglosci', 'name' => 'Narodowe Święto Niepodległości', 'rule' => ['fixed', '11-11']], + // A public holiday from 2025. + ['id' => 'wigilia', 'name' => 'Wigilia Bożego Narodzenia', 'rule' => ['fixed', '12-24'], 'from' => 2025], + ['id' => 'boze_narodzenie_1', 'name' => 'Boże Narodzenie (pierwszy dzień)', 'rule' => ['fixed', '12-25']], + ['id' => 'boze_narodzenie_2', 'name' => 'Boże Narodzenie (drugi dzień)', 'rule' => ['fixed', '12-26']], + ], + ], + 'CA' => [ + 'name' => 'Canada', + 'regions' => [ + 'AB' => 'Alberta', 'BC' => 'British Columbia', 'MB' => 'Manitoba', 'NB' => 'New Brunswick', + 'NL' => 'Newfoundland and Labrador', 'NS' => 'Nova Scotia', 'NT' => 'Northwest Territories', + 'NU' => 'Nunavut', 'ON' => 'Ontario', 'PE' => 'Prince Edward Island', 'QC' => 'Québec', + 'SK' => 'Saskatchewan', 'YT' => 'Yukon', + ], + 'rules' => [ + ['id' => 'new_year', 'name' => "New Year's Day / Jour de l'An", 'rule' => ['fixed', '01-01'], 'observed' => 'next_monday'], + ['id' => 'family_day', 'name' => 'Family Day', 'rule' => ['nth_weekday', 2, 'mon', 3], 'regions' => ['AB', 'BC', 'NB', 'ON', 'SK']], + ['id' => 'louis_riel_day', 'name' => 'Louis Riel Day', 'rule' => ['nth_weekday', 2, 'mon', 3], 'regions' => ['MB']], + ['id' => 'islander_day', 'name' => 'Islander Day', 'rule' => ['nth_weekday', 2, 'mon', 3], 'regions' => ['PE']], + ['id' => 'heritage_day_ns', 'name' => 'Heritage Day', 'rule' => ['nth_weekday', 2, 'mon', 3], 'regions' => ['NS']], + ['id' => 'good_friday', 'name' => 'Good Friday / Vendredi saint', 'rule' => ['easter', -2]], + ['id' => 'easter_monday', 'name' => 'Easter Monday / Lundi de Pâques', 'rule' => ['easter', 1], 'optional' => true], + // The Monday before 25 May (Journée nationale des patriotes in Québec). + ['id' => 'victoria_day', 'name' => 'Victoria Day / Fête de la Reine', 'rule' => ['weekday_on_or_before', '05-24', 'mon']], + ['id' => 'national_indigenous_peoples_day', 'name' => 'National Indigenous Peoples Day', 'rule' => ['fixed', '06-21'], 'regions' => ['NT', 'YT']], + ['id' => 'fete_nationale_quebec', 'name' => 'Fête nationale du Québec', 'rule' => ['fixed', '06-24'], 'regions' => ['QC']], + ['id' => 'canada_day', 'name' => 'Canada Day / Fête du Canada', 'rule' => ['fixed', '07-01'], 'observed' => 'next_monday'], + ['id' => 'nunavut_day', 'name' => 'Nunavut Day', 'rule' => ['fixed', '07-09'], 'regions' => ['NU']], + ['id' => 'civic_holiday', 'name' => 'Civic Holiday', 'rule' => ['nth_weekday', 8, 'mon', 1], 'regions' => ['AB', 'BC', 'NB', 'NT', 'NU', 'ON', 'SK']], + ['id' => 'discovery_day', 'name' => 'Discovery Day', 'rule' => ['nth_weekday', 8, 'mon', 3], 'regions' => ['YT']], + ['id' => 'labour_day', 'name' => 'Labour Day / Fête du Travail', 'rule' => ['nth_weekday', 9, 'mon', 1]], + ['id' => 'truth_and_reconciliation', 'name' => 'National Day for Truth and Reconciliation', 'rule' => ['fixed', '09-30'], 'from' => 2021], + ['id' => 'thanksgiving', 'name' => 'Thanksgiving / Action de grâce', 'rule' => ['nth_weekday', 10, 'mon', 2]], + ['id' => 'remembrance_day', 'name' => 'Remembrance Day / Jour du Souvenir', 'rule' => ['fixed', '11-11']], + ['id' => 'christmas', 'name' => 'Christmas Day / Noël', 'rule' => ['fixed', '12-25'], 'observed' => 'pair'], + ['id' => 'boxing_day', 'name' => 'Boxing Day / Lendemain de Noël', 'rule' => ['fixed', '12-26'], 'observed' => 'pair'], + ], + ], + 'AU' => [ + 'name' => 'Australia', + 'regions' => [ + 'ACT' => 'Australian Capital Territory', 'NSW' => 'New South Wales', 'NT' => 'Northern Territory', + 'QLD' => 'Queensland', 'SA' => 'South Australia', 'TAS' => 'Tasmania', 'VIC' => 'Victoria', 'WA' => 'Western Australia', + ], + 'rules' => [ + ['id' => 'new_year', 'name' => "New Year's Day", 'rule' => ['fixed', '01-01'], 'observed' => 'next_monday'], + ['id' => 'australia_day', 'name' => 'Australia Day', 'rule' => ['fixed', '01-26'], 'observed' => 'next_monday'], + ['id' => 'labour_day_wa', 'name' => 'Labour Day', 'rule' => ['nth_weekday', 3, 'mon', 1], 'regions' => ['WA']], + ['id' => 'labour_day_vic', 'name' => 'Labour Day', 'rule' => ['nth_weekday', 3, 'mon', 2], 'regions' => ['VIC']], + ['id' => 'eight_hours_day', 'name' => 'Eight Hours Day', 'rule' => ['nth_weekday', 3, 'mon', 2], 'regions' => ['TAS']], + ['id' => 'canberra_day', 'name' => 'Canberra Day', 'rule' => ['nth_weekday', 3, 'mon', 2], 'regions' => ['ACT']], + ['id' => 'adelaide_cup', 'name' => 'Adelaide Cup Day', 'rule' => ['nth_weekday', 3, 'mon', 2], 'regions' => ['SA']], + ['id' => 'good_friday', 'name' => 'Good Friday', 'rule' => ['easter', -2]], + ['id' => 'easter_saturday', 'name' => 'Easter Saturday', 'rule' => ['easter', -1], 'regions' => ['ACT', 'NSW', 'NT', 'QLD', 'SA', 'VIC']], + ['id' => 'easter_sunday', 'name' => 'Easter Sunday', 'rule' => ['easter', 0], 'regions' => ['ACT', 'NSW', 'QLD', 'VIC']], + // Public and Bank Holidays Act 1972 as amended in 2021: from Easter 2022. + ['id' => 'easter_sunday_wa', 'name' => 'Easter Sunday', 'rule' => ['easter', 0], 'regions' => ['WA'], 'from' => 2022], + // Gazetted every year since 2022. + ['id' => 'easter_sunday_nt', 'name' => 'Easter Sunday', 'rule' => ['easter', 0], 'regions' => ['NT'], 'from' => 2022], + // Declared by the 2023 amendment, from Easter 2024. + ['id' => 'easter_sunday_sa', 'name' => 'Easter Sunday', 'rule' => ['easter', 0], 'regions' => ['SA'], 'from' => 2024], + ['id' => 'easter_monday', 'name' => 'Easter Monday', 'rule' => ['easter', 1]], + ['id' => 'anzac_day', 'name' => 'Anzac Day', 'rule' => ['fixed', '04-25']], + // WA: on a Saturday or Sunday the following Monday is also a public holiday. + ['id' => 'anzac_day_monday_wa', 'name' => 'Anzac Day (additional Monday)', 'rule' => ['fixed_if', '04-25', ['sat', 'sun']], 'observed' => 'next_monday', 'regions' => ['WA']], + ['id' => 'labour_day_qld', 'name' => 'Labour Day', 'rule' => ['nth_weekday', 5, 'mon', 1], 'regions' => ['QLD']], + ['id' => 'may_day_nt', 'name' => 'May Day', 'rule' => ['nth_weekday', 5, 'mon', 1], 'regions' => ['NT']], + ['id' => 'reconciliation_day', 'name' => 'Reconciliation Day', 'rule' => ['weekday_on_or_after', '05-27', 'mon'], 'regions' => ['ACT'], 'from' => 2018], + ['id' => 'western_australia_day', 'name' => 'Western Australia Day', 'rule' => ['nth_weekday', 6, 'mon', 1], 'regions' => ['WA']], + ['id' => 'kings_birthday', 'name' => "King's Birthday", 'rule' => ['nth_weekday', 6, 'mon', 2], 'regions' => ['ACT', 'NSW', 'NT', 'SA', 'TAS', 'VIC']], + ['id' => 'picnic_day', 'name' => 'Picnic Day', 'rule' => ['nth_weekday', 8, 'mon', 1], 'regions' => ['NT']], + ['id' => 'labour_day_october', 'name' => 'Labour Day', 'rule' => ['nth_weekday', 10, 'mon', 1], 'regions' => ['ACT', 'NSW', 'SA']], + ['id' => 'kings_birthday_qld', 'name' => "King's Birthday", 'rule' => ['nth_weekday', 10, 'mon', 1], 'regions' => ['QLD']], + ['id' => 'melbourne_cup', 'name' => 'Melbourne Cup Day', 'rule' => ['nth_weekday', 11, 'tue', 1], 'regions' => ['VIC'], 'optional' => true], + ['id' => 'christmas', 'name' => 'Christmas Day', 'rule' => ['fixed', '12-25'], 'observed' => 'pair'], + ['id' => 'boxing_day', 'name' => 'Boxing Day', 'rule' => ['fixed', '12-26'], 'observed' => 'pair'], + ], + ], + 'BR' => [ + 'name' => 'Brasil', + 'rules' => [ + ['id' => 'confraternizacao', 'name' => 'Confraternização Universal', 'rule' => ['fixed', '01-01']], + // Ponto facultativo: optional days, picked by the library. + ['id' => 'carnaval_segunda', 'name' => 'Carnaval (segunda-feira)', 'rule' => ['easter', -48], 'optional' => true], + ['id' => 'carnaval_terca', 'name' => 'Carnaval (terça-feira)', 'rule' => ['easter', -47], 'optional' => true], + ['id' => 'sexta_feira_santa', 'name' => 'Sexta-feira Santa', 'rule' => ['easter', -2]], + ['id' => 'tiradentes', 'name' => 'Tiradentes', 'rule' => ['fixed', '04-21']], + ['id' => 'dia_do_trabalho', 'name' => 'Dia do Trabalho', 'rule' => ['fixed', '05-01']], + ['id' => 'corpus_christi', 'name' => 'Corpus Christi', 'rule' => ['easter', 60], 'optional' => true], + ['id' => 'independencia', 'name' => 'Independência do Brasil', 'rule' => ['fixed', '09-07']], + ['id' => 'nossa_senhora_aparecida', 'name' => 'Nossa Senhora Aparecida', 'rule' => ['fixed', '10-12']], + ['id' => 'finados', 'name' => 'Finados', 'rule' => ['fixed', '11-02']], + ['id' => 'proclamacao_da_republica', 'name' => 'Proclamação da República', 'rule' => ['fixed', '11-15']], + ['id' => 'consciencia_negra', 'name' => 'Dia Nacional de Zumbi e da Consciência Negra', 'rule' => ['fixed', '11-20'], 'from' => 2024], + ['id' => 'natal', 'name' => 'Natal', 'rule' => ['fixed', '12-25']], + ], + ], + 'AR' => [ + 'name' => 'Argentina', + 'rules' => [ + ['id' => 'ano_nuevo', 'name' => 'Año Nuevo', 'rule' => ['fixed', '01-01']], + ['id' => 'carnaval_lunes', 'name' => 'Carnaval (lunes)', 'rule' => ['easter', -48]], + ['id' => 'carnaval_martes', 'name' => 'Carnaval (martes)', 'rule' => ['easter', -47]], + ['id' => 'memoria', 'name' => 'Día Nacional de la Memoria por la Verdad y la Justicia', 'rule' => ['fixed', '03-24']], + ['id' => 'malvinas', 'name' => 'Día del Veterano y de los Caídos en la Guerra de Malvinas', 'rule' => ['fixed', '04-02']], + ['id' => 'jueves_santo', 'name' => 'Jueves Santo', 'rule' => ['easter', -3], 'optional' => true], + ['id' => 'viernes_santo', 'name' => 'Viernes Santo', 'rule' => ['easter', -2]], + ['id' => 'dia_del_trabajador', 'name' => 'Día del Trabajador', 'rule' => ['fixed', '05-01']], + ['id' => 'revolucion_de_mayo', 'name' => 'Día de la Revolución de Mayo', 'rule' => ['fixed', '05-25']], + // Trasladables (law 27.399): Tuesday/Wednesday to the Monday + // before, Thursday/Friday to the Monday after. + ['id' => 'guemes', 'name' => 'Paso a la Inmortalidad del General Martín Miguel de Güemes', 'rule' => ['fixed', '06-17'], 'observed' => 'ar_movable'], + ['id' => 'belgrano', 'name' => 'Paso a la Inmortalidad del General Manuel Belgrano', 'rule' => ['fixed', '06-20']], + ['id' => 'independencia', 'name' => 'Día de la Independencia', 'rule' => ['fixed', '07-09']], + ['id' => 'san_martin', 'name' => 'Paso a la Inmortalidad del General José de San Martín', 'rule' => ['fixed', '08-17'], 'observed' => 'ar_movable'], + ['id' => 'diversidad_cultural', 'name' => 'Día del Respeto a la Diversidad Cultural', 'rule' => ['fixed', '10-12'], 'observed' => 'ar_movable'], + ['id' => 'soberania_nacional', 'name' => 'Día de la Soberanía Nacional', 'rule' => ['fixed', '11-20'], 'observed' => 'ar_movable'], + ['id' => 'inmaculada', 'name' => 'Inmaculada Concepción de María', 'rule' => ['fixed', '12-08']], + ['id' => 'navidad', 'name' => 'Navidad', 'rule' => ['fixed', '12-25']], + ], + ], + 'MX' => [ + 'name' => 'México', + 'rules' => [ + ['id' => 'ano_nuevo', 'name' => 'Año Nuevo', 'rule' => ['fixed', '01-01']], + ['id' => 'dia_de_la_constitucion', 'name' => 'Día de la Constitución', 'rule' => ['nth_weekday', 2, 'mon', 1]], + ['id' => 'natalicio_benito_juarez', 'name' => 'Natalicio de Benito Juárez', 'rule' => ['nth_weekday', 3, 'mon', 3]], + ['id' => 'jueves_santo', 'name' => 'Jueves Santo', 'rule' => ['easter', -3], 'optional' => true], + ['id' => 'viernes_santo', 'name' => 'Viernes Santo', 'rule' => ['easter', -2], 'optional' => true], + ['id' => 'dia_del_trabajo', 'name' => 'Día del Trabajo', 'rule' => ['fixed', '05-01']], + ['id' => 'dia_de_la_independencia', 'name' => 'Día de la Independencia', 'rule' => ['fixed', '09-16']], + ['id' => 'dia_de_la_revolucion', 'name' => 'Día de la Revolución', 'rule' => ['nth_weekday', 11, 'mon', 3]], + ['id' => 'navidad', 'name' => 'Navidad', 'rule' => ['fixed', '12-25']], + ], + ], + ]; + } +} diff --git a/app/Support/Holidays/WorldCalendars.php b/app/Support/Holidays/WorldCalendars.php new file mode 100644 index 000000000..5519a2b54 --- /dev/null +++ b/app/Support/Holidays/WorldCalendars.php @@ -0,0 +1,123 @@ + ['Muharram', 'Safar', "Rabi' al-awwal", "Rabi' al-thani", 'Jumada al-awwal', 'Jumada al-thani', 'Rajab', "Sha'ban", 'Ramadan', 'Shawwal', "Dhu al-Qa'dah", 'Dhu al-Hijjah'], + // ICU numbering: Adar I is month 6 and exists in leap years only. + 'hebrew' => ['Tishrei', 'Cheshvan', 'Kislev', 'Tevet', 'Shevat', 'Adar I', 'Adar (Adar II)', 'Nisan', 'Iyar', 'Sivan', 'Tammuz', 'Av', 'Elul'], + 'chinese' => ['正月 (1)', '二月 (2)', '三月 (3)', '四月 (4)', '五月 (5)', '六月 (6)', '七月 (7)', '八月 (8)', '九月 (9)', '十月 (10)', '冬月 (11)', '腊月 (12)'], + 'persian' => ['Farvardin', 'Ordibehesht', 'Khordad', 'Tir', 'Mordad', 'Shahrivar', 'Mehr', 'Aban', 'Azar', 'Dey', 'Bahman', 'Esfand'], + 'indian' => ['Chaitra', 'Vaishakha', 'Jyeshtha', 'Ashadha', 'Shravana', 'Bhadra', 'Ashvin', 'Kartika', 'Agrahayana', 'Pausha', 'Magha', 'Phalguna'], + 'ethiopic' => ['Meskerem', 'Tikimt', 'Hidar', 'Tahsas', 'Tir', 'Yekatit', 'Megabit', 'Miyazya', 'Ginbot', 'Sene', 'Hamle', 'Nehase', 'Pagume'], + 'coptic' => ['Thout', 'Paopi', 'Hathor', 'Koiak', 'Tobi', 'Meshir', 'Paremhat', 'Parmouti', 'Pashons', 'Paoni', 'Epip', 'Mesori', 'Pi Kogi Enavot'], + ]; + + /** @return list calendar codes */ + public static function codes(): array + { + return array_keys(self::MONTHS); + } + + public static function exists(string $code): bool + { + return isset(self::MONTHS[$code]); + } + + /** Name of the calendar in the interface language. */ + public static function label(string $code): string + { + return match ($code) { + 'islamic-umalqura' => __('Islamico (Umm al-Qura)'), + 'hebrew' => __('Ebraico'), + 'chinese' => __('Cinese (lunisolare)'), + 'persian' => __('Persiano (Hijri solare)'), + 'indian' => __('Indiano (Saka)'), + 'ethiopic' => __('Etiope'), + 'coptic' => __('Copto'), + default => $code, + }; + } + + /** @return list month names, month 1 first */ + public static function months(string $code): array + { + return self::MONTHS[$code] ?? []; + } + + /** True when the calendar can be computed on this server (ICU calendars need ext-intl). */ + public static function available(string $code): bool + { + return $code === 'chinese' || (self::exists($code) && class_exists(\IntlCalendar::class)); + } + + /** + * The days of Gregorian $year that are day $day of month $month (1-based) + * of calendar $code. Usually one; a lunar year is shorter than a solar + * one, so an Islamic date falls twice in some Gregorian years, and none + * when the month has no such day that year (Adar I outside a leap year). + * + * @return list + */ + public static function datesIn(string $code, int $month, int $day, int $year): array + { + if (!self::exists($code) || $month < 1 || $month > count(self::MONTHS[$code]) || $day < 1 || $day > 30) { + return []; + } + if ($code === 'chinese') { + return ChineseLunarCalendar::datesIn($month, $day, $year); + } + if (!class_exists(\IntlCalendar::class)) { + return []; + } + $calendar = \IntlCalendar::createInstance('UTC', 'en_US@calendar=' . $code); + $gregorian = \IntlCalendar::createInstance('UTC', 'en_US@calendar=gregorian'); + $extendedYears = []; + foreach ([[0, 1], [11, 31]] as [$gMonth, $gDay]) { + $gregorian->clear(); + // Two-argument set(): setDate() needs PHP 8.3. + $gregorian->set(\IntlCalendar::FIELD_YEAR, $year); + $gregorian->set(\IntlCalendar::FIELD_MONTH, $gMonth); + $gregorian->set(\IntlCalendar::FIELD_DATE, $gDay); + $calendar->setTime($gregorian->getTime()); + $extendedYears[] = (int) $calendar->get(\IntlCalendar::FIELD_EXTENDED_YEAR); + } + $out = []; + for ($extended = $extendedYears[0]; $extended <= $extendedYears[1]; $extended++) { + $calendar->clear(); + $calendar->set(\IntlCalendar::FIELD_EXTENDED_YEAR, $extended); + $calendar->set(\IntlCalendar::FIELD_MONTH, $month - 1); + $calendar->set(\IntlCalendar::FIELD_DATE, $day); + $time = $calendar->getTime(); + // A lenient calendar rolls a missing day into the next month: skip it. + $calendar->setTime($time); + if ((int) $calendar->get(\IntlCalendar::FIELD_MONTH) !== $month - 1 || (int) $calendar->get(\IntlCalendar::FIELD_DATE) !== $day) { + continue; + } + $gregorian->setTime($time); + $ymd = sprintf('%04d-%02d-%02d', $gregorian->get(\IntlCalendar::FIELD_YEAR), $gregorian->get(\IntlCalendar::FIELD_MONTH) + 1, $gregorian->get(\IntlCalendar::FIELD_DATE)); + if ((int) substr($ymd, 0, 4) === $year) { + $out[] = $ymd; + } + } + sort($out); + return $out; + } +} diff --git a/app/Support/Holidays/data/AD.json b/app/Support/Holidays/data/AD.json new file mode 100644 index 000000000..d731a4df7 --- /dev/null +++ b/app/Support/Holidays/data/AD.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"AD","language":"ca","years":[2010,2060],"subdivisions":{"02":"Canillo","03":"Encamp","04":"La Massana","05":"Ordino","06":"Sant Julià de Lòria","07":"Andorra la Vella","08":"Escaldes-Engordany"},"holidays":[{"id":"new_year_s_day","name":"Cap d'Any","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"epiphany","name":"Reis","en":"Epiphany","category":"public","dates":"d|0106:2010-2060"},{"id":"saint_julian_s_day","name":"Sant Julià","en":"Saint Julian's Day","category":"public","regional":{"06":"d|0107:2010-2060"}},{"id":"saint_anthony_s_day","name":"Sant Antoni","en":"Saint Anthony's Day","category":"public","regional":{"04":"d|0117:2010-2060"}},{"id":"carnival","name":"Carnaval","en":"Carnival","category":"public","dates":"d|0205:2035,2046|0208:2016,2027|0209:2032,2043,2054|0210:2059|0211:2013|0212:2018,2024,2029|0213:2040,2051|0214:2056|0215:2010,2021|0216:2015,2026,2037|0217:2042,2048,2053|0220:2012,2023,2034,2045|0221:2039,2050|0224:2020,2031|0225:2036,2047,2058|0227:2017|0228:2022,2028,2033|0229:2044|0301:2049,2055,2060|0303:2014,2025|0304:2019,2030,2041,2052|0305:2057|0307:2011|0308:2038"},{"id":"constitution_day","name":"Dia de la Constitució","en":"Constitution Day","category":"public","dates":"d|0314:2010-2060"},{"id":"good_friday","name":"Divendres Sant","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_monday","name":"Dilluns de Pasqua","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"labor_day","name":"Festa del treball","en":"Labor Day","category":"public","dates":"d|0501:2010-2060"},{"id":"saint_michael_of_engolasters_day","name":"Sant Miquel d'Engolasters","en":"Saint Michael of Engolasters' Day","category":"public","regional":{"08":"d|0507:2010-2060"}},{"id":"pentecost_monday","name":"Dilluns de Pentecosta","en":"Pentecost Monday","category":"public","dates":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038"},{"id":"virgin_mary_of_canolich","name":"Diada de Canòlich","en":"Virgin Mary of Canòlich","category":"public","regional":{"06":"d|0525:2013,2019,2024,2030,2041,2047,2052,2058|0526:2012,2018,2029,2035,2040,2046,2057|0527:2017,2023,2028,2034,2045,2051,2056|0528:2011,2016,2022,2033,2039,2044,2050|0529:2010,2021,2027,2032,2038,2049,2055,2060|0530:2015,2020,2026,2037,2043,2048,2054|0531:2014,2025,2031,2036,2042,2053,2059"}},{"id":"parish_foundation_day","name":"Diada de la creació de la parròquia","en":"Parish foundation day","category":"public","regional":{"08":"d|0614:2015,2020,2026,2037,2043,2048,2054|0615:2014,2025,2031,2036,2042,2053,2059|0616:2013,2019,2024,2030,2041,2047,2052,2058|0617:2012,2018,2029,2035,2040,2046,2057|0618:2017,2023,2028,2034,2045,2051,2056|0619:2011,2016,2022,2033,2039,2044,2050|0620:2010,2021,2027,2032,2038,2049,2055,2060"}},{"id":"saint_peter_s_day","name":"Sant Pere","en":"Saint Peter's Day","category":"public","regional":{"05":"d|0629:2010-2060"}},{"id":"escaldes_engordany_festival","name":"Festa Major d'Escaldes-Engordany","en":"Escaldes-Engordany Festival","category":"public","regional":{"08":"d|0725:2010-2060|0726:2010-2060"}},{"id":"sant_julia_de_loria_festival","name":"Festa Major de Sant Julià de Lòria","en":"Sant Julià de Lòria Festival","category":"public","regional":{"06":"d|0727:2015,2020,2026,2037,2043,2048,2054|0728:2014-2015,2020,2025-2026,2031,2036-2037,2042-2043,2048,2053-2054,2059|0729:2013-2014,2019,2024-2025,2030-2031,2036,2041-2042,2047,2052-2053,2058-2059|0730:2012-2013,2018-2019,2024,2029-2030,2035,2040-2041,2046-2047,2052,2057-2058|0731:2012,2017-2018,2023,2028-2029,2034-2035,2040,2045-2046,2051,2056-2057|0801:2011,2016-2017,2022-2023,2028,2033-2034,2039,2044-2045,2050-2051,2056|0802:2010-2011,2016,2021-2022,2027,2032-2033,2038-2039,2044,2049-2050,2055,2060|0803:2010,2021,2027,2032,2038,2049,2055,2060"}},{"id":"andorra_la_vella_festival","name":"Festa Major d'Andorra la Vella","en":"Andorra la Vella Festival","category":"public","regional":{"07":"d|0801:2015,2020,2026,2037,2043,2048,2054|0802:2014-2015,2020,2025-2026,2031,2036-2037,2042-2043,2048,2053-2054,2059|0803:2013-2015,2019-2020,2024-2026,2030-2031,2036-2037,2041-2043,2047-2048,2052-2054,2058-2059|0804:2012-2014,2018-2019,2024-2025,2029-2031,2035-2036,2040-2042,2046-2047,2052-2053,2057-2059|0805:2012-2013,2017-2019,2023-2024,2028-2030,2034-2035,2040-2041,2045-2047,2051-2052,2056-2058|0806:2011-2012,2016-2018,2022-2023,2028-2029,2033-2035,2039-2040,2044-2046,2050-2051,2056-2057|0807:2010-2011,2016-2017,2021-2023,2027-2028,2032-2034,2038-2039,2044-2045,2049-2051,2055-2056,2060|0808:2010-2011,2016,2021-2022,2027,2032-2033,2038-2039,2044,2049-2050,2055,2060|0809:2010,2021,2027,2032,2038,2049,2055,2060"}},{"id":"assumption_day","name":"Assumpció","en":"Assumption Day","category":"public","dates":"d|0815:2010-2060"},{"id":"saint_roch_s_day","name":"Sant Roc","en":"Saint Roch's Day","category":"public","regional":{"02":"d|0816:2010-2060"}},{"id":"our_lady_of_meritxell","name":"Nostra Senyora de Meritxell","en":"Our Lady of Meritxell","category":"public","dates":"d|0908:2010-2060"},{"id":"all_saints_day","name":"Tots Sants","en":"All Saints' Day","category":"public","dates":"d|1101:2010-2060"},{"id":"immaculate_conception","name":"Immaculada Concepció","en":"Immaculate Conception","category":"public","dates":"d|1208:2010-2060"},{"id":"christmas_day","name":"Nadal","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"},{"id":"saint_stephen_s_day","name":"Sant Esteve","en":"Saint Stephen's Day","category":"public","dates":"d|1226:2010-2060"},{"id":"epiphany_eve_government","name":"Vigília de Reis","en":"Epiphany Eve","category":"government","dates":"d|0105:2010-2060"},{"id":"maundy_thursday_government","name":"Dijous Sant","en":"Maundy Thursday","category":"government","dates":"d|0322:2035,2046|0324:2016|0325:2027,2032|0326:2043,2054|0327:2059|0328:2013,2024|0329:2018,2029,2040|0330:2051,2056|0401:2010,2021|0402:2015,2026,2037,2048|0403:2042,2053|0405:2012|0406:2023,2034,2045|0407:2039,2050|0409:2020|0410:2031,2036|0411:2047,2058|0413:2017,2028|0414:2022,2033,2044|0415:2049,2055,2060|0417:2014,2025|0418:2019,2030,2041,2052|0419:2057|0421:2011|0422:2038"},{"id":"christmas_eve_government","name":"Vigília de Nadal","en":"Christmas Eve","category":"government","dates":"d|1224:2010-2060"},{"id":"new_year_s_eve_government","name":"Vigília de Cap d'Any","en":"New Year's Eve","category":"government","dates":"d|1231:2010-2060"}]} diff --git a/app/Support/Holidays/data/AE.json b/app/Support/Holidays/data/AE.json new file mode 100644 index 000000000..8aa60525b --- /dev/null +++ b/app/Support/Holidays/data/AE.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"AE","language":"ar","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"رأس السنة الميلادية","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"eid_al_fitr","name":"عيد الفطر","en":"Eid al-Fitr","category":"public","dates":"y|2010:0910|2011:0830|2012:0819|2013:0808|2014:0728|2015:0717|2016:0706|2017:0625|2018:0615|2019:0604|2020:0524|2021:0513|2022:0502|2023:0421|2024:0410|2025:0330|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304","estimated":"y|2010:0910|2011:0830|2012:0819|2013:0808|2014:0728|2015:0717|2016:0706|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304"},{"id":"eid_al_fitr_holiday","name":"عطلة عيد الفطر","en":"Eid al-Fitr Holiday","category":"public","dates":"y|2010:0911-0912|2011:0831-0901|2012:0820-0821|2013:0809-0810|2014:0729-0730|2015:0718-0719|2016:0707-0708|2017:0626-0627|2018:0616-0617|2019:0603,0605-0606|2020:0522-0523,0525-0526|2021:0511-0512,0514-0515|2022:0430-0501,0503-0504|2023:0420,0422-0423|2024:0408-0409,0411-0412|2025:0331-0401|2026:0321-0322|2027:0310-0311|2028:0227-0228|2029:0215-0216|2030:0205-0206|2031:0125-0126|2032:0115-0116|2033:0103-0104,1224-1225|2034:1213-1214|2035:1202-1203|2036:1120-1121|2037:1109-1110|2038:1030-1031|2039:1020-1021|2040:1008-1009|2041:0927-0928|2042:0916-0917|2043:0905-0906|2044:0825-0826|2045:0815-0816|2046:0804-0805|2047:0725-0726|2048:0713-0714|2049:0702-0703|2050:0621-0622|2051:0611-0612|2052:0530-0531|2053:0520-0521|2054:0510-0511|2055:0429-0430|2056:0417-0418|2057:0406-0407|2058:0326-0327|2059:0316-0317|2060:0305-0306","estimated":"y|2010:0911-0912|2011:0831-0901|2012:0820-0821|2013:0809-0810|2014:0729-0730|2015:0718-0719|2016:0707-0708|2026:0321-0322|2027:0310-0311|2028:0227-0228|2029:0215-0216|2030:0205-0206|2031:0125-0126|2032:0115-0116|2033:0103-0104,1224-1225|2034:1213-1214|2035:1202-1203|2036:1120-1121|2037:1109-1110|2038:1030-1031|2039:1020-1021|2040:1008-1009|2041:0927-0928|2042:0916-0917|2043:0905-0906|2044:0825-0826|2045:0815-0816|2046:0804-0805|2047:0725-0726|2048:0713-0714|2049:0702-0703|2050:0621-0622|2051:0611-0612|2052:0530-0531|2053:0520-0521|2054:0510-0511|2055:0429-0430|2056:0417-0418|2057:0406-0407|2058:0326-0327|2059:0316-0317|2060:0305-0306"},{"id":"prophet_s_birthday","name":"يوم الشهيد","en":"Prophet's Birthday","category":"public","dates":"y|2010:0226|2011:0215|2012:0204|2013:0124|2014:0113|2015:0103,1223|2016:1211|2017:1130|2018:1118|2019:1109|2020:1029|2021:1021|2022:1008|2023:0929|2024:0915|2025:0905|2026:0825|2027:0814|2028:0803|2029:0724|2030:0713|2031:0702|2032:0620|2033:0609|2034:0530|2035:0520|2036:0508|2037:0428|2038:0417|2039:0406|2040:0325|2041:0315|2042:0304|2043:0222|2044:0211|2045:0130|2046:0119|2047:0108,1229|2048:1218|2049:1207|2050:1126|2051:1116|2052:1104|2053:1024|2054:1013|2055:1003|2056:0922|2057:0911|2058:0831|2059:0820|2060:0808","estimated":"y|2010:0226|2011:0215|2012:0204|2013:0124|2014:0113|2015:0103,1223|2016:1211|2026:0825|2027:0814|2028:0803|2029:0724|2030:0713|2031:0702|2032:0620|2033:0609|2034:0530|2035:0520|2036:0508|2037:0428|2038:0417|2039:0406|2040:0325|2041:0315|2042:0304|2043:0222|2044:0211|2045:0130|2046:0119|2047:0108,1229|2048:1218|2049:1207|2050:1126|2051:1116|2052:1104|2053:1024|2054:1013|2055:1003|2056:0922|2057:0911|2058:0831|2059:0820|2060:0808"},{"id":"day_of_arafah","name":"وقفة عرفة","en":"Day of Arafah","category":"public","dates":"y|2010:1115|2011:1105|2012:1025|2013:1014|2014:1003|2015:0922|2016:0910|2017:0831|2018:0821|2019:0810|2020:0730|2021:0719|2022:0708|2023:0627|2024:0615|2025:0603|2026:0526|2027:0515|2028:0504|2029:0423|2030:0412|2031:0401|2032:0321|2033:0310|2034:0228|2035:0217|2036:0206|2037:0125|2038:0115|2039:0104,1225|2040:1213|2041:1203|2042:1122|2043:1111|2044:1030|2045:1020|2046:1009|2047:0929|2048:0918|2049:0907|2050:0827|2051:0816|2052:0804|2053:0725|2054:0714|2055:0704|2056:0622|2057:0611|2058:0531|2059:0521|2060:0509","estimated":"y|2010:1115|2011:1105|2012:1025|2013:1014|2014:1003|2015:0922|2016:0910|2026:0526|2027:0515|2028:0504|2029:0423|2030:0412|2031:0401|2032:0321|2033:0310|2034:0228|2035:0217|2036:0206|2037:0125|2038:0115|2039:0104,1225|2040:1213|2041:1203|2042:1122|2043:1111|2044:1030|2045:1020|2046:1009|2047:0929|2048:0918|2049:0907|2050:0827|2051:0816|2052:0804|2053:0725|2054:0714|2055:0704|2056:0622|2057:0611|2058:0531|2059:0521|2060:0509"},{"id":"islamic_new_year","name":"رأس السنة الهجرية","en":"Islamic New Year","category":"public","dates":"y|2010:1207|2011:1126|2012:1115|2013:1104|2014:1025|2015:1014|2016:1002|2017:0922|2018:0911|2019:0831|2020:0823|2021:0812|2022:0730|2023:0721|2024:0707|2025:0626|2026:0616|2027:0606|2028:0525|2029:0514|2030:0503|2031:0423|2032:0411|2033:0401|2034:0321|2035:0311|2036:0228|2037:0216|2038:0205|2039:0126|2040:0115|2041:0104,1224|2042:1214|2043:1203|2044:1121|2045:1110|2046:1031|2047:1020|2048:1009|2049:0928|2050:0917|2051:0906|2052:0826|2053:0815|2054:0805|2055:0725|2056:0714|2057:0703|2058:0622|2059:0611|2060:0531","estimated":"y|2010:1207|2011:1126|2012:1115|2013:1104|2014:1025|2015:1014|2016:1002|2026:0616|2027:0606|2028:0525|2029:0514|2030:0503|2031:0423|2032:0411|2033:0401|2034:0321|2035:0311|2036:0228|2037:0216|2038:0205|2039:0126|2040:0115|2041:0104,1224|2042:1214|2043:1203|2044:1121|2045:1110|2046:1031|2047:1020|2048:1009|2049:0928|2050:0917|2051:0906|2052:0826|2053:0815|2054:0805|2055:0725|2056:0714|2057:0703|2058:0622|2059:0611|2060:0531"},{"id":"eid_al_adha","name":"عيد الأضحى","en":"Eid al-Adha","category":"public","dates":"y|2010:1116|2011:1106|2012:1026|2013:1015|2014:1004|2015:0923|2016:0911|2017:0901|2018:0822|2019:0811|2020:0731|2021:0720|2022:0709|2023:0628|2024:0616|2025:0604|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510","estimated":"y|2010:1116|2011:1106|2012:1026|2013:1015|2014:1004|2015:0923|2016:0911|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510"},{"id":"eid_al_adha_holiday","name":"عطلة عيد الأضحى","en":"Eid al-Adha Holiday","category":"public","dates":"y|2010:1117-1118|2011:1107-1108|2012:1027-1028|2013:1016-1017|2014:1005-1006|2015:0924-0925|2016:0912-0913|2017:0902-0903|2018:0823-0824|2019:0812-0813|2020:0801-0802|2021:0721-0722|2022:0710-0711|2023:0629-0630|2024:0617-0618|2025:0605-0606|2026:0528-0529|2027:0517-0518|2028:0506-0507|2029:0425-0426|2030:0414-0415|2031:0403-0404|2032:0323-0324|2033:0312-0313|2034:0302-0303|2035:0219-0220|2036:0208-0209|2037:0127-0128|2038:0117-0118|2039:0106-0107,1227-1228|2040:1215-1216|2041:1205-1206|2042:1124-1125|2043:1113-1114|2044:1101-1102|2045:1022-1023|2046:1011-1012|2047:1001-1002|2048:0920-0921|2049:0909-0910|2050:0829-0830|2051:0818-0819|2052:0806-0807|2053:0727-0728|2054:0716-0717|2055:0706-0707|2056:0624-0625|2057:0613-0614|2058:0602-0603|2059:0523-0524|2060:0511-0512","estimated":"y|2010:1117-1118|2011:1107-1108|2012:1027-1028|2013:1016-1017|2014:1005-1006|2015:0924-0925|2016:0912-0913|2026:0528-0529|2027:0517-0518|2028:0506-0507|2029:0425-0426|2030:0414-0415|2031:0403-0404|2032:0323-0324|2033:0312-0313|2034:0302-0303|2035:0219-0220|2036:0208-0209|2037:0127-0128|2038:0117-0118|2039:0106-0107,1227-1228|2040:1215-1216|2041:1205-1206|2042:1124-1125|2043:1113-1114|2044:1101-1102|2045:1022-1023|2046:1011-1012|2047:1001-1002|2048:0920-0921|2049:0909-0910|2050:0829-0830|2051:0818-0819|2052:0806-0807|2053:0727-0728|2054:0716-0717|2055:0706-0707|2056:0624-0625|2057:0613-0614|2058:0602-0603|2059:0523-0524|2060:0511-0512"},{"id":"national_day","name":"اليوم الوطني","en":"National Day","category":"public","dates":"d|1202:2010-2060|1203:2010-2060"}]} diff --git a/app/Support/Holidays/data/AF.json b/app/Support/Holidays/data/AF.json new file mode 100644 index 000000000..1b2aef6a5 --- /dev/null +++ b/app/Support/Holidays/data/AF.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"AF","language":"fa_AF","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"eid_al_fitr","name":"عید فطر","en":"Eid al-Fitr","category":"public","dates":"y|2010:0910-0912|2011:0830-0901|2012:0819-0821|2013:0808-0810|2014:0729-0731|2015:0718-0720|2016:0707-0709|2017:0626-0628|2018:0615-0617|2019:0604-0606|2020:0524-0526|2021:0513-0515|2022:0501-0503|2023:0422-0424|2024:0410-0412|2025:0330-0401|2026:0320-0322|2027:0309-0311|2028:0226-0228|2029:0214-0216|2030:0204-0206|2031:0124-0126|2032:0114-0116|2033:0102-0104,1223-1225|2034:1212-1214|2035:1201-1203|2036:1119-1121|2037:1108-1110|2038:1029-1031|2039:1019-1021|2040:1007-1009|2041:0926-0928|2042:0915-0917|2043:0904-0906|2044:0824-0826|2045:0814-0816|2046:0803-0805|2047:0724-0726|2048:0712-0714|2049:0701-0703|2050:0620-0622|2051:0610-0612|2052:0529-0531|2053:0519-0521|2054:0509-0511|2055:0428-0430|2056:0416-0418|2057:0405-0407|2058:0325-0327|2059:0315-0317|2060:0304-0306","estimated":"y|2010:0910-0912|2011:0830-0901|2012:0819-0821|2013:0808-0810|2026:0320-0322|2027:0309-0311|2028:0226-0228|2029:0214-0216|2030:0204-0206|2031:0124-0126|2032:0114-0116|2033:0102-0104,1223-1225|2034:1212-1214|2035:1201-1203|2036:1119-1121|2037:1108-1110|2038:1029-1031|2039:1019-1021|2040:1007-1009|2041:0926-0928|2042:0915-0917|2043:0904-0906|2044:0824-0826|2045:0814-0816|2046:0803-0805|2047:0724-0726|2048:0712-0714|2049:0701-0703|2050:0620-0622|2051:0610-0612|2052:0529-0531|2053:0519-0521|2054:0509-0511|2055:0428-0430|2056:0416-0418|2057:0405-0407|2058:0325-0327|2059:0315-0317|2060:0304-0306"},{"id":"prophet_s_birthday","name":"میلاد پیامبر","en":"Prophet's Birthday","category":"public","dates":"y|2010:0226|2011:0215|2012:0204|2013:0124|2014:0114|2015:0103,1224|2016:1212|2017:1201|2018:1121|2019:1110|2020:1029|2021:1019|2022:1008|2023:0927|2024:0916|2025:0905|2026:0825|2027:0814|2028:0803|2029:0724|2030:0713|2031:0702|2032:0620|2033:0609|2034:0530|2035:0520|2036:0508|2037:0428|2038:0417|2039:0406|2040:0325|2041:0315|2042:0304|2043:0222|2044:0211|2045:0130|2046:0119|2047:0108,1229|2048:1218|2049:1207|2050:1126|2051:1116|2052:1104|2053:1024|2054:1013|2055:1003|2056:0922|2057:0911|2058:0831|2059:0820|2060:0808","estimated":"y|2010:0226|2011:0215|2012:0204|2013:0124|2026:0825|2027:0814|2028:0803|2029:0724|2030:0713|2031:0702|2032:0620|2033:0609|2034:0530|2035:0520|2036:0508|2037:0428|2038:0417|2039:0406|2040:0325|2041:0315|2042:0304|2043:0222|2044:0211|2045:0130|2046:0119|2047:0108,1229|2048:1218|2049:1207|2050:1126|2051:1116|2052:1104|2053:1024|2054:1013|2055:1003|2056:0922|2057:0911|2058:0831|2059:0820|2060:0808"},{"id":"day_of_arafah","name":"روز عرفه","en":"Day of Arafah","category":"public","dates":"y|2010:1115|2011:1105|2012:1025|2013:1014|2014:1004|2015:0922|2016:0912|2017:0901|2018:0821|2019:0810|2020:0730|2021:0719|2022:0708|2023:0627|2024:0616|2025:0606|2026:0526|2027:0515|2028:0504|2029:0423|2030:0412|2031:0401|2032:0321|2033:0310|2034:0228|2035:0217|2036:0206|2037:0125|2038:0115|2039:0104,1225|2040:1213|2041:1203|2042:1122|2043:1111|2044:1030|2045:1020|2046:1009|2047:0929|2048:0918|2049:0907|2050:0827|2051:0816|2052:0804|2053:0725|2054:0714|2055:0704|2056:0622|2057:0611|2058:0531|2059:0521|2060:0509","estimated":"y|2010:1115|2011:1105|2012:1025|2013:1014|2026:0526|2027:0515|2028:0504|2029:0423|2030:0412|2031:0401|2032:0321|2033:0310|2034:0228|2035:0217|2036:0206|2037:0125|2038:0115|2039:0104,1225|2040:1213|2041:1203|2042:1122|2043:1111|2044:1030|2045:1020|2046:1009|2047:0929|2048:0918|2049:0907|2050:0827|2051:0816|2052:0804|2053:0725|2054:0714|2055:0704|2056:0622|2057:0611|2058:0531|2059:0521|2060:0509"},{"id":"eid_al_adha","name":"عید قربانی","en":"Eid al-Adha","category":"public","dates":"y|2010:1116-1118|2011:1106-1108|2012:1026-1028|2013:1015-1017|2014:1005-1007|2015:0923-0925|2016:0913-0915|2017:0902-0904|2018:0822-0824|2019:0811-0813|2020:0731-0802|2021:0720-0722|2022:0709-0711|2023:0628-0630|2024:0617-0619|2025:0607-0609|2026:0527-0529|2027:0516-0518|2028:0505-0507|2029:0424-0426|2030:0413-0415|2031:0402-0404|2032:0322-0324|2033:0311-0313|2034:0301-0303|2035:0218-0220|2036:0207-0209|2037:0126-0128|2038:0116-0118|2039:0105-0107,1226-1228|2040:1214-1216|2041:1204-1206|2042:1123-1125|2043:1112-1114|2044:1031-1102|2045:1021-1023|2046:1010-1012|2047:0930-1002|2048:0919-0921|2049:0908-0910|2050:0828-0830|2051:0817-0819|2052:0805-0807|2053:0726-0728|2054:0715-0717|2055:0705-0707|2056:0623-0625|2057:0612-0614|2058:0601-0603|2059:0522-0524|2060:0510-0512","estimated":"y|2010:1116-1118|2011:1106-1108|2012:1026-1028|2013:1015-1017|2026:0527-0529|2027:0516-0518|2028:0505-0507|2029:0424-0426|2030:0413-0415|2031:0402-0404|2032:0322-0324|2033:0311-0313|2034:0301-0303|2035:0218-0220|2036:0207-0209|2037:0126-0128|2038:0116-0118|2039:0105-0107,1226-1228|2040:1214-1216|2041:1204-1206|2042:1123-1125|2043:1112-1114|2044:1031-1102|2045:1021-1023|2046:1010-1012|2047:0930-1002|2048:0919-0921|2049:0908-0910|2050:0828-0830|2051:0817-0819|2052:0805-0807|2053:0726-0728|2054:0715-0717|2055:0705-0707|2056:0623-0625|2057:0612-0614|2058:0601-0603|2059:0522-0524|2060:0510-0512"},{"id":"first_day_of_ramadan","name":"اول رمضان","en":"First Day of Ramadan","category":"public","dates":"y|2010:0811|2011:0801|2012:0720|2013:0709|2014:0629|2015:0618|2016:0607|2017:0527|2018:0516|2019:0506|2020:0424|2021:0413|2022:0402|2023:0323|2024:0311|2025:0301|2026:0218|2027:0208|2028:0128|2029:0116|2030:0105,1226|2031:1215|2032:1204|2033:1123|2034:1112|2035:1101|2036:1020|2037:1010|2038:0930|2039:0919|2040:0907|2041:0828|2042:0817|2043:0806|2044:0726|2045:0715|2046:0705|2047:0624|2048:0612|2049:0602|2050:0522|2051:0511|2052:0430|2053:0420|2054:0409|2055:0329|2056:0317|2057:0306|2058:0224|2059:0214|2060:0203","estimated":"y|2010:0811|2011:0801|2012:0720|2013:0709|2026:0218|2027:0208|2028:0128|2029:0116|2030:0105,1226|2031:1215|2032:1204|2033:1123|2034:1112|2035:1101|2036:1020|2037:1010|2038:0930|2039:0919|2040:0907|2041:0828|2042:0817|2043:0806|2044:0726|2045:0715|2046:0705|2047:0624|2048:0612|2049:0602|2050:0522|2051:0511|2052:0430|2053:0420|2054:0409|2055:0329|2056:0317|2057:0306|2058:0224|2059:0214|2060:0203"},{"id":"liberation_day","name":"روز آزادی","en":"Liberation Day","category":"public","dates":"d|0215:2010-2060"},{"id":"mojahedin_s_victory_day","name":"روز پیروزی مجاهدین","en":"Mojahedin's Victory Day","category":"public","dates":"d|0428:2010-2060"},{"id":"islamic_emirate_victory_day","name":"روز پیروزی امارت اسلامی","en":"Islamic Emirate Victory Day","category":"public","dates":"d|0814:2024,2028-2029,2032-2033,2036-2037,2040-2041,2044-2045,2048-2049,2052-2053,2056-2057,2060|0815:2022-2023,2025-2027,2030-2031,2034-2035,2038-2039,2042-2043,2046-2047,2050-2051,2054-2055,2058-2059"},{"id":"afghanistan_independence_day","name":"روز استقلال افغانستان","en":"Afghanistan Independence Day","category":"public","dates":"d|0819:2010-2060"},{"id":"american_withdrawal_day","name":"روز خروج آمریکایی ها","en":"American Withdrawal Day","category":"public","dates":"d|0831:2022-2060"}]} diff --git a/app/Support/Holidays/data/AG.json b/app/Support/Holidays/data/AG.json new file mode 100644 index 000000000..807a9f3be --- /dev/null +++ b/app/Support/Holidays/data/AG.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"AG","language":null,"years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051"},{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_monday","name":"Easter Monday","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"labour_day","name":"Labour Day","en":"Labour Day","category":"public","dates":"d|0501:2017,2023,2028,2034,2045,2051,2056|0502:2011,2016,2022,2033,2039,2044,2050|0503:2010,2021,2027,2032,2038,2049,2055,2060|0504:2015,2020,2026,2037,2043,2048,2054|0505:2014,2025,2031,2036,2042,2053,2059|0506:2013,2019,2024,2030,2041,2047,2052,2058|0507:2012,2018,2029,2035,2040,2046,2057"},{"id":"whit_monday","name":"Whit Monday","en":"Whit Monday","category":"public","dates":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038"},{"id":"carnival_monday","name":"Carnival Monday","en":"Carnival Monday","category":"public","dates":"d|0801:2011,2016,2022,2033,2039,2044,2050|0802:2010,2021,2027,2032,2038,2049,2055,2060|0803:2015,2020,2026,2037,2043,2048,2054|0804:2014,2025,2031,2036,2042,2053,2059|0805:2013,2019,2024,2030,2041,2047,2052,2058|0806:2012,2018,2029,2035,2040,2046,2057|0807:2017,2023,2028,2034,2045,2051,2056"},{"id":"carnival_tuesday","name":"Carnival Tuesday","en":"Carnival Tuesday","category":"public","dates":"d|0802:2011,2016,2022,2033,2039,2044,2050|0803:2010,2021,2027,2032,2038,2049,2055,2060|0804:2015,2020,2026,2037,2043,2048,2054|0805:2014,2025,2031,2036,2042,2053,2059|0806:2013,2019,2024,2030,2041,2047,2052,2058|0807:2012,2018,2029,2035,2040,2046,2057|0808:2017,2023,2028,2034,2045,2051,2056"},{"id":"independence_day","name":"Independence Day","en":"Independence Day","category":"public","dates":"d|1101:2010-2060|1102:2015,2020,2026,2037,2043,2048,2054|1103:2014,2025,2031,2036,2042,2053,2059"},{"id":"sir_vere_cornwall_bird_snr_day","name":"Sir Vere Cornwall Bird Snr. Day","en":"Sir Vere Cornwall Bird Snr. Day","category":"public","dates":"d|1209:2014-2060|1210:2029,2035,2040,2046,2057|1211:2023,2028,2034,2045,2051,2056"},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060|1227:2010-2011,2016,2021-2022,2027,2032-2033,2038-2039,2044,2049-2050,2055,2060"},{"id":"boxing_day","name":"Boxing Day","en":"Boxing Day","category":"public","dates":"d|1226:2010-2060|1228:2010,2015,2020-2021,2026-2027,2032,2037-2038,2043,2048-2049,2054-2055,2060"}]} diff --git a/app/Support/Holidays/data/AI.json b/app/Support/Holidays/data/AI.json new file mode 100644 index 000000000..34c31178b --- /dev/null +++ b/app/Support/Holidays/data/AI.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"AI","language":"en_AI","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051|0103:2011,2022,2028,2033,2039,2050,2056"},{"id":"james_ronald_webster_day","name":"James Ronald Webster Day","en":"James Ronald Webster Day","category":"public","dates":"d|0302:2010-2060|0303:2014,2025,2031,2036,2042,2053,2059|0304:2013,2019,2024,2030,2041,2047,2052,2058"},{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_sunday","name":"Easter Sunday","en":"Easter Sunday","category":"public","dates":"d|0325:2035,2046|0327:2016|0328:2027,2032|0329:2043,2054|0330:2059|0331:2013,2024|0401:2018,2029,2040|0402:2051,2056|0404:2010,2021|0405:2015,2026,2037,2048|0406:2042,2053|0408:2012|0409:2023,2034,2045|0410:2039,2050|0412:2020|0413:2031,2036|0414:2047,2058|0416:2017,2028|0417:2022,2033,2044|0418:2049,2055,2060|0420:2014,2025|0421:2019,2030,2041,2052|0422:2057|0424:2011|0425:2038"},{"id":"easter_monday","name":"Easter Monday","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"labor_day","name":"Labour Day","en":"Labor Day","category":"public","dates":"d|0501:2010-2060|0502:2011,2016,2022,2033,2039,2044,2050|0503:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"pentecost_monday","name":"Whit Monday","en":"Pentecost Monday","category":"public","dates":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038"},{"id":"anguilla_day","name":"Anguilla Day","en":"Anguilla Day","category":"public","dates":"d|0530:2010-2060|0531:2010,2021,2027,2032,2038-2039,2049-2050,2055,2060|0601:2015,2026,2037,2043,2048,2054|0602:2020"},{"id":"king_s_birthday","name":"Celebration of the Birthday of His Majesty the King","en":"King's Birthday","category":"public","dates":"d|0615:2026,2037,2043,2048,2054|0616:2025,2031,2036,2042,2053,2059|0617:2024,2030,2041,2047,2052,2058|0618:2029,2035,2040,2046,2057|0619:2023,2028,2034,2045,2051,2056|0620:2033,2039,2044,2050|0621:2027,2032,2038,2049,2055,2060"},{"id":"august_monday","name":"August Monday","en":"August Monday","category":"public","dates":"d|0801:2011,2016,2022,2033,2039,2044,2050|0802:2010,2021,2027,2032,2038,2049,2055,2060|0803:2015,2020,2026,2037,2043,2048,2054|0804:2014,2025,2031,2036,2042,2053,2059|0805:2013,2019,2024,2030,2041,2047,2052,2058|0806:2012,2018,2029,2035,2040,2046,2057|0807:2017,2023,2028,2034,2045,2051,2056"},{"id":"august_thursday","name":"August Thursday","en":"August Thursday","category":"public","dates":"d|0804:2011,2016,2022,2033,2039,2044,2050|0805:2010,2021,2027,2032,2038,2049,2055,2060|0806:2015,2020,2026,2037,2043,2048,2054|0807:2014,2025,2031,2036,2042,2053,2059|0808:2013,2019,2024,2030,2041,2047,2052,2058|0809:2012,2018,2029,2035,2040,2046,2057|0810:2017,2023,2028,2034,2045,2051,2056"},{"id":"constitution_day","name":"Constitution Day","en":"Constitution Day","category":"public","dates":"d|0805:2011,2016,2022,2033,2039,2044,2050|0806:2010,2021,2027,2032,2038,2049,2055,2060|0807:2015,2020,2026,2037,2043,2048,2054|0808:2014,2025,2031,2036,2042,2053,2059|0809:2013,2019,2024,2030,2041,2047,2052,2058|0810:2012,2018,2029,2035,2040,2046,2057|0811:2017,2023,2028,2034,2045,2051,2056"},{"id":"national_heroes_and_heroines_day","name":"National Heroes and Heroines Day","en":"National Heroes and Heroines Day","category":"public","dates":"d|1217:2021,2027,2032,2038,2049,2055,2060|1218:2015,2020,2026,2037,2043,2048,2054|1219:2011-2060"},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060|1227:2010-2011,2016,2021-2022,2027,2032-2033,2038-2039,2044,2049-2050,2055,2060"},{"id":"boxing_day","name":"Boxing Day","en":"Boxing Day","category":"public","dates":"d|1226:2010-2060|1228:2010,2015,2020-2021,2026-2027,2032,2037-2038,2043,2048-2049,2054-2055,2060"}]} diff --git a/app/Support/Holidays/data/AL.json b/app/Support/Holidays/data/AL.json new file mode 100644 index 000000000..9d4c6c4a0 --- /dev/null +++ b/app/Support/Holidays/data/AL.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"AL","language":"sq","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Festat e Vitit të Ri","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2010-2060|0103:2011-2012,2017,2022-2023,2028,2033-2034,2039-2040,2045,2050-2051,2056|0104:2010-2011,2016,2021-2022,2027-2028,2033,2038-2039,2044,2049-2050,2055-2056"},{"id":"eid_al_fitr","name":"Dita e Bajramit të Madh","en":"Eid al-Fitr","category":"public","dates":"y|2010:0910|2011:0830|2012:0819-0820|2013:0808|2014:0728|2015:0717|2016:0706|2017:0625-0626|2018:0615|2019:0604|2020:0524-0525|2021:0513|2022:0502|2023:0421|2024:0410|2025:0330-0331|2026:0320|2027:0309|2028:0226,0228|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,0104,1223|2034:1212|2035:1201,1203|2036:1119|2037:1108-1109|2038:1029|2039:1019|2040:1007-1008|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712-0713|2049:0701|2050:0620|2051:0610,0612|2052:0529|2053:0519|2054:0509,0511|2055:0428|2056:0416-0417|2057:0405|2058:0325|2059:0315,0317|2060:0304","estimated":"y|2010:0910|2011:0830|2012:0819|2013:0808|2014:0728|2015:0717|2016:0706|2017:0625|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304"},{"id":"eid_al_adha","name":"Dita e Kurban Bajramit","en":"Eid al-Adha","category":"public","dates":"y|2010:1116|2011:1106-1107|2012:1026|2013:1015|2014:1004,1006|2015:0923|2016:0911-0912|2017:0901|2018:0821|2019:0811-0812|2020:0731|2021:0720|2022:0709,0711|2023:0628|2024:0616-0617|2025:0606|2026:0527|2027:0516-0517|2028:0505|2029:0424|2030:0413,0415|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218-0219|2036:0207|2037:0126|2038:0116,0118|2039:0105,1226|2040:1214|2041:1204|2042:1123,1125|2043:1112|2044:1031|2045:1021,1023|2046:1010|2047:0930|2048:0919,0921|2049:0908|2050:0828-0829|2051:0817|2052:0805|2053:0726,0728|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601,0603|2059:0522|2060:0510","estimated":"y|2010:1116|2011:1106|2012:1026|2013:1015|2014:1004|2015:0923|2016:0911|2017:0901|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510"},{"id":"summer_day","name":"Dita e Verës","en":"Summer Day","category":"public","dates":"d|0314:2010-2060|0315:2010,2021,2027,2032,2038,2049,2055,2060|0316:2015,2020,2026,2037,2043,2048,2054"},{"id":"nowruz_day","name":"Dita e Nevruzit","en":"Nowruz Day","category":"public","dates":"d|0322:2010-2060|0323:2015,2020,2026,2037,2043,2048,2054|0324:2014,2025,2031,2036,2042,2053,2059"},{"id":"catholic_easter_sunday","name":"E diela e Pashkëve Katolike","en":"Catholic Easter Sunday","category":"public","dates":"d|0325:2035,2046|0326:2035,2046|0327:2016|0328:2016,2027,2032|0329:2027,2032,2043,2054|0330:2043,2054,2059|0331:2013,2024,2059|0401:2013,2018,2024,2029,2040|0402:2018,2029,2040,2051,2056|0403:2051,2056|0404:2010,2021|0405:2010,2015,2021,2026,2037,2048|0406:2015,2026,2037,2042,2048,2053|0407:2042,2053|0408:2012|0409:2012,2023,2034,2045|0410:2023,2034,2039,2045,2050|0411:2039,2050|0412:2020|0413:2020,2031,2036|0414:2031,2036,2047,2058|0415:2047,2058|0416:2017,2028|0417:2017,2022,2028,2033,2044|0418:2022,2033,2044,2049,2055,2060|0419:2049,2055,2060|0420:2014,2025|0421:2014,2019,2025,2030,2041,2052|0422:2019,2030,2041,2052,2057|0423:2057|0424:2011|0425:2011,2038|0426:2038"},{"id":"orthodox_easter_sunday","name":"E diela e Pashkëve Ortodokse","en":"Orthodox Easter Sunday","category":"public","dates":"d|0404:2010|0405:2010,2037,2048|0406:2037,2048|0408:2018,2029|0409:2018,2029,2034,2045,2056|0410:2034,2045,2056|0412:2015,2026|0413:2015,2026,2031,2042,2053|0414:2031,2042,2053,2058|0415:2012,2058|0416:2012,2017,2023,2028|0417:2017,2023,2028,2039,2050|0418:2039,2050,2055|0419:2020,2055|0420:2014,2020,2025,2036|0421:2014,2025,2036,2041,2047,2052|0422:2041,2047,2052|0424:2011,2022,2033,2044|0425:2011,2022,2033,2038,2044,2049,2060|0426:2038,2049,2060|0428:2019,2030|0429:2019,2030,2035,2046,2057|0430:2035,2046,2057|0501:2016|0502:2016,2021,2027,2032|0503:2043,2054|0504:2021,2027,2032,2043,2054,2059|0505:2013,2024,2059|0506:2013,2024,2040|0507:2040,2051|0508:2051"},{"id":"international_workers_day","name":"Dita Ndërkombëtare e Punëtorëve","en":"International Workers' Day","category":"public","dates":"d|0501:2010-2060|0502:2011,2016,2033,2039,2044,2050|0503:2010,2021-2022,2027,2032,2038,2049,2055,2060"},{"id":"mother_teresa_canonization_day","name":"Dita e Shenjtërimit të Shenjt Terezës","en":"Mother Teresa Canonization Day","category":"public","dates":"d|0905:2018-2060|0906:2021,2027,2032,2038,2049,2055,2060|0907:2020,2026,2037,2043,2048,2054"},{"id":"alphabet_day","name":"Dita e Alfabetit","en":"Alphabet Day","category":"public","dates":"d|1122:2024-2060|1123:2026,2037,2043,2048,2054|1124:2025,2031,2036,2042,2053,2059"},{"id":"flag_and_independence_day","name":"Dita Flamurit dhe e Pavarësisë","en":"Flag and Independence Day","category":"public","dates":"d|1128:2010-2060|1130:2010,2015,2020-2021,2026-2027,2032,2037-2038,2043,2048-2049,2054-2055,2060"},{"id":"liberation_day","name":"Dita e Çlirimit","en":"Liberation Day","category":"public","dates":"d|1129:2010-2060|1201:2014-2015,2020,2025-2026,2031,2036-2037,2042-2043,2048,2053-2054,2059"},{"id":"national_youth_day","name":"Dita Kombëtare e Rinisë","en":"National Youth Day","category":"public","dates":"d|1208:2010-2060|1209:2013,2019,2024,2030,2041,2047,2052,2058|1210:2012,2018,2029,2035,2040,2046,2057"},{"id":"christmas_day","name":"Krishtlindjet","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060|1226:2011,2016,2022,2033,2044,2050|1227:2010,2021,2027,2032,2038-2039,2049,2055,2060"}]} diff --git a/app/Support/Holidays/data/AM.json b/app/Support/Holidays/data/AM.json new file mode 100644 index 000000000..262e5c301 --- /dev/null +++ b/app/Support/Holidays/data/AM.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"AM","language":"hy","years":[2010,2060],"subdivisions":{"AG":"Aragac̣otn","AR":"Ararat","AV":"Armavir","ER":"Erevan","GR":"Geġark'unik'","KT":"Kotayk'","LO":"Loṙi","SH":"Širak","SU":"Syunik'","TV":"Tavuš","VD":"Vayoć Jor"},"holidays":[{"id":"new_year_s_day","name":"Նոր տարվա օր","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2010-2060"},{"id":"christmas_and_epiphany","name":"Սուրբ Ծնունդ եւ Հայտնություն","en":"Christmas and Epiphany","category":"public","dates":"d|0106:2010-2060"},{"id":"day_of_remembrance_for_the_fallen_defenders_of_t","name":"Հայրենիքի պաշտպանության համար զոհվածների հիշատակի օր","en":"Day of Remembrance for the Fallen Defenders of the Fatherland","category":"public","dates":"d|0127:2026-2060"},{"id":"army_day","name":"Բանակի օր","en":"Army Day","category":"public","dates":"d|0128:2010-2060"},{"id":"women_s_day","name":"Կանանց տոն","en":"Women's Day","category":"public","dates":"d|0308:2010-2060"},{"id":"day_of_remembrance_of_the_victims_of_armenian_ge","name":"Հայոց ցեղասպանության զոհերի հիշատակի օր","en":"Day of Remembrance of the Victims of Armenian Genocide","category":"public","dates":"d|0424:2015-2060"},{"id":"labor_day","name":"Աշխատանքի օր","en":"Labor Day","category":"public","dates":"d|0501:2010-2060"},{"id":"victory_and_peace_day","name":"Հաղթանակի և Խաղաղության տոն","en":"Victory and Peace Day","category":"public","dates":"d|0509:2010-2060"},{"id":"republic_day","name":"Հանրապետության օր","en":"Republic Day","category":"public","dates":"d|0528:2010-2060"},{"id":"constitution_day","name":"Սահմանադրության օր","en":"Constitution Day","category":"public","dates":"d|0705:2010-2060"},{"id":"independence_day","name":"Անկախության օր","en":"Independence Day","category":"public","dates":"d|0921:2010-2060"},{"id":"new_year_s_eve","name":"Նոր տարվա գիշեր","en":"New Year's Eve","category":"public","dates":"d|1231:2010-2060"}]} diff --git a/app/Support/Holidays/data/AO.json b/app/Support/Holidays/data/AO.json new file mode 100644 index 000000000..f635ba0db --- /dev/null +++ b/app/Support/Holidays/data/AO.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"AO","language":"pt_AO","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Dia do Ano Novo","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"day_off_for_new_year_s_day","name":"Dia do Ano Novo (ponte)","en":"Day off for New Year's Day","category":"public","dates":"d|0102:2026,2032,2037,2043,2054,2060|1231:2018,2029,2035,2040,2046,2057"},{"id":"day_off_for_liberation_movement_day","name":"Dia do Início da Luta Armada de Libertação Nacional (ponte)","en":"Day off for Liberation Movement Day","category":"public","dates":"d|0203:2020,2025,2031,2042,2048,2053,2059|0205:2018,2021,2027,2038,2044,2049,2055"},{"id":"liberation_movement_day","name":"Dia do Início da Luta Armada","en":"Liberation Movement Day","category":"public","dates":"d|0204:2010-2060"},{"id":"day_off_for_carnival_day","name":"Dia do Carnaval (ponte)","en":"Day off for Carnival Day","category":"public","dates":"d|0205:2035,2046|0208:2027|0209:2032,2043,2054|0210:2059|0212:2024,2029|0213:2040,2051|0214:2056|0215:2021|0216:2026,2037|0217:2042,2048,2053|0220:2023,2034,2045|0221:2039,2050|0224:2020,2031|0225:2036,2047,2058|0228:2022,2028,2033|0229:2044|0301:2049,2055,2060|0303:2025|0304:2019,2030,2041,2052|0305:2057|0308:2038"},{"id":"carnival_day","name":"Dia do Carnaval","en":"Carnival Day","category":"public","dates":"d|0206:2035,2046|0209:2016,2027|0210:2032,2043,2054|0211:2059|0212:2013|0213:2018,2024,2029|0214:2040,2051|0215:2056|0216:2010,2021|0217:2015,2026,2037|0218:2042,2048,2053|0221:2012,2023,2034,2045|0222:2039,2050|0225:2020,2031|0226:2036,2047,2058|0228:2017|0229:2028|0301:2022,2033,2044|0302:2049,2055,2060|0304:2014,2025|0305:2019,2030,2041,2052|0306:2057|0308:2011|0309:2038"},{"id":"day_off_for_international_women_s_day","name":"Dia Internacional da Mulher (ponte)","en":"Day off for International Women's Day","category":"public","dates":"d|0307:2022,2033,2039,2044,2050|0309:2015,2029,2035,2040,2046,2057"},{"id":"international_women_s_day","name":"Dia Internacional da Mulher","en":"International Women's Day","category":"public","dates":"d|0308:2010-2060"},{"id":"day_off_for_southern_africa_liberation_day","name":"Dia da Libertação da África Austral (ponte)","en":"Day off for Southern Africa Liberation Day","category":"public","dates":"d|0322:2021,2027,2032,2038,2049,2055,2060|0324:2023,2028,2034,2045,2051,2056"},{"id":"good_friday","name":"Sexta-Feira Santa","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"southern_africa_liberation_day","name":"Dia da Libertação da África Austral","en":"Southern Africa Liberation Day","category":"public","dates":"d|0323:2019-2060"},{"id":"day_off_for_peace_and_national_reconciliation_da","name":"Dia da Paz e Reconciliação Nacional (ponte)","en":"Day off for Peace and National Reconciliation Day","category":"public","dates":"d|0403:2023,2028,2034,2045,2051,2056|0405:2010,2019,2024,2030,2041,2047,2052,2058"},{"id":"peace_and_national_reconciliation_day","name":"Dia da Paz e Reconciliação Nacional","en":"Peace and National Reconciliation Day","category":"public","dates":"d|0404:2010-2060"},{"id":"day_off_for_international_worker_s_day","name":"Dia Internacional do Trabalhador (ponte)","en":"Day off for International Worker's Day","category":"public","dates":"d|0430:2029,2035,2040,2046,2057|0502:2011,2016,2025,2031,2036,2042,2053,2059"},{"id":"international_worker_s_day","name":"Dia Internacional do Trabalhador","en":"International Worker's Day","category":"public","dates":"d|0501:2010-2060"},{"id":"day_off_for_national_heroes_day","name":"Dia do Fundador da Nação e do Herói Nacional (ponte)","en":"Day off for National Heroes' Day","category":"public","dates":"d|0916:2019,2024,2030,2041,2047,2052,2058|0918:2017,2020,2026,2037,2043,2048,2054"},{"id":"national_heroes_day","name":"Dia do Fundador da Nação e do Herói Nacional","en":"National Heroes' Day","category":"public","dates":"d|0917:2010-2060"},{"id":"day_off_for_all_souls_day","name":"Dia dos Finados (ponte)","en":"Day off for All Souls' Day","category":"public","dates":"d|1101:2021,2027,2032,2038,2049,2055,2060|1103:2023,2028,2034,2045,2051,2056"},{"id":"all_souls_day","name":"Dia dos Finados","en":"All Souls' Day","category":"public","dates":"d|1102:2010-2060"},{"id":"day_off_for_national_independence_day","name":"Dia da Independência Nacional (ponte)","en":"Day off for National Independence Day","category":"public","dates":"d|1110:2025,2031,2036,2042,2053,2059|1112:2012,2021,2027,2032,2038,2049,2055,2060"},{"id":"national_independence_day","name":"Dia da Independência Nacional","en":"National Independence Day","category":"public","dates":"d|1111:2010-2060"},{"id":"day_off_for_christmas_and_family_day","name":"Dia de Natal e da Família (ponte)","en":"Day off for Christmas and Family Day","category":"public","dates":"d|1224:2018,2029,2035,2040,2046,2057|1226:2025,2031,2036,2042,2053,2059"},{"id":"christmas_and_family_day","name":"Dia de Natal e da Família","en":"Christmas and Family Day","category":"public","dates":"d|1225:2011-2060"}]} diff --git a/app/Support/Holidays/data/AQ.json b/app/Support/Holidays/data/AQ.json new file mode 100644 index 000000000..7d10ce746 --- /dev/null +++ b/app/Support/Holidays/data/AQ.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"AQ","language":null,"years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"midwinter_day","name":"Midwinter Day","en":"Midwinter Day","category":"public","dates":"d|0620:2012,2016,2020,2024,2028,2032,2036,2040,2044,2048,2052,2056,2060|0621:2010-2011,2013-2015,2017-2019,2021-2023,2025-2027,2029-2031,2033-2035,2037-2039,2041-2043,2045-2047,2049-2051,2053-2055,2057-2059"},{"id":"antarctica_day","name":"Antarctica Day","en":"Antarctica Day","category":"public","dates":"d|1201:2010-2060"},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"}]} diff --git a/app/Support/Holidays/data/AR.json b/app/Support/Holidays/data/AR.json new file mode 100644 index 000000000..06f317f32 --- /dev/null +++ b/app/Support/Holidays/data/AR.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"AR","language":"es","years":[2010,2060],"subdivisions":{"A":"Salta","B":"Buenos Aires","C":"Ciudad Autónoma de Buenos Aires","D":"San Luis","E":"Entre Ríos","F":"La Rioja","G":"Santiago del Estero","H":"Chaco","J":"San Juan","K":"Catamarca","L":"La Pampa","M":"Mendoza","N":"Misiones","P":"Formosa","Q":"Neuquén","R":"Río Negro","S":"Santa Fe","T":"Tucumán","U":"Chubut","V":"Tierra del Fuego","W":"Corrientes","X":"Córdoba","Y":"Jujuy","Z":"Santa Cruz"},"holidays":[{"id":"new_year_s_day","name":"Año Nuevo","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"saint_john_bosco_s_day","name":"Homenaje al Patrono de la Provincia San Juan Bosco","en":"Saint John Bosco's Day","category":"public","regional":{"Z":"d|0131:2010-2014|0816:2015-2060"}},{"id":"commemoration_of_the_battle_of_caseros","name":"Conmemoración de la Batalla de Caseros","en":"Commemoration of the Battle of Caseros","category":"public","regional":{"E":"d|0203:2010-2060"}},{"id":"carnival_monday","name":"Lunes de Carnaval","en":"Carnival Monday","category":"public","dates":"d|0205:2035,2046|0208:2016,2027|0209:2032,2043,2054|0210:2059|0211:2013|0212:2018,2024,2029|0213:2040,2051|0214:2056|0215:2021|0216:2015,2026,2037|0217:2042,2048,2053|0220:2012,2023,2034,2045|0221:2039,2050|0224:2020,2031|0225:2036,2047,2058|0227:2017|0228:2022,2028,2033|0229:2044|0301:2049,2055,2060|0303:2014,2025|0304:2019,2030,2041,2052|0305:2057|0307:2011|0308:2038","regional":{"Y":"d|0215:2010"}},{"id":"carnival_tuesday","name":"Martes de Carnaval","en":"Carnival Tuesday","category":"public","dates":"d|0206:2035,2046|0209:2016,2027|0210:2032,2043,2054|0211:2059|0212:2013|0213:2018,2024,2029|0214:2040,2051|0215:2056|0216:2021|0217:2015,2026,2037|0218:2042,2048,2053|0221:2012,2023,2034,2045|0222:2039,2050|0225:2020,2031|0226:2036,2047,2058|0228:2017|0229:2028|0301:2022,2033,2044|0302:2049,2055,2060|0304:2014,2025|0305:2019,2030,2041,2052|0306:2057|0308:2011|0309:2038","regional":{"Y":"d|0216:2010"}},{"id":"day_of_the_death_of_juan_facundo_quiroga","name":"Martes de Carnaval","en":"Day of the Death of Juan Facundo Quiroga","category":"public","regional":{"F":"d|0216:2021-2060"}},{"id":"anniversary_of_the_battle_of_salta","name":"Aniversario de la Batalla de Salta","en":"Anniversary of the Battle of Salta","category":"public","regional":{"A":"d|0220:2010-2060"}},{"id":"provincial_autonomy_day","name":"Día de la Autonomía Provincial","en":"Provincial Autonomy Day","category":"public","regional":{"F":"d|0301:2020-2060"}},{"id":"maundy_thursday","name":"Jueves Santo","en":"Maundy Thursday","category":"public","dates":"d|0322:2035,2046|0324:2016|0325:2027,2032|0326:2043,2054|0327:2059|0328:2013,2024|0329:2018,2029,2040|0330:2051,2056|0401:2021|0402:2015,2026,2037,2048|0403:2042,2053|0405:2012|0406:2023,2034,2045|0407:2039,2050|0409:2020|0410:2031,2036|0411:2047,2058|0413:2017,2028|0414:2022,2033,2044|0415:2049,2055,2060|0417:2014,2025|0418:2019,2030,2041,2052|0419:2057|0421:2011|0422:2038"},{"id":"bridge_public_holiday","name":"Feriado con fines turísticos","en":"Bridge Public Holiday","category":"public","dates":"y|2011:0325,1209|2012:0430,1224|2013:0401,0621|2014:0502,1226|2015:0323,1207|2016:0708,1209|2018:0430,1224,1231|2019:0708,0819,1014|2020:0323,0710,1207|2021:0524,1008,1122|2022:1007,1121,1209|2023:0526,0619,1013|2024:0401,0621,1011|2025:0502,0815,1121|2026:0323,0710,1207"},{"id":"good_friday","name":"Día del Veterano y de los Caidos en la Guerra de Malvinas","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"day_of_remembrance_for_truth_and_justice","name":"Día Nacional de la Memoria por la Verdad y la Justicia","en":"Day of Remembrance for Truth and Justice","category":"public","regional":{"F":"d|0324:2017-2060"}},{"id":"national_day_of_remembrance_for_truth_and_justic","name":"Día Nacional de la Memoria por la Verdad y la Justicia","en":"National Day of Remembrance for Truth and Justice","category":"public","dates":"d|0324:2010-2060"},{"id":"provincial_day_of_remembrance_for_truth_and_just","name":"Día Provincial de la Memoria por la Verdad y la Justicia","en":"Provincial Day of Remembrance for Truth and Justice","category":"public","regional":{"E":"d|0324:2018-2060"}},{"id":"veteran_s_day_and_the_fallen_in_the_malvinas_war","name":"Viernes Santo","en":"Veteran's Day and the Fallen in the Malvinas War","category":"public","dates":"d|0331:2020|0402:2010-2019,2021-2060"},{"id":"great_day_of_jujuy","name":"Día Grande de Jujuy","en":"Great Day of Jujuy","category":"public","regional":{"Y":"d|0427:2021-2060"}},{"id":"plebiscite_1902_trevelin","name":"Plebiscito 1902 Trevelin","en":"Plebiscite 1902 Trevelin","category":"public","regional":{"U":"d|0430:2010-2060"}},{"id":"labor_day","name":"Día del Trabajo","en":"Labor Day","category":"public","dates":"d|0501:2010-2060"},{"id":"exaltation_of_the_holy_cross_day","name":"Día de la Exaltación de la Santa Cruz","en":"Exaltation of the Holy Cross Day","category":"public","regional":{"D":"d|0503:2010-2060"}},{"id":"birthday_of_mamerto_esquiu","name":"Natalicio de Fray Mamerto Esquiú","en":"Birthday of Mamerto Esquiú","category":"public","regional":{"K":"d|0511:2010-2060"}},{"id":"la_rioja_foundation_day","name":"Día de la fundación de La Rioja","en":"La Rioja Foundation Day","category":"public","regional":{"F":"d|0520:2010-2060"}},{"id":"may_revolution_day","name":"Día de la Revolución de Mayo","en":"May Revolution Day","category":"public","dates":"d|0525:2010-2060"},{"id":"day_of_the_province_of_tierra_del_fuego_antarcti","name":"Día de la Provincia de Tierra del Fuego, Antártida e Islas del Atlántico Sur","en":"Day of the Province of Tierra del Fuego, Antarctica and the South Atlantic Islands","category":"public","regional":{"V":"d|0601:2010-2060"}},{"id":"pass_to_the_immortality_of_general_don_martin_mi","name":"Paso a la Inmortalidad del General Don Martín Miguel de Güemes","en":"Pass to the Immortality of General Don Martín Miguel de Güemes","category":"public","dates":"d|0615:2020,2026,2037,2043,2048,2054|0616:2025,2031,2036,2042,2053,2059|0617:2016-2019,2022-2024,2028-2030,2033-2035,2039-2041,2044-2047,2050-2052,2056-2058|0621:2021,2027,2032,2038,2049,2055,2060"},{"id":"day_of_memory_of_general_don_martin_miguel_de_gu","name":"Dia de la memoria del Guerrero de la Independencia y Gobernador de la Provincia de Salta General Don Martín Miguel de Güemes","en":"Day of Memory of General Don Martín Miguel de Güemes","category":"public","regional":{"A":"d|0617:2010-2060"}},{"id":"anniversary_of_the_death_of_general_manuel_belgr","name":"Día del Aniversario del Fallecimiento del General Manuel José Joaquín del Corazón de Jesús Belgrano","en":"Anniversary of the Death of General Manuel Belgrano","category":"public","regional":{"W":"d|0620:2010-2060"}},{"id":"pass_to_the_immortality_of_general_don_manuel_be","name":"Paso a la Inmortalidad del General Don Manuel Belgrano","en":"Pass to the Immortality of General Don Manuel Belgrano","category":"public","dates":"d|0620:2011-2060|0621:2010"},{"id":"state_worker_s_day","name":"Día del Trabajador Estatal","en":"State Worker's Day","category":"public","regional":{"E":"d|0627:2010-2060"}},{"id":"independence_day","name":"Día de la Independencia","en":"Independence Day","category":"public","dates":"d|0709:2010-2060"},{"id":"saint_james_day","name":"Día del Apóstol Santiago","en":"Saint James' Day","category":"public","regional":{"M":"d|0725:2010-2060"}},{"id":"anniversary_of_the_arrival_of_the_first_welsh_se","name":"Aniversario del arribo de los primeros colonizadores galeses","en":"Anniversary of the arrival of the first Welsh settlers","category":"public","regional":{"U":"d|0728:2010-2060"}},{"id":"pachamama_day","name":"Día de la Pachamama","en":"Pachamama Day","category":"public","regional":{"Y":"d|0801:2010-2060"}},{"id":"anniversary_of_the_death_of_enrique_angelelli","name":"Día del Aniversario del Fallecimiento de Monseñor Enrique Angelelli","en":"Anniversary of the Death of Enrique Angelelli","category":"public","regional":{"F":"d|0804:2016-2060"}},{"id":"pass_to_the_immortality_of_general_don_jose_de_s","name":"Paso a la Inmortalidad del General Don José de San Martín","en":"Pass to the Immortality of General Don José de San Martín","category":"public","dates":"d|0815:2016,2022,2033,2039,2044,2050|0816:2010,2021,2027,2032,2038,2049,2055,2060|0817:2015,2019-2020,2024-2026,2030-2031,2036-2037,2041-2043,2047-2048,2052-2054,2058-2059|0818:2014|0819:2013|0820:2012,2018,2029,2035,2040,2046,2057|0821:2017,2023,2028,2034,2045,2051,2056|0822:2011"},{"id":"anniversary_of_the_death_of_general_jose_francis","name":"Día del Aniversario del Fallecimiento del General José Francisco de San Martín","en":"Anniversary of the Death of General José Francisco de San Martín","category":"public","regional":{"W":"d|0817:2010-2060"}},{"id":"jujuy_exodus_day","name":"Día del Éxodo Jujeño","en":"Jujuy Exodus Day","category":"public","regional":{"Y":"d|0823:2010-2060"}},{"id":"catamarca_autonomy_day","name":"Autonomía de Catamarca","en":"Catamarca Autonomy Day","category":"public","regional":{"K":"d|0825:2010-2060"}},{"id":"saint_louis_the_king_of_france_s_day","name":"Día de San Luis Rey de Francia","en":"Saint Louis the King of France's Day","category":"public","regional":{"D":"d|0825:2010-2060"}},{"id":"miracle_day","name":"Día del Milagro","en":"Miracle Day","category":"public","regional":{"K":"d|0907:2018-2060"}},{"id":"teacher_s_day","name":"Día del Maestro","en":"Teacher's Day","category":"public","regional":{"J":"d|0911:2015-2060"}},{"id":"feasts_of_the_lord_and_the_virgin_of_miracle","name":"Festividades del Señor y de la Virgen del Milagro","en":"Feasts of the Lord and the Virgin of Miracle","category":"public","regional":{"A":"d|0913:2010-2060|0914:2010-2060|0915:2010-2060"}},{"id":"anniversary_of_the_battle_of_tucuman","name":"Aniversario de la Batalla de Tucumán","en":"Anniversary of the Battle of Tucumán","category":"public","regional":{"T":"d|0924:2010-2060"}},{"id":"saint_michael_the_archangel_s_day","name":"San Miguel Arcángel","en":"Saint Michael the Archangel's Day","category":"public","regional":{"E":"d|0929:2010-2060"}},{"id":"day_of_the_virgin_of_the_rosary_of_rio_blanco_an","name":"Día de la Virgen del Rosario de Río Blanco y Paypaya","en":"Day of the Virgin of the Rosary of Río Blanco and Paypaya","category":"public","regional":{"Y":"d|1007:2010-2060"}},{"id":"respect_for_cultural_diversity_day","name":"Día del Respeto a la Diversidad Cultural","en":"Respect for Cultural Diversity Day","category":"public","dates":"d|1008:2012|1010:2011,2016,2022,2033,2039,2044,2050|1011:2010,2021,2027,2032,2038,2049,2055,2060|1012:2015,2019-2020,2024-2026,2030-2031,2036-2037,2041-2043,2047-2048,2052-2054,2058-2059|1013:2014|1014:2013|1015:2018,2029,2035,2040,2046,2057|1016:2017,2023,2028,2034,2045,2051,2056"},{"id":"anniversary_of_the_death_of_nestor_carlos_kirchn","name":"Día del Aniversario del Fallecimiento del ex Presidente de la Nación Doctor Néstor Carlos Kirchner","en":"Anniversary of the Death of Néstor Carlos Kirchner","category":"public","regional":{"Z":"d|1027:2014-2060"}},{"id":"tehuelches_and_mapuches_declare_loyalty_to_the_a","name":"Tehuelches y Mapuches declaran lealtad a la bandera Argentina","en":"Tehuelches and Mapuches declare loyalty to the Argentine flag","category":"public","regional":{"U":"d|1103:2015-2060"}},{"id":"visit_of_his_holiness_pope_leo_xiv","name":"Visita de Su Santidad el Papa León XIV","en":"Visit of His Holiness Pope Leo XIV","category":"public","dates":"d|1109:2026","regional":{"B":"d|1111:2026","C":"d|1110:2026","X":"d|1110:2026"}},{"id":"anniversary_of_the_death_of_angel_vicente_penalo","name":"Día del Aniversario del Fallecimiento de Ángel Vicente Peñaloza","en":"Anniversary of the Death of Ángel Vicente Peñaloza","category":"public","regional":{"F":"d|1112:2020-2060"}},{"id":"jujuy_political_autonomy_day","name":"Autonomía Política de Jujuy","en":"Jujuy Political Autonomy Day","category":"public","regional":{"Y":"d|1118:2010-2060"}},{"id":"national_sovereignty_day","name":"Día de la Soberanía Nacional","en":"National Sovereignty Day","category":"public","dates":"d|1118:2019,2024,2030,2041,2047,2052,2058|1119:2018,2029,2035,2040,2046,2057|1120:2017,2021-2023,2027-2028,2032-2034,2038-2039,2044-2045,2049-2051,2055-2056,2060|1122:2010|1123:2020,2026,2037,2043,2048,2054|1124:2014,2025,2031,2036,2042,2053,2059|1125:2013|1126:2012|1127:2015|1128:2011,2016"},{"id":"selk_nam_genocide_day","name":"Día del Genocidio Selk'Nam","en":"Selk'Nam Genocide Day","category":"public","regional":{"V":"d|1125:2021-2060"}},{"id":"commemoration_of_the_workers_shot_in_the_patagon","name":"Conmemoración a los obreros fusilados en las Huelgas Patagónicas","en":"Commemoration of the workers shot in the Patagonian Strikes","category":"public","regional":{"Z":"d|1207:2019-2060"}},{"id":"immaculate_conception","name":"Inmaculada Concepción de María","en":"Immaculate Conception","category":"public","dates":"d|1208:2010-2060"},{"id":"national_petroleum_day","name":"Día del Petróleo Nacional","en":"National Petroleum Day","category":"public","regional":{"U":"d|1213:2010-2060"}},{"id":"christmas_day","name":"Navidad","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"},{"id":"tinkunaco_festival","name":"Día del Tinkunaco Riojano","en":"Tinkunaco Festival","category":"public","regional":{"F":"d|1231:2010-2060"}},{"id":"eid_al_fitr_islamic","name":"Día posterior a la culminación del ayuno (Id Al-Fitr)","en":"Eid al-Fitr","category":"islamic","dates":"y|2010:0910|2011:0830|2012:0819|2013:0808|2014:0728|2015:0717|2016:0706|2017:0625|2018:0615|2019:0604|2020:0524|2021:0513|2022:0502|2023:0421|2024:0410|2025:0331|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304","estimated":"y|2010:0910|2011:0830|2012:0819|2013:0808|2014:0728|2015:0717|2016:0706|2017:0625|2018:0615|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304"},{"id":"islamic_new_year_islamic","name":"Año Nuevo Musulmán (Hégira)","en":"Islamic New Year","category":"islamic","dates":"y|2010:1207|2011:1126|2012:1115|2013:1104|2014:1025|2015:1014|2016:1002|2017:0921|2018:0911|2019:0831|2020:0820|2021:0808|2022:0730|2023:0719|2024:0707|2025:0626|2026:0616|2027:0606|2028:0525|2029:0514|2030:0503|2031:0423|2032:0411|2033:0401|2034:0321|2035:0311|2036:0228|2037:0216|2038:0205|2039:0126|2040:0115|2041:0104,1224|2042:1214|2043:1203|2044:1121|2045:1110|2046:1031|2047:1020|2048:1009|2049:0928|2050:0917|2051:0906|2052:0826|2053:0815|2054:0805|2055:0725|2056:0714|2057:0703|2058:0622|2059:0611|2060:0531","estimated":"y|2010:1207|2011:1126|2012:1115|2013:1104|2014:1025|2015:1014|2016:1002|2017:0921|2018:0911|2026:0616|2027:0606|2028:0525|2029:0514|2030:0503|2031:0423|2032:0411|2033:0401|2034:0321|2035:0311|2036:0228|2037:0216|2038:0205|2039:0126|2040:0115|2041:0104,1224|2042:1214|2043:1203|2044:1121|2045:1110|2046:1031|2047:1020|2048:1009|2049:0928|2050:0917|2051:0906|2052:0826|2053:0815|2054:0805|2055:0725|2056:0714|2057:0703|2058:0622|2059:0611|2060:0531"},{"id":"eid_al_adha_islamic","name":"Día de la Fiesta del Sacrificio (Id Al-Adha)","en":"Eid al-Adha","category":"islamic","dates":"y|2010:1116|2011:1106|2012:1026|2013:1015|2014:1004|2015:0923|2016:0911|2017:0901|2018:0821|2019:0811|2020:0731|2021:0720|2022:0709|2023:0628|2024:0616|2025:0610|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510","estimated":"y|2010:1116|2011:1106|2012:1026|2013:1015|2014:1004|2015:0923|2016:0911|2017:0901|2018:0821|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510"},{"id":"pesach_hebrew","name":"Pascua Judía (Pésaj)","en":"Pesach","category":"hebrew","dates":"d|0326:2013|0327:2013,2032|0328:2021,2032,2051|0329:2021,2040,2048,2051,2059|0330:2010,2040,2048,2059|0331:2010,2018,2029,2037|0401:2013,2018,2029,2037,2056|0402:2013,2026,2032,2045,2056|0403:2021,2026,2032,2045,2051,2053|0404:2015,2021,2034,2040,2048,2051,2053,2059|0405:2010,2015,2034,2040,2042,2048,2059|0406:2010,2018,2023,2029,2037,2042|0407:2012,2018,2023,2029,2037,2050,2056|0408:2012,2026,2031,2045,2050,2056|0409:2020,2026,2031,2039,2045,2053,2058|0410:2015,2020,2034,2039,2053,2058|0411:2015,2017,2028,2034,2042,2047|0412:2017,2023,2028,2036,2042,2044,2047|0413:2012,2023,2025,2036,2044,2050,2055|0414:2012,2025,2031,2033,2050,2052,2055|0415:2014,2020,2031,2033,2039,2052,2058,2060|0416:2014,2020,2022,2039,2041,2058,2060|0417:2017,2022,2028,2041,2047,2049|0418:2017,2028,2030,2036,2044,2047,2049|0419:2011,2025,2030,2036,2044,2055,2057|0420:2011,2019,2025,2033,2038,2052,2055,2057|0421:2014,2019,2033,2038,2046,2052,2060|0422:2014,2022,2027,2041,2046,2060|0423:2016,2022,2024,2027,2041,2049,2054|0424:2016,2024,2030,2035,2049,2054|0425:2011,2030,2035,2043,2057|0426:2011,2019,2038,2043,2057|0427:2019,2038,2046|0428:2027,2046|0429:2016,2024,2027,2054|0430:2016,2024,2035,2054|0501:2035,2043|0502:2043"},{"id":"day_of_action_for_tolerance_and_respect_among_pe_armenian","name":"Día de acción por la tolerancia y el respeto entre los pueblos","en":"Day of Action for Tolerance and Respect among Peoples","category":"armenian","dates":"d|0424:2010-2060"},{"id":"state_worker_s_day_government","name":"Día del Trabajador del Estado","en":"State Worker's Day","category":"government","dates":"d|0627:2014-2060"},{"id":"rosh_hashanah_hebrew","name":"Año Nuevo Judío (Rosh Hashana)","en":"Rosh Hashanah","category":"hebrew","dates":"d|0905:2013|0906:2013,2032|0907:2021,2032,2051|0908:2021,2040,2048,2051,2059|0909:2010,2040,2048,2059|0910:2010,2018,2029,2037|0911:2018,2029,2037,2056|0912:2026,2045,2056|0913:2026,2045,2053|0914:2015,2034,2053|0915:2015,2034,2042|0916:2023,2042|0917:2012,2023,2050|0918:2012,2031,2050|0919:2020,2031,2039,2058|0920:2020,2039,2058|0921:2017,2028,2047|0922:2017,2028,2036,2044,2047|0923:2025,2036,2044,2055|0924:2025,2033,2052,2055|0925:2014,2033,2052,2060|0926:2014,2022,2041,2060|0927:2022,2041,2049|0928:2030,2049|0929:2011,2030,2057|0930:2011,2019,2038,2057|1001:2019,2038,2046|1002:2027,2046|1003:2016,2024,2027,2054|1004:2016,2024,2035,2054|1005:2035,2043|1006:2043"},{"id":"yom_kippur_hebrew","name":"Día del Perdón (Iom Kipur)","en":"Yom Kippur","category":"hebrew","dates":"d|0914:2013|0915:2032|0916:2021,2051|0917:2040,2048,2059|0918:2010|0919:2018,2029,2037|0920:2056|0921:2026,2045|0922:2053|0923:2015,2034|0924:2042|0925:2023|0926:2012,2050|0927:2031|0928:2020,2039,2058|0930:2017,2028,2047|1001:2036,2044|1002:2025,2055|1003:2033,2052|1004:2014,2060|1005:2022,2041|1006:2049|1007:2030|1008:2011,2057|1009:2019,2038|1010:2046|1011:2027|1012:2016,2024,2054|1013:2035|1014:2043"},{"id":"bankers_day_bank","name":"Día del Bancario","en":"Bankers' Day","category":"bank","dates":"d|1106:2010-2060"},{"id":"visit_of_his_holiness_pope_leo_xiv_bank","name":"Visita de Su Santidad el Papa León XIV","en":"Visit of His Holiness Pope Leo XIV","category":"bank","dates":"d|1110:2026"}]} diff --git a/app/Support/Holidays/data/AS.json b/app/Support/Holidays/data/AS.json new file mode 100644 index 000000000..2885a71bc --- /dev/null +++ b/app/Support/Holidays/data/AS.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"AS","language":"en_US","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051|1231:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"martin_luther_king_jr_day","name":"Martin Luther King Jr. Day","en":"Martin Luther King Jr. Day","category":"public","dates":"d|0115:2018,2024,2029,2035,2046,2052,2057|0116:2012,2017,2023,2034,2040,2045,2051|0117:2011,2022,2028,2033,2039,2050,2056|0118:2010,2016,2021,2027,2038,2044,2049,2055|0119:2015,2026,2032,2037,2043,2054,2060|0120:2014,2020,2025,2031,2042,2048,2053,2059|0121:2013,2019,2030,2036,2041,2047,2058"},{"id":"washington_s_birthday","name":"Washington's Birthday","en":"Washington's Birthday","category":"public","dates":"d|0215:2010,2016,2021,2027,2038,2044,2049,2055|0216:2015,2026,2032,2037,2043,2054,2060|0217:2014,2020,2025,2031,2042,2048,2053,2059|0218:2013,2019,2030,2036,2041,2047,2058|0219:2018,2024,2029,2035,2046,2052,2057|0220:2012,2017,2023,2034,2040,2045,2051|0221:2011,2022,2028,2033,2039,2050,2056"},{"id":"american_samoa_flag_day","name":"American Samoa Flag Day","en":"American Samoa Flag Day","category":"public","dates":"d|0416:2010,2021,2027,2032,2038,2049,2055,2060|0417:2010-2060|0418:2011,2016,2022,2033,2039,2044,2050"},{"id":"memorial_day","name":"Memorial Day","en":"Memorial Day","category":"public","dates":"d|0525:2015,2020,2026,2037,2043,2048,2054|0526:2014,2025,2031,2036,2042,2053,2059|0527:2013,2019,2024,2030,2041,2047,2052,2058|0528:2012,2018,2029,2035,2040,2046,2057|0529:2017,2023,2028,2034,2045,2051,2056|0530:2011,2016,2022,2033,2039,2044,2050|0531:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"juneteenth_national_independence_day","name":"Juneteenth National Independence Day","en":"Juneteenth National Independence Day","category":"public","dates":"d|0618:2021,2027,2032,2038,2049,2055,2060|0619:2021-2060|0620:2022,2033,2039,2044,2050"},{"id":"independence_day","name":"Independence Day","en":"Independence Day","category":"public","dates":"d|0703:2015,2020,2026,2037,2043,2048,2054|0704:2010-2060|0705:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"manu_a_islands_cession_day","name":"Manu'a Islands Cession Day","en":"Manu'a Islands Cession Day","category":"public","dates":"d|0715:2011,2016,2022,2033,2039,2044,2050|0716:2010-2060|0717:2017,2023,2028,2034,2045,2051,2056"},{"id":"labor_day","name":"Labor Day","en":"Labor Day","category":"public","dates":"d|0901:2014,2025,2031,2036,2042,2053,2059|0902:2013,2019,2024,2030,2041,2047,2052,2058|0903:2012,2018,2029,2035,2040,2046,2057|0904:2017,2023,2028,2034,2045,2051,2056|0905:2011,2016,2022,2033,2039,2044,2050|0906:2010,2021,2027,2032,2038,2049,2055,2060|0907:2015,2020,2026,2037,2043,2048,2054"},{"id":"columbus_day","name":"Columbus Day","en":"Columbus Day","category":"public","dates":"d|1008:2012,2018,2029,2035,2040,2046,2057|1009:2017,2023,2028,2034,2045,2051,2056|1010:2011,2016,2022,2033,2039,2044,2050|1011:2010,2021,2027,2032,2038,2049,2055,2060|1012:2015,2020,2026,2037,2043,2048,2054|1013:2014,2025,2031,2036,2042,2053,2059|1014:2013,2019,2024,2030,2041,2047,2052,2058"},{"id":"white_sunday","name":"White Sunday","en":"White Sunday","category":"public","dates":"d|1008:2017,2023,2028,2034,2045,2051,2056|1009:2011,2016,2022,2033,2039,2044,2050|1010:2010,2021,2027,2032,2038,2049,2055,2060|1011:2015,2020,2026,2037,2043,2048,2054|1012:2014,2025,2031,2036,2042,2053,2059|1013:2013,2019,2024,2030,2041,2047,2052,2058|1014:2012,2018,2029,2035,2040,2046,2057"},{"id":"veterans_day","name":"Veterans Day","en":"Veterans Day","category":"public","dates":"d|1110:2017,2023,2028,2034,2045,2051,2056|1111:2010-2060|1112:2012,2018,2029,2035,2040,2046,2057"},{"id":"thanksgiving_day","name":"Thanksgiving Day","en":"Thanksgiving Day","category":"public","dates":"d|1122:2012,2018,2029,2035,2040,2046,2057|1123:2017,2023,2028,2034,2045,2051,2056|1124:2011,2016,2022,2033,2039,2044,2050|1125:2010,2021,2027,2032,2038,2049,2055,2060|1126:2015,2020,2026,2037,2043,2048,2054|1127:2014,2025,2031,2036,2042,2053,2059|1128:2013,2019,2024,2030,2041,2047,2052,2058"},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1224:2010,2021,2027,2032,2038,2049,2055,2060|1225:2010-2060|1226:2011,2016,2022,2033,2039,2044,2050"},{"id":"valentine_s_day_unofficial","name":"Valentine's Day","en":"Valentine's Day","category":"unofficial","dates":"d|0214:2010-2060"},{"id":"saint_patrick_s_day_unofficial","name":"Saint Patrick's Day","en":"Saint Patrick's Day","category":"unofficial","dates":"d|0317:2010-2060"},{"id":"good_friday_unofficial","name":"Good Friday","en":"Good Friday","category":"unofficial","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_sunday_unofficial","name":"Easter Sunday","en":"Easter Sunday","category":"unofficial","dates":"d|0325:2035,2046|0327:2016|0328:2027,2032|0329:2043,2054|0330:2059|0331:2013,2024|0401:2018,2029,2040|0402:2051,2056|0404:2010,2021|0405:2015,2026,2037,2048|0406:2042,2053|0408:2012|0409:2023,2034,2045|0410:2039,2050|0412:2020|0413:2031,2036|0414:2047,2058|0416:2017,2028|0417:2022,2033,2044|0418:2049,2055,2060|0420:2014,2025|0421:2019,2030,2041,2052|0422:2057|0424:2011|0425:2038"},{"id":"mother_s_day_unofficial","name":"Mother's Day","en":"Mother's Day","category":"unofficial","dates":"d|0508:2011,2016,2022,2033,2039,2044,2050|0509:2010,2021,2027,2032,2038,2049,2055,2060|0510:2015,2020,2026,2037,2043,2048,2054|0511:2014,2025,2031,2036,2042,2053,2059|0512:2013,2019,2024,2030,2041,2047,2052,2058|0513:2012,2018,2029,2035,2040,2046,2057|0514:2017,2023,2028,2034,2045,2051,2056"},{"id":"father_s_day_unofficial","name":"Father's Day","en":"Father's Day","category":"unofficial","dates":"d|0615:2014,2025,2031,2036,2042,2053,2059|0616:2013,2019,2024,2030,2041,2047,2052,2058|0617:2012,2018,2029,2035,2040,2046,2057|0618:2017,2023,2028,2034,2045,2051,2056|0619:2011,2016,2022,2033,2039,2044,2050|0620:2010,2021,2027,2032,2038,2049,2055,2060|0621:2015,2020,2026,2037,2043,2048,2054"},{"id":"halloween_unofficial","name":"Halloween","en":"Halloween","category":"unofficial","dates":"d|1031:2010-2060"},{"id":"christmas_eve_unofficial","name":"Christmas Eve","en":"Christmas Eve","category":"unofficial","dates":"d|1224:2010-2060"},{"id":"new_year_s_eve_unofficial","name":"New Year's Eve","en":"New Year's Eve","category":"unofficial","dates":"d|1231:2010-2060"}]} diff --git a/app/Support/Holidays/data/AT.json b/app/Support/Holidays/data/AT.json new file mode 100644 index 000000000..9a39dc0d0 --- /dev/null +++ b/app/Support/Holidays/data/AT.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"AT","language":"de","years":[2010,2060],"subdivisions":{"1":"Burgenland","2":"Kärnten","3":"Niederösterreich","4":"Oberösterreich","5":"Salzburg","6":"Steiermark","7":"Tirol","8":"Vorarlberg","9":"Wien"},"holidays":[{"id":"new_year_s_day","name":"Neujahr","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"epiphany","name":"Heilige Drei Könige","en":"Epiphany","category":"public","dates":"d|0106:2010-2060"},{"id":"easter_monday","name":"Ostermontag","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"labor_day","name":"Staatsfeiertag","en":"Labor Day","category":"public","dates":"d|0501:2010-2060"},{"id":"ascension_day","name":"Christi Himmelfahrt","en":"Ascension Day","category":"public","dates":"d|0503:2035,2046|0505:2016|0506:2027,2032|0507:2043,2054|0508:2059|0509:2013,2024|0510:2018,2029,2040|0511:2051,2056|0513:2010,2021|0514:2015,2026,2037,2048|0515:2042,2053|0517:2012|0518:2023,2034,2045|0519:2039,2050|0521:2020|0522:2031,2036|0523:2047,2058|0525:2017,2028|0526:2022,2033,2044|0527:2049,2055,2060|0529:2014,2025|0530:2019,2030,2041,2052|0531:2057|0602:2011|0603:2038"},{"id":"pentecost_monday","name":"Pfingstmontag","en":"Pentecost Monday","category":"public","dates":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038"},{"id":"corpus_christi","name":"Fronleichnam","en":"Corpus Christi","category":"public","dates":"d|0524:2035,2046|0526:2016|0527:2027,2032|0528:2043,2054|0529:2059|0530:2013,2024|0531:2018,2029,2040|0601:2051,2056|0603:2010,2021|0604:2015,2026,2037,2048|0605:2042,2053|0607:2012|0608:2023,2034,2045|0609:2039,2050|0611:2020|0612:2031,2036|0613:2047,2058|0615:2017,2028|0616:2022,2033,2044|0617:2049,2055,2060|0619:2014,2025|0620:2019,2030,2041,2052|0621:2057|0623:2011|0624:2038"},{"id":"assumption_day","name":"Mariä Himmelfahrt","en":"Assumption Day","category":"public","dates":"d|0815:2010-2060"},{"id":"national_day","name":"Nationalfeiertag","en":"National Day","category":"public","dates":"d|1026:2010-2060"},{"id":"all_saints_day","name":"Allerheiligen","en":"All Saints' Day","category":"public","dates":"d|1101:2010-2060"},{"id":"immaculate_conception","name":"Mariä Empfängnis","en":"Immaculate Conception","category":"public","dates":"d|1208:2010-2060"},{"id":"christmas_day","name":"Christtag","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"},{"id":"saint_stephen_s_day","name":"Stephanstag","en":"Saint Stephen's Day","category":"public","dates":"d|1226:2010-2060"},{"id":"saint_joseph_s_day_bank","name":"Hl. Josef","en":"Saint Joseph's Day","category":"bank","regional":{"2":"d|0319:2010-2060","6":"d|0319:2010-2060","7":"d|0319:2010-2060","8":"d|0319:2010-2060"}},{"id":"good_friday_bank","name":"Karfreitag","en":"Good Friday","category":"bank","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"saint_florian_s_day_bank","name":"Hl. Florian","en":"Saint Florian's Day","category":"bank","regional":{"4":"d|0504:2010-2060"}},{"id":"saint_rupert_s_day_bank","name":"Hl. Rupert","en":"Saint Rupert's Day","category":"bank","regional":{"5":"d|0924:2010-2060"}},{"id":"1920_carinthian_plebiscite_bank","name":"Tag der Volksabstimmung","en":"1920 Carinthian plebiscite","category":"bank","regional":{"2":"d|1010:2010-2060"}},{"id":"saint_martin_s_day_bank","name":"Hl. Martin","en":"Saint Martin's Day","category":"bank","regional":{"1":"d|1111:2010-2060"}},{"id":"saint_leopold_s_day_bank","name":"Hl. Leopold","en":"Saint Leopold's Day","category":"bank","regional":{"3":"d|1115:2010-2060","9":"d|1115:2010-2060"}},{"id":"christmas_eve_bank","name":"Heiliger Abend","en":"Christmas Eve","category":"bank","dates":"d|1224:2010-2060"},{"id":"new_year_s_eve_bank","name":"Silvester","en":"New Year's Eve","category":"bank","dates":"d|1231:2010-2060"}]} diff --git a/app/Support/Holidays/data/AU.json b/app/Support/Holidays/data/AU.json new file mode 100644 index 000000000..380e5a48f --- /dev/null +++ b/app/Support/Holidays/data/AU.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"AU","language":"en_AU","years":[2010,2060],"subdivisions":{"ACT":"Australian Capital Territory","NSW":"New South Wales","NT":"Northern Territory","QLD":"Queensland","SA":"South Australia","TAS":"Tasmania","VIC":"Victoria","WA":"Western Australia","NSW Western":"New South Wales Western Division"},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060","regional":{"ACT":"d|0102:2012,2017,2023,2034,2040,2045,2051|0103:2011,2022,2028,2033,2039,2050,2056","NSW":"d|0102:2012,2017,2023,2034,2040,2045,2051|0103:2011,2022,2028,2033,2039,2050,2056","NSW Western":"d|0102:2012,2017,2023,2034,2040,2045,2051|0103:2011,2022,2028,2033,2039,2050,2056","NT":"d|0102:2012,2017,2023,2034,2040,2045,2051|0103:2011,2022,2028,2033,2039,2050,2056","QLD":"d|0102:2012,2017,2023,2034,2040,2045,2051|0103:2011,2022,2028,2033,2039,2050,2056","SA":"d|0102:2012,2017,2023,2034,2040,2045,2051|0103:2011,2022,2028,2033,2039,2050,2056","TAS":"d|0102:2012,2017,2023,2034,2040,2045,2051|0103:2011,2022,2028,2033,2039,2050,2056","VIC":"d|0102:2012,2017,2023,2034,2040,2045,2051|0103:2011,2022,2028,2033,2039,2050,2056","WA":"d|0102:2012,2017,2023,2034,2040,2045,2051|0103:2011,2022,2028,2033,2039,2050,2056"}},{"id":"australia_day","name":"Australia Day","en":"Australia Day","category":"public","dates":"d|0126:2010-2060","regional":{"ACT":"d|0127:2014,2020,2025,2031,2042,2048,2053,2059|0128:2013,2019,2030,2036,2041,2047,2058","NSW":"d|0127:2014,2020,2025,2031,2042,2048,2053,2059|0128:2013,2019,2030,2036,2041,2047,2058","NSW Western":"d|0127:2014,2020,2025,2031,2042,2048,2053,2059|0128:2013,2019,2030,2036,2041,2047,2058","NT":"d|0127:2014,2020,2025,2031,2042,2048,2053,2059|0128:2013,2019,2030,2036,2041,2047,2058","QLD":"d|0127:2014,2020,2025,2031,2042,2048,2053,2059|0128:2013,2019,2030,2036,2041,2047,2058","SA":"d|0127:2014,2020,2025,2031,2042,2048,2053,2059|0128:2013,2019,2030,2036,2041,2047,2058","TAS":"d|0127:2014,2020,2025,2031,2042,2048,2053,2059|0128:2013,2019,2030,2036,2041,2047,2058","VIC":"d|0127:2014,2020,2025,2031,2042,2048,2053,2059|0128:2013,2019,2030,2036,2041,2047,2058","WA":"d|0127:2014,2020,2025,2031,2042,2048,2053,2059|0128:2013,2019,2030,2036,2041,2047,2058"}},{"id":"labor_day","name":"Labour Day","en":"Labor Day","category":"public","regional":{"ACT":"d|1001:2012,2018,2029,2035,2040,2046,2057|1002:2017,2023,2028,2034,2045,2051,2056|1003:2011,2016,2022,2033,2039,2044,2050|1004:2010,2021,2027,2032,2038,2049,2055,2060|1005:2015,2020,2026,2037,2043,2048,2054|1006:2014,2025,2031,2036,2042,2053,2059|1007:2013,2019,2024,2030,2041,2047,2052,2058","NSW":"d|1001:2012,2018,2029,2035,2040,2046,2057|1002:2017,2023,2028,2034,2045,2051,2056|1003:2011,2016,2022,2033,2039,2044,2050|1004:2010,2021,2027,2032,2038,2049,2055,2060|1005:2015,2020,2026,2037,2043,2048,2054|1006:2014,2025,2031,2036,2042,2053,2059|1007:2013,2019,2024,2030,2041,2047,2052,2058","NSW Western":"d|1001:2012,2018,2029,2035,2040,2046,2057|1002:2017,2023,2028,2034,2045,2051,2056|1003:2011,2016,2022,2033,2039,2044,2050|1004:2010,2021,2027,2032,2038,2049,2055,2060|1005:2015,2020,2026,2037,2043,2048,2054|1006:2014,2025,2031,2036,2042,2053,2059|1007:2013,2019,2024,2030,2041,2047,2052,2058","QLD":"d|0501:2017,2023,2028,2034,2045,2051,2056|0502:2011,2016,2022,2033,2039,2044,2050|0503:2010,2021,2027,2032,2038,2049,2055,2060|0504:2020,2026,2037,2043,2048,2054|0505:2025,2031,2036,2042,2053,2059|0506:2019,2024,2030,2041,2047,2052,2058|0507:2012,2018,2029,2035,2040,2046,2057|1005:2015|1006:2014|1007:2013","SA":"d|1001:2012,2018,2029,2035,2040,2046,2057|1002:2017,2023,2028,2034,2045,2051,2056|1003:2011,2016,2022,2033,2039,2044,2050|1004:2010,2021,2027,2032,2038,2049,2055,2060|1005:2015,2020,2026,2037,2043,2048,2054|1006:2014,2025,2031,2036,2042,2053,2059|1007:2013,2019,2024,2030,2041,2047,2052,2058","VIC":"d|0308:2010,2021,2027,2032,2038,2049,2055,2060|0309:2015,2020,2026,2037,2043,2048,2054|0310:2014,2025,2031,2036,2042,2053,2059|0311:2013,2019,2024,2030,2041,2047,2052,2058|0312:2012,2018,2029,2035,2040,2046,2057|0313:2017,2023,2028,2034,2045,2051,2056|0314:2011,2016,2022,2033,2039,2044,2050","WA":"d|0301:2010,2021,2027,2032,2038,2049,2055,2060|0302:2015,2020,2026,2037,2043,2048,2054|0303:2014,2025,2031,2036,2042,2053,2059|0304:2013,2019,2024,2030,2041,2047,2052,2058|0305:2012,2018,2029,2035,2040,2046,2057|0306:2017,2023,2028,2034,2045,2051,2056|0307:2011,2016,2022,2033,2039,2044,2050"}},{"id":"adelaide_cup_day","name":"Adelaide Cup Day","en":"Adelaide Cup Day","category":"public","regional":{"SA":"d|0308:2010,2021,2027,2032,2038,2049,2055,2060|0309:2015,2020,2026,2037,2043,2048,2054|0310:2014,2025,2031,2036,2042,2053,2059|0311:2013,2019,2024,2030,2041,2047,2052,2058|0312:2012,2018,2029,2035,2040,2046,2057|0313:2017,2023,2028,2034,2045,2051,2056|0314:2011,2016,2022,2033,2039,2044,2050"}},{"id":"canberra_day","name":"Canberra Day","en":"Canberra Day","category":"public","regional":{"ACT":"d|0308:2010,2021,2027,2032,2038,2049,2055,2060|0309:2015,2020,2026,2037,2043,2048,2054|0310:2014,2025,2031,2036,2042,2053,2059|0311:2013,2019,2024,2030,2041,2047,2052,2058|0312:2012,2018,2029,2035,2040,2046,2057|0313:2017,2023,2028,2034,2045,2051,2056|0314:2011,2016,2022,2033,2039,2044,2050"}},{"id":"eight_hours_day","name":"Eight Hours Day","en":"Eight Hours Day","category":"public","regional":{"TAS":"d|0308:2010,2021,2027,2032,2038,2049,2055,2060|0309:2015,2020,2026,2037,2043,2048,2054|0310:2014,2025,2031,2036,2042,2053,2059|0311:2013,2019,2024,2030,2041,2047,2052,2058|0312:2012,2018,2029,2035,2040,2046,2057|0313:2017,2023,2028,2034,2045,2051,2056|0314:2011,2016,2022,2033,2039,2044,2050"}},{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_saturday","name":"Easter Saturday","en":"Easter Saturday","category":"public","regional":{"ACT":"d|0324:2035,2046|0326:2016|0327:2027,2032|0328:2043,2054|0329:2059|0330:2013,2024|0331:2018,2029,2040|0401:2051,2056|0403:2010,2021|0404:2015,2026,2037,2048|0405:2042,2053|0407:2012|0408:2023,2034,2045|0409:2039,2050|0411:2020|0412:2031,2036|0413:2047,2058|0415:2017,2028|0416:2022,2033,2044|0417:2049,2055,2060|0419:2014,2025|0420:2019,2030,2041,2052|0421:2057|0423:2011|0424:2038","NSW":"d|0324:2035,2046|0326:2016|0327:2027,2032|0328:2043,2054|0329:2059|0330:2013,2024|0331:2018,2029,2040|0401:2051,2056|0403:2010,2021|0404:2015,2026,2037,2048|0405:2042,2053|0407:2012|0408:2023,2034,2045|0409:2039,2050|0411:2020|0412:2031,2036|0413:2047,2058|0415:2017,2028|0416:2022,2033,2044|0417:2049,2055,2060|0419:2014,2025|0420:2019,2030,2041,2052|0421:2057|0423:2011|0424:2038","NSW Western":"d|0324:2035,2046|0326:2016|0327:2027,2032|0328:2043,2054|0329:2059|0330:2013,2024|0331:2018,2029,2040|0401:2051,2056|0403:2010,2021|0404:2015,2026,2037,2048|0405:2042,2053|0407:2012|0408:2023,2034,2045|0409:2039,2050|0411:2020|0412:2031,2036|0413:2047,2058|0415:2017,2028|0416:2022,2033,2044|0417:2049,2055,2060|0419:2014,2025|0420:2019,2030,2041,2052|0421:2057|0423:2011|0424:2038","NT":"d|0324:2035,2046|0326:2016|0327:2027,2032|0328:2043,2054|0329:2059|0330:2013,2024|0331:2018,2029,2040|0401:2051,2056|0403:2010,2021|0404:2015,2026,2037,2048|0405:2042,2053|0407:2012|0408:2023,2034,2045|0409:2039,2050|0411:2020|0412:2031,2036|0413:2047,2058|0415:2017,2028|0416:2022,2033,2044|0417:2049,2055,2060|0419:2014,2025|0420:2019,2030,2041,2052|0421:2057|0423:2011|0424:2038","QLD":"d|0324:2035,2046|0326:2016|0327:2027,2032|0328:2043,2054|0329:2059|0330:2013,2024|0331:2018,2029,2040|0401:2051,2056|0403:2010,2021|0404:2015,2026,2037,2048|0405:2042,2053|0407:2012|0408:2023,2034,2045|0409:2039,2050|0411:2020|0412:2031,2036|0413:2047,2058|0415:2017,2028|0416:2022,2033,2044|0417:2049,2055,2060|0419:2014,2025|0420:2019,2030,2041,2052|0421:2057|0423:2011|0424:2038","SA":"d|0324:2035,2046|0326:2016|0327:2027,2032|0328:2043,2054|0329:2059|0330:2013,2024|0331:2018,2029,2040|0401:2051,2056|0403:2010,2021|0404:2015,2026,2037,2048|0405:2042,2053|0407:2012|0408:2023,2034,2045|0409:2039,2050|0411:2020|0412:2031,2036|0413:2047,2058|0415:2017,2028|0416:2022,2033,2044|0417:2049,2055,2060|0419:2014,2025|0420:2019,2030,2041,2052|0421:2057|0423:2011|0424:2038","VIC":"d|0324:2035,2046|0326:2016|0327:2027,2032|0328:2043,2054|0329:2059|0330:2013,2024|0331:2018,2029,2040|0401:2051,2056|0403:2010,2021|0404:2015,2026,2037,2048|0405:2042,2053|0407:2012|0408:2023,2034,2045|0409:2039,2050|0411:2020|0412:2031,2036|0413:2047,2058|0415:2017,2028|0416:2022,2033,2044|0417:2049,2055,2060|0419:2014,2025|0420:2019,2030,2041,2052|0421:2057|0423:2011|0424:2038"}},{"id":"easter_sunday","name":"Easter Sunday","en":"Easter Sunday","category":"public","regional":{"ACT":"d|0325:2035,2046|0327:2016|0328:2027,2032|0329:2043,2054|0330:2059|0331:2024|0401:2018,2029,2040|0402:2051,2056|0404:2021|0405:2026,2037,2048|0406:2042,2053|0409:2023,2034,2045|0410:2039,2050|0412:2020|0413:2031,2036|0414:2047,2058|0416:2017,2028|0417:2022,2033,2044|0418:2049,2055,2060|0420:2025|0421:2019,2030,2041,2052|0422:2057|0425:2038","NSW":"d|0325:2035,2046|0327:2016|0328:2027,2032|0329:2043,2054|0330:2059|0331:2013,2024|0401:2018,2029,2040|0402:2051,2056|0404:2021|0405:2015,2026,2037,2048|0406:2042,2053|0408:2012|0409:2023,2034,2045|0410:2039,2050|0412:2020|0413:2031,2036|0414:2047,2058|0416:2017,2028|0417:2022,2033,2044|0418:2049,2055,2060|0420:2014,2025|0421:2019,2030,2041,2052|0422:2057|0424:2011|0425:2038","NSW Western":"d|0325:2035,2046|0327:2016|0328:2027,2032|0329:2043,2054|0330:2059|0331:2013,2024|0401:2018,2029,2040|0402:2051,2056|0404:2021|0405:2015,2026,2037,2048|0406:2042,2053|0408:2012|0409:2023,2034,2045|0410:2039,2050|0412:2020|0413:2031,2036|0414:2047,2058|0416:2017,2028|0417:2022,2033,2044|0418:2049,2055,2060|0420:2014,2025|0421:2019,2030,2041,2052|0422:2057|0424:2011|0425:2038","NT":"d|0325:2035,2046|0328:2027,2032|0329:2043,2054|0330:2059|0331:2024|0401:2029,2040|0402:2051,2056|0405:2026,2037,2048|0406:2042,2053|0409:2034,2045|0410:2039,2050|0413:2031,2036|0414:2047,2058|0416:2028|0417:2033,2044|0418:2049,2055,2060|0420:2025|0421:2030,2041,2052|0422:2057|0425:2038","QLD":"d|0325:2035,2046|0328:2027,2032|0329:2043,2054|0330:2059|0331:2024|0401:2018,2029,2040|0402:2051,2056|0404:2021|0405:2026,2037,2048|0406:2042,2053|0409:2023,2034,2045|0410:2039,2050|0412:2020|0413:2031,2036|0414:2047,2058|0416:2017,2028|0417:2022,2033,2044|0418:2049,2055,2060|0420:2025|0421:2019,2030,2041,2052|0422:2057|0425:2038","SA":"d|0325:2035,2046|0328:2027,2032|0329:2043,2054|0330:2059|0331:2024|0401:2029,2040|0402:2051,2056|0405:2026,2037,2048|0406:2042,2053|0409:2034,2045|0410:2039,2050|0413:2031,2036|0414:2047,2058|0416:2028|0417:2033,2044|0418:2049,2055,2060|0420:2025|0421:2030,2041,2052|0422:2057|0425:2038","TAS":"d|0325:2035,2046|0328:2027,2032|0329:2043,2054|0330:2059|0401:2029,2040|0402:2051,2056|0405:2037,2048|0406:2042,2053|0409:2034,2045|0410:2039,2050|0413:2031,2036|0414:2047,2058|0416:2028|0417:2033,2044|0418:2049,2055,2060|0421:2030,2041,2052|0422:2057|0425:2038","VIC":"d|0325:2035,2046|0327:2016|0328:2027,2032|0329:2043,2054|0330:2059|0331:2024|0401:2018,2029,2040|0402:2051,2056|0404:2021|0405:2026,2037,2048|0406:2042,2053|0409:2023,2034,2045|0410:2039,2050|0412:2020|0413:2031,2036|0414:2047,2058|0416:2017,2028|0417:2022,2033,2044|0418:2049,2055,2060|0420:2025|0421:2019,2030,2041,2052|0422:2057|0425:2038","WA":"d|0325:2035,2046|0328:2027,2032|0329:2043,2054|0330:2059|0331:2024|0401:2029,2040|0402:2051,2056|0405:2026,2037,2048|0406:2042,2053|0409:2023,2034,2045|0410:2039,2050|0413:2031,2036|0414:2047,2058|0416:2028|0417:2022,2033,2044|0418:2049,2055,2060|0420:2025|0421:2030,2041,2052|0422:2057|0425:2038|0426:2038"}},{"id":"easter_monday","name":"Easter Monday","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"anzac_day","name":"ANZAC Day","en":"ANZAC Day","category":"public","dates":"d|0425:2010-2060","regional":{"ACT":"d|0426:2010,2021,2027,2032,2038,2049,2055,2060|0427:2026","NSW":"d|0426:2010,2027|0427:2026","NSW Western":"d|0426:2010,2027|0427:2026","NT":"d|0426:2010,2021,2027,2032,2038,2049,2055,2060","QLD":"d|0426:2010,2021,2027,2032,2038,2049,2055,2060","SA":"d|0426:2010,2021","WA":"d|0426:2010,2021,2027,2032,2038,2049,2055,2060|0427:2015,2020,2026,2037,2043,2048,2054"}},{"id":"may_day","name":"May Day","en":"May Day","category":"public","regional":{"NT":"d|0501:2017,2023,2028,2034,2045,2051,2056|0502:2011,2016,2022,2033,2039,2044,2050|0503:2010,2021,2027,2032,2038,2049,2055,2060|0504:2015,2020,2026,2037,2043,2048,2054|0505:2014,2025,2031,2036,2042,2053,2059|0506:2013,2019,2024,2030,2041,2047,2052,2058|0507:2012,2018,2029,2035,2040,2046,2057"}},{"id":"reconciliation_day","name":"Reconciliation Day","en":"Reconciliation Day","category":"public","regional":{"ACT":"d|0527:2019,2024,2030,2041,2047,2052,2058|0528:2018,2029,2035,2040,2046,2057|0529:2023,2028,2034,2045,2051,2056|0530:2022,2033,2039,2044,2050|0531:2021,2027,2032,2038,2049,2055,2060|0601:2020,2026,2037,2043,2048,2054|0602:2025,2031,2036,2042,2053,2059"}},{"id":"western_australia_day","name":"Western Australia Day","en":"Western Australia Day","category":"public","regional":{"WA":"d|0601:2015,2020,2026,2037,2043,2048,2054|0602:2014,2025,2031,2036,2042,2053,2059|0603:2013,2019,2024,2030,2041,2047,2052,2058|0604:2012,2018,2029,2035,2040,2046,2057|0605:2017,2023,2028,2034,2045,2051,2056|0606:2016,2022,2033,2039,2044,2050|0607:2021,2027,2032,2038,2049,2055,2060"}},{"id":"king_s_birthday","name":"King's Birthday","en":"King's Birthday","category":"public","regional":{"ACT":"d|0608:2026,2037,2043,2048,2054|0609:2025,2031,2036,2042,2053,2059|0610:2024,2030,2041,2047,2052,2058|0611:2029,2035,2040,2046,2057|0612:2023,2028,2034,2045,2051,2056|0613:2033,2039,2044,2050|0614:2027,2032,2038,2049,2055,2060","NSW":"d|0608:2026,2037,2043,2048,2054|0609:2025,2031,2036,2042,2053,2059|0610:2024,2030,2041,2047,2052,2058|0611:2029,2035,2040,2046,2057|0612:2023,2028,2034,2045,2051,2056|0613:2033,2039,2044,2050|0614:2027,2032,2038,2049,2055,2060","NSW Western":"d|0608:2026,2037,2043,2048,2054|0609:2025,2031,2036,2042,2053,2059|0610:2024,2030,2041,2047,2052,2058|0611:2029,2035,2040,2046,2057|0612:2023,2028,2034,2045,2051,2056|0613:2033,2039,2044,2050|0614:2027,2032,2038,2049,2055,2060","NT":"d|0608:2026,2037,2043,2048,2054|0609:2025,2031,2036,2042,2053,2059|0610:2024,2030,2041,2047,2052,2058|0611:2029,2035,2040,2046,2057|0612:2023,2028,2034,2045,2051,2056|0613:2033,2039,2044,2050|0614:2027,2032,2038,2049,2055,2060","QLD":"d|1001:2029,2035,2040,2046,2057|1002:2023,2028,2034,2045,2051,2056|1003:2033,2039,2044,2050|1004:2027,2032,2038,2049,2055,2060|1005:2026,2037,2043,2048,2054|1006:2025,2031,2036,2042,2053,2059|1007:2024,2030,2041,2047,2052,2058","SA":"d|0608:2026,2037,2043,2048,2054|0609:2025,2031,2036,2042,2053,2059|0610:2024,2030,2041,2047,2052,2058|0611:2029,2035,2040,2046,2057|0612:2023,2028,2034,2045,2051,2056|0613:2033,2039,2044,2050|0614:2027,2032,2038,2049,2055,2060","TAS":"d|0608:2026,2037,2043,2048,2054|0609:2025,2031,2036,2042,2053,2059|0610:2024,2030,2041,2047,2052,2058|0611:2029,2035,2040,2046,2057|0612:2023,2028,2034,2045,2051,2056|0613:2033,2039,2044,2050|0614:2027,2032,2038,2049,2055,2060","VIC":"d|0608:2026,2037,2043,2048,2054|0609:2025,2031,2036,2042,2053,2059|0610:2024,2030,2041,2047,2052,2058|0611:2029,2035,2040,2046,2057|0612:2023,2028,2034,2045,2051,2056|0613:2033,2039,2044,2050|0614:2027,2032,2038,2049,2055,2060","WA":"d|0923:2024|0924:2029,2035,2040,2046,2057|0925:2023,2028,2034,2045,2051,2056|0926:2033,2039,2044,2050|0927:2027,2032,2038,2049,2055,2060|0928:2026,2037,2043,2048,2054|0929:2025,2031,2036,2042,2053,2059|0930:2030,2041,2047,2052,2058"}},{"id":"picnic_day","name":"Picnic Day","en":"Picnic Day","category":"public","regional":{"NT":"d|0801:2011,2016,2022,2033,2039,2044,2050|0802:2010,2021,2027,2032,2038,2049,2055,2060|0803:2015,2020,2026,2037,2043,2048,2054|0804:2014,2025,2031,2036,2042,2053,2059|0805:2013,2019,2024,2030,2041,2047,2052,2058|0806:2012,2018,2029,2035,2040,2046,2057|0807:2017,2023,2028,2034,2045,2051,2056"}},{"id":"the_royal_queensland_show","name":"The Royal Queensland Show","en":"The Royal Queensland Show","category":"public","regional":{"QLD":"d|0810:2011,2016,2022,2033,2039,2044,2050|0811:2010,2027,2032,2038,2049,2055,2060|0812:2015,2026,2037,2043,2048,2054|0813:2014,2025,2031,2036,2042,2053,2059|0814:2013,2019-2020,2024,2030,2041,2047,2052,2058|0815:2012,2018,2029,2035,2040,2046,2057|0816:2017,2023,2028,2034,2045,2051,2056|1029:2021"}},{"id":"friday_before_the_afl_grand_final","name":"Friday before the AFL Grand Final","en":"Friday before the AFL Grand Final","category":"public","regional":{"VIC":"d|0923:2022,2033,2039,2044,2050|0924:2021,2027,2032,2038,2049,2055,2060|0925:2026,2037,2043,2048,2054|0926:2025,2031,2036,2042,2053,2059|0927:2019,2024,2030,2041,2047,2052,2058|0928:2018,2029,2035,2040,2046,2057|0929:2017,2023,2028,2034,2045,2051,2056|0930:2016|1002:2015|1023:2020"}},{"id":"melbourne_cup_day","name":"Melbourne Cup Day","en":"Melbourne Cup Day","category":"public","regional":{"VIC":"d|1101:2011,2016,2022,2033,2039,2044,2050|1102:2010,2021,2027,2032,2038,2049,2055,2060|1103:2015,2020,2026,2037,2043,2048,2054|1104:2014,2025,2031,2036,2042,2053,2059|1105:2013,2019,2024,2030,2041,2047,2052,2058|1106:2012,2018,2029,2035,2040,2046,2057|1107:2017,2023,2028,2034,2045,2051,2056"}},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060","regional":{"ACT":"d|1226:2011|1227:2010,2016,2021-2022,2027,2032-2033,2038-2039,2044,2049-2050,2055,2060","NSW":"d|1227:2011,2016,2021-2022,2027,2032-2033,2038-2039,2044,2049-2050,2055,2060","NSW Western":"d|1227:2011,2016,2021-2022,2027,2032-2033,2038-2039,2044,2049-2050,2055,2060","NT":"d|1226:2011,2016,2022,2033,2039,2044,2050|1227:2010,2021,2027,2032,2038,2049,2055,2060","QLD":"d|1227:2011,2016,2021-2022,2027,2032-2033,2038-2039,2044,2049-2050,2055,2060|1228:2010","SA":"d|1226:2011,2016,2022,2033,2039,2044,2050|1227:2010,2021,2027,2032,2038,2049,2055,2060","TAS":"d|1227:2010-2011,2016,2021-2022,2027,2032-2033,2038-2039,2044,2049-2050,2055,2060","VIC":"d|1227:2010-2011,2016,2021-2022,2027,2032-2033,2038-2039,2044,2049-2050,2055,2060","WA":"d|1226:2011,2016,2022,2033,2039,2044,2050|1227:2010,2021,2027,2032,2038,2049,2055,2060"}},{"id":"boxing_day","name":"Boxing Day","en":"Boxing Day","category":"public","dates":"d|1226:2010-2060","regional":{"ACT":"d|1227:2011|1228:2010,2015,2020-2021,2026-2027,2032,2037-2038,2043,2048-2049,2054-2055,2060","NSW":"d|1227:2010|1228:2015,2020-2021,2026-2027,2032,2037-2038,2043,2048-2049,2054-2055,2060","NSW Western":"d|1227:2010|1228:2015,2020-2021,2026-2027,2032,2037-2038,2043,2048-2049,2054-2055,2060","NT":"d|1227:2011,2016,2022,2033,2039,2044,2050|1228:2010,2015,2020-2021,2026-2027,2032,2037-2038,2043,2048-2049,2054-2055,2060","QLD":"d|1227:2010|1228:2015,2020-2021,2026-2027,2032,2037-2038,2043,2048-2049,2054-2055,2060","TAS":"d|1228:2010,2015,2020-2021,2026-2027,2032,2037-2038,2043,2048-2049,2054-2055,2060","VIC":"d|1228:2010,2015,2020-2021,2026-2027,2032,2037-2038,2043,2048-2049,2054-2055,2060","WA":"d|1227:2011,2016,2022,2033,2039,2044,2050|1228:2010,2015,2020-2021,2026-2027,2032,2037-2038,2043,2048-2049,2054-2055,2060"}},{"id":"proclamation_day","name":"Proclamation Day","en":"Proclamation Day","category":"public","regional":{"SA":"d|1226:2010-2014,2016-2019,2021-2060|1227:2011,2016,2022,2033,2039,2044,2050|1228:2010,2015,2020-2021,2026-2027,2032,2037-2038,2043,2048-2049,2054-2055,2060"}},{"id":"summer_school_holidays_school","name":"Summer school holidays","en":"Summer school holidays","category":"school","regional":{"ACT":"y|2026:0101-0201,1219-1231|2027:0101-0201,1218-1231","NSW":"y|2026:0101-0126,1218-1231|2027:0101-0127,1221-1231|2028:0101-0128,1222-1231|2029:0101-0125,1221-1231|2030:0101-0130,1220-1231|2031:0101-0127","NSW Western":"y|2026:0101-0202,1218-1231|2027:0101-0203,1221-1231|2028:0101-0204,1222-1231|2029:0101-0202,1221-1231|2030:0101-0206,1220-1231|2031:0101-0203","NT":"y|2026:0101-0128,1211-1231|2027:0101-0126,1210-1231|2028:0101-0124,1215-1231|2029:0101-0129,1214-1231|2030:0101-0129,1213-1231|2031:0101-0128,1212-1231|2032:0101-0127,1217-1231","QLD":"y|2026:0101-0126,1212-1231|2027:0101-0126,1211-1231|2028:0101-0123,1209-1231|2029:0101-0121,1208-1231","SA":"y|2026:0101-0126,1212-1231|2027:0101-0126,1211-1231|2028:0101-0130,1216-1231|2029:0101-0128,1215-1231|2030:0101-0128,1214-1231","TAS":"y|2026:0101-0204,1219-1231|2027:0101-0203,1217-1231","VIC":"y|2026:0101-0127,1219-1231|2027:0101-0127,1218-1231|2028:0101-0127,1222-1231|2029:0101-0129,1222-1231|2030:0101-0129,1221-1231","WA":"y|2026:0101-0201,1218-1231|2027:0101-0131,1217-1231|2028:0101-0201,1215-1231|2029:0101-0130,1220-1231"}},{"id":"term_1_school_holidays_school","name":"Term 1 school holidays","en":"Term 1 school holidays","category":"school","regional":{"ACT":"y|2026:0403-0420|2027:0410-0427","NSW":"y|2026:0407-0417|2027:0412-0423|2028:0410-0421|2029:0416-0427|2030:0415-0426","NSW Western":"y|2026:0407-0417|2027:0412-0423|2028:0410-0421|2029:0416-0427|2030:0415-0426","NT":"y|2026:0403-0413|2027:0403-0412|2028:0408-0418|2029:0407-0416|2030:0406-0415|2031:0405-0415|2032:0410-0419","QLD":"y|2026:0403-0419|2027:0326-0411|2028:0401-0417|2029:0330-0415","SA":"y|2026:0411-0426|2027:0410-0425|2028:0414-0430|2029:0414-0429|2030:0413-0428","TAS":"y|2026:0418-0503|2027:0410-0425","VIC":"y|2026:0403-0419|2027:0326-0411|2028:0401-0417|2029:0330-0415|2030:0406-0422","WA":"y|2026:0403-0419|2027:0410-0425|2028:0408-0423|2029:0330-0415"}},{"id":"term_2_school_holidays_school","name":"Term 2 school holidays","en":"Term 2 school holidays","category":"school","regional":{"ACT":"y|2026:0704-0720|2027:0703-0719","NSW":"y|2026:0706-0717|2027:0705-0716|2028:0710-0721|2029:0709-0720|2030:0708-0719","NSW Western":"y|2026:0706-0717|2027:0705-0716|2028:0710-0721|2029:0709-0720|2030:0708-0719","NT":"y|2026:0620-0713|2027:0619-0712|2028:0624-0717|2029:0623-0716|2030:0622-0715|2031:0621-0714|2032:0626-0719","QLD":"y|2026:0627-0712|2027:0626-0711|2028:0624-0709|2029:0623-0708","SA":"y|2026:0704-0719|2027:0703-0718|2028:0708-0723|2029:0707-0722|2030:0706-0721","TAS":"y|2026:0711-0726|2027:0703-0718","VIC":"y|2026:0627-0712|2027:0626-0711|2028:0701-0716|2029:0630-0715|2030:0629-0714","WA":"y|2026:0704-0719|2027:0703-0718|2028:0701-0716|2029:0630-0715"}},{"id":"bank_holiday_bank","name":"Bank Holiday","en":"Bank Holiday","category":"bank","regional":{"ACT":"d|0801:2011,2016,2022,2033,2039,2044,2050|0802:2010,2021,2027,2032,2038,2049,2055,2060|0803:2015,2020,2026,2037,2043,2048,2054|0804:2014,2025,2031,2036,2042,2053,2059|0805:2013,2019,2024,2030,2041,2047,2052,2058|0806:2012,2018,2029,2035,2040,2046,2057|0807:2017,2023,2028,2034,2045,2051,2056","NSW":"d|0801:2011,2016,2022,2033,2039,2044,2050|0802:2021,2027,2032,2038,2049,2055,2060|0803:2015,2020,2026,2037,2043,2048,2054|0804:2014,2025,2031,2036,2042,2053,2059|0805:2013,2019,2024,2030,2041,2047,2052,2058|0806:2012,2018,2029,2035,2040,2046,2057|0807:2017,2023,2028,2034,2045,2051,2056","NSW Western":"d|0801:2011,2016,2022,2033,2039,2044,2050|0802:2021,2027,2032,2038,2049,2055,2060|0803:2015,2020,2026,2037,2043,2048,2054|0804:2014,2025,2031,2036,2042,2053,2059|0805:2013,2019,2024,2030,2041,2047,2052,2058|0806:2012,2018,2029,2035,2040,2046,2057|0807:2017,2023,2028,2034,2045,2051,2056"}},{"id":"term_3_school_holidays_school","name":"Term 3 school holidays","en":"Term 3 school holidays","category":"school","regional":{"ACT":"y|2026:0926-1012|2027:0925-1011","NSW":"y|2026:0928-1009|2027:0927-1008|2028:1003-1013|2029:1002-1012|2030:0930-1011","NSW Western":"y|2026:0928-1009|2027:0927-1008|2028:1003-1013|2029:1002-1012|2030:0930-1011","NT":"y|2026:0919-1005|2027:0918-1004|2028:0923-1009|2029:0922-1008|2030:0921-1007|2031:0920-1006|2032:0925-1011","QLD":"y|2026:0919-1005|2027:0918-1004|2028:0916-1002|2029:0915-1001","SA":"y|2026:0926-1011|2027:0925-1010|2028:0930-1015|2029:0929-1014|2030:0928-1013","TAS":"y|2026:1003-1018|2027:0925-1010","VIC":"y|2026:0919-1004|2027:0918-1003|2028:0923-1008|2029:0922-1007|2030:0921-1006","WA":"y|2026:0926-1011|2027:0925-1010|2028:0923-1008|2029:0922-1007"}},{"id":"christmas_eve_half_day","name":"Christmas Eve","en":"Christmas Eve","category":"half_day","regional":{"NT":"d|1224:2016-2060","QLD":"d|1224:2019-2060","SA":"d|1224:2012-2060"}},{"id":"new_year_s_eve_half_day","name":"New Year's Eve","en":"New Year's Eve","category":"half_day","regional":{"NT":"d|1231:2016-2060","SA":"d|1231:2012-2060"}}]} diff --git a/app/Support/Holidays/data/AW.json b/app/Support/Holidays/data/AW.json new file mode 100644 index 000000000..2b4dd7ef0 --- /dev/null +++ b/app/Support/Holidays/data/AW.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"AW","language":"pap_AW","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Aña Nobo","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"betico_day","name":"Dia di Betico","en":"Betico Day","category":"public","dates":"d|0125:2010-2060"},{"id":"monday_before_ash_wednesday","name":"Dialuna prome cu diaranson di shinish","en":"Monday before Ash Wednesday","category":"public","dates":"d|0205:2035,2046|0208:2027|0209:2032,2043,2054|0210:2059|0212:2024,2029|0213:2040,2051|0214:2056|0216:2026,2037|0217:2042,2048,2053|0220:2023,2034,2045|0221:2039,2050|0224:2031|0225:2036,2047,2058|0228:2028,2033|0229:2044|0301:2049,2055,2060|0303:2025|0304:2030,2041,2052|0305:2057|0308:2038"},{"id":"national_anthem_and_flag_day","name":"Dia di Himno y Bandera","en":"National Anthem and Flag Day","category":"public","dates":"d|0318:2010-2060"},{"id":"good_friday","name":"Bierna Santo","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_monday","name":"Di dos dia di Pasco di Resureccion","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"king_s_day","name":"Aña di Rey","en":"King's Day","category":"public","dates":"d|0426:2014,2025,2031,2036,2042,2053,2059|0427:2015-2024,2026-2030,2032-2035,2037-2041,2043-2052,2054-2058,2060"},{"id":"labor_day","name":"Dia di Obrero","en":"Labor Day","category":"public","dates":"d|0501:2010,2012-2015,2017-2021,2023-2032,2034-2038,2040-2043,2045-2049,2051-2060|0502:2011,2016,2022,2033,2039,2044,2050"},{"id":"ascension_day","name":"Dia di Asuncion","en":"Ascension Day","category":"public","dates":"d|0503:2035,2046|0505:2016|0506:2027,2032|0507:2043,2054|0508:2059|0509:2013,2024|0510:2018,2029,2040|0511:2051,2056|0513:2010,2021|0514:2015,2026,2037,2048|0515:2042,2053|0517:2012|0518:2023,2034,2045|0519:2039,2050|0521:2020|0522:2031,2036|0523:2047,2058|0525:2017,2028|0526:2022,2033,2044|0527:2049,2055,2060|0529:2014,2025|0530:2019,2030,2041,2052|0531:2057|0602:2011|0603:2038"},{"id":"christmas_day","name":"Pasco di Nacemento","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"},{"id":"second_day_of_christmas","name":"Di dos dia di Pasco di Nacemento","en":"Second Day of Christmas","category":"public","dates":"d|1226:2010-2060"}]} diff --git a/app/Support/Holidays/data/AX.json b/app/Support/Holidays/data/AX.json new file mode 100644 index 000000000..cc0054956 --- /dev/null +++ b/app/Support/Holidays/data/AX.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"AX","language":"fi","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Uudenvuodenpäivä","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"epiphany","name":"Loppiainen","en":"Epiphany","category":"public","dates":"d|0106:2010-2060"},{"id":"good_friday","name":"Pitkäperjantai","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_sunday","name":"Pääsiäispäivä","en":"Easter Sunday","category":"public","dates":"d|0325:2035,2046|0327:2016|0328:2027,2032|0329:2043,2054|0330:2059|0331:2013,2024|0401:2018,2029,2040|0402:2051,2056|0404:2010,2021|0405:2015,2026,2037,2048|0406:2042,2053|0408:2012|0409:2023,2034,2045|0410:2039,2050|0412:2020|0413:2031,2036|0414:2047,2058|0416:2017,2028|0417:2022,2033,2044|0418:2049,2055,2060|0420:2014,2025|0421:2019,2030,2041,2052|0422:2057|0424:2011|0425:2038"},{"id":"easter_monday","name":"Toinen pääsiäispäivä","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"may_day","name":"Vappu","en":"May Day","category":"public","dates":"d|0501:2010-2060"},{"id":"ascension_day","name":"Helatorstai","en":"Ascension Day","category":"public","dates":"d|0503:2035,2046|0505:2016|0506:2027,2032|0507:2043,2054|0508:2059|0509:2013,2024|0510:2018,2029,2040|0511:2051,2056|0513:2010,2021|0514:2015,2026,2037,2048|0515:2042,2053|0517:2012|0518:2023,2034,2045|0519:2039,2050|0521:2020|0522:2031,2036|0523:2047,2058|0525:2017,2028|0526:2022,2033,2044|0527:2049,2055,2060|0529:2014,2025|0530:2019,2030,2041,2052|0531:2057|0602:2011|0603:2038"},{"id":"pentecost","name":"Helluntaipäivä","en":"Pentecost","category":"public","dates":"d|0513:2035,2046|0515:2016|0516:2027,2032|0517:2043,2054|0518:2059|0519:2013,2024|0520:2018,2029,2040|0521:2051,2056|0523:2010,2021|0524:2015,2026,2037,2048|0525:2042,2053|0527:2012|0528:2023,2034,2045|0529:2039,2050|0531:2020|0601:2031,2036|0602:2047,2058|0604:2017,2028|0605:2022,2033,2044|0606:2049,2055,2060|0608:2014,2025|0609:2019,2030,2041,2052|0610:2057|0612:2011|0613:2038"},{"id":"aland_s_autonomy_day","name":"Ahvenanmaan itsehallintopäivä","en":"Åland's Autonomy Day","category":"public","dates":"d|0609:2010-2060"},{"id":"midsummer_eve","name":"Juhannusaatto","en":"Midsummer Eve","category":"public","dates":"d|0619:2015,2020,2026,2037,2043,2048,2054|0620:2014,2025,2031,2036,2042,2053,2059|0621:2013,2019,2024,2030,2041,2047,2052,2058|0622:2012,2018,2029,2035,2040,2046,2057|0623:2017,2023,2028,2034,2045,2051,2056|0624:2011,2016,2022,2033,2039,2044,2050|0625:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"midsummer_day","name":"Juhannuspäivä","en":"Midsummer Day","category":"public","dates":"d|0620:2015,2020,2026,2037,2043,2048,2054|0621:2014,2025,2031,2036,2042,2053,2059|0622:2013,2019,2024,2030,2041,2047,2052,2058|0623:2012,2018,2029,2035,2040,2046,2057|0624:2017,2023,2028,2034,2045,2051,2056|0625:2011,2016,2022,2033,2039,2044,2050|0626:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"all_saints_day","name":"Pyhäinpäivä","en":"All Saints' Day","category":"public","dates":"d|1031:2015,2020,2026,2037,2043,2048,2054|1101:2014,2025,2031,2036,2042,2053,2059|1102:2013,2019,2024,2030,2041,2047,2052,2058|1103:2012,2018,2029,2035,2040,2046,2057|1104:2017,2023,2028,2034,2045,2051,2056|1105:2011,2016,2022,2033,2039,2044,2050|1106:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"independence_day","name":"Itsenäisyyspäivä","en":"Independence Day","category":"public","dates":"d|1206:2010-2060"},{"id":"christmas_eve","name":"Jouluaatto","en":"Christmas Eve","category":"public","dates":"d|1224:2010-2060"},{"id":"christmas_day","name":"Joulupäivä","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"},{"id":"second_day_of_christmas","name":"Tapaninpäivä","en":"Second Day of Christmas","category":"public","dates":"d|1226:2010-2060"},{"id":"runeberg_day_unofficial","name":"Runebergin päivä","en":"Runeberg Day","category":"unofficial","dates":"d|0205:2010-2060"},{"id":"minna_canth_day_day_of_equality_unofficial","name":"Minna Canthin päivä, tasa-arvon päivä","en":"Minna Canth Day, Day of Equality","category":"unofficial","dates":"d|0319:2010-2060"},{"id":"mikael_agricola_day_day_of_the_finnish_language_unofficial","name":"Mikael Agricolan päivä, suomen kielen päivä","en":"Mikael Agricola Day, Day of the Finnish Language","category":"unofficial","dates":"d|0409:2010-2060"},{"id":"national_war_veterans_day_unofficial","name":"Kansallinen veteraanipäivä","en":"National War Veterans' Day","category":"unofficial","dates":"d|0427:2010-2060"},{"id":"europe_day_unofficial","name":"Eurooppa-päivä","en":"Europe Day","category":"unofficial","dates":"d|0509:2019-2060"},{"id":"j_v_snellman_day_day_of_finnish_heritage_unofficial","name":"J.V. Snellmanin päivä, suomalaisuuden päivä","en":"J. V. Snellman Day, Day of Finnish Heritage","category":"unofficial","dates":"d|0512:2010-2060"},{"id":"remembrance_day_unofficial","name":"Kaatuneitten muistopäivä","en":"Remembrance Day","category":"unofficial","dates":"d|0515:2011,2016,2022,2033,2039,2044,2050|0516:2010,2021,2027,2032,2038,2049,2055,2060|0517:2015,2020,2026,2037,2043,2048,2054|0518:2014,2025,2031,2036,2042,2053,2059|0519:2013,2019,2024,2030,2041,2047,2052,2058|0520:2012,2018,2029,2035,2040,2046,2057|0521:2017,2023,2028,2034,2045,2051,2056"},{"id":"eino_leino_day_day_of_summer_and_poetry_unofficial","name":"Eino Leinon päivä, runon ja suven päivä","en":"Eino Leino Day, Day of Summer and Poetry","category":"unofficial","dates":"d|0706:2010-2060"},{"id":"finland_s_nature_day_unofficial","name":"Suomen luonnon päivä","en":"Finland's Nature Day","category":"unofficial","dates":"d|0825:2029,2035,2040,2046,2057|0826:2023,2028,2034,2045,2051,2056|0827:2033,2039,2044,2050|0828:2027,2032,2038,2049,2055,2060|0829:2026,2037,2043,2048,2054|0830:2025,2031,2036,2042,2053,2059|0831:2024,2030,2041,2047,2052,2058"},{"id":"miina_sillanpaa_day_day_of_civic_participation_unofficial","name":"Miina Sillanpään ja kansalaisvaikuttamisen päivä","en":"Miina Sillanpää Day, Day of Civic Participation","category":"unofficial","dates":"d|1001:2016-2060"},{"id":"aleksis_kivi_day_day_of_finnish_literature_unofficial","name":"Aleksis Kiven päivä, suomalaisen kirjallisuuden päivä","en":"Aleksis Kivi Day, Day of Finnish Literature","category":"unofficial","dates":"d|1010:2010-2060"},{"id":"united_nations_day_unofficial","name":"YK:n päivä","en":"United Nations Day","category":"unofficial","dates":"d|1024:2010-2060"},{"id":"finnish_swedish_heritage_day_svenska_dagen_unofficial","name":"Ruotsalaisuuden päivä, Kustaa Aadolfin päivä","en":"Finnish Swedish Heritage Day, svenska dagen","category":"unofficial","dates":"d|1106:2010-2060"},{"id":"day_of_children_s_rights_unofficial","name":"Lapsen oikeuksien päivä","en":"Day of Children's Rights","category":"unofficial","dates":"d|1120:2020-2060"},{"id":"jean_sibelius_day_day_of_finnish_music_unofficial","name":"Jean Sibeliuksen päivä, suomalaisen musiikin päivä","en":"Jean Sibelius Day, Day of Finnish Music","category":"unofficial","dates":"d|1208:2011-2060"}]} diff --git a/app/Support/Holidays/data/AZ.json b/app/Support/Holidays/data/AZ.json new file mode 100644 index 000000000..a2f67d1a3 --- /dev/null +++ b/app/Support/Holidays/data/AZ.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"AZ","language":"az","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Yeni il bayramı","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2010-2060|0103:2011,2022,2028,2039,2050,2056|0104:2010-2012,2016-2017,2021-2023,2027-2028,2033-2034,2038-2040,2044-2045,2049-2051,2055-2056|0106:2033"},{"id":"eid_al_fitr","name":"Ramazan bayrami","en":"Eid al-Fitr","category":"public","dates":"y|2010:0909-0910|2011:0830-0831|2012:0819-0821|2013:0808-0809|2014:0728-0729|2015:0717-0718,0720|2016:0706-0707|2017:0626-0628|2018:0615-0616,0618-0619|2019:0605-0606|2020:0524-0526|2021:0513-0514|2022:0502-0503|2023:0421-0422,0424|2024:0410-0411|2025:0330-0331|2026:0320-0321,0325,0330|2027:0309-0310|2028:0226-0229|2029:0214-0215|2030:0204-0205|2031:0124-0125,0127|2032:0114-0115|2033:0102-0103,0105,1223-1224,1226|2034:1212-1213|2035:1201-1204|2036:1119-1120|2037:1108-1110,1112|2038:1029-1030,1101|2039:1019-1020|2040:1007-1009|2041:0926-0927|2042:0915-0916|2043:0904-0905,0907|2044:0824-0825|2045:0814-0815|2046:0803-0804,0806|2047:0724-0725|2048:0712-0714|2049:0701-0702|2050:0620-0621|2051:0610-0613|2052:0529-0530|2053:0519-0520|2054:0509-0511,0513|2055:0428-0429|2056:0416-0418|2057:0405-0406|2058:0325-0326|2059:0315-0318|2060:0304-0305","estimated":"y|2027:0309-0310|2028:0226-0227|2029:0214-0215|2030:0204-0205|2031:0124-0125|2032:0114-0115|2033:0102-0103,1223-1224|2034:1212-1213|2035:1201-1202|2036:1119-1120|2037:1108-1109|2038:1029-1030|2039:1019-1020|2040:1007-1008|2041:0926-0927|2042:0915-0916|2043:0904-0905|2044:0824-0825|2045:0814-0815|2046:0803-0804|2047:0724-0725|2048:0712-0713|2049:0701-0702|2050:0620-0621|2051:0610-0611|2052:0529-0530|2053:0519-0520|2054:0509-0510|2055:0428-0429|2056:0416-0417|2057:0405-0406|2058:0325-0326|2059:0315-0316|2060:0304-0305"},{"id":"international_azerbaijanis_solidarity_day","name":"Dünya azərbaycanlılarının həmrəyliyi günü","en":"International Azerbaijanis Solidarity Day","category":"public","dates":"d|0103:2012,2017-2018,2023-2024,2029,2034-2035,2040,2045-2046,2051-2052,2057|1231:2010-2060"},{"id":"eid_al_adha","name":"Qurban bayrami","en":"Eid al-Adha","category":"public","dates":"y|2010:1116-1117|2011:1106-1108|2012:1025-1026|2013:1015-1016|2014:1004-1007|2015:0924-0925|2016:0912-0913|2017:0901-0902,0904|2018:0822-0823|2019:0812-0813|2020:0731-0801,0803|2021:0720-0721|2022:0709-0712|2023:0628-0629|2024:0616-0617,0619|2025:0606-0607,0609|2026:0527-0529|2027:0516-0518|2028:0505-0506,0508|2029:0424-0425|2030:0413-0416|2031:0402-0403|2032:0322-0323,0329-0330|2033:0311-0312,0314|2034:0301-0302|2035:0218-0220|2036:0207-0208|2037:0126-0127|2038:0116-0119|2039:0105-0106,1226-1227|2040:1214-1215,1217|2041:1204-1205|2042:1123-1125|2043:1112-1113|2044:1031-1101|2045:1021-1024|2046:1010-1011|2047:0930-1001|2048:0919-0922|2049:0908-0909|2050:0828-0830|2051:0817-0818|2052:0805-0806|2053:0726-0729|2054:0715-0716|2055:0705-0706|2056:0623-0624,0627|2057:0612-0613|2058:0601-0604|2059:0522-0523|2060:0510-0511","estimated":"y|2027:0516-0517|2028:0505-0506|2029:0424-0425|2030:0413-0414|2031:0402-0403|2032:0322-0323|2033:0311-0312|2034:0301-0302|2035:0218-0219|2036:0207-0208|2037:0126-0127|2038:0116-0117|2039:0105-0106,1226-1227|2040:1214-1215|2041:1204-1205|2042:1123-1124|2043:1112-1113|2044:1031-1101|2045:1021-1022|2046:1010-1011|2047:0930-1001|2048:0919-0920|2049:0908-0909|2050:0828-0829|2051:0817-0818|2052:0805-0806|2053:0726-0727|2054:0715-0716|2055:0705-0706|2056:0623-0624|2057:0612-0613|2058:0601-0602|2059:0522-0523|2060:0510-0511"},{"id":"martyrs_day","name":"Ümumxalq hüzn günü","en":"Martyrs' Day","category":"public","dates":"d|0120:2010-2060"},{"id":"women_s_day","name":"Qadınlar günü","en":"Women's Day","category":"public","dates":"d|0308:2010-2060|0309:2015,2020,2026,2037,2043,2048,2054|0310:2014,2031,2036,2042,2053,2059"},{"id":"spring_festival","name":"Novruz bayramı","en":"Spring Festival","category":"public","dates":"d|0320:2010-2060|0321:2010-2060|0322:2010-2060|0323:2010-2060|0324:2010-2060|0325:2010-2011,2013-2016,2019-2022,2024-2025,2027,2030-2033,2036-2039,2041-2044,2047-2050,2052-2055,2059-2060|0326:2010,2012-2015,2018-2021,2024-2027,2029-2032,2035-2038,2040-2043,2046-2049,2052-2055,2057,2059-2060|0327:2026,2058|0328:2058"},{"id":"victory_over_fascism_day","name":"Faşizm üzərində qələbə günü","en":"Victory over Fascism Day","category":"public","dates":"d|0509:2010-2060|0510:2010,2021,2027,2032,2038,2049,2055|0511:2015,2020,2026,2037,2043,2048|0512:2054,2060"},{"id":"independence_day","name":"Müstəqillik Günü","en":"Independence Day","category":"public","dates":"d|0528:2021-2060|0529:2023,2028,2034,2045,2051,2056|0530:2022,2033,2039,2044,2050"},{"id":"national_liberation_day","name":"Azərbaycan xalqının milli qurtuluş günü","en":"National Liberation Day","category":"public","dates":"d|0615:2010-2060|0616:2014,2025,2031,2036,2042,2053,2059|0617:2013,2019,2030,2041,2047,2052,2058|0618:2024"},{"id":"armed_forces_day","name":"Azərbaycan Respublikasının Silahlı Qüvvələri günü","en":"Armed Forces Day","category":"public","dates":"d|0626:2010-2060|0627:2011,2016,2022,2033,2039,2044,2050|0628:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"victory_day","name":"Zəfər Günü","en":"Victory Day","category":"public","dates":"d|1108:2021-2060|1110:2025-2026,2031,2036,2042-2043,2048,2053-2054,2059|1111:2037"},{"id":"national_flag_day","name":"Azərbaycan Respublikasının Dövlət bayrağı günü","en":"National Flag Day","category":"public","dates":"d|1109:2010-2060|1110:2014|1111:2013,2019,2024-2025,2030-2031,2036,2041-2042,2047,2052-2053,2058-2059"}]} diff --git a/app/Support/Holidays/data/BA.json b/app/Support/Holidays/data/BA.json new file mode 100644 index 000000000..9e40d3502 --- /dev/null +++ b/app/Support/Holidays/data/BA.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"BA","language":"bs","years":[2010,2060],"subdivisions":{"BIH":"Federacija Bosne i Hercegovine","BRC":"Brčko distrikt","SRP":"Republika Srpska"},"holidays":[{"id":"new_year_s_day","name":"Nova godina","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2010-2060","regional":{"BIH":"d|0103:2012,2017,2023,2034,2040,2045,2051","BRC":"d|0103:2011-2012,2017,2022-2023,2028,2033-2034,2039-2040,2045,2050-2051,2056","SRP":"d|0103:2011,2022,2028,2033,2039,2050,2056"}},{"id":"eid_al_fitr","name":"Ramazanski Bajram","en":"Eid al-Fitr","category":"public","dates":"y|2010:0910|2011:0831|2012:0819|2013:0808|2014:0728|2015:0718|2016:0707|2017:0626|2018:0615|2019:0604|2020:0524|2021:0513|2022:0502|2023:0421|2024:0410|2025:0330|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304","regional":{"BIH":"y|2010:0911|2011:0901|2012:0820|2013:0809|2014:0729|2015:0719|2016:0708|2017:0627|2018:0616|2019:0605|2020:0525|2021:0514|2022:0503|2023:0422|2024:0411|2025:0331|2026:0321|2027:0310|2028:0227|2029:0215|2030:0205|2031:0125|2032:0115|2033:0103,1224|2034:1213|2035:1202|2036:1120|2037:1109|2038:1030|2039:1020|2040:1008|2041:0927|2042:0916|2043:0905|2044:0825|2045:0815|2046:0804|2047:0725|2048:0713|2049:0702|2050:0621|2051:0611|2052:0530|2053:0520|2054:0510|2055:0429|2056:0417|2057:0406|2058:0326|2059:0316|2060:0305","SRP":"y|2010:0911|2011:0901|2012:0820|2013:0809|2014:0729|2015:0719|2016:0708|2017:0627|2018:0616|2019:0605|2020:0525|2021:0514|2022:0503|2023:0422|2024:0411|2025:0331|2026:0321|2027:0310|2028:0227|2029:0215|2030:0205|2031:0125|2032:0115|2033:0103,1224|2034:1213|2035:1202|2036:1120|2037:1109|2038:1030|2039:1020|2040:1008|2041:0927|2042:0916|2043:0905|2044:0825|2045:0815|2046:0804|2047:0725|2048:0713|2049:0702|2050:0621|2051:0611|2052:0530|2053:0520|2054:0510|2055:0429|2056:0417|2057:0406|2058:0326|2059:0316|2060:0305"},"estimated":"y|2025:0330-0331|2026:0320-0321|2027:0309-0310|2028:0226-0227|2029:0214-0215|2030:0204-0205|2031:0124-0125|2032:0114-0115|2033:0102-0103,1223-1224|2034:1212-1213|2035:1201-1202|2036:1119-1120|2037:1108-1109|2038:1029-1030|2039:1019-1020|2040:1007-1008|2041:0926-0927|2042:0915-0916|2043:0904-0905|2044:0824-0825|2045:0814-0815|2046:0803-0804|2047:0724-0725|2048:0712-0713|2049:0701-0702|2050:0620-0621|2051:0610-0611|2052:0529-0530|2053:0519-0520|2054:0509-0510|2055:0428-0429|2056:0416-0417|2057:0405-0406|2058:0325-0326|2059:0315-0316|2060:0304-0305"},{"id":"eid_al_adha","name":"Kurban Bajram","en":"Eid al-Adha","category":"public","dates":"y|2010:1117|2011:1107|2012:1026|2013:1015|2014:1004|2015:0924|2016:0913|2017:0902|2018:0822|2019:0811|2020:0731|2021:0720|2022:0709|2023:0628|2024:0616|2025:0606|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510","regional":{"BIH":"y|2010:1118|2011:1108|2012:1027|2013:1016|2014:1005|2015:0925|2016:0914|2017:0903|2018:0823|2019:0812|2020:0801|2021:0721|2022:0710|2023:0629|2024:0617|2025:0607|2026:0528|2027:0517|2028:0506|2029:0425|2030:0414|2031:0403|2032:0323|2033:0312|2034:0302|2035:0219|2036:0208|2037:0127|2038:0117|2039:0106,1227|2040:1215|2041:1205|2042:1124|2043:1113|2044:1101|2045:1022|2046:1011|2047:1001|2048:0920|2049:0909|2050:0829|2051:0818|2052:0806|2053:0727|2054:0716|2055:0706|2056:0624|2057:0613|2058:0602|2059:0523|2060:0511","SRP":"y|2010:1118|2011:1108|2012:1027|2013:1016|2014:1005|2015:0925|2016:0914|2017:0903|2018:0823|2019:0812|2020:0801|2021:0721|2022:0710|2023:0629|2024:0617|2025:0607|2026:0528|2027:0517|2028:0506|2029:0425|2030:0414|2031:0403|2032:0323|2033:0312|2034:0302|2035:0219|2036:0208|2037:0127|2038:0117|2039:0106,1227|2040:1215|2041:1205|2042:1124|2043:1113|2044:1101|2045:1022|2046:1011|2047:1001|2048:0920|2049:0909|2050:0829|2051:0818|2052:0806|2053:0727|2054:0716|2055:0706|2056:0624|2057:0613|2058:0602|2059:0523|2060:0511"},"estimated":"y|2024:0616-0617|2025:0606-0607|2026:0527-0528|2027:0516-0517|2028:0505-0506|2029:0424-0425|2030:0413-0414|2031:0402-0403|2032:0322-0323|2033:0311-0312|2034:0301-0302|2035:0218-0219|2036:0207-0208|2037:0126-0127|2038:0116-0117|2039:0105-0106,1226-1227|2040:1214-1215|2041:1204-1205|2042:1123-1124|2043:1112-1113|2044:1031-1101|2045:1021-1022|2046:1010-1011|2047:0930-1001|2048:0919-0920|2049:0908-0909|2050:0828-0829|2051:0817-0818|2052:0805-0806|2053:0726-0727|2054:0715-0716|2055:0705-0706|2056:0623-0624|2057:0612-0613|2058:0601-0602|2059:0522-0523|2060:0510-0511"},{"id":"orthodox_christmas_eve","name":"Badnji dan (Pravoslavni)","en":"Orthodox Christmas Eve","category":"public","regional":{"BIH":"d|0106:2010-2060","SRP":"d|0106:2010-2060"}},{"id":"orthodox_christmas_day","name":"Božić (Pravoslavni)","en":"Orthodox Christmas Day","category":"public","dates":"d|0107:2010-2060","regional":{"BRC":"d|0108:2018,2024,2029,2035,2046,2052,2057"}},{"id":"orthodox_new_year","name":"Pravoslavna Nova godina","en":"Orthodox New Year","category":"public","regional":{"SRP":"d|0114:2010-2060"}},{"id":"independence_day","name":"Dan nezavisnosti","en":"Independence Day","category":"public","regional":{"BIH":"d|0301:2010-2060"}},{"id":"day_of_establishment_of_brcko_district","name":"Dan uspostavljanja Brčko distrikta","en":"Day of establishment of Brčko District","category":"public","regional":{"BRC":"d|0308:2010-2060|0309:2015,2020,2026,2037,2043,2048,2054"}},{"id":"catholic_good_friday","name":"Veliki petak (Katolički)","en":"Catholic Good Friday","category":"public","regional":{"BIH":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038","SRP":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"}},{"id":"catholic_easter_sunday","name":"Uskrs (Katolički)","en":"Catholic Easter Sunday","category":"public","regional":{"BIH":"d|0325:2035,2046|0327:2016|0328:2027,2032|0329:2043,2054|0330:2059|0331:2013,2024|0401:2018,2029,2040|0402:2051,2056|0404:2010,2021|0405:2015,2026,2037,2048|0406:2042,2053|0408:2012|0409:2023,2034,2045|0410:2039,2050|0412:2020|0413:2031,2036|0414:2047,2058|0416:2017,2028|0417:2022,2033,2044|0418:2049,2055,2060|0420:2014,2025|0421:2019,2030,2041,2052|0422:2057|0424:2011|0425:2038","SRP":"d|0325:2035,2046|0327:2016|0328:2027,2032|0329:2043,2054|0330:2059|0331:2013,2024|0401:2018,2029,2040|0402:2051,2056|0404:2010,2021|0405:2015,2026,2037,2048|0406:2042,2053|0408:2012|0409:2023,2034,2045|0410:2039,2050|0412:2020|0413:2031,2036|0414:2047,2058|0416:2017,2028|0417:2022,2033,2044|0418:2049,2055,2060|0420:2014,2025|0421:2019,2030,2041,2052|0422:2057|0424:2011|0425:2038"}},{"id":"catholic_easter_monday","name":"Uskrsni ponedjeljak (Katolički)","en":"Catholic Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"orthodox_good_friday","name":"Veliki petak (Pravoslavni)","en":"Orthodox Good Friday","category":"public","dates":"d|0402:2010|0403:2037,2048|0406:2018,2029|0407:2034,2045,2056|0410:2015,2026|0411:2031,2042,2053|0412:2058|0413:2012|0414:2017,2023,2028|0415:2039,2050|0416:2055|0417:2020|0418:2014,2025,2036|0419:2041,2047,2052|0422:2011,2022,2033,2044|0423:2038,2049,2060|0426:2019,2030|0427:2035,2046,2057|0429:2016|0430:2021,2027,2032|0501:2043,2054|0502:2059|0503:2013,2024|0504:2040|0505:2051"},{"id":"orthodox_easter_sunday","name":"Vaskrs (Pravoslavni)","en":"Orthodox Easter Sunday","category":"public","regional":{"BIH":"d|0404:2010|0405:2037,2048|0408:2018,2029|0409:2034,2045,2056|0412:2015,2026|0413:2031,2042,2053|0414:2058|0415:2012|0416:2017,2023,2028|0417:2039,2050|0418:2055|0419:2020|0420:2014,2025,2036|0421:2041,2047,2052|0424:2011,2022,2033,2044|0425:2038,2049,2060|0428:2019,2030|0429:2035,2046,2057|0501:2016|0502:2021,2027,2032|0503:2016,2043,2054|0504:2059|0505:2013,2024|0506:2040|0507:2051","SRP":"d|0404:2010|0405:2037,2048|0408:2018,2029|0409:2034,2045,2056|0412:2015,2026|0413:2031,2042,2053|0414:2058|0415:2012|0416:2017,2023,2028|0417:2039,2050|0418:2055|0419:2020|0420:2014,2025,2036|0421:2041,2047,2052|0424:2011,2022,2033,2044|0425:2038,2049,2060|0428:2019,2030|0429:2035,2046,2057|0501:2016|0502:2021,2027,2032|0503:2043,2054|0504:2059|0505:2013,2024|0506:2040|0507:2051"}},{"id":"orthodox_easter_monday","name":"Uskrsni ponedjeljak (Pravoslavni)","en":"Orthodox Easter Monday","category":"public","regional":{"BIH":"d|0405:2010|0406:2037,2048|0409:2018,2029|0410:2034,2045,2056|0413:2015,2026|0414:2031,2042,2053|0415:2058|0416:2012|0417:2017,2023,2028|0418:2039,2050|0419:2055|0420:2020|0421:2014,2025,2036|0422:2041,2047,2052|0425:2011,2022,2033,2044|0426:2038,2049,2060|0429:2019,2030|0430:2035,2046,2057|0502:2016|0503:2021,2027,2032|0504:2043,2054|0505:2059|0506:2013,2024|0507:2040|0508:2051","SRP":"d|0405:2010|0406:2037,2048|0409:2018,2029|0410:2034,2045,2056|0413:2015,2026|0414:2031,2042,2053|0415:2058|0416:2012|0417:2017,2023,2028|0418:2039,2050|0419:2055|0420:2020|0421:2014,2025,2036|0422:2041,2047,2052|0425:2011,2022,2033,2044|0426:2038,2049,2060|0429:2019,2030|0430:2035,2046,2057|0502:2016|0503:2021,2027,2032|0504:2043,2054|0505:2059|0506:2013,2024|0507:2040|0508:2051"}},{"id":"international_labor_day","name":"Međunarodni praznik rada","en":"International Labor Day","category":"public","dates":"d|0501:2010-2060|0502:2010-2060","regional":{"BIH":"d|0503:2011,2016,2022,2033,2039,2044,2050","BRC":"d|0503:2010-2011,2016,2021-2022,2027,2032-2033,2038-2039,2044,2049-2050,2055,2060","SRP":"d|0503:2010,2021,2027,2032,2038,2049,2055,2060"}},{"id":"victory_day","name":"Dan pobjede nad fašizmom","en":"Victory Day","category":"public","regional":{"BIH":"d|0509:2010-2060","SRP":"d|0509:2010-2060"}},{"id":"dayton_agreement_day","name":"Dan uspostave Opšteg okvirnog sporazuma za mir u Bosni i Hercegovini","en":"Dayton Agreement Day","category":"public","regional":{"SRP":"d|1121:2010-2060"}},{"id":"statehood_day","name":"Dan državnosti","en":"Statehood Day","category":"public","regional":{"BIH":"d|1125:2010-2060"}},{"id":"catholic_christmas_eve","name":"Badnji dan (Katolički)","en":"Catholic Christmas Eve","category":"public","regional":{"BIH":"d|1224:2010-2060","SRP":"d|1224:2010-2060"}},{"id":"catholic_christmas_day","name":"Božić (Katolički)","en":"Catholic Christmas Day","category":"public","dates":"d|1225:2010-2060","regional":{"BRC":"d|1226:2011,2016,2022,2033,2039,2044,2050"}}]} diff --git a/app/Support/Holidays/data/BB.json b/app/Support/Holidays/data/BB.json new file mode 100644 index 000000000..006a2c9ed --- /dev/null +++ b/app/Support/Holidays/data/BB.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"BB","language":null,"years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051"},{"id":"errol_barrow_day","name":"Errol Barrow Day","en":"Errol Barrow Day","category":"public","dates":"d|0121:2010-2060|0122:2018,2024,2029,2035,2046,2052,2057"},{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_monday","name":"Easter Monday","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"national_heroes_day","name":"National Heroes Day","en":"National Heroes Day","category":"public","dates":"d|0428:2010-2060|0429:2013,2019,2024,2030,2041,2047,2052,2058"},{"id":"may_day","name":"May Day","en":"May Day","category":"public","dates":"d|0501:2010-2060|0502:2011,2016,2022,2033,2039,2044,2050"},{"id":"whit_monday","name":"Whit Monday","en":"Whit Monday","category":"public","dates":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038"},{"id":"emancipation_day","name":"Emancipation Day","en":"Emancipation Day","category":"public","dates":"d|0801:2010-2060|0802:2011,2016,2022,2033,2039,2044,2050|0803:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"kadooment_day","name":"Kadooment Day","en":"Kadooment Day","category":"public","dates":"d|0801:2011,2016,2022,2033,2039,2044,2050|0802:2010,2021,2027,2032,2038,2049,2055,2060|0803:2015,2020,2026,2037,2043,2048,2054|0804:2014,2025,2031,2036,2042,2053,2059|0805:2013,2019,2024,2030,2041,2047,2052,2058|0806:2012,2018,2029,2035,2040,2046,2057|0807:2017,2023,2028,2034,2045,2051,2056"},{"id":"independence_day","name":"Independence Day","en":"Independence Day","category":"public","dates":"d|1130:2010-2060|1201:2014,2025,2031,2036,2042,2053,2059"},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060|1227:2011,2016,2022,2033,2039,2044,2050"},{"id":"boxing_day","name":"Boxing Day","en":"Boxing Day","category":"public","dates":"d|1226:2010-2060|1227:2010,2021,2027,2032,2038,2049,2055,2060"}]} diff --git a/app/Support/Holidays/data/BD.json b/app/Support/Holidays/data/BD.json new file mode 100644 index 000000000..9b2be663b --- /dev/null +++ b/app/Support/Holidays/data/BD.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"BD","language":"bn","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"mid_sha_ban","name":"শবে বরাত","en":"Mid-Sha'ban","category":"public","dates":"y|2010:0728|2011:0717|2012:0706|2013:0625|2014:0614|2015:0603|2016:0523|2017:0512|2018:0502|2019:0421|2020:0409|2021:0329|2022:0319|2023:0308|2024:0226|2025:0215|2026:0204|2027:0124|2028:0113|2029:0101,1222|2030:1211|2031:1201|2032:1119|2033:1108|2034:1028|2035:1017|2036:1006|2037:0926|2038:0915|2039:0905|2040:0824|2041:0813|2042:0802|2043:0723|2044:0711|2045:0701|2046:0620|2047:0610|2048:0529|2049:0518|2050:0507|2051:0427|2052:0416|2053:0405|2054:0325|2055:0314|2056:0303|2057:0220|2058:0209|2059:0130|2060:0120","estimated":"y|2010:0728|2011:0717|2012:0706|2013:0625|2014:0614|2015:0603|2016:0523|2017:0512|2018:0502|2019:0421|2020:0409|2021:0329|2027:0124|2028:0113|2029:0101,1222|2030:1211|2031:1201|2032:1119|2033:1108|2034:1028|2035:1017|2036:1006|2037:0926|2038:0915|2039:0905|2040:0824|2041:0813|2042:0802|2043:0723|2044:0711|2045:0701|2046:0620|2047:0610|2048:0529|2049:0518|2050:0507|2051:0427|2052:0416|2053:0405|2054:0325|2055:0314|2056:0303|2057:0220|2058:0209|2059:0130|2060:0120"},{"id":"ashura","name":"আশুরা","en":"Ashura","category":"public","dates":"y|2010:1217|2011:1206|2012:1125|2013:1114|2014:1104|2015:1024|2016:1012|2017:1001|2018:0921|2019:0910|2020:0830|2021:0819|2022:0809|2023:0729|2024:0717|2025:0706|2026:0626|2027:0616|2028:0604|2029:0524|2030:0513|2031:0503|2032:0421|2033:0411|2034:0331|2035:0321|2036:0309|2037:0226|2038:0215|2039:0205|2040:0125|2041:0114|2042:0103,1224|2043:1213|2044:1201|2045:1120|2046:1110|2047:1030|2048:1019|2049:1008|2050:0927|2051:0916|2052:0905|2053:0825|2054:0815|2055:0804|2056:0724|2057:0713|2058:0702|2059:0621|2060:0610","estimated":"y|2010:1217|2011:1206|2012:1125|2013:1114|2014:1104|2015:1024|2016:1012|2017:1001|2018:0921|2019:0910|2020:0830|2021:0819|2027:0616|2028:0604|2029:0524|2030:0513|2031:0503|2032:0421|2033:0411|2034:0331|2035:0321|2036:0309|2037:0226|2038:0215|2039:0205|2040:0125|2041:0114|2042:0103,1224|2043:1213|2044:1201|2045:1120|2046:1110|2047:1030|2048:1019|2049:1008|2050:0927|2051:0916|2052:0905|2053:0825|2054:0815|2055:0804|2056:0724|2057:0713|2058:0702|2059:0621|2060:0610"},{"id":"eid_al_fitr","name":"ঈদুল ফিতর","en":"Eid al-Fitr","category":"public","dates":"y|2010:0911-0913|2011:0831-0902|2012:0820-0822|2013:0809-0811|2014:0729-0731|2015:0718-0720|2016:0707-0709|2017:0626-0628|2018:0616-0618|2019:0605-0607|2020:0525-0527|2021:0514-0516|2022:0503-0505|2023:0422-0424|2024:0410-0412|2025:0331-0402|2026:0321-0323|2027:0310-0312|2028:0227-0229|2029:0215-0217|2030:0205-0207|2031:0125-0127|2032:0115-0117|2033:0103-0105,1224-1226|2034:1213-1215|2035:1202-1204|2036:1120-1122|2037:1109-1111|2038:1030-1101|2039:1020-1022|2040:1008-1010|2041:0927-0929|2042:0916-0918|2043:0905-0907|2044:0825-0827|2045:0815-0817|2046:0804-0806|2047:0725-0727|2048:0713-0715|2049:0702-0704|2050:0621-0623|2051:0611-0613|2052:0530-0601|2053:0520-0522|2054:0510-0512|2055:0429-0501|2056:0417-0419|2057:0406-0408|2058:0326-0328|2059:0316-0318|2060:0305-0307","estimated":"y|2010:0911-0913|2011:0831-0902|2012:0820-0822|2013:0809-0811|2014:0729-0731|2015:0718-0720|2016:0707-0709|2017:0626-0628|2018:0616-0618|2019:0605-0607|2020:0525-0527|2021:0514-0516|2027:0310-0312|2028:0227-0229|2029:0215-0217|2030:0205-0207|2031:0125-0127|2032:0115-0117|2033:0103-0105,1224-1226|2034:1213-1215|2035:1202-1204|2036:1120-1122|2037:1109-1111|2038:1030-1101|2039:1020-1022|2040:1008-1010|2041:0927-0929|2042:0916-0918|2043:0905-0907|2044:0825-0827|2045:0815-0817|2046:0804-0806|2047:0725-0727|2048:0713-0715|2049:0702-0704|2050:0621-0623|2051:0611-0613|2052:0530-0601|2053:0520-0522|2054:0510-0512|2055:0429-0501|2056:0417-0419|2057:0406-0408|2058:0326-0328|2059:0316-0318|2060:0305-0307"},{"id":"prophet_s_birthday","name":"ঈদে মিলাদুন্নবী","en":"Prophet's Birthday","category":"public","dates":"y|2010:0227|2011:0216|2012:0205|2013:0125|2014:0114|2015:0104,1224|2016:1212|2017:1201|2018:1121|2019:1110|2020:1030|2021:1019|2022:1009|2023:0928|2024:0916|2025:0906|2026:0826|2027:0815|2028:0804|2029:0725|2030:0714|2031:0703|2032:0621|2033:0610|2034:0531|2035:0521|2036:0509|2037:0429|2038:0418|2039:0407|2040:0326|2041:0316|2042:0305|2043:0223|2044:0212|2045:0131|2046:0120|2047:0109,1230|2048:1219|2049:1208|2050:1127|2051:1117|2052:1105|2053:1025|2054:1014|2055:1004|2056:0923|2057:0912|2058:0901|2059:0821|2060:0809","estimated":"y|2010:0227|2011:0216|2012:0205|2013:0125|2014:0114|2015:0104,1224|2016:1212|2017:1201|2018:1121|2019:1110|2020:1030|2021:1019|2027:0815|2028:0804|2029:0725|2030:0714|2031:0703|2032:0621|2033:0610|2034:0531|2035:0521|2036:0509|2037:0429|2038:0418|2039:0407|2040:0326|2041:0316|2042:0305|2043:0223|2044:0212|2045:0131|2046:0120|2047:0109,1230|2048:1219|2049:1208|2050:1127|2051:1117|2052:1105|2053:1025|2054:1014|2055:1004|2056:0923|2057:0912|2058:0901|2059:0821|2060:0809"},{"id":"eid_al_adha","name":"ঈদুল আজহা","en":"Eid al-Adha","category":"public","dates":"y|2010:1117-1119|2011:1107-1109|2012:1027-1029|2013:1016-1018|2014:1005-1007|2015:0924-0926|2016:0912-0914|2017:0902-0904|2018:0822-0824|2019:0812-0814|2020:0801-0803|2021:0721-0723|2022:0710-0712|2023:0629-0701|2024:0617-0619|2025:0607-0609|2026:0528-0530|2027:0517-0519|2028:0506-0508|2029:0425-0427|2030:0414-0416|2031:0403-0405|2032:0323-0325|2033:0312-0314|2034:0302-0304|2035:0219-0221|2036:0208-0210|2037:0127-0129|2038:0117-0119|2039:0106-0108,1227-1229|2040:1215-1217|2041:1205-1207|2042:1124-1126|2043:1113-1115|2044:1101-1103|2045:1022-1024|2046:1011-1013|2047:1001-1003|2048:0920-0922|2049:0909-0911|2050:0829-0831|2051:0818-0820|2052:0806-0808|2053:0727-0729|2054:0716-0718|2055:0706-0708|2056:0624-0626|2057:0613-0615|2058:0602-0604|2059:0523-0525|2060:0511-0513","estimated":"y|2010:1117-1119|2011:1107-1109|2012:1027-1029|2013:1016-1018|2014:1005-1007|2015:0924-0926|2016:0912-0914|2017:0902-0904|2018:0822-0824|2019:0812-0814|2020:0801-0803|2021:0721-0723|2027:0517-0519|2028:0506-0508|2029:0425-0427|2030:0414-0416|2031:0403-0405|2032:0323-0325|2033:0312-0314|2034:0302-0304|2035:0219-0221|2036:0208-0210|2037:0127-0129|2038:0117-0119|2039:0106-0108,1227-1229|2040:1215-1217|2041:1205-1207|2042:1124-1126|2043:1113-1115|2044:1101-1103|2045:1022-1024|2046:1011-1013|2047:1001-1003|2048:0920-0922|2049:0909-0911|2050:0829-0831|2051:0818-0820|2052:0806-0808|2053:0727-0729|2054:0716-0718|2055:0706-0708|2056:0624-0626|2057:0613-0615|2058:0602-0604|2059:0523-0525|2060:0511-0513"},{"id":"jumu_atul_wida","name":"জুমাতুল বিদা","en":"Jumu'atul-Wida","category":"public","dates":"d|0109:2032|0117:2031|0201:2030|0209:2029|0225:2028|0227:2060|0305:2027|0314:2059|0320:2026|0322:2058|0328:2025|0330:2057|0405:2024|0414:2056|0421:2023|0423:2055|0429:2022|0507:2021|0508:2054|0516:2053|0522:2020|0524:2052|0531:2019|0608:2018|0609:2051|0617:2050|0623:2017|0625:2049|0701:2016|0710:2015,2048|0719:2047|0725:2014|0727:2046|0802:2013|0811:2045|0817:2012|0819:2044|0826:2011|0828:2043|0903:2010|0912:2042|0920:2041|1005:2040|1014:2039|1022:2038|1106:2037|1114:2036|1130:2035|1208:2034|1216:2033|1231:2032","estimated":"d|0109:2032|0117:2031|0201:2030|0209:2029|0225:2028|0227:2060|0305:2027|0314:2059|0322:2058|0330:2057|0414:2056|0423:2055|0507:2021|0508:2054|0516:2053|0522:2020|0524:2052|0531:2019|0608:2018|0609:2051|0617:2050|0623:2017|0625:2049|0701:2016|0710:2015,2048|0719:2047|0725:2014|0727:2046|0802:2013|0811:2045|0817:2012|0819:2044|0826:2011|0828:2043|0903:2010|0912:2042|0920:2041|1005:2040|1014:2039|1022:2038|1106:2037|1114:2036|1130:2035|1208:2034|1216:2033|1231:2032"},{"id":"laylat_al_qadr","name":"শবে কদর","en":"Laylat al-Qadr","category":"public","dates":"y|2010:0907|2011:0828|2012:0816|2013:0805|2014:0725|2015:0715|2016:0703|2017:0623|2018:0612|2019:0602|2020:0521|2021:0510|2022:0429|2023:0419|2024:0407|2025:0328|2026:0317|2027:0307|2028:0224|2029:0212|2030:0201|2031:0122|2032:0111,1231|2033:1220|2034:1209|2035:1128|2036:1116|2037:1106|2038:1027|2039:1016|2040:1004|2041:0924|2042:0913|2043:0902|2044:0822|2045:0811|2046:0801|2047:0721|2048:0709|2049:0629|2050:0618|2051:0607|2052:0527|2053:0517|2054:0506|2055:0425|2056:0413|2057:0402|2058:0323|2059:0313|2060:0301","estimated":"y|2010:0907|2011:0828|2012:0816|2013:0805|2014:0725|2015:0715|2016:0703|2017:0623|2018:0612|2019:0602|2020:0521|2021:0510|2027:0307|2028:0224|2029:0212|2030:0201|2031:0122|2032:0111,1231|2033:1220|2034:1209|2035:1128|2036:1116|2037:1106|2038:1027|2039:1016|2040:1004|2041:0924|2042:0913|2043:0902|2044:0822|2045:0811|2046:0801|2047:0721|2048:0709|2049:0629|2050:0618|2051:0607|2052:0527|2053:0517|2054:0506|2055:0425|2056:0413|2057:0402|2058:0323|2059:0313|2060:0301"},{"id":"martyrs_day_and_international_mother_language_da","name":"শহীদ দিবস ও আন্তর্জাতিক মাতৃভাষা দিবস","en":"Martyrs' Day and International Mother Language Day","category":"public","dates":"d|0221:2010-2060"},{"id":"independence_day","name":"স্বাধীনতা দিবস","en":"Independence Day","category":"public","dates":"d|0326:2010-2060"},{"id":"bengali_new_year_s_day","name":"পহেলা বৈশাখ","en":"Bengali New Year's Day","category":"public","dates":"d|0414:2010-2060"},{"id":"may_day","name":"মে দিবস","en":"May Day","category":"public","dates":"d|0501:2010-2060"},{"id":"july_mass_uprising_day","name":"জুলাই গণ-অভ্যুত্থান দিবস","en":"July Mass Uprising Day","category":"public","dates":"d|0805:2025-2060"},{"id":"victory_day","name":"বিজয় দিবস","en":"Victory Day","category":"public","dates":"d|1216:2010-2060"},{"id":"christmas_day","name":"বড়দিন","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"}]} diff --git a/app/Support/Holidays/data/BE.json b/app/Support/Holidays/data/BE.json new file mode 100644 index 000000000..35db6b6b9 --- /dev/null +++ b/app/Support/Holidays/data/BE.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"BE","language":"nl","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Nieuwjaar","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"easter_sunday","name":"Pasen","en":"Easter Sunday","category":"public","dates":"d|0325:2035,2046|0327:2016|0328:2027,2032|0329:2043,2054|0330:2059|0331:2013,2024|0401:2018,2029,2040|0402:2051,2056|0404:2010,2021|0405:2015,2026,2037,2048|0406:2042,2053|0408:2012|0409:2023,2034,2045|0410:2039,2050|0412:2020|0413:2031,2036|0414:2047,2058|0416:2017,2028|0417:2022,2033,2044|0418:2049,2055,2060|0420:2014,2025|0421:2019,2030,2041,2052|0422:2057|0424:2011|0425:2038"},{"id":"easter_monday","name":"Paasmaandag","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"labor_day","name":"Dag van de Arbeid","en":"Labor Day","category":"public","dates":"d|0501:2010-2060"},{"id":"ascension_day","name":"O. L. H. Hemelvaart","en":"Ascension Day","category":"public","dates":"d|0503:2035,2046|0505:2016|0506:2027,2032|0507:2043,2054|0508:2059|0509:2013,2024|0510:2018,2029,2040|0511:2051,2056|0513:2010,2021|0514:2015,2026,2037,2048|0515:2042,2053|0517:2012|0518:2023,2034,2045|0519:2039,2050|0521:2020|0522:2031,2036|0523:2047,2058|0525:2017,2028|0526:2022,2033,2044|0527:2049,2055,2060|0529:2014,2025|0530:2019,2030,2041,2052|0531:2057|0602:2011|0603:2038"},{"id":"pentecost","name":"Pinksteren","en":"Pentecost","category":"public","dates":"d|0513:2035,2046|0515:2016|0516:2027,2032|0517:2043,2054|0518:2059|0519:2013,2024|0520:2018,2029,2040|0521:2051,2056|0523:2010,2021|0524:2015,2026,2037,2048|0525:2042,2053|0527:2012|0528:2023,2034,2045|0529:2039,2050|0531:2020|0601:2031,2036|0602:2047,2058|0604:2017,2028|0605:2022,2033,2044|0606:2049,2055,2060|0608:2014,2025|0609:2019,2030,2041,2052|0610:2057|0612:2011|0613:2038"},{"id":"pentecost_monday","name":"Pinkstermaandag","en":"Pentecost Monday","category":"public","dates":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038"},{"id":"national_day","name":"Nationale feestdag","en":"National Day","category":"public","dates":"d|0721:2010-2060"},{"id":"assumption_day","name":"O. L. V. Hemelvaart","en":"Assumption Day","category":"public","dates":"d|0815:2010-2060"},{"id":"all_saints_day","name":"Allerheiligen","en":"All Saints' Day","category":"public","dates":"d|1101:2010-2060"},{"id":"armistice_day","name":"Wapenstilstand","en":"Armistice Day","category":"public","dates":"d|1111:2010-2060"},{"id":"christmas_day","name":"Kerstmis","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"},{"id":"good_friday_bank","name":"Goede vrijdag","en":"Good Friday","category":"bank","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"friday_after_ascension_day_bank","name":"Vrijdag na O. L. H. Hemelvaart","en":"Friday after Ascension Day","category":"bank","dates":"d|0504:2035,2046|0506:2016|0507:2027,2032|0508:2043,2054|0509:2059|0510:2013,2024|0511:2018,2029,2040|0512:2051,2056|0514:2010,2021|0515:2015,2026,2037,2048|0516:2042,2053|0518:2012|0519:2023,2034,2045|0520:2039,2050|0522:2020|0523:2031,2036|0524:2047,2058|0526:2017,2028|0527:2022,2033,2044|0528:2049,2055,2060|0530:2014,2025|0531:2019,2030,2041,2052|0601:2057|0603:2011|0604:2038"},{"id":"bank_holiday_bank","name":"Banksluitingsdag","en":"Bank Holiday","category":"bank","dates":"d|1226:2010-2060"}]} diff --git a/app/Support/Holidays/data/BF.json b/app/Support/Holidays/data/BF.json new file mode 100644 index 000000000..0a2f2a23c --- /dev/null +++ b/app/Support/Holidays/data/BF.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"BF","language":"en_BF","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051"},{"id":"eid_al_fitr","name":"Eid al-Fitr","en":"Eid al-Fitr","category":"public","dates":"y|2010:0910|2011:0830|2012:0819|2013:0808|2014:0729|2015:0718|2016:0707|2017:0626|2018:0615|2019:0604|2020:0524|2021:0513|2022:0502|2023:0421|2024:0410|2025:0330|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304","estimated":"y|2010:0910|2011:0830|2012:0819|2013:0808|2025:0330|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304"},{"id":"prophet_s_birthday","name":"Mawlid","en":"Prophet's Birthday","category":"public","dates":"y|2010:0226|2011:0215|2012:0204|2013:0124|2014:0114|2015:0103,1224|2016:1212|2017:1201|2018:1121|2019:1110|2020:1029|2021:1019|2022:1009|2023:0927|2024:0915|2025:0904|2026:0825|2027:0814|2028:0803|2029:0724|2030:0713|2031:0702|2032:0620|2033:0609|2034:0530|2035:0520|2036:0508|2037:0428|2038:0417|2039:0406|2040:0325|2041:0315|2042:0304|2043:0222|2044:0211|2045:0130|2046:0119|2047:0108,1229|2048:1218|2049:1207|2050:1126|2051:1116|2052:1104|2053:1024|2054:1013|2055:1003|2056:0922|2057:0911|2058:0831|2059:0820|2060:0808","estimated":"y|2010:0226|2011:0215|2012:0204|2013:0124|2023:0927|2024:0915|2025:0904|2026:0825|2027:0814|2028:0803|2029:0724|2030:0713|2031:0702|2032:0620|2033:0609|2034:0530|2035:0520|2036:0508|2037:0428|2038:0417|2039:0406|2040:0325|2041:0315|2042:0304|2043:0222|2044:0211|2045:0130|2046:0119|2047:0108,1229|2048:1218|2049:1207|2050:1126|2051:1116|2052:1104|2053:1024|2054:1013|2055:1003|2056:0922|2057:0911|2058:0831|2059:0820|2060:0808"},{"id":"revolution_day","name":"Revolution Day","en":"Revolution Day","category":"public","dates":"d|0103:2010-2060|0104:2010,2016,2021,2027,2038,2044,2049,2055"},{"id":"eid_al_adha","name":"Eid al-Adha","en":"Eid al-Adha","category":"public","dates":"y|2010:1116|2011:1106|2012:1026|2013:1015|2014:1005|2015:0924|2016:0913|2017:0902|2018:0821|2019:0811|2020:0731|2021:0720|2022:0709|2023:0628|2024:0616|2025:0606|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510","estimated":"y|2010:1116|2011:1106|2012:1026|2013:1015|2024:0616|2025:0606|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510"},{"id":"international_women_s_day","name":"International Women's Day","en":"International Women's Day","category":"public","dates":"d|0308:2010-2060|0309:2015,2020,2026,2037,2043,2048,2054"},{"id":"easter_monday","name":"Easter Monday","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"labor_day","name":"Labour Day","en":"Labor Day","category":"public","dates":"d|0501:2010-2060|0502:2011,2016,2022,2033,2039,2044,2050"},{"id":"ascension_day","name":"Ascension Day","en":"Ascension Day","category":"public","dates":"d|0503:2035,2046|0505:2016|0506:2027,2032|0507:2043,2054|0508:2059|0509:2013,2024|0510:2018,2029,2040|0511:2051,2056|0513:2010,2021|0514:2015,2026,2037,2048|0515:2042,2053|0517:2012|0518:2023,2034,2045|0519:2039,2050|0521:2020|0522:2031,2036|0523:2047,2058|0525:2017,2028|0526:2022,2033,2044|0527:2049,2055,2060|0529:2014,2025|0530:2019,2030,2041,2052|0531:2057|0602:2011|0603:2038"},{"id":"independence_day","name":"Independence Day","en":"Independence Day","category":"public","dates":"d|0805:2010-2060|0806:2012,2018,2029,2035,2040,2046,2057"},{"id":"assumption_day","name":"Assumption Day","en":"Assumption Day","category":"public","dates":"d|0815:2010-2060|0816:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"martyrs_day","name":"Martyrs' Day","en":"Martyrs' Day","category":"public","dates":"d|1031:2016-2060|1101:2021,2027,2032,2038,2049,2055,2060"},{"id":"all_saints_day","name":"All Saints' Day","en":"All Saints' Day","category":"public","dates":"d|1101:2010-2060|1102:2015,2020,2026,2037,2043,2048,2054"},{"id":"proclamation_of_independence_day","name":"Proclamation of Independence Day","en":"Proclamation of Independence Day","category":"public","dates":"d|1211:2010-2060|1212:2011,2016,2022,2033,2039,2044,2050"},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060|1226:2011,2016,2022,2033,2039,2044,2050"}]} diff --git a/app/Support/Holidays/data/BG.json b/app/Support/Holidays/data/BG.json new file mode 100644 index 000000000..4ec046a8b --- /dev/null +++ b/app/Support/Holidays/data/BG.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"BG","language":"bg","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Нова година","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2017,2023,2034,2040,2045,2051|0103:2022,2028,2033,2039,2050,2056"},{"id":"non_working_day","name":"Неприсъствен ден","en":"Non-working day","category":"public","dates":"d|0102:2026|1231:2025"},{"id":"liberation_day","name":"Ден на Освобождението на България от османско иго","en":"Liberation Day","category":"public","dates":"d|0303:2010-2060|0304:2019,2024,2030,2041,2047,2052,2058|0305:2018,2029,2035,2040,2046,2057"},{"id":"good_friday","name":"Велики петък","en":"Good Friday","category":"public","dates":"d|0402:2010|0403:2037,2048|0406:2018,2029|0407:2034,2045,2056|0410:2015,2026|0411:2031,2042,2053|0412:2058|0413:2012|0414:2017,2023,2028|0415:2039,2050|0416:2055|0417:2020|0418:2014,2025,2036|0419:2041,2047,2052|0422:2011,2022,2033,2044|0423:2038,2049,2060|0426:2019,2030|0427:2035,2046,2057|0429:2016|0430:2021,2027,2032|0501:2043,2054|0502:2059|0503:2013,2024|0504:2040|0505:2051"},{"id":"holy_saturday","name":"Велика събота","en":"Holy Saturday","category":"public","dates":"d|0403:2010|0404:2037,2048|0407:2018,2029|0408:2034,2045,2056|0411:2015,2026|0412:2031,2042,2053|0413:2058|0414:2012|0415:2017,2023,2028|0416:2039,2050|0417:2055|0418:2020|0419:2014,2025,2036|0420:2041,2047,2052|0423:2011,2022,2033,2044|0424:2038,2049,2060|0427:2019,2030|0428:2035,2046,2057|0430:2016|0501:2021,2027,2032|0502:2043,2054|0503:2059|0504:2013,2024|0505:2040|0506:2051"},{"id":"easter","name":"Великден","en":"Easter","category":"public","dates":"d|0404:2010|0405:2010,2037,2048|0406:2037,2048|0408:2018,2029|0409:2018,2029,2034,2045,2056|0410:2034,2045,2056|0412:2015,2026|0413:2015,2026,2031,2042,2053|0414:2031,2042,2053,2058|0415:2012,2058|0416:2012,2017,2023,2028|0417:2017,2023,2028,2039,2050|0418:2039,2050,2055|0419:2020,2055|0420:2014,2020,2025,2036|0421:2014,2025,2036,2041,2047,2052|0422:2041,2047,2052|0424:2011,2022,2033,2044|0425:2011,2022,2033,2038,2044,2049,2060|0426:2038,2049,2060|0428:2019,2030|0429:2019,2030,2035,2046,2057|0430:2035,2046,2057|0501:2016|0502:2016,2021,2027,2032|0503:2021,2027,2032,2043,2054|0504:2043,2054,2059|0505:2013,2024,2059|0506:2013,2024,2040|0507:2040,2051|0508:2051"},{"id":"labor_day_and_international_workers_solidarity_d","name":"Ден на труда и на международната работническа солидарност","en":"Labor Day and International Workers' Solidarity Day","category":"public","dates":"d|0501:2010-2060|0502:2022,2033,2039,2044,2050|0503:2038,2049,2055,2060|0504:2021,2027,2032"},{"id":"saint_george_s_day_day_of_the_bulgarian_army","name":"Гергьовден, Ден на храбростта и Българската армия","en":"Saint George's Day, Day of the Bulgarian Army","category":"public","dates":"d|0506:2010-2060|0507:2018,2029,2035,2046,2057|0508:2017,2023,2028,2034,2040,2045,2056|0509:2051"},{"id":"day_of_slavonic_alphabet_bulgarian_enlightenment","name":"Ден на светите братя Кирил и Методий, на българската азбука, просвета и култура и на славянската книжовност","en":"Day of Slavonic Alphabet, Bulgarian Enlightenment and Culture","category":"public","dates":"d|0524:2021-2060|0525:2026,2037,2043,2048,2054|0526:2025,2031,2036,2042,2053,2059"},{"id":"unification_day","name":"Ден на Съединението","en":"Unification Day","category":"public","dates":"d|0906:2010-2060|0907:2020,2026,2037,2043,2048,2054|0908:2025,2031,2036,2042,2053,2059"},{"id":"independence_day","name":"Ден на Независимостта на България","en":"Independence Day","category":"public","dates":"d|0922:2010-2060|0923:2019,2024,2030,2041,2047,2052,2058|0924:2018,2029,2035,2040,2046,2057"},{"id":"christmas_eve","name":"Бъдни вечер","en":"Christmas Eve","category":"public","dates":"d|1224:2010-2060|1227:2017,2022-2023,2028,2033-2034,2039,2044-2045,2050-2051,2056"},{"id":"christmas_day","name":"Рождество Христово","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060|1226:2010-2060|1227:2021,2027,2032,2038,2049,2055,2060|1228:2020-2022,2026-2027,2032-2033,2037-2039,2043-2044,2048-2050,2054-2055,2060"},{"id":"the_day_of_the_people_s_awakeners_school","name":"Ден на народните будители","en":"The Day of the People's Awakeners","category":"school","dates":"d|1101:2010-2060"}]} diff --git a/app/Support/Holidays/data/BH.json b/app/Support/Holidays/data/BH.json new file mode 100644 index 000000000..42c8015f6 --- /dev/null +++ b/app/Support/Holidays/data/BH.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"BH","language":"ar","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"ashura","name":"عاشوراء","en":"Ashura","category":"public","dates":"y|2010:1215-1216|2011:1204-1205|2012:1123-1124|2013:1112-1113|2014:1102-1103|2015:1022-1023|2016:1010-1011|2017:0929-0930|2018:0919-0920|2019:0909-0910|2020:0828-0829|2021:0817-0818|2022:0807-0808|2023:0728-0729|2024:0715-0716|2025:0704-0705|2026:0624-0625|2027:0614-0615|2028:0602-0603|2029:0522-0523|2030:0511-0512|2031:0501-0502|2032:0419-0420|2033:0409-0410|2034:0329-0330|2035:0319-0320|2036:0307-0308|2037:0224-0225|2038:0213-0214|2039:0203-0204|2040:0123-0124|2041:0112-0113|2042:0101-0102,1222-1223|2043:1211-1212|2044:1129-1130|2045:1118-1119|2046:1108-1109|2047:1028-1029|2048:1017-1018|2049:1006-1007|2050:0925-0926|2051:0914-0915|2052:0903-0904|2053:0823-0824|2054:0813-0814|2055:0802-0803|2056:0722-0723|2057:0711-0712|2058:0630-0701|2059:0619-0620|2060:0608-0609","estimated":"y|2010:1215-1216|2011:1204-1205|2012:1123-1124|2013:1112-1113|2014:1102-1103|2015:1022-1023|2016:1010-1011|2017:0929-0930|2018:0919-0920|2026:0624-0625|2027:0614-0615|2028:0602-0603|2029:0522-0523|2030:0511-0512|2031:0501-0502|2032:0419-0420|2033:0409-0410|2034:0329-0330|2035:0319-0320|2036:0307-0308|2037:0224-0225|2038:0213-0214|2039:0203-0204|2040:0123-0124|2041:0112-0113|2042:0101-0102,1222-1223|2043:1211-1212|2044:1129-1130|2045:1118-1119|2046:1108-1109|2047:1028-1029|2048:1017-1018|2049:1006-1007|2050:0925-0926|2051:0914-0915|2052:0903-0904|2053:0823-0824|2054:0813-0814|2055:0802-0803|2056:0722-0723|2057:0711-0712|2058:0630-0701|2059:0619-0620|2060:0608-0609"},{"id":"new_year_s_day","name":"رأس السنة الميلادية","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"eid_al_fitr","name":"عيد الفطر","en":"Eid al-Fitr","category":"public","dates":"y|2010:0910-0912|2011:0830-0901|2012:0819-0821|2013:0808-0810|2014:0728-0730|2015:0717-0719|2016:0706-0708|2017:0625-0627|2018:0615-0617|2019:0604-0606|2020:0524-0526|2021:0513-0515|2022:0502-0504|2023:0421-0423|2024:0410-0412|2025:0330-0401|2026:0320-0322|2027:0309-0311|2028:0226-0228|2029:0214-0216|2030:0204-0206|2031:0124-0126|2032:0114-0116|2033:0102-0104,1223-1225|2034:1212-1214|2035:1201-1203|2036:1119-1121|2037:1108-1110|2038:1029-1031|2039:1019-1021|2040:1007-1009|2041:0926-0928|2042:0915-0917|2043:0904-0906|2044:0824-0826|2045:0814-0816|2046:0803-0805|2047:0724-0726|2048:0712-0714|2049:0701-0703|2050:0620-0622|2051:0610-0612|2052:0529-0531|2053:0519-0521|2054:0509-0511|2055:0428-0430|2056:0416-0418|2057:0405-0407|2058:0325-0327|2059:0315-0317|2060:0304-0306","estimated":"y|2010:0910-0912|2011:0830-0901|2012:0819-0821|2013:0808-0810|2014:0728-0730|2015:0717-0719|2016:0706-0708|2017:0625-0627|2018:0615-0617|2026:0320-0322|2027:0309-0311|2028:0226-0228|2029:0214-0216|2030:0204-0206|2031:0124-0126|2032:0114-0116|2033:0102-0104,1223-1225|2034:1212-1214|2035:1201-1203|2036:1119-1121|2037:1108-1110|2038:1029-1031|2039:1019-1021|2040:1007-1009|2041:0926-0928|2042:0915-0917|2043:0904-0906|2044:0824-0826|2045:0814-0816|2046:0803-0805|2047:0724-0726|2048:0712-0714|2049:0701-0703|2050:0620-0622|2051:0610-0612|2052:0529-0531|2053:0519-0521|2054:0509-0511|2055:0428-0430|2056:0416-0418|2057:0405-0407|2058:0325-0327|2059:0315-0317|2060:0304-0306"},{"id":"prophet_s_birthday","name":"المولد النبوي الشريف","en":"Prophet's Birthday","category":"public","dates":"y|2010:0226|2011:0215|2012:0204|2013:0124|2014:0113|2015:0103,1223|2016:1211|2017:1130|2018:1120|2019:1109|2020:1029|2021:1018|2022:1008|2023:0927|2024:0915|2025:0904|2026:0825|2027:0814|2028:0803|2029:0724|2030:0713|2031:0702|2032:0620|2033:0609|2034:0530|2035:0520|2036:0508|2037:0428|2038:0417|2039:0406|2040:0325|2041:0315|2042:0304|2043:0222|2044:0211|2045:0130|2046:0119|2047:0108,1229|2048:1218|2049:1207|2050:1126|2051:1116|2052:1104|2053:1024|2054:1013|2055:1003|2056:0922|2057:0911|2058:0831|2059:0820|2060:0808","estimated":"y|2010:0226|2011:0215|2012:0204|2013:0124|2014:0113|2015:0103,1223|2016:1211|2017:1130|2018:1120|2026:0825|2027:0814|2028:0803|2029:0724|2030:0713|2031:0702|2032:0620|2033:0609|2034:0530|2035:0520|2036:0508|2037:0428|2038:0417|2039:0406|2040:0325|2041:0315|2042:0304|2043:0222|2044:0211|2045:0130|2046:0119|2047:0108,1229|2048:1218|2049:1207|2050:1126|2051:1116|2052:1104|2053:1024|2054:1013|2055:1003|2056:0922|2057:0911|2058:0831|2059:0820|2060:0808"},{"id":"islamic_new_year","name":"رأس السنة الهجرية","en":"Islamic New Year","category":"public","dates":"y|2010:1207|2011:1126|2012:1115|2013:1104|2014:1025|2015:1014|2016:1002|2017:0921|2018:0911|2019:0831|2020:0820|2021:0809|2022:0730|2023:0719|2024:0707|2025:0626|2026:0616|2027:0606|2028:0525|2029:0514|2030:0503|2031:0423|2032:0411|2033:0401|2034:0321|2035:0311|2036:0228|2037:0216|2038:0205|2039:0126|2040:0115|2041:0104,1224|2042:1214|2043:1203|2044:1121|2045:1110|2046:1031|2047:1020|2048:1009|2049:0928|2050:0917|2051:0906|2052:0826|2053:0815|2054:0805|2055:0725|2056:0714|2057:0703|2058:0622|2059:0611|2060:0531","estimated":"y|2010:1207|2011:1126|2012:1115|2013:1104|2014:1025|2015:1014|2016:1002|2017:0921|2018:0911|2026:0616|2027:0606|2028:0525|2029:0514|2030:0503|2031:0423|2032:0411|2033:0401|2034:0321|2035:0311|2036:0228|2037:0216|2038:0205|2039:0126|2040:0115|2041:0104,1224|2042:1214|2043:1203|2044:1121|2045:1110|2046:1031|2047:1020|2048:1009|2049:0928|2050:0917|2051:0906|2052:0826|2053:0815|2054:0805|2055:0725|2056:0714|2057:0703|2058:0622|2059:0611|2060:0531"},{"id":"eid_al_adha","name":"عيد الأضحى","en":"Eid al-Adha","category":"public","dates":"y|2010:1116-1118|2011:1106-1108|2012:1026-1028|2013:1015-1017|2014:1004-1006|2015:0923-0925|2016:0911-0913|2017:0901-0903|2018:0821-0823|2019:0811-0813|2020:0731-0802|2021:0720-0722|2022:0709-0711|2023:0628-0630|2024:0616-0618|2025:0606-0608|2026:0527-0529|2027:0516-0518|2028:0505-0507|2029:0424-0426|2030:0413-0415|2031:0402-0404|2032:0322-0324|2033:0311-0313|2034:0301-0303|2035:0218-0220|2036:0207-0209|2037:0126-0128|2038:0116-0118|2039:0105-0107,1226-1228|2040:1214-1216|2041:1204-1206|2042:1123-1125|2043:1112-1114|2044:1031-1102|2045:1021-1023|2046:1010-1012|2047:0930-1002|2048:0919-0921|2049:0908-0910|2050:0828-0830|2051:0817-0819|2052:0805-0807|2053:0726-0728|2054:0715-0717|2055:0705-0707|2056:0623-0625|2057:0612-0614|2058:0601-0603|2059:0522-0524|2060:0510-0512","estimated":"y|2010:1116-1118|2011:1106-1108|2012:1026-1028|2013:1015-1017|2014:1004-1006|2015:0923-0925|2016:0911-0913|2017:0901-0903|2018:0821-0823|2026:0527-0529|2027:0516-0518|2028:0505-0507|2029:0424-0426|2030:0413-0415|2031:0402-0404|2032:0322-0324|2033:0311-0313|2034:0301-0303|2035:0218-0220|2036:0207-0209|2037:0126-0128|2038:0116-0118|2039:0105-0107,1226-1228|2040:1214-1216|2041:1204-1206|2042:1123-1125|2043:1112-1114|2044:1031-1102|2045:1021-1023|2046:1010-1012|2047:0930-1002|2048:0919-0921|2049:0908-0910|2050:0828-0830|2051:0817-0819|2052:0805-0807|2053:0726-0728|2054:0715-0717|2055:0705-0707|2056:0623-0625|2057:0612-0614|2058:0601-0603|2059:0522-0524|2060:0510-0512"},{"id":"labor_day","name":"عيد العمال","en":"Labor Day","category":"public","dates":"d|0501:2010-2060"},{"id":"national_day","name":"عاشوراء (تقديري)","en":"National Day","category":"public","dates":"d|1216:2010-2060|1217:2010-2060"}]} diff --git a/app/Support/Holidays/data/BI.json b/app/Support/Holidays/data/BI.json new file mode 100644 index 000000000..103429bcd --- /dev/null +++ b/app/Support/Holidays/data/BI.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"BI","language":"fr_BI","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Jour de l'an","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051"},{"id":"eid_al_fitr","name":"Aid-El-Fithr","en":"Eid al-Fitr","category":"public","dates":"y|2010:0910|2011:0830|2012:0819-0820|2013:0808|2014:0729|2015:0718|2016:0707|2017:0626|2018:0615|2019:0604|2020:0524-0525|2021:0513|2022:0502|2023:0421|2024:0410|2025:0330-0331|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102-0103,1223|2034:1212|2035:1201|2036:1119|2037:1108-1109|2038:1029|2039:1019|2040:1007-1008|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712-0713|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416-0417|2057:0405|2058:0325|2059:0315|2060:0304","estimated":"y|2010:0910|2011:0830|2012:0819|2013:0808|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304"},{"id":"eid_al_adha","name":"Aid-El-Adha","en":"Eid al-Adha","category":"public","dates":"y|2010:1116|2011:1106-1107|2012:1026|2013:1015|2014:1005-1006|2015:0924|2016:0913|2017:0902|2018:0821|2019:0811-0812|2020:0731|2021:0720|2022:0709|2023:0628|2024:0616-0617|2025:0606|2026:0527|2027:0516-0517|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218-0219|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123-1124|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828-0829|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510","estimated":"y|2010:1116|2011:1106|2012:1026|2013:1015|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510"},{"id":"unity_day","name":"Fête de l'Unité","en":"Unity Day","category":"public","dates":"d|0205:2010-2060|0206:2012,2017,2023,2034,2040,2045,2051"},{"id":"commemoration_of_the_assassination_of_president_","name":"Commémoration de l'Assassinat du Président Cyprien Ntaryamira","en":"Commemoration of the Assassination of President Cyprien Ntaryamira","category":"public","dates":"d|0406:2010-2060|0407:2014,2025,2031,2036,2042,2053,2059|1021:2010-2060|1022:2012,2018,2029,2035,2040,2046,2057"},{"id":"international_labor_day","name":"Fête Internationale du Travail","en":"International Labor Day","category":"public","dates":"d|0501:2010-2060|0502:2011,2016,2022,2033,2039,2044,2050"},{"id":"ascension_day","name":"Jour de l'Ascension","en":"Ascension Day","category":"public","dates":"d|0503:2035,2046|0505:2016|0506:2027,2032|0507:2043,2054|0508:2059|0509:2013,2024|0510:2018,2029,2040|0511:2051,2056|0513:2010,2021|0514:2015,2026,2037,2048|0515:2042,2053|0517:2012|0518:2023,2034,2045|0519:2039,2050|0521:2020|0522:2031,2036|0523:2047,2058|0525:2017,2028|0526:2022,2033,2044|0527:2049,2055,2060|0529:2014,2025|0530:2019,2030,2041,2052|0531:2057|0602:2011|0603:2038"},{"id":"national_day_of_patriotism_and_commemoration_of_","name":"Journée Nationale du Patriotisme et Commémoration de la Mort du Président Pierre Nkurunziza","en":"National Day of Patriotism and Commemoration of the Death of President Pierre Nkurunziza","category":"public","dates":"d|0608:2022-2060|0609:2025,2031,2036,2042,2053,2059"},{"id":"independence_day","name":"Anniversaire de l'Indépendance","en":"Independence Day","category":"public","dates":"d|0701:2010-2060|0702:2012,2018,2029,2035,2040,2046,2057"},{"id":"assumption_day","name":"Assomption","en":"Assumption Day","category":"public","dates":"d|0815:2010-2060|0816:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"commemoration_of_the_assassination_of_national_h","name":"Commémoration de l'Assassinat du Héros National, le Prince Louis Rwagasore","en":"Commemoration of the Assassination of National Hero, Prince Louis Rwagasore","category":"public","dates":"d|1013:2010-2060|1014:2013,2019,2024,2030,2041,2047,2052,2058"},{"id":"all_saints_day","name":"Toussaint","en":"All Saints' Day","category":"public","dates":"d|1101:2010-2060|1102:2015,2020,2026,2037,2043,2048,2054"},{"id":"christmas_day","name":"Noël","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060|1226:2011,2016,2022,2033,2039,2044,2050"}]} diff --git a/app/Support/Holidays/data/BJ.json b/app/Support/Holidays/data/BJ.json new file mode 100644 index 000000000..27a307475 --- /dev/null +++ b/app/Support/Holidays/data/BJ.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"BJ","language":"fr_BJ","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Fête du Nouvel An","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"eid_al_fitr","name":"Jour du Ramadan","en":"Eid al-Fitr","category":"public","dates":"y|2010:0910|2011:0830|2012:0819|2013:0808|2014:0728|2015:0717|2016:0706|2017:0625|2018:0615|2019:0604|2020:0524|2021:0513|2022:0502|2023:0421|2024:0410|2025:0330|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304","estimated":"y|2010:0910|2011:0830|2012:0819|2013:0808|2014:0728|2015:0717|2016:0706|2017:0625|2018:0615|2019:0604|2020:0524|2021:0513|2022:0502|2023:0421|2024:0410|2025:0330|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304"},{"id":"prophet_s_birthday","name":"Journée Maouloud","en":"Prophet's Birthday","category":"public","dates":"y|2010:0226|2011:0215|2012:0204|2013:0124|2014:0113|2015:0103,1223|2016:1211|2017:1130|2018:1120|2019:1109|2020:1029|2021:1018|2022:1008|2023:0927|2024:0915|2025:0904|2026:0825|2027:0814|2028:0803|2029:0724|2030:0713|2031:0702|2032:0620|2033:0609|2034:0530|2035:0520|2036:0508|2037:0428|2038:0417|2039:0406|2040:0325|2041:0315|2042:0304|2043:0222|2044:0211|2045:0130|2046:0119|2047:0108,1229|2048:1218|2049:1207|2050:1126|2051:1116|2052:1104|2053:1024|2054:1013|2055:1003|2056:0922|2057:0911|2058:0831|2059:0820|2060:0808","estimated":"y|2010:0226|2011:0215|2012:0204|2013:0124|2014:0113|2015:0103,1223|2016:1211|2017:1130|2018:1120|2019:1109|2020:1029|2021:1018|2022:1008|2023:0927|2024:0915|2025:0904|2026:0825|2027:0814|2028:0803|2029:0724|2030:0713|2031:0702|2032:0620|2033:0609|2034:0530|2035:0520|2036:0508|2037:0428|2038:0417|2039:0406|2040:0325|2041:0315|2042:0304|2043:0222|2044:0211|2045:0130|2046:0119|2047:0108,1229|2048:1218|2049:1207|2050:1126|2051:1116|2052:1104|2053:1024|2054:1013|2055:1003|2056:0922|2057:0911|2058:0831|2059:0820|2060:0808"},{"id":"eid_al_adha","name":"Jour de la Tabaski","en":"Eid al-Adha","category":"public","dates":"y|2010:1116|2011:1106|2012:1026|2013:1015|2014:1004|2015:0923|2016:0911|2017:0901|2018:0821|2019:0811|2020:0731|2021:0720|2022:0709|2023:0628|2024:0616|2025:0606|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510","estimated":"y|2010:1116|2011:1106|2012:1026|2013:1015|2014:1004|2015:0923|2016:0911|2017:0901|2018:0821|2019:0811|2020:0731|2021:0720|2022:0709|2023:0628|2024:0616|2025:0606|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510"},{"id":"vodoun_festival","name":"Fête annuelle des religions traditionnelles","en":"Vodoun Festival","category":"public","dates":"d|0107:2027,2038,2044,2049,2055|0108:2026-2027,2032,2037-2038,2043-2044,2049,2054-2055,2060|0109:2025-2026,2031-2032,2037,2042-2043,2048,2053-2054,2059-2060|0110:2010-2025,2030-2031,2036,2041-2042,2047-2048,2053,2058-2059|0111:2029-2030,2035-2036,2041,2046-2047,2052,2057-2058|0112:2029,2034-2035,2040,2045-2046,2051-2052,2057|0113:2028,2033-2034,2039-2040,2045,2050-2051,2056|0114:2028,2033,2039,2050,2056"},{"id":"easter_monday","name":"Lundi de Pâques","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"labor_day","name":"Fête du Travail","en":"Labor Day","category":"public","dates":"d|0501:2010-2060"},{"id":"ascension_day","name":"Jour de l'Ascension","en":"Ascension Day","category":"public","dates":"d|0503:2035,2046|0505:2016|0506:2027,2032|0507:2043,2054|0508:2059|0509:2013,2024|0510:2018,2029,2040|0511:2051,2056|0513:2010,2021|0514:2015,2026,2037,2048|0515:2042,2053|0517:2012|0518:2023,2034,2045|0519:2039,2050|0521:2020|0522:2031,2036|0523:2047,2058|0525:2017,2028|0526:2022,2033,2044|0527:2049,2055,2060|0529:2014,2025|0530:2019,2030,2041,2052|0531:2057|0602:2011|0603:2038"},{"id":"pentecost_monday","name":"Lundi de Pentecôte","en":"Pentecost Monday","category":"public","dates":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038"},{"id":"national_day","name":"Fête Nationale","en":"National Day","category":"public","dates":"d|0801:2010-2060"},{"id":"assumption_day","name":"Jour de l'Assomption","en":"Assumption Day","category":"public","dates":"d|0815:2010-2060"},{"id":"all_saints_day","name":"La Toussaint","en":"All Saints' Day","category":"public","dates":"d|1101:2010-2060"},{"id":"christmas_day","name":"Jour de Noël","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"}]} diff --git a/app/Support/Holidays/data/BL.json b/app/Support/Holidays/data/BL.json new file mode 100644 index 000000000..5ea102105 --- /dev/null +++ b/app/Support/Holidays/data/BL.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"BL","language":"fr","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Jour de l'an","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"easter_monday","name":"Lundi de Pâques","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"labor_day","name":"Fête du Travail","en":"Labor Day","category":"public","dates":"d|0501:2010-2060"},{"id":"ascension_day","name":"Ascension","en":"Ascension Day","category":"public","dates":"d|0503:2035,2046|0505:2016|0506:2027,2032|0507:2043,2054|0508:2059|0509:2013,2024|0510:2018,2029,2040|0511:2051,2056|0513:2010,2021|0514:2015,2026,2037,2048|0515:2042,2053|0517:2012|0518:2023,2034,2045|0519:2039,2050|0521:2020|0522:2031,2036|0523:2047,2058|0525:2017,2028|0526:2022,2033,2044|0527:2049,2055,2060|0529:2014,2025|0530:2019,2030,2041,2052|0531:2057|0602:2011|0603:2038"},{"id":"victory_day","name":"Fête de la Victoire","en":"Victory Day","category":"public","dates":"d|0508:2010-2060"},{"id":"pentecost_monday","name":"Lundi de Pentecôte","en":"Pentecost Monday","category":"public","dates":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038"},{"id":"national_day","name":"Fête nationale","en":"National Day","category":"public","dates":"d|0714:2010-2060"},{"id":"assumption_day","name":"Assomption","en":"Assumption Day","category":"public","dates":"d|0815:2010-2060"},{"id":"abolition_of_slavery","name":"Abolition de l'esclavage","en":"Abolition of Slavery","category":"public","dates":"d|1009:2012-2060"},{"id":"all_saints_day","name":"Toussaint","en":"All Saints' Day","category":"public","dates":"d|1101:2010-2060"},{"id":"armistice_day","name":"Armistice","en":"Armistice Day","category":"public","dates":"d|1111:2010-2060"},{"id":"christmas_day","name":"Noël","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"}]} diff --git a/app/Support/Holidays/data/BM.json b/app/Support/Holidays/data/BM.json new file mode 100644 index 000000000..a03ea5d06 --- /dev/null +++ b/app/Support/Holidays/data/BM.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"BM","language":"en_BM","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051|0103:2011,2022,2028,2033,2039,2050,2056"},{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"bermuda_day","name":"Bermuda Day","en":"Bermuda Day","category":"public","dates":"d|0522:2026,2037,2043,2048,2054|0523:2025,2031,2036,2042,2053,2059|0524:2010-2017,2019,2024,2030,2041,2047,2052,2058|0525:2015,2018,2029,2035,2040,2046,2057|0526:2014,2023,2028,2034,2045,2051,2056|0527:2022,2033,2039,2044,2050|0528:2021,2027,2032,2038,2049,2055,2060|0529:2020"},{"id":"national_heroes_day","name":"National Heroes Day","en":"National Heroes Day","category":"public","dates":"d|0615:2015,2020,2026,2037,2043,2048,2054|0616:2014,2025,2031,2036,2042,2053,2059|0617:2013,2019,2024,2030,2041,2047,2052,2058|0618:2012,2018,2029,2035,2040,2046,2057|0619:2017,2023,2028,2034,2045,2051,2056|0620:2011,2016,2022,2033,2039,2044,2050|0621:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"emancipation_day","name":"Emancipation Day","en":"Emancipation Day","category":"public","dates":"d|0728:2011,2016,2022,2033,2039,2044,2050|0729:2010,2021,2027,2032,2038,2049,2055,2060|0730:2015,2020,2026,2037,2043,2048,2054|0731:2014,2025,2031,2036,2042,2053,2059|0801:2013,2019,2024,2030,2041,2047,2052,2058|0802:2012,2018,2029,2035,2040,2046,2057|0803:2017,2023,2028,2034,2045,2051,2056"},{"id":"mary_prince_day","name":"Mary Prince Day","en":"Mary Prince Day","category":"public","dates":"d|0729:2022,2033,2039,2044,2050|0730:2021,2027,2032,2038,2049,2055,2060|0731:2020,2026,2037,2043,2048,2054|0801:2025,2031,2036,2042,2053,2059|0802:2024,2030,2041,2047,2052,2058|0803:2029,2035,2040,2046,2057|0804:2023,2028,2034,2045,2051,2056"},{"id":"labor_day","name":"Labour Day","en":"Labor Day","category":"public","dates":"d|0901:2014,2025,2031,2036,2042,2053,2059|0902:2013,2019,2024,2030,2041,2047,2052,2058|0903:2012,2018,2029,2035,2040,2046,2057|0904:2017,2023,2028,2034,2045,2051,2056|0905:2011,2016,2022,2033,2039,2044,2050|0906:2010,2021,2027,2032,2038,2049,2055,2060|0907:2015,2020,2026,2037,2043,2048,2054"},{"id":"remembrance_day","name":"Remembrance Day","en":"Remembrance Day","category":"public","dates":"d|1111:2010-2060|1112:2012,2018,2029,2035,2040,2046,2057|1113:2017,2023,2028,2034,2045,2051,2056"},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060|1227:2010-2011,2016,2021-2022,2027,2032-2033,2038-2039,2044,2049-2050,2055,2060"},{"id":"boxing_day","name":"Boxing Day","en":"Boxing Day","category":"public","dates":"d|1226:2010-2060|1228:2010,2015,2020-2021,2026-2027,2032,2037-2038,2043,2048-2049,2054-2055,2060"}]} diff --git a/app/Support/Holidays/data/BN.json b/app/Support/Holidays/data/BN.json new file mode 100644 index 000000000..d579d472d --- /dev/null +++ b/app/Support/Holidays/data/BN.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"BN","language":"ms","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"anniversary_of_the_revelation_of_the_quran","name":"Hari Nuzul Al-Quran","en":"Anniversary of the revelation of the Quran","category":"public","dates":"y|2010:0827-0828|2011:0817|2012:0806|2013:0726-0727|2014:0715-0716|2015:0704|2016:0622|2017:0612|2018:0602|2019:0522|2020:0510-0511|2021:0429|2022:0419|2023:0408|2024:0328|2025:0318|2026:0307|2027:0224|2028:0213-0214|2029:0201|2030:0122|2031:0111|2032:0101,0103,1220|2033:1210|2034:1129|2035:1118-1119|2036:1106|2037:1026|2038:1016|2039:1006|2040:0924|2041:0913-0914|2042:0902|2043:0823-0824|2044:0811|2045:0801|2046:0722-0723|2047:0711|2048:0629|2049:0618-0619|2050:0607|2051:0528-0529|2052:0517-0518|2053:0506|2054:0426-0427|2055:0415|2056:0403|2057:0323-0324|2058:0312|2059:0302-0303|2060:0220-0221","estimated":"y|2027:0224|2028:0213|2029:0201|2030:0122|2031:0111|2032:0101,1220|2033:1210|2034:1129|2035:1118|2036:1106|2037:1026|2038:1016|2039:1006|2040:0924|2041:0913|2042:0902|2043:0823|2044:0811|2045:0801|2046:0722|2047:0711|2048:0629|2049:0618|2050:0607|2051:0528|2052:0517|2053:0506|2054:0426|2055:0415|2056:0403|2057:0323|2058:0312|2059:0302|2060:0220"},{"id":"new_year_s_day","name":"Awal Tahun Masihi","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2010,2012,2016-2017,2021,2023,2027,2034,2038,2040,2044-2045,2049,2051,2055|0103:2032"},{"id":"isra_and_mi_raj","name":"Israk dan Mikraj","en":"Isra' and Mi'raj","category":"public","dates":"y|2010:0710|2011:0629|2012:0617-0618|2013:0606|2014:0527|2015:0516|2016:0505|2017:0424|2018:0414|2019:0403|2020:0322-0323|2021:0311|2022:0228|2023:0218|2024:0208|2025:0127|2026:0117|2027:0106,1226-1227|2028:1214|2029:1204|2030:1123|2031:1113|2032:1101|2033:1021-1022|2034:1010|2035:0930-1001|2036:0918|2037:0908|2038:0829-0830|2039:0818|2040:0806|2041:0726-0727|2042:0715-0716|2043:0705-0706|2044:0624-0625|2045:0613|2046:0603-0604|2047:0523|2048:0511|2049:0430-0501|2050:0420|2051:0409-0410|2052:0329-0330|2053:0318|2054:0307|2055:0224|2056:0213-0214|2057:0202-0203|2058:0123|2059:0112-0113|2060:0102-0103,1221","estimated":"y|2027:0106,1226|2028:1214|2029:1204|2030:1123|2031:1113|2032:1101|2033:1021|2034:1010|2035:0930|2036:0918|2037:0908|2038:0829|2039:0818|2040:0806|2041:0726|2042:0715|2043:0705|2044:0624|2045:0613|2046:0603|2047:0523|2048:0511|2049:0430|2050:0420|2051:0409|2052:0329|2053:0318|2054:0307|2055:0224|2056:0213|2057:0202|2058:0123|2059:0112|2060:0102,1221"},{"id":"eid_al_fitr","name":"Hari Raya Aidil Fitri","en":"Eid al-Fitr","category":"public","dates":"y|2010:0910-0911,0913|2011:0830-0831|2012:0819-0822|2013:0808-0810,0812|2014:0728-0730|2015:0717-0721|2016:0706-0709|2017:0625-0628|2018:0615-0619|2019:0605-0608|2020:0524-0527|2021:0513-0515,0517|2022:0502-0504|2023:0422-0425|2024:0410-0413|2025:0331-0402|2026:0321-0324|2027:0310-0313|2028:0227-0301|2029:0215-0217,0219|2030:0204-0206|2031:0125-0128|2032:0114-0117|2033:0103-0105,1223-1226,1228|2034:1212-1214|2035:1201-1204|2036:1119-1122|2037:1109-1111|2038:1030-1102|2039:1019-1022|2040:1008-1010|2041:0927-1001|2042:0916-0918|2043:0905-0908|2044:0825-0827,0829|2045:0814-0816|2046:0804-0807|2047:0724-0727|2048:0713-0716|2049:0702-0706|2050:0621-0623|2051:0610-0613|2052:0530-0601,0604|2053:0520-0522|2054:0509-0512|2055:0428-0501|2056:0416-0419|2057:0405-0407,0409|2058:0326-0328|2059:0315-0318|2060:0304-0306,0308","estimated":"y|2027:0310-0312|2028:0227-0229|2029:0215-0217|2030:0204-0206|2031:0125-0127|2032:0114-0116|2033:0103-0105,1223-1225|2034:1212-1214|2035:1201-1203|2036:1119-1121|2037:1109-1111|2038:1030-1101|2039:1019-1021|2040:1008-1010|2041:0927-0929|2042:0916-0918|2043:0905-0907|2044:0825-0827|2045:0814-0816|2046:0804-0806|2047:0724-0726|2048:0713-0715|2049:0702-0704|2050:0621-0623|2051:0610-0612|2052:0530-0601|2053:0520-0522|2054:0509-0511|2055:0428-0430|2056:0416-0418|2057:0405-0407|2058:0326-0328|2059:0315-0317|2060:0304-0306"},{"id":"prophet_s_birthday","name":"Maulidur Rasul","en":"Prophet's Birthday","category":"public","dates":"y|2010:0226-0227|2011:0215|2012:0205-0206|2013:0124|2014:0114|2015:0103,1224|2016:1212|2017:1201-1202|2018:1120|2019:1109|2020:1029|2021:1019|2022:1008|2023:0928|2024:0916|2025:0905-0906|2026:0825|2027:0815-0816|2028:0804-0805|2029:0724|2030:0713|2031:0702|2032:0621|2033:0610-0611|2034:0530|2035:0520-0521|2036:0509-0510|2037:0428|2038:0417|2039:0406|2040:0325-0326|2041:0315-0316|2042:0305|2043:0222,0224|2044:0212-0213|2045:0131|2046:0120|2047:0109,1229-1230|2048:1218-1219|2049:1208|2050:1127-1128|2051:1116|2052:1104|2053:1024-1025|2054:1014|2055:1003-1004|2056:0922-0923|2057:0912|2058:0901-0902|2059:0821|2060:0809","estimated":"y|2027:0815|2028:0804|2029:0724|2030:0713|2031:0702|2032:0621|2033:0610|2034:0530|2035:0520|2036:0509|2037:0428|2038:0417|2039:0406|2040:0325|2041:0315|2042:0305|2043:0222|2044:0212|2045:0131|2046:0120|2047:0109,1229|2048:1218|2049:1208|2050:1127|2051:1116|2052:1104|2053:1024|2054:1014|2055:1003|2056:0922|2057:0912|2058:0901|2059:0821|2060:0809"},{"id":"islamic_new_year","name":"Awal Tahun Hijrah","en":"Islamic New Year","category":"public","dates":"y|2010:1207|2011:1127-1128|2012:1115|2013:1105|2014:1025|2015:1014|2016:1002-1003|2017:0922-0923|2018:0911|2019:0901-0902|2020:0820|2021:0810|2022:0730|2023:0719|2024:0707-0708|2025:0627-0628|2026:0617|2027:0606-0607|2028:0526-0527|2029:0515|2030:0504|2031:0423|2032:0412|2033:0401-0402|2034:0322|2035:0311-0312|2036:0229-0301|2037:0217|2038:0206|2039:0126|2040:0116|2041:0104-0105,1225-1226|2042:1214-1215|2043:1203|2044:1121|2045:1110-1111|2046:1031|2047:1021|2048:1009-1010|2049:0929|2050:0918-0919|2051:0907|2052:0826|2053:0816|2054:0805|2055:0726|2056:0714,0717|2057:0704|2058:0623-0624|2059:0612|2060:0531-0601","estimated":"y|2027:0606|2028:0526|2029:0515|2030:0504|2031:0423|2032:0412|2033:0401|2034:0322|2035:0311|2036:0229|2037:0217|2038:0206|2039:0126|2040:0116|2041:0104,1225|2042:1214|2043:1203|2044:1121|2045:1110|2046:1031|2047:1021|2048:1009|2049:0929|2050:0918|2051:0907|2052:0826|2053:0816|2054:0805|2055:0726|2056:0714|2057:0704|2058:0623|2059:0612|2060:0531"},{"id":"eid_al_adha","name":"Hari Raya Aidil Adha","en":"Eid al-Adha","category":"public","dates":"y|2010:1116|2011:1106-1107|2012:1026-1027|2013:1015|2014:1005-1006|2015:0924|2016:0912|2017:0901-0902|2018:0822|2019:0811-0812|2020:0731-0801|2021:0720|2022:0709|2023:0629|2024:0617|2025:0607|2026:0527|2027:0517|2028:0505-0506|2029:0425|2030:0414-0415|2031:0403|2032:0322|2033:0312|2034:0301|2035:0219|2036:0208-0209|2037:0127|2038:0116|2039:0106,1226|2040:1215|2041:1204|2042:1123-1124|2043:1112|2044:1101|2045:1021|2046:1011|2047:0930|2048:0919|2049:0908|2050:0828-0829|2051:0817|2052:0806|2053:0726|2054:0716|2055:0706|2056:0624|2057:0613|2058:0602-0603|2059:0522|2060:0511","estimated":"y|2027:0517|2028:0505|2029:0425|2030:0414|2031:0403|2032:0322|2033:0312|2034:0301|2035:0219|2036:0208|2037:0127|2038:0116|2039:0106,1226|2040:1215|2041:1204|2042:1123|2043:1112|2044:1101|2045:1021|2046:1011|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0806|2053:0726|2054:0716|2055:0706|2056:0624|2057:0613|2058:0602|2059:0522|2060:0511"},{"id":"first_day_of_ramadan","name":"Hari Pertama Berpuasa","en":"First Day of Ramadan","category":"public","dates":"y|2010:0811|2011:0801|2012:0721|2013:0710|2014:0629-0630|2015:0618|2016:0606|2017:0527|2018:0517|2019:0506|2020:0424-0425|2021:0413|2022:0403-0404|2023:0323|2024:0312|2025:0302-0303|2026:0219|2027:0208|2028:0128-0129|2029:0116|2030:0106-0107,1226|2031:1216|2032:1204|2033:1124|2034:1113|2035:1102-1103|2036:1021|2037:1010|2038:0930|2039:0920|2040:0908|2041:0828|2042:0817-0818|2043:0807-0808|2044:0726|2045:0716-0717|2046:0706-0707|2047:0625|2048:0613|2049:0602|2050:0522-0523|2051:0512-0513|2052:0501|2053:0420-0421|2054:0410-0411|2055:0330|2056:0318|2057:0307|2058:0224-0225|2059:0214-0215|2060:0204","estimated":"y|2027:0208|2028:0128|2029:0116|2030:0106,1226|2031:1216|2032:1204|2033:1124|2034:1113|2035:1102|2036:1021|2037:1010|2038:0930|2039:0920|2040:0908|2041:0828|2042:0817|2043:0807|2044:0726|2045:0716|2046:0706|2047:0625|2048:0613|2049:0602|2050:0522|2051:0512|2052:0501|2053:0420|2054:0410|2055:0330|2056:0318|2057:0307|2058:0224|2059:0214|2060:0204"},{"id":"lunar_new_year","name":"Tahun Baru Cina","en":"Lunar New Year","category":"public","dates":"d|0122:2023,2042|0123:2012,2023,2031,2050|0124:2039,2050,2058|0125:2020|0126:2028,2047|0128:2017,2036,2055|0129:2025|0130:2044|0131:2014,2033|0201:2014,2022,2041,2052|0202:2041,2049,2060|0203:2011,2030|0204:2038,2057|0205:2019,2057|0206:2027,2046|0207:2030|0208:2016,2035,2054|0209:2054|0210:2013,2024,2043|0211:2013,2032,2051|0212:2021,2040,2059|0213:2021,2029,2040|0214:2010,2048|0215:2010,2037,2048,2056|0216:2018,2037|0217:2018,2026,2045|0218:2045|0219:2015,2034,2053|0220:2034"},{"id":"national_day","name":"Hari Kebangsaan","en":"National Day","category":"public","dates":"d|0223:2010-2060|0224:2014,2018,2020,2024-2025,2029,2031,2035,2042,2046,2048,2052-2053,2057,2059"},{"id":"armed_forces_day","name":"Hari Angkatan Bersenjata Diraja Brunei","en":"Armed Forces Day","category":"public","dates":"d|0531:2010-2060|0601:2013,2015,2019-2020,2024,2026,2030,2037,2041,2043,2047-2048,2054,2058,2060|0603:2052"},{"id":"sultan_hassanal_bolkiah_s_birthday","name":"Hari Keputeraan KDYMM Sultan Brunei","en":"Sultan Hassanal Bolkiah's Birthday","category":"public","dates":"d|0715:2010-2060|0716:2011-2012,2014,2016,2018,2022,2029,2033,2035,2039-2040,2042,2044,2046,2048,2050,2057"},{"id":"christmas_day","name":"Hari Natal","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060|1226:2011,2015-2016,2020,2022,2026,2037,2041,2043-2044,2048,2050,2054|1227:2033,2039"}]} diff --git a/app/Support/Holidays/data/BO.json b/app/Support/Holidays/data/BO.json new file mode 100644 index 000000000..3612a6e21 --- /dev/null +++ b/app/Support/Holidays/data/BO.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"BO","language":"es","years":[2010,2060],"subdivisions":{"B":"El Beni","C":"Cochabamba","H":"Chuquisaca","L":"La Paz","N":"Pando","O":"Oruro","P":"Potosí","S":"Santa Cruz","T":"Tarija"},"holidays":[{"id":"new_year_s_day","name":"Año Nuevo","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051"},{"id":"plurinational_state_foundation_day","name":"Día de la Creación del Estado Plurinacional de Bolivia","en":"Plurinational State Foundation Day","category":"public","dates":"d|0122:2010-2060|0123:2012,2017,2023,2034,2040,2045,2051"},{"id":"carnival_in_oruro","name":"Carnaval de Oruro","en":"Carnival in Oruro","category":"public","regional":{"O":"d|0202:2035,2046|0205:2016,2027|0206:2032,2043,2054|0207:2059|0208:2013|0209:2018,2024,2029|0210:2040,2051|0211:2056|0212:2010,2021|0213:2015,2026,2037|0214:2042,2048,2053|0217:2012,2023,2034,2045|0218:2039,2050|0221:2020,2031|0222:2036,2047,2058|0224:2017|0225:2022,2028,2033|0226:2044,2049,2055|0227:2060|0228:2014,2025|0301:2019,2030,2041,2052|0302:2057|0304:2011|0305:2038"}},{"id":"carnival","name":"Carnaval","en":"Carnival","category":"public","dates":"d|0205:2035,2046|0206:2035,2046|0208:2016,2027|0209:2016,2027,2032,2043,2054|0210:2032,2043,2054,2059|0211:2013,2059|0212:2013,2018,2024,2029|0213:2018,2024,2029,2040,2051|0214:2040,2051,2056|0215:2010,2021,2056|0216:2010,2015,2021,2026,2037|0217:2015,2026,2037,2042,2048,2053|0218:2042,2048,2053|0220:2012,2023,2034,2045|0221:2012,2023,2034,2039,2045,2050|0222:2039,2050|0224:2020,2031|0225:2020,2031,2036,2047,2058|0226:2036,2047,2058|0227:2017|0228:2017,2022,2028,2033|0229:2028,2044|0301:2022,2033,2044,2049,2055,2060|0302:2049,2055,2060|0303:2014,2025|0304:2014,2019,2025,2030,2041,2052|0305:2019,2030,2041,2052,2057|0306:2057|0307:2011|0308:2011,2038|0309:2038"},{"id":"good_friday","name":"Viernes Santo","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"la_tablada","name":"La Tablada","en":"La Tablada","category":"public","regional":{"T":"d|0415:2010-2060"}},{"id":"labor_day","name":"Día del Trabajo","en":"Labor Day","category":"public","dates":"d|0430:2012|0501:2010-2060|0502:2011,2014,2016,2022,2033,2039,2044,2050"},{"id":"corpus_christi","name":"Corpus Christi","en":"Corpus Christi","category":"public","dates":"d|0524:2035,2046|0526:2016|0527:2027,2032|0528:2043,2054|0529:2059|0530:2013,2024|0531:2018,2029,2040|0601:2051,2056|0603:2010,2021|0604:2015,2026,2037,2048|0605:2042,2053|0607:2012|0608:2023,2034,2045|0609:2039,2050|0611:2020|0612:2031,2036|0613:2047,2058|0615:2017,2028|0616:2022,2033,2044|0617:2049,2055,2060|0619:2014,2025|0620:2019,2030,2041,2052|0621:2057|0623:2011|0624:2038"},{"id":"chuquisaca_day","name":"Día del departamento de Chuquisaca","en":"Chuquisaca Day","category":"public","regional":{"H":"d|0525:2010-2060"}},{"id":"aymara_new_year","name":"Año Nuevo Aymara Amazónico","en":"Aymara New Year","category":"public","dates":"d|0621:2010-2060|0622:2015,2020,2026,2037,2043,2048,2054"},{"id":"la_paz_day","name":"Día del departamento de La Paz","en":"La Paz Day","category":"public","regional":{"L":"d|0716:2010-2060"}},{"id":"independence_day","name":"Día de la Independencia de Bolivia","en":"Independence Day","category":"public","dates":"d|0806:2010-2060|0807:2017,2023,2028,2034,2045,2051,2056"},{"id":"cochabamba_day","name":"Día del departamento de Cochabamba","en":"Cochabamba Day","category":"public","regional":{"C":"d|0914:2010-2060"}},{"id":"santa_cruz_day","name":"Día del departamento de Santa Cruz","en":"Santa Cruz Day","category":"public","regional":{"S":"d|0924:2010-2060"}},{"id":"pando_day","name":"Día del departamento de Pando","en":"Pando Day","category":"public","regional":{"N":"d|1011:2010-2060"}},{"id":"national_dignity_day","name":"Día de la Dignidad Nacional","en":"National Dignity Day","category":"public","dates":"d|1017:2020-2060"},{"id":"all_souls_day","name":"Día de Todos los Difuntos","en":"All Souls' Day","category":"public","dates":"d|1102:2010-2060|1103:2014"},{"id":"potosi_day","name":"Día del departamento de Potosí","en":"Potosí Day","category":"public","regional":{"P":"d|1110:2010-2060"}},{"id":"beni_day","name":"Día del departamento de Beni","en":"Beni Day","category":"public","regional":{"B":"d|1118:2010-2060"}},{"id":"christmas_day","name":"Navidad","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060|1226:2011,2016,2022,2033,2039,2044,2050"}]} diff --git a/app/Support/Holidays/data/BQ.json b/app/Support/Holidays/data/BQ.json new file mode 100644 index 000000000..90605a213 --- /dev/null +++ b/app/Support/Holidays/data/BQ.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"BQ","language":"nl","years":[2010,2060],"subdivisions":{"BON":"Bonaire","SAB":"Saba","STA":"Sint Eustatius"},"holidays":[{"id":"new_year_s_day","name":"Nieuwjaarsdag","en":"New Year's Day","category":"public","dates":"d|0101:2011-2060"},{"id":"good_friday","name":"Goede vrijdag","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_sunday","name":"Eerste paasdag","en":"Easter Sunday","category":"public","dates":"d|0325:2035,2046|0327:2016|0328:2027,2032|0329:2043,2054|0330:2059|0331:2013,2024|0401:2018,2029,2040|0402:2051,2056|0404:2021|0405:2015,2026,2037,2048|0406:2042,2053|0408:2012|0409:2023,2034,2045|0410:2039,2050|0412:2020|0413:2031,2036|0414:2047,2058|0416:2017,2028|0417:2022,2033,2044|0418:2049,2055,2060|0420:2014,2025|0421:2019,2030,2041,2052|0422:2057|0424:2011|0425:2038"},{"id":"easter_monday","name":"Tweede paasdag","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"king_s_day","name":"Koningsdag","en":"King's Day","category":"public","dates":"d|0426:2014,2025,2031,2036,2042,2053,2059|0427:2015-2024,2026-2030,2032-2035,2037-2041,2043-2052,2054-2058,2060"},{"id":"rincon_day","name":"Rincondag","en":"Rincon Day","category":"public","regional":{"BON":"d|0430:2020-2060"}},{"id":"labor_day","name":"Dag van de Arbeid","en":"Labor Day","category":"public","dates":"d|0501:2011-2060"},{"id":"ascension_day","name":"Hemelvaartsdag","en":"Ascension Day","category":"public","dates":"d|0503:2035,2046|0505:2016|0506:2027,2032|0507:2043,2054|0508:2059|0509:2013,2024|0510:2018,2029,2040|0511:2051,2056|0513:2021|0514:2015,2026,2037,2048|0515:2042,2053|0517:2012|0518:2023,2034,2045|0519:2039,2050|0521:2020|0522:2031,2036|0523:2047,2058|0525:2017,2028|0526:2022,2033,2044|0527:2049,2055,2060|0529:2014,2025|0530:2019,2030,2041,2052|0531:2057|0602:2011|0603:2038"},{"id":"pentecost","name":"Eerste pinksterdag","en":"Pentecost","category":"public","dates":"d|0513:2035,2046|0515:2016|0516:2027,2032|0517:2043,2054|0518:2059|0519:2013,2024|0520:2018,2029,2040|0521:2051,2056|0523:2021|0524:2015,2026,2037,2048|0525:2042,2053|0527:2012|0528:2023,2034,2045|0529:2039,2050|0531:2020|0601:2031,2036|0602:2047,2058|0604:2017,2028|0605:2022,2033,2044|0606:2049,2055,2060|0608:2014,2025|0609:2019,2030,2041,2052|0610:2057|0612:2011|0613:2038"},{"id":"emancipation_day","name":"Emancipatiedag","en":"Emancipation Day","category":"public","regional":{"STA":"d|0701:2022-2060"}},{"id":"carnival_monday","name":"Dag na de carnavalsoptocht","en":"Carnival Monday","category":"public","regional":{"SAB":"d|0725:2011,2016,2022,2033,2039,2044,2050|0726:2021,2027,2032,2038,2049,2055,2060|0727:2015,2020,2026,2037,2043,2048,2054|0728:2014,2025,2031,2036,2042,2053,2059|0729:2013,2019,2024,2030,2041,2047,2052,2058|0730:2012,2018,2029,2035,2040,2046,2057|0731:2017,2023,2028,2034,2045,2051,2056"}},{"id":"bonaire_day","name":"Bonairedag","en":"Bonaire Day","category":"public","regional":{"BON":"d|0906:2011-2060"}},{"id":"statia_day","name":"Statiadag","en":"Statia Day","category":"public","regional":{"STA":"d|1116:2011-2060"}},{"id":"saba_day","name":"Sabadag","en":"Saba Day","category":"public","regional":{"SAB":"d|1201:2017,2023,2028,2034,2045,2051,2056|1202:2011,2016,2022,2033,2039,2044,2050|1203:2021,2027,2032,2038,2049,2055,2060|1204:2015,2020,2026,2037,2043,2048,2054|1205:2014,2025,2031,2036,2042,2053,2059|1206:2013,2019,2024,2030,2041,2047,2052,2058|1207:2012,2018,2029,2035,2040,2046,2057"}},{"id":"christmas_day","name":"Eerste kerstdag","en":"Christmas Day","category":"public","dates":"d|1225:2011-2060"},{"id":"second_day_of_christmas","name":"Tweede kerstdag","en":"Second Day of Christmas","category":"public","dates":"d|1226:2011-2060"}]} diff --git a/app/Support/Holidays/data/BR.json b/app/Support/Holidays/data/BR.json new file mode 100644 index 000000000..0c30eac29 --- /dev/null +++ b/app/Support/Holidays/data/BR.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"BR","language":"pt_BR","years":[2010,2060],"subdivisions":{"AC":"Acre","AL":"Alagoas","AM":"Amazonas","AP":"Amapá","BA":"Bahia","CE":"Ceará","DF":"Distrito Federal","ES":"Espírito Santo","GO":"Goiás","MA":"Maranhão","MG":"Minas Gerais","MS":"Mato Grosso do Sul","MT":"Mato Grosso","PA":"Pará","PB":"Paraíba","PE":"Pernambuco","PI":"Piauí","PR":"Paraná","RJ":"Rio de Janeiro","RN":"Rio Grande do Norte","RO":"Rondônia","RR":"Roraima","RS":"Rio Grande do Sul","SC":"Santa Catarina","SE":"Sergipe","SP":"São Paulo","TO":"Tocantins","São Paulo Capital":"São Paulo Capital"},"holidays":[{"id":"universal_fraternization_day","name":"Confraternização Universal","en":"Universal Fraternization Day","category":"public","dates":"d|0101:2010-2060"},{"id":"state_creation_day","name":"Criação do Estado","en":"State Creation Day","category":"public","regional":{"MS":"d|1011:2010-2060","RO":"d|0104:2010-2060","RR":"d|1005:2010-2060","TO":"d|1005:2010-2060"}},{"id":"evangelical_day","name":"Dia do Evangélico","en":"Evangelical Day","category":"public","regional":{"AC":"d|0123:2010-2012,2015-2017,2021-2023,2026-2028,2032-2034,2037-2040,2043-2045,2049-2051,2054-2056,2060|0124:2014,2020,2025,2031,2042,2048,2053,2059|0125:2013,2019,2030,2036,2041,2047,2058|0126:2018,2024,2029,2035,2046,2052,2057","AL":"d|1130:2013-2060","DF":"d|1130:2010-2060","RO":"d|0618:2010-2060"}},{"id":"sao_paulo_city_anniversary","name":"Aniversário da Cidade de São Paulo","en":"São Paulo City Anniversary","category":"public","regional":{"São Paulo Capital":"d|0125:2010-2060"}},{"id":"carnival","name":"Carnaval","en":"Carnival","category":"public","regional":{"RJ":"d|0206:2035,2046|0209:2016,2027|0210:2032,2043,2054|0211:2059|0212:2013|0213:2018,2024,2029|0214:2040,2051|0215:2056|0216:2010,2021|0217:2015,2026,2037|0218:2042,2048,2053|0221:2012,2023,2034,2045|0222:2039,2050|0225:2020,2031|0226:2036,2047,2058|0228:2017|0229:2028|0301:2022,2033,2044|0302:2049,2055,2060|0304:2014,2025|0305:2019,2030,2041,2052|0306:2057|0308:2011|0309:2038"}},{"id":"pernambuco_revolution_of_1817","name":"Revolução Pernambucana de 1817","en":"Pernambuco Revolution of 1817","category":"public","regional":{"PE":"d|0301:2015|0302:2014|0303:2013|0304:2012|0305:2017|0306:2011,2016,2018-2060|0307:2010"}},{"id":"international_women_s_day","name":"Dia Internacional da Mulher","en":"International Women's Day","category":"public","regional":{"AC":"d|0308:2010,2013-2015,2019-2021,2024-2027,2030-2032,2036-2038,2041-2043,2047-2049,2052-2055,2058-2060|0309:2012,2018,2029,2035,2040,2046,2057|0310:2017,2023,2028,2034,2045,2051,2056|0311:2011,2016,2022,2033,2039,2044,2050"}},{"id":"autonomy_day","name":"Dia da Autonomia","en":"Autonomy Day","category":"public","regional":{"TO":"d|0318:2010-2060"}},{"id":"saint_joseph_s_day","name":"São José","en":"Saint Joseph's Day","category":"public","regional":{"AP":"d|0319:2010-2060","CE":"d|0319:2010-2060"}},{"id":"good_friday","name":"Sexta-feira Santa","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"abolition_of_slavery_in_ceara","name":"Abolição da escravidão no Ceará","en":"Abolition of slavery in Ceará","category":"public","regional":{"CE":"d|0325:2010-2060"}},{"id":"our_lady_of_penha","name":"Nossa Senhora da Penha","en":"Our Lady of Penha","category":"public","regional":{"ES":"d|0402:2035,2046|0405:2027,2032|0406:2043,2054|0407:2059|0408:2024|0409:2029,2040|0410:2051,2056|0412:2021|0413:2026,2037,2048|0414:2042,2053|0417:2023,2034,2045|0418:2039,2050|0420:2020|0421:2031,2036|0422:2047,2058|0424:2028|0425:2022,2033,2044|0426:2049,2055,2060|0428:2025|0429:2030,2041,2052|0430:2057|0503:2038"}},{"id":"founding_of_brasilia","name":"Fundação de Brasília","en":"Founding of Brasilia","category":"public","regional":{"DF":"d|0421:2010-2060"}},{"id":"tiradentes_day","name":"Tiradentes","en":"Tiradentes' Day","category":"public","dates":"d|0421:2010-2060"},{"id":"tiradentes_execution","name":"Tiradentes","en":"Tiradentes' Execution","category":"public","regional":{"MG":"d|0421:2010-2060"}},{"id":"saint_george_s_day","name":"São Jorge","en":"Saint George's Day","category":"public","regional":{"RJ":"d|0423:2010-2060"}},{"id":"worker_s_day","name":"Dia do Trabalhador","en":"Worker's Day","category":"public","dates":"d|0501:2010-2060"},{"id":"corpus_christi","name":"Corpus Christi","en":"Corpus Christi","category":"public","regional":{"São Paulo Capital":"d|0524:2035,2046|0526:2016|0527:2027,2032|0528:2043,2054|0529:2059|0530:2013,2024|0531:2018,2029,2040|0601:2051,2056|0603:2010,2021|0604:2015,2026,2037,2048|0605:2042,2053|0607:2012|0608:2023,2034,2045|0609:2039,2050|0611:2020|0612:2031,2036|0613:2047,2058|0615:2017,2028|0616:2022,2033,2044|0617:2049,2055,2060|0619:2014,2025|0620:2019,2030,2041,2052|0621:2057|0623:2011|0624:2038"}},{"id":"founding_of_acre","name":"Aniversário do Acre","en":"Founding of Acre","category":"public","regional":{"AC":"d|0615:2010-2060"}},{"id":"saint_john_s_day","name":"São João","en":"Saint John's Day","category":"public","regional":{"AL":"d|0624:2010-2060"}},{"id":"saint_peter_s_day","name":"São Pedro","en":"Saint Peter's Day","category":"public","regional":{"AL":"d|0629:2010-2060"}},{"id":"bahia_independence_day","name":"Independência da Bahia","en":"Bahia Independence Day","category":"public","regional":{"BA":"d|0702:2010-2060"}},{"id":"sergipe_political_emancipation_day","name":"Emancipação política de Sergipe","en":"Sergipe Political Emancipation Day","category":"public","regional":{"SE":"d|0708:2010-2060"}},{"id":"constitutionalist_revolution","name":"Revolução Constitucionalista","en":"Constitutionalist Revolution","category":"public","regional":{"SP":"d|0709:2010-2060","São Paulo Capital":"d|0709:2010-2060"}},{"id":"saint_james_day","name":"São Tiago","en":"Saint James' Day","category":"public","regional":{"AP":"d|0725:2012-2060"}},{"id":"foundation_of_goias_city","name":"Fundação da cidade de Goiás","en":"Foundation of Goiás city","category":"public","regional":{"GO":"d|0726:2010-2060"}},{"id":"maranhao_joining_to_independence_of_brazil","name":"Adesão do Maranhão à independência do Brasil","en":"Maranhão joining to independence of Brazil","category":"public","regional":{"MA":"d|0728:2010-2060"}},{"id":"state_founding_day","name":"Fundação do Estado","en":"State Founding Day","category":"public","regional":{"PB":"d|0805:2010-2060"}},{"id":"rio_grande_do_norte_day","name":"Dia do Rio Grande do Norte","en":"Rio Grande do Norte Day","category":"public","regional":{"RN":"d|0807:2010-2060"}},{"id":"santa_catarina_state_day","name":"Dia do Estado de Santa Catarina","en":"Santa Catarina State Day","category":"public","regional":{"SC":"d|0811:2013,2019,2024,2030,2041,2047,2052,2058|0812:2012,2018,2029,2035,2040,2046,2057|0813:2017,2023,2028,2034,2045,2051,2056|0814:2011,2016,2022,2033,2039,2044,2050|0815:2010,2021,2027,2032,2038,2049,2055,2060|0816:2015,2020,2026,2037,2043,2048,2054|0817:2014,2025,2031,2036,2042,2053,2059"}},{"id":"grao_para_joining_to_independence_of_brazil","name":"Adesão do Grão-Pará à independência do Brasil","en":"Grão-Pará joining to independence of Brazil","category":"public","regional":{"PA":"d|0815:2010-2060"}},{"id":"our_lady_of_assumption","name":"Nossa Senhora da Assunção","en":"Our Lady of Assumption","category":"public","regional":{"CE":"d|0815:2010-2060"}},{"id":"amazonia_day","name":"Dia da Amazônia","en":"Amazonia Day","category":"public","regional":{"AC":"d|0905:2010-2011,2014-2016,2020-2022,2025-2027,2031-2033,2036-2039,2042-2044,2048-2050,2053-2055,2059-2060|0906:2013,2019,2024,2030,2041,2047,2052,2058|0907:2012,2018,2029,2035,2040,2046,2057|0908:2017,2023,2028,2034,2045,2051,2056"}},{"id":"elevation_of_amazonas_to_province","name":"Elevação do Amazonas à categoria de província","en":"Elevation of Amazonas to province","category":"public","regional":{"AM":"d|0905:2010-2060"}},{"id":"independence_day","name":"Independência do Brasil","en":"Independence Day","category":"public","dates":"d|0907:2010-2060"},{"id":"our_lady_of_nativity","name":"Nossa Senhora da Natividade","en":"Our Lady of Nativity","category":"public","regional":{"TO":"d|0908:2010-2060"}},{"id":"creation_of_the_federal_territory","name":"Criação do Território Federal","en":"Creation of the Federal Territory","category":"public","regional":{"AP":"d|0913:2010-2060"}},{"id":"political_emancipation_of_alagoas","name":"Emancipação Política de Alagoas","en":"Political Emancipation of Alagoas","category":"public","regional":{"AL":"d|0916:2010-2060"}},{"id":"gaucho_day","name":"Dia do Gaúcho","en":"Gaucho Day","category":"public","regional":{"RS":"d|0920:2010-2060"}},{"id":"uruacu_and_cunhau_martyrs_day","name":"Mártires de Cunhaú e Uruaçu","en":"Uruaçu and Cunhaú Martyrs Day","category":"public","regional":{"RN":"d|1003:2010-2060"}},{"id":"our_lady_of_aparecida","name":"Nossa Senhora Aparecida","en":"Our Lady of Aparecida","category":"public","dates":"d|1012:2010-2060"},{"id":"piaui_day","name":"Dia do Piauí","en":"Piauí Day","category":"public","regional":{"PI":"d|1019:2010-2060"}},{"id":"foundation_of_goiania","name":"Pedra fundamental de Goiânia","en":"Foundation of Goiânia","category":"public","regional":{"GO":"d|1024:2010-2060"}},{"id":"all_souls_day","name":"Finados","en":"All Souls' Day","category":"public","dates":"d|1102:2010-2060"},{"id":"our_lady_of_rocio","name":"Nossa Senhora do Rocio","en":"Our Lady of Rocio","category":"public","regional":{"PR":"d|1115:2014-2060"}},{"id":"republic_proclamation_day","name":"Proclamação da República","en":"Republic Proclamation Day","category":"public","dates":"d|1115:2010-2060"},{"id":"signing_of_the_petropolis_treaty","name":"Assinatura do Tratado de Petrópolis","en":"Signing of the Petropolis Treaty","category":"public","regional":{"AC":"d|1117:2012-2014,2017-2019,2023-2025,2028-2031,2034-2036,2040-2042,2045-2047,2051-2053,2056-2059|1118:2011,2016,2022,2033,2039,2044,2050|1119:2010,2021,2027,2032,2038,2049,2055,2060|1120:2015,2020,2026,2037,2043,2048,2054"}},{"id":"national_day_of_zumbi_and_black_awareness","name":"Dia Nacional de Zumbi e da Consciência Negra","en":"National Day of Zumbi and Black Awareness","category":"public","dates":"d|1120:2024-2060"},{"id":"saint_catherine_of_alexandria_day","name":"Dia de Santa Catarina de Alexandria","en":"Saint Catherine of Alexandria Day","category":"public","regional":{"SC":"d|1125:2012,2018,2029,2035,2040,2046,2057|1126:2017,2023,2028,2034,2045,2051,2056|1127:2011,2016,2022,2033,2039,2044,2050|1128:2010,2021,2027,2032,2038,2049,2055,2060|1129:2015,2020,2026,2037,2043,2048,2054|1130:2014,2025,2031,2036,2042,2053,2059|1201:2013,2019,2024,2030,2041,2047,2052,2058"}},{"id":"christmas_day","name":"Natal","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"},{"id":"carnival_optional","name":"Carnaval","en":"Carnival","category":"optional","dates":"d|0205:2035,2046|0206:2035,2046|0208:2016,2027|0209:2016,2027,2032,2043,2054|0210:2032,2043,2054,2059|0211:2013,2059|0212:2013,2018,2024,2029|0213:2018,2024,2029,2040,2051|0214:2040,2051,2056|0215:2010,2021,2056|0216:2010,2015,2021,2026,2037|0217:2015,2026,2037,2042,2048,2053|0218:2042,2048,2053|0220:2012,2023,2034,2045|0221:2012,2023,2034,2039,2045,2050|0222:2039,2050|0224:2020,2031|0225:2020,2031,2036,2047,2058|0226:2036,2047,2058|0227:2017|0228:2017,2022,2028,2033|0229:2028,2044|0301:2022,2033,2044,2049,2055,2060|0302:2049,2055,2060|0303:2014,2025|0304:2014,2019,2025,2030,2041,2052|0305:2019,2030,2041,2052,2057|0306:2057|0307:2011|0308:2011,2038|0309:2038"},{"id":"ash_wednesday_optional","name":"Quarta-feira de Cinzas","en":"Ash Wednesday","category":"optional","dates":"d|0207:2035,2046|0210:2016,2027|0211:2032,2043,2054|0212:2059|0213:2013|0214:2018,2024,2029|0215:2040,2051|0216:2056|0217:2010,2021|0218:2015,2026,2037|0219:2042,2048,2053|0222:2012,2023,2034,2045|0223:2039,2050|0226:2020,2031|0227:2036,2047,2058|0301:2017,2028|0302:2022,2033,2044|0303:2049,2055,2060|0305:2014,2025|0306:2019,2030,2041,2052|0307:2057|0309:2011|0310:2038"},{"id":"corpus_christi_optional","name":"Corpus Christi","en":"Corpus Christi","category":"optional","dates":"d|0524:2035,2046|0526:2016|0527:2027,2032|0528:2043,2054|0529:2059|0530:2013,2024|0531:2018,2029,2040|0601:2051,2056|0603:2010,2021|0604:2015,2026,2037,2048|0605:2042,2053|0607:2012|0608:2023,2034,2045|0609:2039,2050|0611:2020|0612:2031,2036|0613:2047,2058|0615:2017,2028|0616:2022,2033,2044|0617:2049,2055,2060|0619:2014,2025|0620:2019,2030,2041,2052|0621:2057|0623:2011|0624:2038"},{"id":"public_servant_s_day_optional","name":"Dia do Servidor Público","en":"Public Servant's Day","category":"optional","dates":"d|1028:2010-2060"},{"id":"christmas_eve_optional","name":"Véspera de Natal","en":"Christmas Eve","category":"optional","dates":"d|1224:2010-2060"},{"id":"new_year_s_eve_optional","name":"Véspera de Ano-Novo","en":"New Year's Eve","category":"optional","dates":"d|1231:2010-2060"}]} diff --git a/app/Support/Holidays/data/BS.json b/app/Support/Holidays/data/BS.json new file mode 100644 index 000000000..20e6caf6f --- /dev/null +++ b/app/Support/Holidays/data/BS.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"BS","language":null,"years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051|0103:2011"},{"id":"majority_rule_day","name":"Majority Rule Day","en":"Majority Rule Day","category":"public","dates":"d|0110:2014-2060|0111:2016,2021,2027,2038,2044,2049,2055|0112:2015,2026,2032,2037,2043,2054,2060"},{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_monday","name":"Easter Monday","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"whit_monday","name":"Whit Monday","en":"Whit Monday","category":"public","dates":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038"},{"id":"randol_fawkes_labour_day","name":"Randol Fawkes Labour Day","en":"Randol Fawkes Labour Day","category":"public","dates":"d|0601:2018,2029,2035,2040,2046,2057|0602:2017,2023,2028,2034,2045,2051,2056|0603:2016,2022,2033,2039,2044,2050|0604:2021,2027,2032,2038,2049,2055,2060|0605:2015,2020,2026,2037,2043,2048,2054|0606:2014,2025,2031,2036,2042,2053,2059|0607:2013,2019,2024,2030,2041,2047,2052,2058"},{"id":"independence_day","name":"Independence Day","en":"Independence Day","category":"public","dates":"d|0710:2010-2060|0711:2011,2016,2022,2033,2039,2044,2050|0712:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"emancipation_day","name":"Emancipation Day","en":"Emancipation Day","category":"public","dates":"d|0801:2011,2016,2022,2033,2039,2044,2050|0802:2010,2021,2027,2032,2038,2049,2055,2060|0803:2015,2020,2026,2037,2043,2048,2054|0804:2014,2025,2031,2036,2042,2053,2059|0805:2013,2019,2024,2030,2041,2047,2052,2058|0806:2012,2018,2029,2035,2040,2046,2057|0807:2017,2023,2028,2034,2045,2051,2056"},{"id":"national_heroes_day","name":"National Heroes Day","en":"National Heroes Day","category":"public","dates":"d|1008:2018,2029,2035,2040,2046,2057|1009:2017,2023,2028,2034,2045,2051,2056|1010:2016,2022,2033,2039,2044,2050|1011:2021,2027,2032,2038,2049,2055,2060|1012:2015,2020,2026,2037,2043,2048,2054|1013:2014,2025,2031,2036,2042,2053,2059|1014:2013,2019,2024,2030,2041,2047,2052,2058"},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060|1227:2011,2016,2022,2033,2039,2044,2050"},{"id":"boxing_day","name":"Boxing Day","en":"Boxing Day","category":"public","dates":"d|1226:2010-2060|1227:2010,2021,2027,2032,2038,2049,2055,2060|1228:2015,2020,2026,2037,2043,2048,2054"}]} diff --git a/app/Support/Holidays/data/BT.json b/app/Support/Holidays/data/BT.json new file mode 100644 index 000000000..9c6a6272e --- /dev/null +++ b/app/Support/Holidays/data/BT.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"BT","language":"dz","years":[2010,2060],"subdivisions":{"11":"Paro","12":"Chhukha","13":"Haa","14":"Samtse","15":"Thimphu","21":"Tsirang","22":"Dagana","23":"Punakha","24":"Wangdue Phodrang","31":"Sarpang","32":"Trongsa","33":"Bumthang","34":"Zhemgang","41":"Trashigang","42":"Monggar","43":"Pema Gatshel","44":"Lhuentse","45":"Samdrup Jongkhar","GA":"Gasa","TY":"Trashi Yangtse"},"holidays":[{"id":"winter_solstice","name":"དགུན་ཉི་ལྡོག་གི་ངལ་གསོལ།","en":"Winter Solstice","category":"public","dates":"d|0101:2022|0102:2010-2018,2020-2021,2023-2060|0103:2019"},{"id":"traditional_day_of_offering","name":"སྔར་སྲོལ་འབུལ་བའི་ལོ་གསར་གྱི་ངལ་གསོལ།","en":"Traditional Day of Offering","category":"public","dates":"d|0103:2022|0104:2030,2049|0105:2011|0106:2019,2038,2057|0108:2027,2046|0109:2054|0110:2016,2035|0112:2013,2024,2043|0113:2032,2051|0114:2021,2040,2059|0115:2029|0116:2010,2048|0117:2018,2037,2056|0119:2026,2045|0120:2053|0121:2015,2034|0122:2023,2042|0123:2031|0124:2012,2050|0125:2020,2039,2058|0126:2047|0127:2028|0128:2017,2055|0129:2036|0130:2025|0131:2014,2033,2044|0201:2052|0202:2041|0203:2060"},{"id":"losar","name":"གནམ་ལོ་གསར་ཚེས་ཀྱིས་དུས་སྟོན་ངལ་གསོལ།","en":"Losar","category":"public","dates":"d|0203:2011,2030,2049|0204:2011,2030,2049,2057|0205:2019,2038,2057|0206:2019,2038,2046|0207:2027,2046|0208:2027,2054|0209:2016,2035,2054|0210:2016,2024,2035,2043|0211:2013,2024,2043|0212:2013,2021,2032,2051|0213:2021,2032,2040,2051,2059|0214:2010,2029,2040,2048,2059|0215:2010,2029,2048|0216:2018,2037,2056|0217:2018,2037,2045,2056|0218:2026,2045|0219:2015,2026,2034,2053|0220:2015,2034,2053|0221:2023,2042|0222:2012,2023,2031,2042,2050|0223:2012,2031,2050,2058|0224:2020,2039,2058|0225:2020,2039,2047|0226:2028,2047|0227:2017,2028,2055|0228:2017,2025,2036,2055|0229:2036,2044|0301:2025,2044|0302:2014,2033,2052|0303:2014,2022,2033,2041,2052,2060|0304:2022,2041,2060"},{"id":"birth_anniversary_of_his_majesty_the_king","name":"མི་དབང་མངའ་བདག་རིན་པོ་ཆེའི་འཁྲུངས་སྐར་དུས་ཆེན་ངལ་གསོལ།","en":"Birth Anniversary of His Majesty the King","category":"public","dates":"d|0221:2010-2060|0222:2010-2060|0223:2010-2060"},{"id":"death_anniversary_of_zhabdrung","name":"མཐུ་ཆེན་ཆོས་ཀྱི་རྒྱལ་པོའི་དུས་ཆེན་ངལ་གསོལ།","en":"Death Anniversary of Zhabdrung","category":"public","dates":"d|0414:2019,2038|0416:2016,2027,2046|0417:2035,2054|0418:2024|0419:2043|0420:2013,2032,2051|0421:2040|0422:2021,2059|0423:2010,2029,2048|0425:2018,2037,2056|0426:2026,2045|0427:2053|0428:2015,2034|0429:2042|0430:2023|0501:2012,2050|0502:2020,2031|0503:2039,2058|0504:2028|0505:2017,2036,2047|0506:2044,2055|0507:2025|0508:2033,2052|0509:2014|0510:2041,2060|0511:2022|0512:2011,2030,2049|0514:2057"},{"id":"birth_anniversary_of_the_3rd_druk_gyalpo","name":"མི་དབང་འབྲུག་རྒྱལ་གསུམ་པའི་འཁྲུངས་སྐར་དུས་ཆེན་ངལ་གསོལ།","en":"Birth Anniversary of the 3rd Druk Gyalpo","category":"public","dates":"d|0502:2010-2060"},{"id":"lord_buddha_s_parinirvana","name":"སངས་རྒྱས་བཅོམ་ལྡན་འདས་ཀྱི་དུས་ཆེན་ལྔ་འཛོམས་ངལ་གསོལ།","en":"Lord Buddha's Parinirvana","category":"public","dates":"d|0520:2027,2046|0521:2016,2054|0522:2035|0523:2024,2043|0525:2013,2032,2051|0526:2021,2040|0527:2010,2029,2048,2059|0529:2018,2037,2056|0530:2045|0531:2026|0601:2053|0602:2015,2034|0603:2042|0604:2012,2023|0605:2020,2031,2050|0606:2039,2058|0607:2028|0608:2036,2047|0609:2017,2055|0610:2044|0611:2025|0612:2033,2052|0613:2014|0614:2022,2041,2060|0615:2011,2030,2049|0617:2019,2038,2057"},{"id":"birth_anniversary_of_guru_rinpoche","name":"ཨོ་རྒྱན་གུ་རུ་རིན་པོ་ཆེའི་འཁྲུངས་སྐར་དུས་ཆེན་ངལ་གསོ།","en":"Birth Anniversary of Guru Rinpoche","category":"public","dates":"d|0613:2027|0615:2016,2035,2054|0616:2024|0617:2043|0618:2013,2032,2051|0620:2021,2040,2059|0621:2010,2029,2048|0622:2037,2056|0623:2018|0624:2026,2045|0625:2053|0626:2015,2034|0628:2023,2042|0629:2012,2050|0630:2020,2031|0701:2028,2039,2058|0702:2047|0703:2017,2036|0704:2044,2055|0705:2025|0706:2052|0707:2014,2033|0708:2041,2060|0709:2022|0710:2011,2030,2049|0711:2019,2038,2057|0713:2046"},{"id":"first_sermon_of_lord_buddha","name":"སྟོན་པ་མཆོག་ཝ་ར་ན་སིར་བདེན་བཞིའི་ཆོས་འཁོར་བསྐོར་བའི་དུས་ཆེན་ངལ་གསོ།","en":"First Sermon of Lord Buddha","category":"public","dates":"d|0708:2016|0709:2035,2054|0710:2024,2043|0711:2032|0712:2013,2051|0713:2040,2059|0714:2021|0715:2010,2029,2048|0716:2018,2037,2056|0718:2026,2045|0719:2034,2053|0720:2015|0721:2023,2042|0723:2012,2031,2050|0724:2020,2058|0725:2028,2039|0726:2047|0727:2017,2036|0728:2025,2044,2055|0729:2052|0730:2033|0731:2014,2060|0801:2022,2041|0802:2049|0803:2011,2030|0804:2019,2038,2057|0806:2027,2046"},{"id":"thimphu_drubchoe","name":"ཐིམ་རྫོང་ལྷ་མོའི་དངོས་འཆམ་མཇལ་རྒྱུའི་ངལ་གསོ།","en":"Thimphu Drubchoe","category":"public","regional":{"15":"d|0909:2024,2043|0910:2013|0911:2021,2032,2051|0912:2040|0913:2010,2029,2048,2059|0915:2018,2037,2056|0916:2045|0917:2026|0918:2034,2053|0919:2015|0920:2023,2042|0921:2012|0922:2020,2031,2050|0923:2039,2058|0924:2028|0925:2036,2047|0926:2017,2055|0927:2044|0928:2025|0929:2014,2033,2052|0930:2022,2060|1001:2041|1002:2011,2030,2049|1004:2019,2038,2057|1005:2027,2046|1007:2016,2035,2054"}},{"id":"thimphu_tshechu","name":"ཐིམ་ཕུ་ཚེས་བཅུའི་ངལ་གསོ།","en":"Thimphu Tshechu","category":"public","regional":{"15":"y|2010:0917-0919|2011:1006-1008|2012:0925-0927|2013:0914-0916|2014:1003-1005|2015:0923-0925|2016:1011-1013|2017:0930-1002|2018:0919-0921|2019:1008-1010|2020:0926-0928|2021:0915-0917|2022:1004-1006|2023:0924-0926|2024:0913-0915|2025:1002-1004|2026:0921-0923|2027:1010-1012|2028:0928-0930|2029:0917-0919|2030:1006-1008|2031:0926-0928|2032:0914-0916|2033:1003-1005|2034:0923-0925|2035:1011-1013|2036:0929-1001|2037:0919-0921|2038:1007-1009|2039:0927-0929|2040:0916-0918|2041:1005-1007|2042:0924-0926|2043:0913-0915|2044:1001-1003|2045:0920-0922|2046:1009-1011|2047:0928-0930|2048:0917-0919|2049:1006-1008|2050:0926-0928|2051:0915-0917|2052:1003-1005|2053:0922-0924|2054:1011-1013|2055:0930-1002|2056:0918-0920|2057:1007-1009|2058:0927-0929|2059:0917-0919|2060:1005-1007"}},{"id":"blessed_rainy_day","name":"རྒྱལ་ཡོངས་ཁྲུས་བབས་ངལ་གསོ།","en":"Blessed Rainy Day","category":"public","dates":"d|0922:2012,2015|0923:2010,2014,2016-2018,2020-2022,2024-2026,2028-2030,2032-2034,2036-2038,2040-2042,2044-2046,2048-2050,2052-2054,2056-2058,2060|0924:2011,2019,2023,2027,2031,2035,2039,2043,2047,2051,2055,2059|1021:2013"},{"id":"dassain","name":"ལྷོ་མཚམས་ད་སའིན་ངལ་གསོ།","en":"Dassain","category":"public","dates":"d|0927:2028|0930:2017|1002:2025|1003:2014,2033|1005:2022|1006:2011,2030|1008:2019|1009:2027|1011:2016,2035|1012:2024|1014:2013,2032|1015:2021|1016:2029|1017:2010|1019:2018|1021:2026|1022:2015,2034|1024:2012,2023|1025:2031|1026:2020","estimated":"d|0927:2028|1003:2033|1006:2030|1009:2027|1011:2035|1014:2032|1016:2029|1022:2034|1025:2031"},{"id":"descending_day_of_lord_buddha","name":"རྒྱལ་བ་ལྷ་ལས་བབས་པའི་དུས་ཆེན་ངལ་གསོ།","en":"Descending Day of Lord Buddha","category":"public","dates":"d|1025:2032|1026:2013,2051|1027:2021,2040,2059|1029:2010,2029,2048|1030:2056|1031:2018,2037|1101:2026,2045|1102:2034,2053|1103:2015|1104:2023,2042|1105:2050|1106:2012,2031|1107:2020,2058|1108:2039|1109:2028,2047|1110:2017,2036|1111:2025,2044,2055|1113:2014,2033,2052|1114:2060|1115:2022,2041|1116:2049|1117:2011,2030|1118:2057|1119:2019,2038|1120:2016,2027,2046|1121:2035,2054|1122:2024|1123:2043"},{"id":"coronation_of_his_majesty_the_king","name":"མི་དབང་མངའ་བདག་རིན་པོ་ཆེའི་གསེར་ཁྲི་མངའ་གསོལ་གྱི་དུས་ཆེན་ངལ་གསོལ།","en":"Coronation of His Majesty the King","category":"public","dates":"d|1101:2010-2060"},{"id":"birth_anniversary_of_the_4th_druk_gyalpo_constit","name":"མི་དབང་འབྲུག་རྒྱལ་བཞི་པའི་འཁྲུངས་སྐར་དུས་ཆེན་ངལ་གསོལ།","en":"Birth Anniversary of the 4th Druk Gyalpo - Constitution Day","category":"public","dates":"d|1111:2010-2060"},{"id":"national_day","name":"རྒྱལ་ཡོངས་དུས་ཆེན་གྱི་ངལ་གསོལ།","en":"National Day","category":"public","dates":"d|1217:2010-2060"}]} diff --git a/app/Support/Holidays/data/BV.json b/app/Support/Holidays/data/BV.json new file mode 100644 index 000000000..5d548ac6e --- /dev/null +++ b/app/Support/Holidays/data/BV.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"BV","language":null,"years":[2010,2060],"subdivisions":{},"holidays":[]} diff --git a/app/Support/Holidays/data/BW.json b/app/Support/Holidays/data/BW.json new file mode 100644 index 000000000..1950edd8f --- /dev/null +++ b/app/Support/Holidays/data/BW.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"BW","language":null,"years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0103:2012,2017,2023,2034,2040,2045,2051"},{"id":"new_year_s_day_holiday","name":"New Year's Day Holiday","en":"New Year's Day Holiday","category":"public","dates":"d|0102:2010-2060|0103:2011,2022,2028,2033,2039,2050,2056"},{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"holy_saturday","name":"Holy Saturday","en":"Holy Saturday","category":"public","dates":"d|0324:2035,2046|0326:2016|0327:2027,2032|0328:2043,2054|0329:2059|0330:2013,2024|0331:2018,2029,2040|0401:2051,2056|0403:2010,2021|0404:2015,2026,2037,2048|0405:2042,2053|0407:2012|0408:2023,2034,2045|0409:2039,2050|0411:2020|0412:2031,2036|0413:2047,2058|0415:2017,2028|0416:2022,2033,2044|0417:2049,2055,2060|0419:2014,2025|0420:2019,2030,2041,2052|0421:2057|0423:2011|0424:2038"},{"id":"easter_monday","name":"Easter Monday","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"labour_day","name":"Labour Day","en":"Labour Day","category":"public","dates":"d|0501:2010-2060|0502:2011,2016,2022,2033,2039,2044,2050"},{"id":"ascension_day","name":"Ascension Day","en":"Ascension Day","category":"public","dates":"d|0503:2035,2046|0505:2016|0506:2027,2032|0507:2043,2054|0508:2059|0509:2013,2024|0510:2018,2029,2040|0511:2051,2056|0513:2010,2021|0514:2015,2026,2037,2048|0515:2042,2053|0517:2012|0518:2023,2034,2045|0519:2039,2050|0521:2020|0522:2031,2036|0523:2047,2058|0525:2017,2028|0526:2022,2033,2044|0527:2049,2055,2060|0529:2014,2025|0530:2019,2030,2041,2052|0531:2057|0602:2011|0603:2038"},{"id":"labour_day_holiday","name":"Labour Day Holiday","en":"Labour Day Holiday","category":"public","dates":"d|0503:2021,2027,2032,2038,2049,2055,2060"},{"id":"sir_seretse_khama_day","name":"Sir Seretse Khama Day","en":"Sir Seretse Khama Day","category":"public","dates":"d|0701:2010-2060|0702:2012,2018,2029,2035,2040,2046,2057"},{"id":"president_s_day","name":"President's Day","en":"President's Day","category":"public","dates":"d|0715:2013,2019,2024,2030,2041,2047,2052,2058|0716:2012,2018,2029,2035,2040,2046,2057|0717:2017,2023,2028,2034,2045,2051,2056|0718:2011,2016,2022,2033,2039,2044,2050|0719:2010,2021,2027,2032,2038,2049,2055,2060|0720:2015,2020,2026,2037,2043,2048,2054|0721:2014,2025,2031,2036,2042,2053,2059"},{"id":"president_s_day_holiday","name":"President's Day Holiday","en":"President's Day Holiday","category":"public","dates":"d|0716:2013,2019,2024,2030,2041,2047,2052,2058|0717:2012,2018,2029,2035,2040,2046,2057|0718:2017,2023,2028,2034,2045,2051,2056|0719:2011,2016,2022,2033,2039,2044,2050|0720:2010,2021,2027,2032,2038,2049,2055,2060|0721:2015,2020,2026,2037,2043,2048,2054|0722:2014,2025,2031,2036,2042,2053,2059"},{"id":"botswana_day","name":"Botswana Day","en":"Botswana Day","category":"public","dates":"d|0930:2010-2060|1002:2012,2018,2029,2035,2040,2046,2057"},{"id":"botswana_day_holiday","name":"Botswana Day Holiday","en":"Botswana Day Holiday","category":"public","dates":"d|1001:2010-2060|1002:2017,2023,2028,2034,2045,2051,2056"},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060|1227:2011,2016,2022,2033,2039,2044,2050"},{"id":"boxing_day","name":"Boxing Day","en":"Boxing Day","category":"public","dates":"d|1226:2010-2060|1227:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"boxing_day_holiday","name":"Boxing Day Holiday","en":"Boxing Day Holiday","category":"public","dates":"d|1228:2020,2026,2037,2043,2048,2054"}]} diff --git a/app/Support/Holidays/data/BY.json b/app/Support/Holidays/data/BY.json new file mode 100644 index 000000000..a90eb6ac3 --- /dev/null +++ b/app/Support/Holidays/data/BY.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"BY","language":"be","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Новы год","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2020-2060"},{"id":"day_off","name":"Выходны","en":"Day off","category":"public","dates":"y|2010:0108,0412,0510|2011:0307,0502|2012:0309,0423,0702,1224,1231|2013:0102,0510|2014:0102,0106,0430,0704,1226|2015:0102,0420|2016:0108,0307|2017:0102,0424,0508,1106|2018:0102,0309,0416,0430,0702,1224,1231|2019:0506,0508,1108|2020:0106,0427|2021:0108,0510|2022:0307,0502|2023:0424,0508,1106|2024:0513,1108|2025:0106,0428,0704,1226|2026:0420"},{"id":"orthodox_christmas_day","name":"Нараджэнне Хрыстова (праваслаўнае Раство)","en":"Orthodox Christmas Day","category":"public","dates":"d|0107:2010-2060"},{"id":"women_s_day","name":"Дзень жанчын","en":"Women's Day","category":"public","dates":"d|0308:2010-2060"},{"id":"catholic_easter","name":"Каталiцкi Вялiкдзень","en":"Catholic Easter","category":"public","dates":"d|0325:2035,2046|0327:2016|0328:2027,2032|0329:2043,2054|0330:2059|0331:2013,2024|0401:2018,2029,2040|0402:2051,2056|0404:2010,2021|0405:2015,2026,2037,2048|0406:2042,2053|0408:2012|0409:2023,2034,2045|0410:2039,2050|0412:2020|0413:2031,2036|0414:2047,2058|0416:2017,2028|0417:2022,2033,2044|0418:2049,2055,2060|0420:2014,2025|0421:2019,2030,2041,2052|0422:2057|0424:2011|0425:2038"},{"id":"orthodox_easter","name":"Праваслаўны Вялiкдзень","en":"Orthodox Easter","category":"public","dates":"d|0404:2010|0405:2037,2048|0408:2018,2029|0409:2034,2045,2056|0412:2015,2026|0413:2031,2042,2053|0414:2058|0415:2012|0416:2017,2023,2028|0417:2039,2050|0418:2055|0419:2020|0420:2014,2025,2036|0421:2041,2047,2052|0424:2011,2022,2033,2044|0425:2038,2049,2060|0428:2019,2030|0429:2035,2046,2057|0501:2016|0502:2021,2027,2032|0503:2043,2054|0504:2059|0505:2013,2024|0506:2040|0507:2051"},{"id":"radunitsa_day_of_rejoicing","name":"Радаўніца","en":"Radunitsa (Day of Rejoicing)","category":"public","dates":"d|0413:2010|0414:2037,2048|0417:2018,2029|0418:2034,2045,2056|0421:2015,2026|0422:2031,2042,2053|0423:2058|0424:2012|0425:2017,2023,2028|0426:2039,2050|0427:2055|0428:2020|0429:2014,2025,2036|0430:2041,2047,2052|0503:2011,2022,2033,2044|0504:2038,2049,2060|0507:2019,2030|0508:2035,2046,2057|0510:2016|0511:2021,2027,2032|0512:2043,2054|0513:2059|0514:2013,2024|0515:2040|0516:2051"},{"id":"labor_day","name":"Свята працы","en":"Labor Day","category":"public","dates":"d|0501:2010-2060"},{"id":"victory_day","name":"Дзень Перамогі","en":"Victory Day","category":"public","dates":"d|0509:2010-2060"},{"id":"independence_day_of_the_republic_of_belarus_day_","name":"Дзень Незалежнасці Рэспублікі Беларусь (Дзень Рэспублікі)","en":"Independence Day of the Republic of Belarus (Day of the Republic)","category":"public","dates":"d|0703:2010-2060"},{"id":"october_revolution_day","name":"Дзень Кастрычніцкай рэвалюцыі","en":"October Revolution Day","category":"public","dates":"d|1107:2010-2060"},{"id":"catholic_christmas_day","name":"Нараджэнне Хрыстова (каталіцкае Раство)","en":"Catholic Christmas Day","category":"public","dates":"d|1225:2010-2060"}]} diff --git a/app/Support/Holidays/data/BZ.json b/app/Support/Holidays/data/BZ.json new file mode 100644 index 000000000..98c5c8945 --- /dev/null +++ b/app/Support/Holidays/data/BZ.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"BZ","language":null,"years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2011,2013-2016,2018-2022,2024-2033,2035-2039,2041-2044,2046-2050,2052-2060|0102:2012,2017,2023,2034,2040,2045,2051"},{"id":"george_price_day","name":"George Price Day","en":"George Price Day","category":"public","dates":"d|0115:2021-2022,2024-2033,2035-2039,2041-2044,2046-2050,2052-2060|0116:2023,2034,2040,2045,2051"},{"id":"national_heroes_and_benefactors_day","name":"National Heroes and Benefactors Day","en":"National Heroes and Benefactors Day","category":"public","dates":"d|0306:2017,2023,2028,2034,2045,2051,2056|0307:2011,2016,2022,2033,2039,2044,2050|0308:2010,2021,2027,2032,2038,2049,2055,2060|0309:2013,2015,2019-2020,2024,2026,2030,2037,2041,2043,2047-2048,2052,2054,2058|0310:2014,2025,2031,2036,2042,2053,2059|0312:2012,2018,2029,2035,2040,2046,2057"},{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"holy_saturday","name":"Holy Saturday","en":"Holy Saturday","category":"public","dates":"d|0324:2035,2046|0326:2016|0327:2027,2032|0328:2043,2054|0329:2059|0330:2013,2024|0331:2018,2029,2040|0401:2051,2056|0403:2010,2021|0404:2015,2026,2037,2048|0405:2042,2053|0407:2012|0408:2023,2034,2045|0409:2039,2050|0411:2020|0412:2031,2036|0413:2047,2058|0415:2017,2028|0416:2022,2033,2044|0417:2049,2055,2060|0419:2014,2025|0420:2019,2030,2041,2052|0421:2057|0423:2011|0424:2038"},{"id":"easter_monday","name":"Easter Monday","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"labour_day","name":"Labour Day","en":"Labour Day","category":"public","dates":"d|0501:2010,2012-2015,2017-2021,2023-2032,2034-2038,2040-2043,2045-2049,2051-2060|0502:2011,2016,2022,2033,2039,2044,2050"},{"id":"emancipation_day","name":"Emancipation Day","en":"Emancipation Day","category":"public","dates":"d|0729:2024,2030,2041,2047,2052,2058|0730:2029,2035,2040,2046,2057|0731:2023,2028,2034,2045,2051,2056|0801:2022,2026,2033,2037,2039,2043-2044,2048,2050,2054|0802:2021,2027,2032,2038,2049,2055,2060|0804:2025,2031,2036,2042,2053,2059"},{"id":"saint_george_s_caye_day","name":"Saint George's Caye Day","en":"Saint George's Caye Day","category":"public","dates":"d|0910:2010-2016,2018-2022,2024-2027,2029-2033,2035-2044,2046-2050,2052-2055,2057-2060|0911:2017,2023,2028,2034,2045,2051,2056"},{"id":"independence_day","name":"Independence Day","en":"Independence Day","category":"public","dates":"d|0921:2010-2013,2015-2024,2026-2030,2032-2035,2037-2041,2043-2052,2054-2058,2060|0922:2014,2025,2031,2036,2042,2053,2059"},{"id":"indigenous_peoples_resistance_day","name":"Indigenous Peoples' Resistance Day","en":"Indigenous Peoples' Resistance Day","category":"public","dates":"d|1009:2023,2028,2034,2045,2051,2056|1010:2022,2033,2039,2044,2050|1011:2021,2027,2032,2038,2049,2055,2060|1012:2024,2026,2030,2037,2041,2043,2047-2048,2052,2054,2058|1013:2025,2031,2036,2042,2053,2059|1015:2029,2035,2040,2046,2057"},{"id":"garifuna_settlement_day","name":"Garifuna Settlement Day","en":"Garifuna Settlement Day","category":"public","dates":"d|1119:2010-2016,2018-2022,2024-2027,2029-2033,2035-2044,2046-2050,2052-2055,2057-2060|1120:2017,2023,2028,2034,2045,2051,2056"},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"},{"id":"boxing_day","name":"Boxing Day","en":"Boxing Day","category":"public","dates":"d|1226:2011-2020,2022-2026,2028-2031,2033-2037,2039-2048,2050-2054,2056-2059|1227:2010,2021,2027,2032,2038,2049,2055,2060"}]} diff --git a/app/Support/Holidays/data/CA.json b/app/Support/Holidays/data/CA.json new file mode 100644 index 000000000..57f5627c5 --- /dev/null +++ b/app/Support/Holidays/data/CA.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"CA","language":"en_CA","years":[2010,2060],"subdivisions":{"AB":"Alberta","BC":"British Columbia","MB":"Manitoba","NB":"New Brunswick","NL":"Newfoundland and Labrador","NS":"Nova Scotia","NT":"Northwest Territories","NU":"Nunavut","ON":"Ontario","PE":"Prince Edward Island","QC":"Quebec","SK":"Saskatchewan","YT":"Yukon"},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051|0103:2011,2022,2028,2033,2039,2050,2056"},{"id":"family_day","name":"Family Day","en":"Family Day","category":"public","regional":{"AB":"d|0215:2010,2016,2021,2027,2038,2044,2049,2055|0216:2015,2026,2032,2037,2043,2054,2060|0217:2014,2020,2025,2031,2042,2048,2053,2059|0218:2013,2019,2030,2036,2041,2047,2058|0219:2018,2024,2029,2035,2046,2052,2057|0220:2012,2017,2023,2034,2040,2045,2051|0221:2011,2022,2028,2033,2039,2050,2056","BC":"d|0208:2016|0209:2015|0210:2014|0211:2013|0212:2018|0213:2017|0215:2021,2027,2038,2044,2049,2055|0216:2026,2032,2037,2043,2054,2060|0217:2020,2025,2031,2042,2048,2053,2059|0218:2019,2030,2036,2041,2047,2058|0219:2024,2029,2035,2046,2052,2057|0220:2023,2034,2040,2045,2051|0221:2022,2028,2033,2039,2050,2056","NB":"d|0215:2021,2027,2038,2044,2049,2055|0216:2026,2032,2037,2043,2054,2060|0217:2020,2025,2031,2042,2048,2053,2059|0218:2019,2030,2036,2041,2047,2058|0219:2018,2024,2029,2035,2046,2052,2057|0220:2023,2034,2040,2045,2051|0221:2022,2028,2033,2039,2050,2056","ON":"d|0215:2010,2016,2021,2027,2038,2044,2049,2055|0216:2015,2026,2032,2037,2043,2054,2060|0217:2014,2020,2025,2031,2042,2048,2053,2059|0218:2013,2019,2030,2036,2041,2047,2058|0219:2018,2024,2029,2035,2046,2052,2057|0220:2012,2017,2023,2034,2040,2045,2051|0221:2011,2022,2028,2033,2039,2050,2056","SK":"d|0215:2010,2016,2021,2027,2038,2044,2049,2055|0216:2015,2026,2032,2037,2043,2054,2060|0217:2014,2020,2025,2031,2042,2048,2053,2059|0218:2013,2019,2030,2036,2041,2047,2058|0219:2018,2024,2029,2035,2046,2052,2057|0220:2012,2017,2023,2034,2040,2045,2051|0221:2011,2022,2028,2033,2039,2050,2056"}},{"id":"heritage_day","name":"Heritage Day","en":"Heritage Day","category":"public","regional":{"NS":"d|0215:2016,2021,2027,2038,2044,2049,2055|0216:2015,2026,2032,2037,2043,2054,2060|0217:2020,2025,2031,2042,2048,2053,2059|0218:2019,2030,2036,2041,2047,2058|0219:2018,2024,2029,2035,2046,2052,2057|0220:2017,2023,2034,2040,2045,2051|0221:2022,2028,2033,2039,2050,2056"}},{"id":"islander_day","name":"Islander Day","en":"Islander Day","category":"public","regional":{"PE":"d|0215:2010,2016,2021,2027,2038,2044,2049,2055|0216:2015,2026,2032,2037,2043,2054,2060|0217:2014,2020,2025,2031,2042,2048,2053,2059|0218:2013,2019,2030,2036,2041,2047,2058|0219:2018,2024,2029,2035,2046,2052,2057|0220:2012,2017,2023,2034,2040,2045,2051|0221:2011,2022,2028,2033,2039,2050,2056"}},{"id":"louis_riel_day","name":"Louis Riel Day","en":"Louis Riel Day","category":"public","regional":{"MB":"d|0215:2010,2016,2021,2027,2038,2044,2049,2055|0216:2015,2026,2032,2037,2043,2054,2060|0217:2014,2020,2025,2031,2042,2048,2053,2059|0218:2013,2019,2030,2036,2041,2047,2058|0219:2018,2024,2029,2035,2046,2052,2057|0220:2012,2017,2023,2034,2040,2045,2051|0221:2011,2022,2028,2033,2039,2050,2056"}},{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"national_patriots_day","name":"National Patriots' Day","en":"National Patriots' Day","category":"public","regional":{"QC":"d|0518:2015,2020,2026,2037,2043,2048,2054|0519:2014,2025,2031,2036,2042,2053,2059|0520:2013,2019,2024,2030,2041,2047,2052,2058|0521:2012,2018,2029,2035,2040,2046,2057|0522:2017,2023,2028,2034,2045,2051,2056|0523:2011,2016,2022,2033,2039,2044,2050|0524:2010,2021,2027,2032,2038,2049,2055,2060"}},{"id":"victoria_day","name":"Victoria Day","en":"Victoria Day","category":"public","regional":{"AB":"d|0518:2015,2020,2026,2037,2043,2048,2054|0519:2014,2025,2031,2036,2042,2053,2059|0520:2013,2019,2024,2030,2041,2047,2052,2058|0521:2012,2018,2029,2035,2040,2046,2057|0522:2017,2023,2028,2034,2045,2051,2056|0523:2011,2016,2022,2033,2039,2044,2050|0524:2010,2021,2027,2032,2038,2049,2055,2060","BC":"d|0518:2015,2020,2026,2037,2043,2048,2054|0519:2014,2025,2031,2036,2042,2053,2059|0520:2013,2019,2024,2030,2041,2047,2052,2058|0521:2012,2018,2029,2035,2040,2046,2057|0522:2017,2023,2028,2034,2045,2051,2056|0523:2011,2016,2022,2033,2039,2044,2050|0524:2010,2021,2027,2032,2038,2049,2055,2060","MB":"d|0518:2015,2020,2026,2037,2043,2048,2054|0519:2014,2025,2031,2036,2042,2053,2059|0520:2013,2019,2024,2030,2041,2047,2052,2058|0521:2012,2018,2029,2035,2040,2046,2057|0522:2017,2023,2028,2034,2045,2051,2056|0523:2011,2016,2022,2033,2039,2044,2050|0524:2010,2021,2027,2032,2038,2049,2055,2060","NT":"d|0518:2015,2020,2026,2037,2043,2048,2054|0519:2014,2025,2031,2036,2042,2053,2059|0520:2013,2019,2024,2030,2041,2047,2052,2058|0521:2012,2018,2029,2035,2040,2046,2057|0522:2017,2023,2028,2034,2045,2051,2056|0523:2011,2016,2022,2033,2039,2044,2050|0524:2010,2021,2027,2032,2038,2049,2055,2060","NU":"d|0518:2015,2020,2026,2037,2043,2048,2054|0519:2014,2025,2031,2036,2042,2053,2059|0520:2013,2019,2024,2030,2041,2047,2052,2058|0521:2012,2018,2029,2035,2040,2046,2057|0522:2017,2023,2028,2034,2045,2051,2056|0523:2011,2016,2022,2033,2039,2044,2050|0524:2010,2021,2027,2032,2038,2049,2055,2060","ON":"d|0518:2015,2020,2026,2037,2043,2048,2054|0519:2014,2025,2031,2036,2042,2053,2059|0520:2013,2019,2024,2030,2041,2047,2052,2058|0521:2012,2018,2029,2035,2040,2046,2057|0522:2017,2023,2028,2034,2045,2051,2056|0523:2011,2016,2022,2033,2039,2044,2050|0524:2010,2021,2027,2032,2038,2049,2055,2060","SK":"d|0518:2015,2020,2026,2037,2043,2048,2054|0519:2014,2025,2031,2036,2042,2053,2059|0520:2013,2019,2024,2030,2041,2047,2052,2058|0521:2012,2018,2029,2035,2040,2046,2057|0522:2017,2023,2028,2034,2045,2051,2056|0523:2011,2016,2022,2033,2039,2044,2050|0524:2010,2021,2027,2032,2038,2049,2055,2060","YT":"d|0518:2015,2020,2026,2037,2043,2048,2054|0519:2014,2025,2031,2036,2042,2053,2059|0520:2013,2019,2024,2030,2041,2047,2052,2058|0521:2012,2018,2029,2035,2040,2046,2057|0522:2017,2023,2028,2034,2045,2051,2056|0523:2011,2016,2022,2033,2039,2044,2050|0524:2010,2021,2027,2032,2038,2049,2055,2060"}},{"id":"national_aboriginal_day","name":"National Aboriginal Day","en":"National Aboriginal Day","category":"public","regional":{"NT":"d|0621:2010-2060","YT":"d|0621:2017-2060"}},{"id":"saint_john_the_baptist_day","name":"Saint Jean Baptiste Day","en":"Saint John the Baptist Day","category":"public","regional":{"QC":"d|0624:2010-2060|0625:2012,2018,2029,2035,2040,2046,2057"}},{"id":"canada_day","name":"Canada Day","en":"Canada Day","category":"public","dates":"d|0701:2010-2060","regional":{"AB":"d|0702:2012,2018,2029,2035,2040,2046,2057","BC":"d|0702:2012,2018,2029,2035,2040,2046,2057","NL":"d|0702:2012,2018,2029,2035,2040,2046,2057|0703:2017,2023,2028,2034,2045,2051,2056","PE":"d|0702:2012,2018,2029,2035,2040,2046,2057|0703:2017,2023,2028,2034,2045,2051,2056","QC":"d|0702:2012,2018,2029,2035,2040,2046,2057","SK":"d|0702:2012,2018,2029,2035,2040,2046,2057|0703:2017,2023,2028,2034,2045,2051,2056","YT":"d|0702:2012,2018,2029,2035,2040,2046,2057|0703:2017,2023,2028,2034,2045,2051,2056"}},{"id":"memorial_day","name":"Memorial Day","en":"Memorial Day","category":"public","regional":{"NL":"d|0701:2010-2060|0702:2012,2018,2029,2035,2040,2046,2057|0703:2017,2023,2028,2034,2045,2051,2056"}},{"id":"nunavut_day","name":"Nunavut Day","en":"Nunavut Day","category":"public","regional":{"NU":"d|0709:2020-2060"}},{"id":"british_columbia_day","name":"British Columbia Day","en":"British Columbia Day","category":"public","regional":{"BC":"d|0801:2011,2016,2022,2033,2039,2044,2050|0802:2010,2021,2027,2032,2038,2049,2055,2060|0803:2015,2020,2026,2037,2043,2048,2054|0804:2014,2025,2031,2036,2042,2053,2059|0805:2013,2019,2024,2030,2041,2047,2052,2058|0806:2012,2018,2029,2035,2040,2046,2057|0807:2017,2023,2028,2034,2045,2051,2056"}},{"id":"civic_holiday","name":"Civic Holiday","en":"Civic Holiday","category":"public","regional":{"NT":"d|0801:2011,2016,2022,2033,2039,2044,2050|0802:2010,2021,2027,2032,2038,2049,2055,2060|0803:2015,2020,2026,2037,2043,2048,2054|0804:2014,2025,2031,2036,2042,2053,2059|0805:2013,2019,2024,2030,2041,2047,2052,2058|0806:2012,2018,2029,2035,2040,2046,2057|0807:2017,2023,2028,2034,2045,2051,2056","NU":"d|0801:2011,2016,2022,2033,2039,2044,2050|0802:2010,2021,2027,2032,2038,2049,2055,2060|0803:2015,2020,2026,2037,2043,2048,2054|0804:2014,2025,2031,2036,2042,2053,2059|0805:2013,2019,2024,2030,2041,2047,2052,2058|0806:2012,2018,2029,2035,2040,2046,2057|0807:2017,2023,2028,2034,2045,2051,2056"}},{"id":"new_brunswick_day","name":"New Brunswick Day","en":"New Brunswick Day","category":"public","regional":{"NB":"d|0801:2011,2016,2022,2033,2039,2044,2050|0802:2010,2021,2027,2032,2038,2049,2055,2060|0803:2015,2020,2026,2037,2043,2048,2054|0804:2014,2025,2031,2036,2042,2053,2059|0805:2013,2019,2024,2030,2041,2047,2052,2058|0806:2012,2018,2029,2035,2040,2046,2057|0807:2017,2023,2028,2034,2045,2051,2056"}},{"id":"saskatchewan_day","name":"Saskatchewan Day","en":"Saskatchewan Day","category":"public","regional":{"SK":"d|0801:2011,2016,2022,2033,2039,2044,2050|0802:2010,2021,2027,2032,2038,2049,2055,2060|0803:2015,2020,2026,2037,2043,2048,2054|0804:2014,2025,2031,2036,2042,2053,2059|0805:2013,2019,2024,2030,2041,2047,2052,2058|0806:2012,2018,2029,2035,2040,2046,2057|0807:2017,2023,2028,2034,2045,2051,2056"}},{"id":"discovery_day","name":"Discovery Day","en":"Discovery Day","category":"public","regional":{"YT":"d|0815:2011,2016,2022,2033,2039,2044,2050|0816:2010,2021,2027,2032,2038,2049,2055,2060|0817:2015,2020,2026,2037,2043,2048,2054|0818:2014,2025,2031,2036,2042,2053,2059|0819:2013,2019,2024,2030,2041,2047,2052,2058|0820:2012,2018,2029,2035,2040,2046,2057|0821:2017,2023,2028,2034,2045,2051,2056"}},{"id":"labor_day","name":"Labour Day","en":"Labor Day","category":"public","dates":"d|0901:2014,2025,2031,2036,2042,2053,2059|0902:2013,2019,2024,2030,2041,2047,2052,2058|0903:2012,2018,2029,2035,2040,2046,2057|0904:2017,2023,2028,2034,2045,2051,2056|0905:2011,2016,2022,2033,2039,2044,2050|0906:2010,2021,2027,2032,2038,2049,2055,2060|0907:2015,2020,2026,2037,2043,2048,2054"},{"id":"national_day_for_truth_and_reconciliation","name":"National Day for Truth and Reconciliation","en":"National Day for Truth and Reconciliation","category":"public","regional":{"BC":"d|0930:2023-2060|1001:2029,2035,2040,2046,2057|1002:2023,2028,2034,2045,2051,2056","MB":"d|0930:2024-2060|1001:2029,2035,2040,2046,2057|1002:2028,2034,2045,2051,2056","NT":"d|0930:2022-2060","NU":"d|0930:2022-2060","PE":"d|0930:2022-2060","YT":"d|0930:2023-2060"}},{"id":"thanksgiving_day","name":"Thanksgiving Day","en":"Thanksgiving Day","category":"public","regional":{"AB":"d|1008:2012,2018,2029,2035,2040,2046,2057|1009:2017,2023,2028,2034,2045,2051,2056|1010:2011,2016,2022,2033,2039,2044,2050|1011:2010,2021,2027,2032,2038,2049,2055,2060|1012:2015,2020,2026,2037,2043,2048,2054|1013:2014,2025,2031,2036,2042,2053,2059|1014:2013,2019,2024,2030,2041,2047,2052,2058","BC":"d|1008:2012,2018,2029,2035,2040,2046,2057|1009:2017,2023,2028,2034,2045,2051,2056|1010:2011,2016,2022,2033,2039,2044,2050|1011:2010,2021,2027,2032,2038,2049,2055,2060|1012:2015,2020,2026,2037,2043,2048,2054|1013:2014,2025,2031,2036,2042,2053,2059|1014:2013,2019,2024,2030,2041,2047,2052,2058","MB":"d|1008:2012,2018,2029,2035,2040,2046,2057|1009:2017,2023,2028,2034,2045,2051,2056|1010:2011,2016,2022,2033,2039,2044,2050|1011:2010,2021,2027,2032,2038,2049,2055,2060|1012:2015,2020,2026,2037,2043,2048,2054|1013:2014,2025,2031,2036,2042,2053,2059|1014:2013,2019,2024,2030,2041,2047,2052,2058","NT":"d|1008:2012,2018,2029,2035,2040,2046,2057|1009:2017,2023,2028,2034,2045,2051,2056|1010:2011,2016,2022,2033,2039,2044,2050|1011:2010,2021,2027,2032,2038,2049,2055,2060|1012:2015,2020,2026,2037,2043,2048,2054|1013:2014,2025,2031,2036,2042,2053,2059|1014:2013,2019,2024,2030,2041,2047,2052,2058","NU":"d|1008:2012,2018,2029,2035,2040,2046,2057|1009:2017,2023,2028,2034,2045,2051,2056|1010:2011,2016,2022,2033,2039,2044,2050|1011:2010,2021,2027,2032,2038,2049,2055,2060|1012:2015,2020,2026,2037,2043,2048,2054|1013:2014,2025,2031,2036,2042,2053,2059|1014:2013,2019,2024,2030,2041,2047,2052,2058","ON":"d|1008:2012,2018,2029,2035,2040,2046,2057|1009:2017,2023,2028,2034,2045,2051,2056|1010:2011,2016,2022,2033,2039,2044,2050|1011:2010,2021,2027,2032,2038,2049,2055,2060|1012:2015,2020,2026,2037,2043,2048,2054|1013:2014,2025,2031,2036,2042,2053,2059|1014:2013,2019,2024,2030,2041,2047,2052,2058","QC":"d|1008:2012,2018,2029,2035,2040,2046,2057|1009:2017,2023,2028,2034,2045,2051,2056|1010:2011,2016,2022,2033,2039,2044,2050|1011:2010,2021,2027,2032,2038,2049,2055,2060|1012:2015,2020,2026,2037,2043,2048,2054|1013:2014,2025,2031,2036,2042,2053,2059|1014:2013,2019,2024,2030,2041,2047,2052,2058","SK":"d|1008:2012,2018,2029,2035,2040,2046,2057|1009:2017,2023,2028,2034,2045,2051,2056|1010:2011,2016,2022,2033,2039,2044,2050|1011:2010,2021,2027,2032,2038,2049,2055,2060|1012:2015,2020,2026,2037,2043,2048,2054|1013:2014,2025,2031,2036,2042,2053,2059|1014:2013,2019,2024,2030,2041,2047,2052,2058","YT":"d|1008:2012,2018,2029,2035,2040,2046,2057|1009:2017,2023,2028,2034,2045,2051,2056|1010:2011,2016,2022,2033,2039,2044,2050|1011:2010,2021,2027,2032,2038,2049,2055,2060|1012:2015,2020,2026,2037,2043,2048,2054|1013:2014,2025,2031,2036,2042,2053,2059|1014:2013,2019,2024,2030,2041,2047,2052,2058"}},{"id":"remembrance_day","name":"Remembrance Day","en":"Remembrance Day","category":"public","regional":{"AB":"d|1111:2010-2060|1112:2012,2018,2029,2035,2040,2046,2057|1113:2017,2023,2028,2034,2045,2051,2056","BC":"d|1111:2010-2060","NB":"d|1111:2010-2060","NL":"d|1111:2010-2060|1112:2012,2018,2029,2035,2040,2046,2057|1113:2017,2023,2028,2034,2045,2051,2056","NS":"d|1111:2010-2060|1112:2012,2018,2029,2035,2040,2046,2057|1113:2017,2023,2028,2034,2045,2051,2056","NT":"d|1111:2010-2060","NU":"d|1111:2010-2060","PE":"d|1111:2010-2060|1112:2012,2018,2029,2035,2040,2046,2057|1113:2017,2023,2028,2034,2045,2051,2056","SK":"d|1111:2010-2060|1112:2012,2018,2029,2035,2040,2046,2057|1113:2017,2023,2028,2034,2045,2051,2056","YT":"d|1111:2010-2060|1112:2012,2018,2029,2035,2040,2046,2057|1113:2017,2023,2028,2034,2045,2051,2056"}},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060|1226:2011,2016,2022,2033,2039,2044,2050|1227:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"boxing_day","name":"Boxing Day","en":"Boxing Day","category":"public","regional":{"ON":"d|1226:2010-2060|1228:2010,2015,2020-2021,2026-2027,2032,2037-2038,2043,2048-2049,2054-2055,2060"}},{"id":"new_year_s_day_government","name":"New Year's Day","en":"New Year's Day","category":"government","dates":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051|0103:2011,2022,2028,2033,2039,2050,2056"},{"id":"heritage_day_optional","name":"Heritage Day","en":"Heritage Day","category":"optional","regional":{"AB":"d|0801:2011,2016,2022,2033,2039,2044,2050|0802:2010,2021,2027,2032,2038,2049,2055,2060|0803:2015,2020,2026,2037,2043,2048,2054|0804:2014,2025,2031,2036,2042,2053,2059|0805:2013,2019,2024,2030,2041,2047,2052,2058|0806:2012,2018,2029,2035,2040,2046,2057|0807:2017,2023,2028,2034,2045,2051,2056","YT":"d|0220:2015,2026,2037,2043,2054|0221:2014,2020,2025,2031,2042,2048,2053,2059|0222:2013,2019,2030,2036,2041,2047,2058|0223:2018,2024,2029,2035,2046,2052,2057|0224:2012,2017,2023,2034,2040,2045,2051|0225:2011,2022,2028,2033,2039,2050,2056|0226:2010,2016,2021,2027,2038,2044,2049,2055|0227:2032,2060"}},{"id":"saint_patrick_s_day_optional","name":"Saint Patrick's Day","en":"Saint Patrick's Day","category":"optional","regional":{"NL":"d|0314:2011,2016,2022,2033,2039,2044,2050|0315:2010,2021,2027,2032,2038,2049,2055,2060|0316:2015,2020,2026,2037,2043,2048,2054|0317:2014,2025,2031,2036,2042,2053,2059|0318:2013,2019,2024,2030,2041,2047,2052,2058|0319:2012,2018,2029,2035,2040,2046,2057|0320:2017,2023,2028,2034,2045,2051,2056"}},{"id":"good_friday_government","name":"Good Friday","en":"Good Friday","category":"government","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_monday_optional","name":"Easter Monday","en":"Easter Monday","category":"optional","regional":{"AB":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038","ON":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038","QC":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"}},{"id":"saint_george_s_day_optional","name":"Saint George's Day","en":"Saint George's Day","category":"optional","regional":{"NL":"d|0419:2010|0420:2015,2020,2026,2037,2043,2048,2054|0421:2014,2025,2031,2036,2042,2053,2059|0422:2013,2019,2024,2030,2041,2047,2052,2058|0423:2012,2018,2029,2035,2040,2046,2057|0424:2017,2023,2028,2034,2045,2051,2056|0425:2011,2016,2022,2033,2039,2044,2050|0426:2021,2027,2032,2038,2049,2055,2060"}},{"id":"victoria_day_government","name":"Victoria Day","en":"Victoria Day","category":"government","dates":"d|0518:2015,2020,2026,2037,2043,2048,2054|0519:2014,2025,2031,2036,2042,2053,2059|0520:2013,2019,2024,2030,2041,2047,2052,2058|0521:2012,2018,2029,2035,2040,2046,2057|0522:2017,2023,2028,2034,2045,2051,2056|0523:2011,2016,2022,2033,2039,2044,2050|0524:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"victoria_day_optional","name":"Victoria Day","en":"Victoria Day","category":"optional","regional":{"NB":"d|0518:2015,2020,2026,2037,2043,2048,2054|0519:2014,2025,2031,2036,2042,2053,2059|0520:2013,2019,2024,2030,2041,2047,2052,2058|0521:2012,2018,2029,2035,2040,2046,2057|0522:2017,2023,2028,2034,2045,2051,2056|0523:2011,2016,2022,2033,2039,2044,2050|0524:2010,2021,2027,2032,2038,2049,2055,2060"}},{"id":"discovery_day_optional","name":"Discovery Day","en":"Discovery Day","category":"optional","regional":{"NL":"d|0621:2010,2021,2027,2032,2038,2049,2055,2060|0622:2015,2020,2026,2037,2043,2048,2054|0623:2014,2025,2031,2036,2042,2053,2059|0624:2013,2019,2024,2030,2041,2047,2052,2058|0625:2012,2018,2029,2035,2040,2046,2057|0626:2017,2023,2028,2034,2045,2051,2056|0627:2011,2016,2022,2033,2039,2044,2050"}},{"id":"canada_day_government","name":"Canada Day","en":"Canada Day","category":"government","dates":"d|0701:2010-2060|0702:2012,2018,2029,2035,2040,2046,2057|0703:2017,2023,2028,2034,2045,2051,2056"},{"id":"orangemen_s_day_optional","name":"Orangemen's Day","en":"Orangemen's Day","category":"optional","regional":{"NL":"d|0709:2012,2018,2029,2035,2040,2046,2057|0710:2017,2023,2028,2034,2045,2051,2056|0711:2011,2016,2022,2033,2039,2044,2050|0712:2010,2021,2027,2032,2038,2049,2055,2060|0713:2015,2020,2026,2037,2043,2048,2054|0714:2014,2025,2031,2036,2042,2053,2059|0715:2013,2019,2024,2030,2041,2047,2052,2058"}},{"id":"civic_holiday_optional","name":"Civic Holiday","en":"Civic Holiday","category":"optional","regional":{"MB":"d|0801:2011|0802:2010|0804:2014|0805:2013|0806:2012","ON":"d|0801:2011,2016,2022,2033,2039,2044,2050|0802:2010,2021,2027,2032,2038,2049,2055,2060|0803:2015,2020,2026,2037,2043,2048,2054|0804:2014,2025,2031,2036,2042,2053,2059|0805:2013,2019,2024,2030,2041,2047,2052,2058|0806:2012,2018,2029,2035,2040,2046,2057|0807:2017,2023,2028,2034,2045,2051,2056"}},{"id":"natal_day_optional","name":"Natal Day","en":"Natal Day","category":"optional","regional":{"NS":"d|0801:2011,2016,2022,2033,2039,2044,2050|0802:2010,2021,2027,2032,2038,2049,2055,2060|0803:2015,2020,2026,2037,2043,2048,2054|0804:2014,2025,2031,2036,2042,2053,2059|0805:2013,2019,2024,2030,2041,2047,2052,2058|0806:2012,2018,2029,2035,2040,2046,2057|0807:2017,2023,2028,2034,2045,2051,2056"}},{"id":"terry_fox_day_optional","name":"Terry Fox Day","en":"Terry Fox Day","category":"optional","regional":{"MB":"d|0801:2016,2022,2033,2039,2044,2050|0802:2021,2027,2032,2038,2049,2055,2060|0803:2015,2020,2026,2037,2043,2048,2054|0804:2025,2031,2036,2042,2053,2059|0805:2019,2024,2030,2041,2047,2052,2058|0806:2018,2029,2035,2040,2046,2057|0807:2017,2023,2028,2034,2045,2051,2056"}},{"id":"labor_day_government","name":"Labour Day","en":"Labor Day","category":"government","dates":"d|0901:2014,2025,2031,2036,2042,2053,2059|0902:2013,2019,2024,2030,2041,2047,2052,2058|0903:2012,2018,2029,2035,2040,2046,2057|0904:2017,2023,2028,2034,2045,2051,2056|0905:2011,2016,2022,2033,2039,2044,2050|0906:2010,2021,2027,2032,2038,2049,2055,2060|0907:2015,2020,2026,2037,2043,2048,2054"},{"id":"national_day_for_truth_and_reconciliation_government","name":"National Day for Truth and Reconciliation","en":"National Day for Truth and Reconciliation","category":"government","dates":"d|0930:2021-2060|1001:2029,2035,2040,2046,2057|1002:2023,2028,2034,2045,2051,2056"},{"id":"national_day_for_truth_and_reconciliation_optional","name":"National Day for Truth and Reconciliation","en":"National Day for Truth and Reconciliation","category":"optional","regional":{"AB":"d|0930:2021-2060"}},{"id":"thanksgiving_day_government","name":"Thanksgiving Day","en":"Thanksgiving Day","category":"government","dates":"d|1008:2012,2018,2029,2035,2040,2046,2057|1009:2017,2023,2028,2034,2045,2051,2056|1010:2011,2016,2022,2033,2039,2044,2050|1011:2010,2021,2027,2032,2038,2049,2055,2060|1012:2015,2020,2026,2037,2043,2048,2054|1013:2014,2025,2031,2036,2042,2053,2059|1014:2013,2019,2024,2030,2041,2047,2052,2058"},{"id":"thanksgiving_day_optional","name":"Thanksgiving Day","en":"Thanksgiving Day","category":"optional","regional":{"NB":"d|1008:2012,2018,2029,2035,2040,2046,2057|1009:2017,2023,2028,2034,2045,2051,2056|1010:2011,2016,2022,2033,2039,2044,2050|1011:2010,2021,2027,2032,2038,2049,2055,2060|1012:2015,2020,2026,2037,2043,2048,2054|1013:2014,2025,2031,2036,2042,2053,2059|1014:2013,2019,2024,2030,2041,2047,2052,2058","NL":"d|1008:2012,2018,2029,2035,2040,2046,2057|1009:2017,2023,2028,2034,2045,2051,2056|1010:2011,2016,2022,2033,2039,2044,2050|1011:2010,2021,2027,2032,2038,2049,2055,2060|1012:2015,2020,2026,2037,2043,2048,2054|1013:2014,2025,2031,2036,2042,2053,2059|1014:2013,2019,2024,2030,2041,2047,2052,2058"}},{"id":"remembrance_day_government","name":"Remembrance Day","en":"Remembrance Day","category":"government","dates":"d|1111:2010-2060|1112:2012,2018,2029,2035,2040,2046,2057|1113:2017,2023,2028,2034,2045,2051,2056"},{"id":"remembrance_day_optional","name":"Remembrance Day","en":"Remembrance Day","category":"optional","regional":{"MB":"d|1111:2010-2060","ON":"d|1111:2010-2060|1112:2012,2018,2029,2035,2040,2046,2057|1113:2017,2023,2028,2034,2045,2051,2056"}},{"id":"christmas_day_government","name":"Christmas Day","en":"Christmas Day","category":"government","dates":"d|1225:2010-2060|1227:2010-2011,2016,2021-2022,2027,2032-2033,2038-2039,2044,2049-2050,2055,2060"},{"id":"christmas_day_optional","name":"Christmas Day","en":"Christmas Day","category":"optional","dates":"d|1225:2010-2060|1227:2010-2011,2016,2021-2022,2027,2032-2033,2038-2039,2044,2049-2050,2055,2060"},{"id":"boxing_day_government","name":"Boxing Day","en":"Boxing Day","category":"government","dates":"d|1226:2010-2060|1228:2010,2015,2020-2021,2026-2027,2032,2037-2038,2043,2048-2049,2054-2055,2060"},{"id":"boxing_day_optional","name":"Boxing Day","en":"Boxing Day","category":"optional","dates":"d|1226:2010-2060|1228:2010,2015,2020-2021,2026-2027,2032,2037-2038,2043,2048-2049,2054-2055,2060"}]} diff --git a/app/Support/Holidays/data/CC.json b/app/Support/Holidays/data/CC.json new file mode 100644 index 000000000..ff1352e3e --- /dev/null +++ b/app/Support/Holidays/data/CC.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"CC","language":"en_CC","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051|0103:2011,2022,2028,2033,2039,2050,2056"},{"id":"eid_al_fitr","name":"Eid al-Fitr","en":"Eid al-Fitr","category":"public","dates":"y|2010:0910|2011:0830|2012:0819-0820|2013:0808|2014:0728|2015:0717|2016:0706|2017:0624,0626|2018:0615|2019:0605|2020:0524-0525|2021:0513|2022:0503|2023:0421|2024:0410|2025:0331|2026:0320|2027:0309|2028:0226,0228|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102-0103,1223|2034:1212|2035:1201,1203|2036:1119|2037:1108-1109|2038:1029|2039:1019|2040:1007-1008|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712-0713|2049:0701|2050:0620|2051:0610,0612|2052:0529|2053:0519|2054:0509,0511|2055:0428|2056:0416-0417|2057:0405|2058:0325|2059:0315,0317|2060:0304","estimated":"y|2011:0830|2012:0819|2015:0717|2018:0615|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304"},{"id":"prophet_s_birthday","name":"Prophet's Birthday","en":"Prophet's Birthday","category":"public","dates":"y|2010:0226|2011:0215|2012:0204,0206|2013:0124|2014:0113|2015:0103,0105,1223|2016:1212|2017:1201|2018:1120|2019:1109,1111|2020:1029|2021:1019|2022:1008,1010|2023:0927|2024:0916|2025:0905|2026:0825|2027:0814,0816|2028:0803|2029:0724|2030:0713,0715|2031:0702|2032:0620-0621|2033:0609|2034:0530|2035:0520-0521|2036:0508|2037:0428|2038:0417,0419|2039:0406|2040:0325-0326|2041:0315|2042:0304|2043:0222-0223|2044:0211|2045:0130|2046:0119|2047:0108,1229-1230|2048:1218|2049:1207|2050:1126,1128|2051:1116|2052:1104|2053:1024|2054:1013|2055:1003-1004|2056:0922|2057:0911|2058:0831,0902|2059:0820|2060:0808-0809","estimated":"y|2011:0215|2012:0204|2015:0103,1223|2018:1120|2026:0825|2027:0814|2028:0803|2029:0724|2030:0713|2031:0702|2032:0620|2033:0609|2034:0530|2035:0520|2036:0508|2037:0428|2038:0417|2039:0406|2040:0325|2041:0315|2042:0304|2043:0222|2044:0211|2045:0130|2046:0119|2047:0108,1229|2048:1218|2049:1207|2050:1126|2051:1116|2052:1104|2053:1024|2054:1013|2055:1003|2056:0922|2057:0911|2058:0831|2059:0820|2060:0808"},{"id":"eid_al_adha","name":"Eid al-Adha","en":"Eid al-Adha","category":"public","dates":"y|2010:1116|2011:1106-1107|2012:1026|2013:1015|2014:1004,1006|2015:0923|2016:0913|2017:0901|2018:0821|2019:0811-0812|2020:0731|2021:0720|2022:0709,0711|2023:0628|2024:0617|2025:0606-0607|2026:0527|2027:0516-0517|2028:0505|2029:0424|2030:0413,0415|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218-0219|2036:0207|2037:0126|2038:0116,0118|2039:0105,1226|2040:1214|2041:1204|2042:1123-1124|2043:1112|2044:1031|2045:1021,1023|2046:1010|2047:0930|2048:0919,0921|2049:0908|2050:0828-0829|2051:0817|2052:0805|2053:0726,0728|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601,0603|2059:0522|2060:0510","estimated":"y|2011:1106|2012:1026|2015:0923|2018:0821|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510"},{"id":"australia_day","name":"Australia Day","en":"Australia Day","category":"public","dates":"d|0126:2010-2060|0127:2014,2020,2025,2031,2042,2048,2053,2059|0128:2013,2019,2030,2036,2041,2047,2058"},{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_monday","name":"Easter Monday","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"act_of_self_determination_day","name":"Act of Self Determination Day","en":"Act of Self Determination Day","category":"public","dates":"d|0406:2010-2060|0407:2014,2025,2031,2036,2042,2053,2059|0408:2013,2024,2030,2041,2047,2052,2058|0410:2019"},{"id":"anzac_day","name":"ANZAC Day","en":"ANZAC Day","category":"public","dates":"d|0425:2010-2060|0426:2010,2021,2027,2032,2038,2049,2055,2060|0427:2015,2020,2026,2037,2043,2048,2054"},{"id":"king_s_birthday","name":"King's Birthday","en":"King's Birthday","category":"public","dates":"d|0606:2023-2024|0608:2026,2037,2043,2048,2054|0609:2025,2031,2036,2042,2053,2059|0610:2030,2041,2047,2052,2058|0611:2029,2035,2040,2046,2057|0612:2028,2034,2045,2051,2056|0613:2033,2039,2044,2050|0614:2027,2032,2038,2049,2055,2060"},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060|1226:2011,2016,2022,2033,2039,2044,2050|1227:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"boxing_day","name":"Boxing Day","en":"Boxing Day","category":"public","dates":"d|1226:2010-2060|1227:2011,2016,2022,2033,2039,2044,2050|1228:2010,2015,2020-2021,2026-2027,2032,2037-2038,2043,2048-2049,2054-2055,2060"}]} diff --git a/app/Support/Holidays/data/CD.json b/app/Support/Holidays/data/CD.json new file mode 100644 index 000000000..237164154 --- /dev/null +++ b/app/Support/Holidays/data/CD.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"CD","language":"fr","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Nouvel an","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|1231:2011,2016,2022,2033,2039,2044,2050"},{"id":"martyrs_day","name":"Martyrs de l'indépendance","en":"Martyrs' Day","category":"public","dates":"d|0103:2015,2026,2032,2037,2043,2054,2060|0104:2015-2060"},{"id":"national_hero_laurent_desire_kabila_day","name":"Journée du héros national Laurent Désiré Kabila","en":"National Hero Laurent Désiré Kabila Day","category":"public","dates":"d|0115:2022,2028,2033,2039,2050,2056|0116:2015-2060"},{"id":"national_hero_patrice_emery_lumumba_day","name":"Journée du héros national Patrice Emery Lumumba","en":"National Hero Patrice Emery Lumumba Day","category":"public","dates":"d|0116:2016,2021,2027,2038,2044,2049,2055|0117:2015-2060"},{"id":"day_of_the_struggle_of_simon_kimbangu_and_africa","name":"Journée du combat de Simon Kimbangu et de la conscience africaine","en":"Day of the Struggle of Simon Kimbangu and African Consciousness","category":"public","dates":"d|0405:2025,2031,2036,2042,2053,2059|0406:2023-2060"},{"id":"labor_day","name":"Fête du travail","en":"Labor Day","category":"public","dates":"d|0430:2011,2016,2022,2033,2039,2044,2050|0501:2010-2060"},{"id":"revolution_and_armed_forces_day","name":"Journée de la Révolution et des Forces Armées","en":"Revolution and Armed Forces Day","category":"public","dates":"d|0516:2015,2020,2026,2037,2043,2048,2054|0517:2014-2060"},{"id":"independence_day","name":"Journée de l'indépendance","en":"Independence Day","category":"public","dates":"d|0629:2013,2019,2024,2030,2041,2047,2052,2058|0630:2010-2060"},{"id":"parents_day","name":"Fête des parents","en":"Parents' Day","category":"public","dates":"d|0731:2010,2021,2027,2032,2038,2049,2055,2060|0801:2010-2060"},{"id":"congolese_genocide_memorial_day","name":"Journée commémorative du génocide Congolais","en":"Congolese Genocide Memorial Day","category":"public","dates":"d|0801:2026,2037,2043,2048,2054|0802:2024-2060"},{"id":"christmas_day","name":"Noël","en":"Christmas Day","category":"public","dates":"d|1224:2011,2016,2022,2033,2039,2044,2050|1225:2010-2060"}]} diff --git a/app/Support/Holidays/data/CF.json b/app/Support/Holidays/data/CF.json new file mode 100644 index 000000000..eae4e5f81 --- /dev/null +++ b/app/Support/Holidays/data/CF.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"CF","language":"fr","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Jour de l'an","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"eid_al_fitr","name":"Aïd al-Fitr","en":"Eid al-Fitr","category":"public","dates":"y|2015:0718|2016:0707|2017:0626|2018:0615|2019:0604|2020:0524|2021:0513|2022:0502|2023:0421|2024:0410|2025:0330|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304","estimated":"y|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304"},{"id":"eid_al_adha","name":"Aïd al-Adha","en":"Eid al-Adha","category":"public","dates":"y|2015:0924|2016:0913|2017:0902|2018:0821|2019:0811|2020:0731|2021:0720|2022:0709|2023:0628|2024:0616|2025:0607|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510","estimated":"y|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510"},{"id":"easter_monday","name":"Lundi de Pâques","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"barthelemy_boganda_day","name":"Journée Barthélemy Boganda","en":"Barthélemy Boganda Day","category":"public","dates":"d|0329:2010-2060"},{"id":"labor_day","name":"Fête du Travail","en":"Labor Day","category":"public","dates":"d|0501:2010-2060"},{"id":"ascension_day","name":"Ascension","en":"Ascension Day","category":"public","dates":"d|0503:2035,2046|0505:2016|0506:2027,2032|0507:2043,2054|0508:2059|0509:2013,2024|0510:2018,2029,2040|0511:2051,2056|0513:2010,2021|0514:2015,2026,2037,2048|0515:2042,2053|0517:2012|0518:2023,2034,2045|0519:2039,2050|0521:2020|0522:2031,2036|0523:2047,2058|0525:2017,2028|0526:2022,2033,2044|0527:2049,2055,2060|0529:2014,2025|0530:2019,2030,2041,2052|0531:2057|0602:2011|0603:2038"},{"id":"pentecost_monday","name":"Lundi de Pentecôte","en":"Pentecost Monday","category":"public","dates":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038"},{"id":"general_prayer_day","name":"Journée de prière générale","en":"General Prayer Day","category":"public","dates":"d|0630:2010-2060"},{"id":"independence_day","name":"Jour de l'indépendance","en":"Independence Day","category":"public","dates":"d|0813:2010-2060"},{"id":"assumption_day","name":"Assomption","en":"Assumption Day","category":"public","dates":"d|0815:2010-2060"},{"id":"all_saints_day","name":"Toussaint","en":"All Saints' Day","category":"public","dates":"d|1101:2010-2060"},{"id":"national_day","name":"Fête nationale","en":"National Day","category":"public","dates":"d|1201:2010-2060"},{"id":"christmas_day","name":"Jour de Noël","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"}]} diff --git a/app/Support/Holidays/data/CG.json b/app/Support/Holidays/data/CG.json new file mode 100644 index 000000000..a517bd257 --- /dev/null +++ b/app/Support/Holidays/data/CG.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"CG","language":"fr","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Jour de l'An","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"easter_monday","name":"Lundi de Pâques","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"labor_day","name":"Fête du Travail","en":"Labor Day","category":"public","dates":"d|0501:2010-2060"},{"id":"ascension_day","name":"Ascension","en":"Ascension Day","category":"public","dates":"d|0503:2035,2046|0505:2016|0506:2027,2032|0507:2043,2054|0508:2059|0509:2013,2024|0510:2018,2029,2040|0511:2051,2056|0513:2010,2021|0514:2015,2026,2037,2048|0515:2042,2053|0517:2012|0518:2023,2034,2045|0519:2039,2050|0521:2020|0522:2031,2036|0523:2047,2058|0525:2017,2028|0526:2022,2033,2044|0527:2049,2055,2060|0529:2014,2025|0530:2019,2030,2041,2052|0531:2057|0602:2011|0603:2038"},{"id":"pentecost_monday","name":"Lundi de Pentecôte","en":"Pentecost Monday","category":"public","dates":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038"},{"id":"reconciliation_day","name":"Fête de la Réconciliation","en":"Reconciliation Day","category":"public","dates":"d|0610:2010-2060"},{"id":"national_day","name":"Fête Nationale","en":"National Day","category":"public","dates":"d|0815:2010-2060"},{"id":"all_saints_day","name":"Toussaint","en":"All Saints' Day","category":"public","dates":"d|1101:2010-2060"},{"id":"republic_day","name":"Jour de la République","en":"Republic Day","category":"public","dates":"d|1128:2010-2060"},{"id":"christmas_day","name":"Noël","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"}]} diff --git a/app/Support/Holidays/data/CH.json b/app/Support/Holidays/data/CH.json new file mode 100644 index 000000000..c65aea20d --- /dev/null +++ b/app/Support/Holidays/data/CH.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"CH","language":"de","years":[2010,2060],"subdivisions":{"AG":"Aargau","AI":"Appenzell Innerrhoden","AR":"Appenzell Ausserrhoden","BE":"Bern","BL":"Basel-Landschaft","BS":"Basel-Stadt","FR":"Freiburg","GE":"Genève","GL":"Glarus","GR":"Graubünden","JU":"Jura","LU":"Luzern","NE":"Neuchâtel","NW":"Nidwalden","OW":"Obwalden","SG":"Sankt Gallen","SH":"Schaffhausen","SO":"Solothurn","SZ":"Schwyz","TG":"Thurgau","TI":"Ticino","UR":"Uri","VD":"Vaud","VS":"Valais","ZG":"Zug","ZH":"Zürich","Stadt Zurich":"Stadt Zürich"},"holidays":[{"id":"new_year_s_day","name":"Neujahrstag","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"saint_berchtold_s_day","name":"Berchtoldstag","en":"Saint Berchtold's Day","category":"public","regional":{"AG":"d|0102:2010-2060","BE":"d|0102:2010-2060","JU":"d|0102:2010-2060","LU":"d|0102:2010-2060","NE":"d|0102:2012,2017,2023,2034,2040,2045,2051","Stadt Zurich":"d|0102:2010-2060","TG":"d|0102:2010-2060","VD":"d|0102:2010-2060"}},{"id":"epiphany","name":"Heilige Drei Könige","en":"Epiphany","category":"public","regional":{"SZ":"d|0106:2010-2060","TI":"d|0106:2010-2060","UR":"d|0106:2010-2060"}},{"id":"republic_day","name":"Jahrestag der Ausrufung der Republik","en":"Republic Day","category":"public","regional":{"NE":"d|0301:2010-2060"}},{"id":"saint_joseph_s_day","name":"Josefstag","en":"Saint Joseph's Day","category":"public","regional":{"NW":"d|0319:2010-2060","SZ":"d|0319:2010-2060","TI":"d|0319:2010-2060","UR":"d|0319:2010-2060","VS":"d|0319:2010-2060"}},{"id":"good_friday","name":"Karfreitag","en":"Good Friday","category":"public","regional":{"AG":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038","AI":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038","AR":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038","BE":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038","BL":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038","BS":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038","FR":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038","GE":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038","GL":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038","JU":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038","LU":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038","NE":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038","NW":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038","OW":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038","SG":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038","SH":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038","SO":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038","SZ":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038","Stadt Zurich":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038","TG":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038","UR":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038","VD":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038","ZG":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038","ZH":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"}},{"id":"easter_monday","name":"Ostermontag","en":"Easter Monday","category":"public","regional":{"AG":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038","AI":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038","AR":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038","BE":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038","BL":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038","BS":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038","GE":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038","GL":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038","GR":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038","JU":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038","LU":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038","SG":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038","SH":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038","SZ":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038","Stadt Zurich":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038","TG":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038","TI":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038","UR":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038","VD":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038","ZH":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"}},{"id":"battle_of_naefels_victory_day","name":"Näfelser Fahrt","en":"Battle of Naefels Victory Day","category":"public","regional":{"GL":"d|0401:2027,2032,2038,2049,2055,2060|0402:2020,2043,2054|0403:2014,2025,2031,2036,2059|0404:2013,2019,2024,2030,2041,2047,2052,2058|0405:2018,2029,2035,2040,2046,2057|0406:2017,2028,2051,2056|0407:2011,2016,2022,2033,2044|0408:2010,2021|0409:2015,2026,2037,2048|0410:2042,2053|0412:2012|0413:2023,2034,2045|0414:2039,2050"}},{"id":"labor_day","name":"Tag der Arbeit","en":"Labor Day","category":"public","regional":{"AG":"d|0501:2010-2060","BL":"d|0501:2010-2060","BS":"d|0501:2010-2060","JU":"d|0501:2010-2060","NE":"d|0501:2010-2060","SH":"d|0501:2010-2060","Stadt Zurich":"d|0501:2010-2060","TG":"d|0501:2010-2060","TI":"d|0501:2010-2060","ZH":"d|0501:2010-2060"}},{"id":"ascension_day","name":"Auffahrt","en":"Ascension Day","category":"public","dates":"d|0503:2035,2046|0505:2016|0506:2027,2032|0507:2043,2054|0508:2059|0509:2013,2024|0510:2018,2029,2040|0511:2051,2056|0513:2010,2021|0514:2015,2026,2037,2048|0515:2042,2053|0517:2012|0518:2023,2034,2045|0519:2039,2050|0521:2020|0522:2031,2036|0523:2047,2058|0525:2017,2028|0526:2022,2033,2044|0527:2049,2055,2060|0529:2014,2025|0530:2019,2030,2041,2052|0531:2057|0602:2011|0603:2038"},{"id":"pentecost_monday","name":"Pfingstmontag","en":"Pentecost Monday","category":"public","regional":{"AG":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038","AI":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038","AR":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038","BE":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038","BL":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038","BS":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038","GE":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038","GL":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038","GR":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038","JU":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038","LU":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038","SG":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038","SH":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038","SZ":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038","Stadt Zurich":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038","TG":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038","TI":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038","UR":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038","VD":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038","ZH":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038"}},{"id":"corpus_christi","name":"Fronleichnam","en":"Corpus Christi","category":"public","regional":{"AG":"d|0524:2035,2046|0526:2016|0527:2027,2032|0528:2043,2054|0529:2059|0530:2013,2024|0531:2018,2029,2040|0601:2051,2056|0603:2010,2021|0604:2015,2026,2037,2048|0605:2042,2053|0607:2012|0608:2023,2034,2045|0609:2039,2050|0611:2020|0612:2031,2036|0613:2047,2058|0615:2017,2028|0616:2022,2033,2044|0617:2049,2055,2060|0619:2014,2025|0620:2019,2030,2041,2052|0621:2057|0623:2011|0624:2038","AI":"d|0524:2035,2046|0526:2016|0527:2027,2032|0528:2043,2054|0529:2059|0530:2013,2024|0531:2018,2029,2040|0601:2051,2056|0603:2010,2021|0604:2015,2026,2037,2048|0605:2042,2053|0607:2012|0608:2023,2034,2045|0609:2039,2050|0611:2020|0612:2031,2036|0613:2047,2058|0615:2017,2028|0616:2022,2033,2044|0617:2049,2055,2060|0619:2014,2025|0620:2019,2030,2041,2052|0621:2057|0623:2011|0624:2038","JU":"d|0524:2035,2046|0526:2016|0527:2027,2032|0528:2043,2054|0529:2059|0530:2013,2024|0531:2018,2029,2040|0601:2051,2056|0603:2010,2021|0604:2015,2026,2037,2048|0605:2042,2053|0607:2012|0608:2023,2034,2045|0609:2039,2050|0611:2020|0612:2031,2036|0613:2047,2058|0615:2017,2028|0616:2022,2033,2044|0617:2049,2055,2060|0619:2014,2025|0620:2019,2030,2041,2052|0621:2057|0623:2011|0624:2038","LU":"d|0524:2035,2046|0526:2016|0527:2027,2032|0528:2043,2054|0529:2059|0530:2013,2024|0531:2018,2029,2040|0601:2051,2056|0603:2010,2021|0604:2015,2026,2037,2048|0605:2042,2053|0607:2012|0608:2023,2034,2045|0609:2039,2050|0611:2020|0612:2031,2036|0613:2047,2058|0615:2017,2028|0616:2022,2033,2044|0617:2049,2055,2060|0619:2014,2025|0620:2019,2030,2041,2052|0621:2057|0623:2011|0624:2038","NE":"d|0524:2035,2046|0526:2016|0527:2027,2032|0528:2043,2054|0529:2059|0530:2013,2024|0531:2018,2029,2040|0601:2051,2056|0603:2010,2021|0604:2015,2026,2037,2048|0605:2042,2053|0607:2012|0608:2023,2034,2045|0609:2039,2050|0611:2020|0612:2031,2036|0613:2047,2058|0615:2017,2028|0616:2022,2033,2044|0617:2049,2055,2060|0619:2014,2025|0620:2019,2030,2041,2052|0621:2057|0623:2011|0624:2038","NW":"d|0524:2035,2046|0526:2016|0527:2027,2032|0528:2043,2054|0529:2059|0530:2013,2024|0531:2018,2029,2040|0601:2051,2056|0603:2010,2021|0604:2015,2026,2037,2048|0605:2042,2053|0607:2012|0608:2023,2034,2045|0609:2039,2050|0611:2020|0612:2031,2036|0613:2047,2058|0615:2017,2028|0616:2022,2033,2044|0617:2049,2055,2060|0619:2014,2025|0620:2019,2030,2041,2052|0621:2057|0623:2011|0624:2038","OW":"d|0524:2035,2046|0526:2016|0527:2027,2032|0528:2043,2054|0529:2059|0530:2013,2024|0531:2018,2029,2040|0601:2051,2056|0603:2010,2021|0604:2015,2026,2037,2048|0605:2042,2053|0607:2012|0608:2023,2034,2045|0609:2039,2050|0611:2020|0612:2031,2036|0613:2047,2058|0615:2017,2028|0616:2022,2033,2044|0617:2049,2055,2060|0619:2014,2025|0620:2019,2030,2041,2052|0621:2057|0623:2011|0624:2038","SZ":"d|0524:2035,2046|0526:2016|0527:2027,2032|0528:2043,2054|0529:2059|0530:2013,2024|0531:2018,2029,2040|0601:2051,2056|0603:2010,2021|0604:2015,2026,2037,2048|0605:2042,2053|0607:2012|0608:2023,2034,2045|0609:2039,2050|0611:2020|0612:2031,2036|0613:2047,2058|0615:2017,2028|0616:2022,2033,2044|0617:2049,2055,2060|0619:2014,2025|0620:2019,2030,2041,2052|0621:2057|0623:2011|0624:2038","TI":"d|0524:2035,2046|0526:2016|0527:2027,2032|0528:2043,2054|0529:2059|0530:2013,2024|0531:2018,2029,2040|0601:2051,2056|0603:2010,2021|0604:2015,2026,2037,2048|0605:2042,2053|0607:2012|0608:2023,2034,2045|0609:2039,2050|0611:2020|0612:2031,2036|0613:2047,2058|0615:2017,2028|0616:2022,2033,2044|0617:2049,2055,2060|0619:2014,2025|0620:2019,2030,2041,2052|0621:2057|0623:2011|0624:2038","UR":"d|0524:2035,2046|0526:2016|0527:2027,2032|0528:2043,2054|0529:2059|0530:2013,2024|0531:2018,2029,2040|0601:2051,2056|0603:2010,2021|0604:2015,2026,2037,2048|0605:2042,2053|0607:2012|0608:2023,2034,2045|0609:2039,2050|0611:2020|0612:2031,2036|0613:2047,2058|0615:2017,2028|0616:2022,2033,2044|0617:2049,2055,2060|0619:2014,2025|0620:2019,2030,2041,2052|0621:2057|0623:2011|0624:2038","VS":"d|0524:2035,2046|0526:2016|0527:2027,2032|0528:2043,2054|0529:2059|0530:2013,2024|0531:2018,2029,2040|0601:2051,2056|0603:2010,2021|0604:2015,2026,2037,2048|0605:2042,2053|0607:2012|0608:2023,2034,2045|0609:2039,2050|0611:2020|0612:2031,2036|0613:2047,2058|0615:2017,2028|0616:2022,2033,2044|0617:2049,2055,2060|0619:2014,2025|0620:2019,2030,2041,2052|0621:2057|0623:2011|0624:2038","ZG":"d|0524:2035,2046|0526:2016|0527:2027,2032|0528:2043,2054|0529:2059|0530:2013,2024|0531:2018,2029,2040|0601:2051,2056|0603:2010,2021|0604:2015,2026,2037,2048|0605:2042,2053|0607:2012|0608:2023,2034,2045|0609:2039,2050|0611:2020|0612:2031,2036|0613:2047,2058|0615:2017,2028|0616:2022,2033,2044|0617:2049,2055,2060|0619:2014,2025|0620:2019,2030,2041,2052|0621:2057|0623:2011|0624:2038"}},{"id":"independence_day","name":"Fest der Unabhängigkeit","en":"Independence Day","category":"public","regional":{"JU":"d|0623:2010-2060"}},{"id":"saints_peter_and_paul","name":"Peter und Paul","en":"Saints Peter and Paul","category":"public","regional":{"TI":"d|0629:2010-2060"}},{"id":"national_day","name":"Nationalfeiertag","en":"National Day","category":"public","dates":"d|0801:2010-2060"},{"id":"assumption_day","name":"Mariä Himmelfahrt","en":"Assumption Day","category":"public","regional":{"AG":"d|0815:2010-2060","AI":"d|0815:2010-2060","JU":"d|0815:2010-2060","LU":"d|0815:2010-2060","NW":"d|0815:2010-2060","OW":"d|0815:2010-2060","SZ":"d|0815:2010-2060","TI":"d|0815:2010-2060","UR":"d|0815:2010-2060","VS":"d|0815:2010-2060","ZG":"d|0815:2010-2060"}},{"id":"genevan_fast","name":"Genfer Bettag","en":"Genevan Fast","category":"public","regional":{"GE":"d|0905:2013,2019,2024,2030,2041,2047,2052,2058|0906:2012,2018,2029,2035,2040,2046,2057|0907:2017,2023,2028,2034,2045,2051,2056|0908:2011,2016,2022,2033,2039,2044,2050|0909:2010,2021,2027,2032,2038,2049,2055,2060|0910:2015,2020,2026,2037,2043,2048,2054|0911:2014,2025,2031,2036,2042,2053,2059"}},{"id":"knabenschiessen","name":"Knabenschiessen","en":"Knabenschiessen","category":"public","regional":{"Stadt Zurich":"d|0907:2013,2019,2024,2030,2041,2047,2052,2058|0908:2012-2013,2018-2019,2024,2029-2030,2035,2040-2041,2046-2047,2052,2057-2058|0909:2012,2017-2018,2023,2028-2029,2034-2035,2040,2045-2046,2051,2056-2057|0910:2011,2016-2017,2022-2023,2028,2033-2034,2039,2044-2045,2050-2051,2056|0911:2010-2011,2016,2021-2022,2027,2032-2033,2038-2039,2044,2049-2050,2055,2060|0912:2010,2015,2020-2021,2026-2027,2032,2037-2038,2043,2048-2049,2054-2055,2060|0913:2014-2015,2020,2025-2026,2031,2036-2037,2042-2043,2048,2053-2054,2059|0914:2014,2025,2031,2036,2042,2053,2059"}},{"id":"prayer_monday","name":"Bettagsmontag","en":"Prayer Monday","category":"public","regional":{"VD":"d|0916:2013,2019,2024,2030,2041,2047,2052,2058|0917:2012,2018,2029,2035,2040,2046,2057|0918:2017,2023,2028,2034,2045,2051,2056|0919:2011,2016,2022,2033,2039,2044,2050|0920:2010,2021,2027,2032,2038,2049,2055,2060|0921:2015,2020,2026,2037,2043,2048,2054|0922:2014,2025,2031,2036,2042,2053,2059"}},{"id":"saint_nicholas_of_flue","name":"Bruder Klaus","en":"Saint Nicholas of Flüe","category":"public","regional":{"OW":"d|0925:2010-2060"}},{"id":"all_saints_day","name":"Allerheiligen","en":"All Saints' Day","category":"public","regional":{"AG":"d|1101:2010-2060","AI":"d|1101:2010-2060","GL":"d|1101:2010-2060","JU":"d|1101:2010-2060","LU":"d|1101:2010-2060","NW":"d|1101:2010-2060","OW":"d|1101:2010-2060","SG":"d|1101:2010-2060","SZ":"d|1101:2010-2060","TI":"d|1101:2010-2060","UR":"d|1101:2010-2060","VS":"d|1101:2010-2060","ZG":"d|1101:2010-2060"}},{"id":"immaculate_conception","name":"Mariä Empfängnis","en":"Immaculate Conception","category":"public","regional":{"AG":"d|1208:2010-2060","AI":"d|1208:2010-2060","LU":"d|1208:2010-2060","NW":"d|1208:2010-2060","OW":"d|1208:2010-2060","SZ":"d|1208:2010-2060","TI":"d|1208:2010-2060","UR":"d|1208:2010-2060","VS":"d|1208:2010-2060","ZG":"d|1208:2010-2060"}},{"id":"christmas_day","name":"Weihnachten","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"},{"id":"saint_stephen_s_day","name":"Stephanstag","en":"Saint Stephen's Day","category":"public","regional":{"AG":"d|1226:2010-2060","AI":"d|1226:2010-2014,2016,2018-2019,2021-2022,2024-2025,2027,2029-2033,2035-2036,2038-2042,2044,2046-2047,2049-2050,2052-2053,2055,2057-2060","AR":"d|1226:2010-2014,2016,2018-2019,2021-2022,2024-2025,2027,2029-2033,2035-2036,2038-2042,2044,2046-2047,2049-2050,2052-2053,2055,2057-2060","BE":"d|1226:2010-2060","BL":"d|1226:2010-2060","BS":"d|1226:2010-2060","GL":"d|1226:2010-2060","GR":"d|1226:2010-2060","LU":"d|1226:2010-2060","NE":"d|1226:2011,2016,2022,2033,2039,2044,2050","SG":"d|1226:2010-2060","SH":"d|1226:2010-2060","SZ":"d|1226:2010-2060","Stadt Zurich":"d|1226:2010-2060","TG":"d|1226:2010-2060","TI":"d|1226:2010-2060","UR":"d|1226:2010-2014,2016,2018-2019,2021-2022,2024-2025,2027,2029-2033,2035-2036,2038-2042,2044,2046-2047,2049-2050,2052-2053,2055,2057-2060","ZH":"d|1226:2010-2060"}},{"id":"restoration_day","name":"Wiederherstellung der Republik","en":"Restoration Day","category":"public","regional":{"GE":"d|1231:2010-2060"}},{"id":"saint_berchtold_s_day_optional","name":"Berchtoldstag","en":"Saint Berchtold's Day","category":"optional","regional":{"FR":"d|0102:2010-2060","GL":"d|0102:2010-2060","NW":"d|0102:2010-2060","OW":"d|0102:2010-2060","SG":"d|0102:2010-2060","SH":"d|0102:2010-2060","SO":"d|0102:2010-2060","VS":"d|0102:2010-2060","ZG":"d|0102:2010-2060"}},{"id":"day_before_good_friday_half_day","name":"Vortag vor Karfreitag","en":"Day before Good Friday","category":"half_day","regional":{"Stadt Zurich":"d|0322:2035,2046|0324:2016|0325:2027,2032|0326:2043,2054|0327:2059|0328:2013,2024|0329:2018,2029,2040|0330:2051,2056|0401:2010,2021|0402:2015,2026,2037,2048|0403:2042,2053|0405:2012|0406:2023,2034,2045|0407:2039,2050|0409:2020|0410:2031,2036|0411:2047,2058|0413:2017,2028|0414:2022,2033,2044|0415:2049,2055,2060|0417:2014,2025|0418:2019,2030,2041,2052|0419:2057|0421:2011|0422:2038"}},{"id":"day_before_good_friday_optional","name":"Vortag vor Karfreitag","en":"Day before Good Friday","category":"optional","regional":{"Stadt Zurich":"d|0322:2035,2046|0324:2016|0325:2027,2032|0326:2043,2054|0327:2059|0328:2013,2024|0329:2018,2029,2040|0330:2051,2056|0401:2010,2021|0402:2015,2026,2037,2048|0403:2042,2053|0405:2012|0406:2023,2034,2045|0407:2039,2050|0409:2020|0410:2031,2036|0411:2047,2058|0413:2017,2028|0414:2022,2033,2044|0415:2049,2055,2060|0417:2014,2025|0418:2019,2030,2041,2052|0419:2057|0421:2011|0422:2038"}},{"id":"good_friday_optional","name":"Karfreitag","en":"Good Friday","category":"optional","regional":{"GR":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"}},{"id":"easter_sunday_de_facto","name":"Ostersonntag","en":"Easter Sunday","category":"de_facto","regional":{"Stadt Zurich":"d|0325:2035,2046|0327:2016|0328:2027,2032|0329:2043,2054|0330:2059|0331:2013,2024|0401:2018,2029,2040|0402:2051,2056|0404:2010,2021|0405:2015,2026,2037,2048|0406:2042,2053|0408:2012|0409:2023,2034,2045|0410:2039,2050|0412:2020|0413:2031,2036|0414:2047,2058|0416:2017,2028|0417:2022,2033,2044|0418:2049,2055,2060|0420:2014,2025|0421:2019,2030,2041,2052|0422:2057|0424:2011|0425:2038","ZH":"d|0325:2035,2046|0327:2016|0328:2027,2032|0329:2043,2054|0330:2059|0331:2013,2024|0401:2018,2029,2040|0402:2051,2056|0404:2010,2021|0405:2015,2026,2037,2048|0406:2042,2053|0408:2012|0409:2023,2034,2045|0410:2039,2050|0412:2020|0413:2031,2036|0414:2047,2058|0416:2017,2028|0417:2022,2033,2044|0418:2049,2055,2060|0420:2014,2025|0421:2019,2030,2041,2052|0422:2057|0424:2011|0425:2038"}},{"id":"easter_monday_optional","name":"Ostermontag","en":"Easter Monday","category":"optional","regional":{"FR":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038","NW":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038","OW":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038","VS":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038","ZG":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"}},{"id":"sechselauten_half_day","name":"Sechseläuten","en":"Sechseläuten","category":"half_day","regional":{"Stadt Zurich":"d|0415:2013,2019,2024,2030,2041,2052|0416:2012,2018,2029,2035,2040,2046,2057|0417:2023,2034,2045,2051,2056|0418:2011,2016,2039,2050|0419:2010,2021,2027,2032,2038|0420:2015,2020,2026,2037,2043,2048,2054|0421:2031,2036,2042,2053,2059|0422:2047,2058|0424:2017,2028|0425:2022,2033,2044|0426:2049,2055,2060|0428:2014,2025"}},{"id":"sechselauten_optional","name":"Sechseläuten","en":"Sechseläuten","category":"optional","regional":{"Stadt Zurich":"d|0415:2013,2019,2024,2030,2041,2052|0416:2012,2018,2029,2035,2040,2046,2057|0417:2023,2034,2045,2051,2056|0418:2011,2016,2039,2050|0419:2010,2021,2027,2032,2038|0420:2015,2020,2026,2037,2043,2048,2054|0421:2031,2036,2042,2053,2059|0422:2047,2058|0424:2017,2028|0425:2022,2033,2044|0426:2049,2055,2060|0428:2014,2025"}},{"id":"labor_day_half_day","name":"Tag der Arbeit","en":"Labor Day","category":"half_day","regional":{"SO":"d|0501:2010-2060"}},{"id":"day_before_ascension_day_half_day","name":"Vortag vor Auffahrt","en":"Day before Ascension Day","category":"half_day","regional":{"Stadt Zurich":"d|0502:2035,2046|0504:2016|0505:2027,2032|0506:2043,2054|0507:2059|0508:2013,2024|0509:2018,2029,2040|0510:2051,2056|0512:2010,2021|0513:2015,2026,2037,2048|0514:2042,2053|0516:2012|0517:2023,2034,2045|0518:2039,2050|0520:2020|0521:2031,2036|0522:2047,2058|0524:2017,2028|0525:2022,2033,2044|0526:2049,2055,2060|0528:2014,2025|0529:2019,2030,2041,2052|0530:2057|0601:2011|0602:2038"}},{"id":"bridge_holiday_for_ascension_day_optional","name":"Brückentag nach Auffahrt","en":"Bridge Holiday for Ascension Day","category":"optional","regional":{"Stadt Zurich":"d|0504:2035,2046|0509:2059|0510:2013,2024|0511:2018,2029,2040|0516:2042,2053|0518:2012|0523:2031,2036|0524:2047,2058|0530:2014,2025|0531:2019,2030,2041,2052|0601:2057"}},{"id":"pentecost_de_facto","name":"Pfingstsonntag","en":"Pentecost","category":"de_facto","regional":{"Stadt Zurich":"d|0513:2035,2046|0515:2016|0516:2027,2032|0517:2043,2054|0518:2059|0519:2013,2024|0520:2018,2029,2040|0521:2051,2056|0523:2010,2021|0524:2015,2026,2037,2048|0525:2042,2053|0527:2012|0528:2023,2034,2045|0529:2039,2050|0531:2020|0601:2031,2036|0602:2047,2058|0604:2017,2028|0605:2022,2033,2044|0606:2049,2055,2060|0608:2014,2025|0609:2019,2030,2041,2052|0610:2057|0612:2011|0613:2038","ZH":"d|0513:2035,2046|0515:2016|0516:2027,2032|0517:2043,2054|0518:2059|0519:2013,2024|0520:2018,2029,2040|0521:2051,2056|0523:2010,2021|0524:2015,2026,2037,2048|0525:2042,2053|0527:2012|0528:2023,2034,2045|0529:2039,2050|0531:2020|0601:2031,2036|0602:2047,2058|0604:2017,2028|0605:2022,2033,2044|0606:2049,2055,2060|0608:2014,2025|0609:2019,2030,2041,2052|0610:2057|0612:2011|0613:2038"}},{"id":"pentecost_monday_optional","name":"Pfingstmontag","en":"Pentecost Monday","category":"optional","regional":{"FR":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038","NW":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038","OW":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038","VS":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038","ZG":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038"}},{"id":"knabenschiessen_half_day","name":"Knabenschiessen","en":"Knabenschiessen","category":"half_day","regional":{"Stadt Zurich":"d|0909:2013,2019,2024,2030,2041,2047,2052,2058|0910:2012,2018,2029,2035,2040,2046,2057|0911:2017,2023,2028,2034,2045,2051,2056|0912:2011,2016,2022,2033,2039,2044,2050|0913:2010,2021,2027,2032,2038,2049,2055,2060|0914:2015,2020,2026,2037,2043,2048,2054|0915:2014,2025,2031,2036,2042,2053,2059"}},{"id":"knabenschiessen_optional","name":"Knabenschiessen","en":"Knabenschiessen","category":"optional","regional":{"Stadt Zurich":"d|0909:2013,2019,2024,2030,2041,2047,2052,2058|0910:2012,2018,2029,2035,2040,2046,2057|0911:2017,2023,2028,2034,2045,2051,2056|0914:2015,2020,2026,2037,2043,2048,2054|0915:2014,2025,2031,2036,2042,2053,2059"}},{"id":"federal_day_of_thanksgiving_repentance_and_praye_de_facto","name":"Eidgenössischer Bettag","en":"Federal Day of Thanksgiving, Repentance, and Prayer","category":"de_facto","regional":{"Stadt Zurich":"d|0915:2013,2019,2024,2030,2041,2047,2052,2058|0916:2012,2018,2029,2035,2040,2046,2057|0917:2017,2023,2028,2034,2045,2051,2056|0918:2011,2016,2022,2033,2039,2044,2050|0919:2010,2021,2027,2032,2038,2049,2055,2060|0920:2015,2020,2026,2037,2043,2048,2054|0921:2014,2025,2031,2036,2042,2053,2059","ZH":"d|0915:2013,2019,2024,2030,2041,2047,2052,2058|0916:2012,2018,2029,2035,2040,2046,2057|0917:2017,2023,2028,2034,2045,2051,2056|0918:2011,2016,2022,2033,2039,2044,2050|0919:2010,2021,2027,2032,2038,2049,2055,2060|0920:2015,2020,2026,2037,2043,2048,2054|0921:2014,2025,2031,2036,2042,2053,2059"}},{"id":"christmas_eve_half_day","name":"Heiligabend","en":"Christmas Eve","category":"half_day","regional":{"Stadt Zurich":"d|1224:2010-2060"}},{"id":"christmas_eve_optional","name":"Heiligabend","en":"Christmas Eve","category":"optional","regional":{"Stadt Zurich":"d|1224:2010-2060"}},{"id":"saint_stephen_s_day_optional","name":"Stephanstag","en":"Saint Stephen's Day","category":"optional","regional":{"FR":"d|1226:2010-2060","NW":"d|1226:2010-2060","OW":"d|1226:2010-2060","VS":"d|1226:2010-2060","ZG":"d|1226:2010-2060"}},{"id":"bridge_holiday_optional","name":"Brückentag","en":"Bridge Holiday","category":"optional","regional":{"Stadt Zurich":"d|1227:2010-2013,2016-2019,2021-2024,2027-2030,2032-2035,2038-2041,2044-2047,2049-2052,2055-2058,2060|1228:2010-2012,2015-2018,2020-2023,2026-2029,2032-2035,2037-2040,2043-2046,2048-2051,2054-2057,2060|1229:2010-2011,2014-2017,2020-2023,2025-2028,2031-2034,2036-2039,2042-2045,2048-2051,2053-2056,2059-2060|1230:2010-2011,2013-2016,2019-2022,2024-2027,2030-2033,2036-2039,2041-2044,2047-2050,2052-2055,2058-2060"}},{"id":"new_year_s_eve_half_day","name":"Vortag vor Neujahr","en":"New Year's Eve","category":"half_day","regional":{"Stadt Zurich":"d|1231:2010-2060"}},{"id":"new_year_s_eve_optional","name":"Vortag vor Neujahr","en":"New Year's Eve","category":"optional","regional":{"Stadt Zurich":"d|1231:2010-2060"}}]} diff --git a/app/Support/Holidays/data/CI.json b/app/Support/Holidays/data/CI.json new file mode 100644 index 000000000..521b7867d --- /dev/null +++ b/app/Support/Holidays/data/CI.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"CI","language":"fr","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"1er janvier","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"eid_al_fitr","name":"Fête de fin du Ramadan","en":"Eid al-Fitr","category":"public","dates":"y|2010:0910|2011:0830|2012:0819|2013:0808|2014:0728|2015:0717|2016:0706|2017:0625|2018:0615|2019:0604|2020:0524|2021:0513|2022:0502|2023:0421|2024:0410|2025:0330|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304"},{"id":"day_after_prophet_s_birthday","name":"Lendemain de l'Anniversaire de la Naissance du Prophète Mahomet","en":"Day after Prophet's Birthday","category":"public","dates":"y|2010:0226|2011:0215|2012:0204|2013:0124|2014:0113|2015:0103,1223|2016:1211|2017:1130|2018:1120|2019:1109|2020:1029|2021:1018|2022:1008|2023:0927|2024:0915|2025:0904|2026:0825|2027:0814|2028:0803|2029:0724|2030:0713|2031:0702|2032:0620|2033:0609|2034:0530|2035:0520|2036:0508|2037:0428|2038:0417|2039:0406|2040:0325|2041:0315|2042:0304|2043:0222|2044:0211|2045:0130|2046:0119|2047:0108,1229|2048:1218|2049:1207|2050:1126|2051:1116|2052:1104|2053:1024|2054:1013|2055:1003|2056:0922|2057:0911|2058:0831|2059:0820|2060:0808"},{"id":"day_after_the_eid_al_fitr","name":"Lendemain de la Fête de fin du Ramadan","en":"Day after the Eid al-Fitr","category":"public","dates":"d|0103:2033|0331:2025|0417:2056|0525:2020|0626:2017|0713:2048|0820:2012|1008:2040|1109:2037"},{"id":"eid_al_adha","name":"Fête de la Tabaski","en":"Eid al-Adha","category":"public","dates":"y|2010:1116|2011:1106|2012:1026|2013:1015|2014:1004|2015:0923|2016:0911|2017:0901|2018:0821|2019:0811|2020:0731|2021:0720|2022:0709|2023:0628|2024:0616|2025:0606|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510"},{"id":"day_after_night_of_power","name":"Lendemain de la Nuit du Destin","en":"Day after Night of Power","category":"public","dates":"y|2010:0906|2011:0827|2012:0815|2013:0804|2014:0724|2015:0714|2016:0702|2017:0622|2018:0611|2019:0601|2020:0520|2021:0509|2022:0428|2023:0418|2024:0406|2025:0327|2026:0316|2027:0306|2028:0223|2029:0211|2030:0131|2031:0121|2032:0110,1230|2033:1219|2034:1208|2035:1127|2036:1115|2037:1105|2038:1026|2039:1015|2040:1003|2041:0923|2042:0912|2043:0901|2044:0821|2045:0810|2046:0731|2047:0720|2048:0708|2049:0628|2050:0617|2051:0606|2052:0526|2053:0516|2054:0505|2055:0424|2056:0412|2057:0401|2058:0322|2059:0312|2060:0229"},{"id":"day_after_the_eid_al_adha","name":"Lendemain de la Fête de la Tabaski","en":"Day after the Eid al-Adha","category":"public","dates":"d|0219:2035|0517:2027|0617:2024|0812:2019|0829:2050|0912:2016|1124:2042"},{"id":"easter_monday","name":"Lundi de Pâques","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"labor_day","name":"Fête du travail","en":"Labor Day","category":"public","dates":"d|0501:2010-2060"},{"id":"day_after_the_labor_day","name":"Lendemain de la Fête du travail","en":"Day after the Labor Day","category":"public","dates":"d|0502:2011,2016,2022,2033,2039,2044,2050"},{"id":"ascension_day","name":"Jour de l'Ascension","en":"Ascension Day","category":"public","dates":"d|0503:2035,2046|0505:2016|0506:2027,2032|0507:2043,2054|0508:2059|0509:2013,2024|0510:2018,2029,2040|0511:2051,2056|0513:2010,2021|0514:2015,2026,2037,2048|0515:2042,2053|0517:2012|0518:2023,2034,2045|0519:2039,2050|0521:2020|0522:2031,2036|0523:2047,2058|0525:2017,2028|0526:2022,2033,2044|0527:2049,2055,2060|0529:2014,2025|0530:2019,2030,2041,2052|0531:2057|0602:2011|0603:2038"},{"id":"pentecost_monday","name":"Lundi de Pentecôte","en":"Pentecost Monday","category":"public","dates":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038"},{"id":"independence_day","name":"Fête Nationale","en":"Independence Day","category":"public","dates":"d|0807:2010-2060"},{"id":"day_after_the_independence_day","name":"Lendemain de la Fête Nationale","en":"Day after the Independence Day","category":"public","dates":"d|0808:2011,2016,2022,2033,2039,2044,2050"},{"id":"assumption_day","name":"Fête de l'Assomption","en":"Assumption Day","category":"public","dates":"d|0815:2010-2060"},{"id":"all_saints_day","name":"Fête de la Toussaint","en":"All Saints' Day","category":"public","dates":"d|1101:2010-2060"},{"id":"national_peace_day","name":"Journée Nationale de la Paix","en":"National Peace Day","category":"public","dates":"d|1115:2010-2060"},{"id":"christmas_day","name":"Fête de Noël","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"},{"id":"day_after_the_christmas_day","name":"Lendemain de la Fête de Noël","en":"Day after the Christmas Day","category":"public","dates":"d|1226:2011,2016,2022,2033,2039,2044,2050"}]} diff --git a/app/Support/Holidays/data/CK.json b/app/Support/Holidays/data/CK.json new file mode 100644 index 000000000..492e7a05d --- /dev/null +++ b/app/Support/Holidays/data/CK.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"CK","language":"en_CK","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0103:2011-2012,2017,2022-2023,2028,2033-2034,2039-2040,2045,2050-2051,2056"},{"id":"day_after_new_year_s_day","name":"Day after New Year's Day","en":"Day after New Year's Day","category":"public","dates":"d|0102:2010-2060|0104:2010-2011,2016,2021-2022,2027-2028,2033,2038-2039,2044,2049-2050,2055-2056"},{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_monday","name":"Easter Monday","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"anzac_day","name":"Anzac Day","en":"ANZAC Day","category":"public","dates":"d|0425:2010-2060"},{"id":"sovereign_s_birthday","name":"Sovereign's Birthday","en":"Sovereign's Birthday","category":"public","dates":"d|0601:2015,2020,2026,2037,2043,2048,2054|0602:2014,2025,2031,2036,2042,2053,2059|0603:2013,2019,2024,2030,2041,2047,2052,2058|0604:2012,2018,2029,2035,2040,2046,2057|0605:2017,2023,2028,2034,2045,2051,2056|0606:2011,2016,2022,2033,2039,2044,2050|0607:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"day_of_the_house_of_ariki","name":"Ra o te Ui Ariki","en":"Day of the House of Ariki","category":"public","dates":"d|0606:2012|0701:2016,2022,2033,2039,2044,2050|0702:2021,2027,2032,2038,2049,2055,2060|0703:2015,2020,2026,2037,2043,2048,2054|0704:2014,2025,2031,2036,2042,2053,2059|0705:2013,2019,2024,2030,2041,2047,2052,2058|0706:2018,2029,2035,2040,2046,2057|0707:2017,2023,2028,2034,2045,2051,2056"},{"id":"constitution_day","name":"Constitution Day","en":"Constitution Day","category":"public","dates":"d|0804:2010-2060|0805:2013,2019,2024,2030,2041,2047,2052,2058|0806:2012,2018,2029,2035,2040,2046,2057"},{"id":"cook_islands_gospel_day","name":"Cook Islands Gospel Day","en":"Cook Islands Gospel Day","category":"public","dates":"d|1026:2010-2060|1027:2014,2025,2031,2036,2042,2053,2059|1028:2013,2019,2024,2030,2041,2047,2052,2058"},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060|1227:2010-2011,2016,2021-2022,2027,2032-2033,2038-2039,2044,2049-2050,2055,2060"},{"id":"boxing_day","name":"Boxing Day","en":"Boxing Day","category":"public","dates":"d|1226:2010-2060|1228:2010,2015,2020-2021,2026-2027,2032,2037-2038,2043,2048-2049,2054-2055,2060"}]} diff --git a/app/Support/Holidays/data/CL.json b/app/Support/Holidays/data/CL.json new file mode 100644 index 000000000..ae08ed2c4 --- /dev/null +++ b/app/Support/Holidays/data/CL.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"CL","language":"es","years":[2010,2060],"subdivisions":{"AI":"Aisén del General Carlos Ibañez del Campo","AN":"Antofagasta","AP":"Arica y Parinacota","AR":"La Araucanía","AT":"Atacama","BI":"Biobío","CO":"Coquimbo","LI":"Libertador General Bernardo O'Higgins","LL":"Los Lagos","LR":"Los Ríos","MA":"Magallanes","ML":"Maule","NB":"Ñuble","RM":"Región Metropolitana de Santiago","TA":"Tarapacá","VS":"Valparaíso"},"holidays":[{"id":"new_year_s_day","name":"Año Nuevo","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"national_holiday","name":"Feriado nacional","en":"National Holiday","category":"public","dates":"d|0102:2017,2023,2034,2040,2045,2051|0916:2022|0917:2010,2012,2018,2021,2027,2029,2032,2035,2038,2040,2046,2049,2055,2057,2060|0920:2010,2013,2019,2024,2030,2041,2047,2052,2058"},{"id":"good_friday","name":"Viernes Santo","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"holy_saturday","name":"Sábado Santo","en":"Holy Saturday","category":"public","dates":"d|0324:2035,2046|0326:2016|0327:2027,2032|0328:2043,2054|0329:2059|0330:2013,2024|0331:2018,2029,2040|0401:2051,2056|0403:2010,2021|0404:2015,2026,2037,2048|0405:2042,2053|0407:2012|0408:2023,2034,2045|0409:2039,2050|0411:2020|0412:2031,2036|0413:2047,2058|0415:2017,2028|0416:2022,2033,2044|0417:2049,2055,2060|0419:2014,2025|0420:2019,2030,2041,2052|0421:2057|0423:2011|0424:2038"},{"id":"labor_day","name":"Día Nacional del Trabajo","en":"Labor Day","category":"public","dates":"d|0501:2010-2060"},{"id":"navy_day","name":"Día de las Glorias Navales","en":"Navy Day","category":"public","dates":"d|0521:2010-2060"},{"id":"assault_and_capture_of_cape_arica","name":"Asalto y Toma del Morro de Arica","en":"Assault and Capture of Cape Arica","category":"public","regional":{"AP":"d|0607:2013-2060"}},{"id":"national_indigenous_peoples_day","name":"Día Nacional de los Pueblos Indígenas","en":"National Indigenous Peoples Day","category":"public","dates":"d|0620:2024-2025,2028-2029,2032-2033,2036-2037,2040-2041,2044-2045,2048-2050,2052-2054,2056-2058,2060|0621:2021-2023,2026-2027,2030-2031,2034-2035,2038-2039,2042-2043,2046-2047,2051,2055,2059"},{"id":"saint_peter_and_saint_paul_s_day","name":"San Pedro y San Pablo","en":"Saint Peter and Saint Paul's Day","category":"public","dates":"d|0626:2017,2023,2028,2034,2045,2051,2056|0627:2011,2016,2022,2033,2039,2044,2050|0628:2010,2021,2027,2032,2038,2049,2055,2060|0629:2013-2015,2019-2020,2024-2026,2030-2031,2036-2037,2041-2043,2047-2048,2052-2054,2058-2059|0702:2012,2018,2029,2035,2040,2046,2057"},{"id":"our_lady_of_mount_carmel","name":"Virgen del Carmen","en":"Our Lady of Mount Carmel","category":"public","dates":"d|0716:2010-2060"},{"id":"assumption_day","name":"Asunción de la Virgen","en":"Assumption Day","category":"public","dates":"d|0815:2010-2060"},{"id":"nativity_of_bernardo_o_higgins_chillan_and_chill","name":"Nacimiento del Prócer de la Independencia (Chillán y Chillán Viejo)","en":"Nativity of Bernardo O'Higgins (Chillán and Chillán Viejo communes)","category":"public","regional":{"NB":"d|0820:2014-2060"}},{"id":"independence_day","name":"Día de la Independencia","en":"Independence Day","category":"public","dates":"d|0918:2010-2060"},{"id":"army_day","name":"Día de las Glorias del Ejército","en":"Army Day","category":"public","dates":"d|0919:2010-2060"},{"id":"meeting_of_two_worlds_day","name":"Día del Encuentro de dos Mundos","en":"Meeting of Two Worlds' Day","category":"public","dates":"d|1009:2017,2023,2028,2034,2045,2051,2056|1010:2011,2016,2022,2033,2039,2044,2050|1011:2010,2021,2027,2032,2038,2049,2055,2060|1012:2013-2015,2019-2020,2024-2026,2030-2031,2036-2037,2041-2043,2047-2048,2052-2054,2058-2059|1015:2012,2018,2029,2035,2040,2046,2057"},{"id":"reformation_day","name":"Día Nacional de las Iglesias Evangélicas y Protestantes","en":"Reformation Day","category":"public","dates":"d|1027:2017,2023,2028,2034,2045,2051,2056|1031:2010-2011,2013-2016,2019-2022,2024-2027,2030-2033,2036-2039,2041-2044,2047-2050,2052-2055,2058-2060|1102:2012,2018,2029,2035,2040,2046,2057"},{"id":"all_saints_day","name":"Día de Todos los Santos","en":"All Saints' Day","category":"public","dates":"d|1101:2010-2060"},{"id":"immaculate_conception","name":"La Inmaculada Concepción","en":"Immaculate Conception","category":"public","dates":"d|1208:2010-2060"},{"id":"christmas_day","name":"Navidad","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"},{"id":"bank_holiday_bank","name":"Feriado bancario","en":"Bank Holiday","category":"bank","dates":"d|1231:2010-2060"}]} diff --git a/app/Support/Holidays/data/CM.json b/app/Support/Holidays/data/CM.json new file mode 100644 index 000000000..8f2c8e007 --- /dev/null +++ b/app/Support/Holidays/data/CM.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"CM","language":null,"years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051"},{"id":"eid_al_fitr","name":"Eid al-Fitr","en":"Eid al-Fitr","category":"public","dates":"y|2010:0910|2011:0831|2012:0819-0820|2013:0808|2014:0728|2015:0718|2016:0707|2017:0626|2018:0615|2019:0604|2020:0524-0525|2021:0513|2022:0502|2023:0421|2024:0410|2025:0330-0331|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102-0103,1223|2034:1212|2035:1201|2036:1119|2037:1108-1109|2038:1029|2039:1019|2040:1007-1008|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712-0713|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416-0417|2057:0405|2058:0325|2059:0315|2060:0304","estimated":"y|2025:0330|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304"},{"id":"mawlid","name":"Mawlid","en":"Mawlid","category":"public","dates":"y|2010:0226|2011:0216|2012:0205-0206|2013:0124|2014:0114|2015:0103,1224|2016:1212|2017:1201|2018:1121|2019:1110-1111|2020:1029|2021:1019|2022:1008|2023:0927|2024:0915-0916|2025:0904|2026:0825|2027:0814|2028:0803|2029:0724|2030:0713|2031:0702|2032:0620-0621|2033:0609|2034:0530|2035:0520-0521|2036:0508|2037:0428|2038:0417|2039:0406|2040:0325-0326|2041:0315|2042:0304|2043:0222-0223|2044:0211|2045:0130|2046:0119|2047:0108,1229-1230|2048:1218|2049:1207|2050:1126|2051:1116|2052:1104|2053:1024|2054:1013|2055:1003-1004|2056:0922|2057:0911|2058:0831|2059:0820|2060:0808-0809","estimated":"y|2023:0927|2024:0915|2025:0904|2026:0825|2027:0814|2028:0803|2029:0724|2030:0713|2031:0702|2032:0620|2033:0609|2034:0530|2035:0520|2036:0508|2037:0428|2038:0417|2039:0406|2040:0325|2041:0315|2042:0304|2043:0222|2044:0211|2045:0130|2046:0119|2047:0108,1229|2048:1218|2049:1207|2050:1126|2051:1116|2052:1104|2053:1024|2054:1013|2055:1003|2056:0922|2057:0911|2058:0831|2059:0820|2060:0808"},{"id":"eid_al_adha","name":"Eid al-Adha","en":"Eid al-Adha","category":"public","dates":"y|2010:1117|2011:1107|2012:1026|2013:1015|2014:1005-1006|2015:0924|2016:0913|2017:0902|2018:0821|2019:0811-0812|2020:0731|2021:0720|2022:0709|2023:0628|2024:0616-0617|2025:0606|2026:0527|2027:0516-0517|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218-0219|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123-1124|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828-0829|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510","estimated":"y|2024:0616|2025:0606|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510"},{"id":"youth_day","name":"Youth Day","en":"Youth Day","category":"public","dates":"d|0211:2010-2060|0212:2018,2024,2029,2035,2046,2052,2057"},{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"labour_day","name":"Labour Day","en":"Labour Day","category":"public","dates":"d|0501:2010-2060|0502:2011,2016,2033,2039,2044,2050|0503:2022"},{"id":"ascension_day","name":"Ascension Day","en":"Ascension Day","category":"public","dates":"d|0503:2035,2046|0505:2016|0506:2027,2032|0507:2043,2054|0508:2059|0509:2013,2024|0510:2018,2029,2040|0511:2051,2056|0513:2010,2021|0514:2015,2026,2037,2048|0515:2042,2053|0517:2012|0518:2023,2034,2045|0519:2039,2050|0521:2020|0522:2031,2036|0523:2047,2058|0525:2017,2028|0526:2022,2033,2044|0527:2049,2055,2060|0529:2014,2025|0530:2019,2030,2041,2052|0531:2057|0602:2011|0603:2038"},{"id":"national_day","name":"National Day","en":"National Day","category":"public","dates":"d|0520:2010-2060|0521:2012,2018,2029,2035,2040,2046,2057"},{"id":"assumption_day","name":"Assumption Day","en":"Assumption Day","category":"public","dates":"d|0815:2010-2060|0816:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060|1226:2011,2016,2022,2033,2044,2050|1227:2039"}]} diff --git a/app/Support/Holidays/data/CN.json b/app/Support/Holidays/data/CN.json new file mode 100644 index 000000000..6c857b7fd --- /dev/null +++ b/app/Support/Holidays/data/CN.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"CN","language":"zh_CN","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"元旦","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051|0103:2011,2022,2028,2033,2039,2050,2056"},{"id":"day_off","name":"休息日","en":"Day off","category":"public","dates":"y|2010:0218-0219,0614-0615,0923-0924,1006-1007|2011:0207-0208,0404,1006-1007|2012:0103,0126-0127,0402-0403,0430,1005|2013:0102-0103,0214-0215,0405,0429-0430,0610-0611,0920,1004,1007|2014:0205-0206,0502,1006-1007|2015:0102,0218,0224,0904,1007|2016:0211-0212,0610,0916,1006-1007|2017:0127,0202,0403,0529,1006|2018:0215,0221,0406,0430,1004-1005,1231|2019:0204,0208,0502-0503,1004,1007|2020:0124,0504-0505,0626,1007-1008|2021:0211,0217,0504-0505,0920,1006-1007|2022:0131,0204,0404,0503-0504,1006-1007|2023:0126-0127,0502-0503,0623,1005-1006|2024:0215-0216,0405,0502-0503,0916,1004,1007|2025:0203-0204,0505,1007-1008|2026:0102,0220,0223,0505,1006-1007"},{"id":"chinese_new_year_s_eve","name":"农历除夕","en":"Chinese New Year's Eve","category":"public","dates":"d|0121:2042|0122:2012,2031,2050|0123:2039,2058|0125:2012,2028,2047|0126:2050|0127:2036,2039,2055|0128:2025|0129:2044|0130:2033|0131:2036,2041,2052|0201:2049,2060|0202:2011,2030|0203:2033,2038,2057|0205:2027,2046,2060|0206:2030|0207:2035,2054,2057|0209:2013,2043|0210:2032,2051|0211:2040,2054,2059|0212:2013,2029|0213:2010,2048|0214:2037,2056|0215:2040|0216:2010,2026,2045|0218:2034,2037,2053|0222:2034"},{"id":"chinese_new_year_spring_festival","name":"春节","en":"Chinese New Year (Spring Festival)","category":"public","dates":"y|2010:0214-0215,0217|2011:0203-0204|2012:0123-0124|2013:0210-0211,0213|2014:0131-0204|2015:0219-0221,0223|2016:0208-0210|2017:0128-0201|2018:0216-0220|2019:0205-0207|2020:0125-0130|2021:0212-0216|2022:0201-0203|2023:0122-0125|2024:0210-0214|2025:0129-0131|2026:0217-0219|2027:0206-0210|2028:0126-0128|2029:0213-0215|2030:0203-0205,0207|2031:0123-0125,0127|2032:0211-0213|2033:0131-0202|2034:0219-0221,0223|2035:0208-0210,0212|2036:0128-0130|2037:0215-0217,0219|2038:0204-0206,0208|2039:0124-0126|2040:0212-0214,0216|2041:0201-0205|2042:0122-0124|2043:0210-0212|2044:0130-0203|2045:0217-0221|2046:0206-0208|2047:0126-0130|2048:0214-0218|2049:0202-0204|2050:0123-0125,0127|2051:0211-0215|2052:0201-0203,0205|2053:0219-0221|2054:0208-0210,0212|2055:0128-0130,0201|2056:0215-0217|2057:0204-0206,0208|2058:0124-0126,0128|2059:0212-0214|2060:0202-0204"},{"id":"tomb_sweeping_day","name":"清明节","en":"Tomb-Sweeping Day","category":"public","dates":"d|0404:2012-2013,2016-2017,2020-2021,2024-2025,2028-2029,2032-2033,2036-2037,2040-2042,2044-2046,2048-2050,2052-2054,2056-2058,2060|0405:2010-2011,2014-2015,2018-2019,2021-2023,2026-2027,2030-2032,2034-2035,2038-2039,2043,2047,2049,2051,2055,2059-2060|0406:2015,2020,2026,2037,2043,2048,2054|0407:2014,2031,2059"},{"id":"labor_day","name":"劳动节","en":"Labor Day","category":"public","dates":"d|0501:2010-2060|0502:2011,2016,2022,2025-2060|0503:2010,2021,2027,2032-2033,2038-2039,2044,2049-2050,2055,2060|0504:2026-2027,2032,2037-2038,2043,2048-2049,2054-2055,2060"},{"id":"dragon_boat_festival","name":"端午节","en":"Dragon Boat Festival","category":"public","dates":"d|0527:2039|0528:2028|0529:2028,2047|0530:2017,2036,2055|0531:2025,2044,2055|0601:2033,2052|0602:2014,2025|0603:2022,2041,2052,2060|0604:2049|0605:2030|0606:2011,2057|0607:2019,2038|0608:2046|0609:2016,2027|0610:2024,2035,2054|0611:2035,2043|0612:2013,2032|0613:2051|0614:2021,2032,2040,2059|0615:2048|0616:2010,2029,2059|0617:2056|0618:2018,2029,2037|0619:2026,2045,2056|0620:2015,2034,2053|0622:2012,2015,2023,2042|0623:2012,2042,2050|0624:2031|0625:2020,2058"},{"id":"mid_autumn_festival","name":"中秋节","en":"Mid-Autumn Festival","category":"public","dates":"d|0907:2052|0908:2014,2033|0909:2052,2060|0910:2022,2041|0911:2049|0912:2011,2022,2030|0913:2019,2038,2049,2057|0915:2016,2027,2046|0916:2035,2054|0917:2024,2035,2043,2046|0919:2013,2032,2051|0920:2032,2040|0921:2021,2059|0922:2010,2029,2048,2059|0924:2018,2029,2037,2056|0925:2026,2045,2056|0926:2053|0927:2015,2034|0928:2042|0929:2023,2042|0930:2012,2050|1001:2020,2031|1002:2039,2058|1003:2028|1004:2012,2017,2036,2047|1005:2039,2044,2055|1006:2015,2020,2025,2036"},{"id":"national_day","name":"国庆节","en":"National Day","category":"public","dates":"d|1001:2010-2060|1002:2010-2060|1003:2010-2060|1004:2010-2011,2016,2021-2023,2027-2028,2032-2034,2038-2039,2044-2045,2049-2051,2055-2056,2060|1005:2010-2011,2015-2017,2020-2022,2026-2027,2032-2033,2037-2038,2043,2048-2050,2054,2060|1006:2039,2044,2055"},{"id":"international_women_s_day_half_day","name":"国际妇女节","en":"International Women's Day","category":"half_day","dates":"d|0308:2010-2060"},{"id":"youth_day_half_day","name":"五四青年节","en":"Youth Day","category":"half_day","dates":"d|0504:2010-2060"},{"id":"children_s_day_half_day","name":"六一儿童节","en":"Children's Day","category":"half_day","dates":"d|0601:2010-2060"},{"id":"army_day_half_day","name":"建军节","en":"Army Day","category":"half_day","dates":"d|0801:2010-2060"}]} diff --git a/app/Support/Holidays/data/CO.json b/app/Support/Holidays/data/CO.json new file mode 100644 index 000000000..327827666 --- /dev/null +++ b/app/Support/Holidays/data/CO.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"CO","language":"es","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Año Nuevo","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"epiphany","name":"Día de los Reyes Magos","en":"Epiphany","category":"public","dates":"d|0106:2014,2020,2025,2031,2042,2048,2053,2059|0107:2013,2019,2030,2036,2041,2047,2058|0108:2018,2024,2029,2035,2046,2052,2057|0109:2012,2017,2023,2034,2040,2045,2051|0110:2011,2022,2028,2033,2039,2050,2056|0111:2010,2016,2021,2027,2038,2044,2049,2055|0112:2015,2026,2032,2037,2043,2054,2060"},{"id":"saint_joseph_s_day","name":"Día de San José","en":"Saint Joseph's Day","category":"public","dates":"d|0319:2012,2018,2029,2035,2040,2046,2057|0320:2017,2023,2028,2034,2045,2051,2056|0321:2011,2016,2022,2033,2039,2044,2050|0322:2010,2021,2027,2032,2038,2049,2055,2060|0323:2015,2020,2026,2037,2043,2048,2054|0324:2014,2025,2031,2036,2042,2053,2059|0325:2013,2019,2024,2030,2041,2047,2052,2058"},{"id":"maundy_thursday","name":"Jueves Santo","en":"Maundy Thursday","category":"public","dates":"d|0322:2035,2046|0324:2016|0325:2027,2032|0326:2043,2054|0327:2059|0328:2013,2024|0329:2018,2029,2040|0330:2051,2056|0401:2010,2021|0402:2015,2026,2037,2048|0403:2042,2053|0405:2012|0406:2023,2034,2045|0407:2039,2050|0409:2020|0410:2031,2036|0411:2047,2058|0413:2017,2028|0414:2022,2033,2044|0415:2049,2055,2060|0417:2014,2025|0418:2019,2030,2041,2052|0419:2057|0421:2011|0422:2038"},{"id":"good_friday","name":"Viernes Santo","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"labor_day","name":"Día del Trabajo","en":"Labor Day","category":"public","dates":"d|0501:2010-2060"},{"id":"ascension_day","name":"Ascensión del señor (observado)","en":"Ascension Day","category":"public","dates":"d|0507:2035,2046|0509:2016|0510:2027,2032|0511:2043,2054|0512:2059|0513:2013,2024|0514:2018,2029,2040|0515:2051,2056|0517:2010,2021|0518:2015,2026,2037,2048|0519:2042,2053|0521:2012|0522:2023,2034,2045|0523:2039,2050|0525:2020|0526:2031,2036|0527:2047,2058|0529:2017,2028|0530:2022,2033,2044|0531:2049,2055,2060|0602:2014,2025|0603:2019,2030,2041,2052|0604:2057|0606:2011|0607:2038"},{"id":"corpus_christi","name":"Corpus Christi (observado)","en":"Corpus Christi","category":"public","dates":"d|0528:2035,2046|0530:2016|0531:2027,2032|0601:2043,2054|0602:2059|0603:2013,2024|0604:2018,2029,2040|0605:2051,2056|0607:2010,2021|0608:2015,2026,2037,2048|0609:2042,2053|0611:2012|0612:2023,2034,2045|0613:2039,2050|0615:2020|0616:2031,2036|0617:2047,2058|0619:2017,2028|0620:2022,2033,2044|0621:2049,2055,2060|0623:2014,2025|0624:2019,2030,2041,2052|0625:2057|0627:2011|0628:2038"},{"id":"sacred_heart","name":"Sagrado Corazón (observado)","en":"Sacred Heart","category":"public","dates":"d|0604:2035,2046|0606:2016|0607:2027,2032|0608:2043,2054|0609:2059|0610:2013,2024|0611:2018,2029,2040|0612:2051,2056|0614:2010,2021|0615:2015,2026,2037,2048|0616:2042,2053|0618:2012|0619:2023,2034,2045|0620:2039,2050|0622:2020|0623:2031,2036|0624:2047,2058|0626:2017,2028|0627:2022,2033,2044|0628:2049,2055,2060|0630:2014,2025|0701:2019,2030,2041,2052|0702:2057|0704:2011|0705:2038"},{"id":"saint_peter_and_saint_paul_s_day","name":"San Pedro y San Pablo","en":"Saint Peter and Saint Paul's Day","category":"public","dates":"d|0629:2015,2020,2026,2037,2043,2048,2054|0630:2014,2025,2031,2036,2042,2053,2059|0701:2013,2019,2024,2030,2041,2047,2052,2058|0702:2012,2018,2029,2035,2040,2046,2057|0703:2017,2023,2028,2034,2045,2051,2056|0704:2011,2016,2022,2033,2039,2044,2050|0705:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"day_of_our_lady_of_the_rosary_of_chiquinquira","name":"Día de Nuestra Señora del Rosario de Chiquinquirá","en":"Day of Our Lady of the Rosary of Chiquinquirá","category":"public","dates":"d|0709:2029,2035,2040,2046,2057|0710:2028,2034,2045,2051,2056|0711:2033,2039,2044,2050|0712:2027,2032,2038,2049,2055,2060|0713:2026,2037,2043,2048,2054|0714:2031,2036,2042,2053,2059|0715:2030,2041,2047,2052,2058"},{"id":"independence_day","name":"Día de la Independencia","en":"Independence Day","category":"public","dates":"d|0720:2010-2060"},{"id":"battle_of_boyaca","name":"Batalla de Boyacá","en":"Battle of Boyacá","category":"public","dates":"d|0807:2010-2060"},{"id":"assumption_day","name":"La Asunción","en":"Assumption Day","category":"public","dates":"d|0815:2011,2016,2022,2033,2039,2044,2050|0816:2010,2021,2027,2032,2038,2049,2055,2060|0817:2015,2020,2026,2037,2043,2048,2054|0818:2014,2025,2031,2036,2042,2053,2059|0819:2013,2019,2024,2030,2041,2047,2052,2058|0820:2012,2018,2029,2035,2040,2046,2057|0821:2017,2023,2028,2034,2045,2051,2056"},{"id":"columbus_day","name":"Día de la Raza","en":"Columbus Day","category":"public","dates":"d|1012:2015,2020,2026,2037,2043,2048,2054|1013:2014,2025,2031,2036,2042,2053,2059|1014:2013,2019,2024,2030,2041,2047,2052,2058|1015:2012,2018,2029,2035,2040,2046,2057|1016:2017,2023,2028,2034,2045,2051,2056|1017:2011,2016,2022,2033,2039,2044,2050|1018:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"all_saints_day","name":"Día de Todos los Santos","en":"All Saints' Day","category":"public","dates":"d|1101:2010,2021,2027,2032,2038,2049,2055,2060|1102:2015,2020,2026,2037,2043,2048,2054|1103:2014,2025,2031,2036,2042,2053,2059|1104:2013,2019,2024,2030,2041,2047,2052,2058|1105:2012,2018,2029,2035,2040,2046,2057|1106:2017,2023,2028,2034,2045,2051,2056|1107:2011,2016,2022,2033,2039,2044,2050"},{"id":"independence_of_cartagena","name":"Independencia de Cartagena","en":"Independence of Cartagena","category":"public","dates":"d|1111:2013,2019,2024,2030,2041,2047,2052,2058|1112:2012,2018,2029,2035,2040,2046,2057|1113:2017,2023,2028,2034,2045,2051,2056|1114:2011,2016,2022,2033,2039,2044,2050|1115:2010,2021,2027,2032,2038,2049,2055,2060|1116:2015,2020,2026,2037,2043,2048,2054|1117:2014,2025,2031,2036,2042,2053,2059"},{"id":"immaculate_conception","name":"La Inmaculada Concepción","en":"Immaculate Conception","category":"public","dates":"d|1208:2010-2060"},{"id":"christmas_day","name":"Navidad","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"}]} diff --git a/app/Support/Holidays/data/CR.json b/app/Support/Holidays/data/CR.json new file mode 100644 index 000000000..297d24680 --- /dev/null +++ b/app/Support/Holidays/data/CR.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"CR","language":"es","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Año Nuevo","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"maundy_thursday","name":"Jueves Santo","en":"Maundy Thursday","category":"public","dates":"d|0322:2035,2046|0324:2016|0325:2027,2032|0326:2043,2054|0327:2059|0328:2013,2024|0329:2018,2029,2040|0330:2051,2056|0401:2010,2021|0402:2015,2026,2037,2048|0403:2042,2053|0405:2012|0406:2023,2034,2045|0407:2039,2050|0409:2020|0410:2031,2036|0411:2047,2058|0413:2017,2028|0414:2022,2033,2044|0415:2049,2055,2060|0417:2014,2025|0418:2019,2030,2041,2052|0419:2057|0421:2011|0422:2038"},{"id":"good_friday","name":"Viernes Santo","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"juan_santamaria_day","name":"Día de Juan Santamaría","en":"Juan Santamaría Day","category":"public","dates":"d|0410:2023|0411:2010-2022,2025-2060|0415:2024"},{"id":"international_labor_day","name":"Día Internacional del Trabajo","en":"International Labor Day","category":"public","dates":"d|0501:2010-2020,2022-2060|0503:2021"},{"id":"annexation_of_the_party_of_nicoya_to_costa_rica","name":"Anexión del Partido de Nicoya a Costa Rica","en":"Annexation of the Party of Nicoya to Costa Rica","category":"public","dates":"d|0724:2023|0725:2010-2019,2022,2025-2060|0726:2021|0727:2020|0729:2024"},{"id":"mother_s_day","name":"Día de la Madre","en":"Mother's Day","category":"public","dates":"d|0814:2023|0815:2010-2019,2021-2022,2025-2060|0817:2020|0819:2024"},{"id":"independence_day","name":"Día de la Independencia","en":"Independence Day","category":"public","dates":"d|0913:2021|0914:2020|0915:2010-2019,2023-2060|0919:2022"},{"id":"christmas_day","name":"Navidad","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"},{"id":"feast_of_our_lady_of_the_angels_optional","name":"Fiesta de Nuestra Señora de los Ángeles","en":"Feast of Our Lady of the Angels","category":"optional","dates":"d|0802:2010-2060"},{"id":"day_of_the_black_person_and_afro_costa_rican_cul_optional","name":"Día de la Persona Negra y la Cultura Afrocostarricense","en":"Day of the Black Person and Afro-Costa Rican Culture","category":"optional","dates":"d|0831:2024-2060|0903:2023|0904:2022|0905:2021"},{"id":"army_abolition_day_optional","name":"Día de la Abolición del Ejército","en":"Army Abolition Day","category":"optional","dates":"d|1129:2021|1130:2020|1201:2023-2060|1205:2022"}]} diff --git a/app/Support/Holidays/data/CU.json b/app/Support/Holidays/data/CU.json new file mode 100644 index 000000000..99e08fc5e --- /dev/null +++ b/app/Support/Holidays/data/CU.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"CU","language":"es","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"liberation_day","name":"Triunfo de la Revolución","en":"Liberation Day","category":"public","dates":"d|0101:2010-2060|0102:2012"},{"id":"victory_day","name":"Día de la Victoria","en":"Victory Day","category":"public","dates":"d|0102:2010-2060"},{"id":"good_friday","name":"Viernes Santo","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0423:2038"},{"id":"international_workers_day","name":"Día Internacional de los Trabajadores","en":"International Workers' Day","category":"public","dates":"d|0501:2010-2060|0502:2011,2016,2022,2033,2039,2044,2050"},{"id":"commemoration_of_the_assault_of_the_moncada_garr","name":"Conmemoración del asalto a Moncada","en":"Commemoration of the Assault of the Moncada garrison","category":"public","dates":"d|0725:2010-2060|0727:2010-2060"},{"id":"day_of_the_national_rebellion","name":"Día de la Rebeldía Nacional","en":"Day of the National Rebellion","category":"public","dates":"d|0726:2010-2060"},{"id":"independence_day","name":"Inicio de las Guerras de Independencia","en":"Independence Day","category":"public","dates":"d|1010:2010-2060|1011:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"christmas_day","name":"Día de Navidad","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"},{"id":"new_year_s_eve","name":"Fiesta de Fin de Año","en":"New Year's Eve","category":"public","dates":"d|1231:2010-2060"}]} diff --git a/app/Support/Holidays/data/CV.json b/app/Support/Holidays/data/CV.json new file mode 100644 index 000000000..c4af61f93 --- /dev/null +++ b/app/Support/Holidays/data/CV.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"CV","language":"pt_CV","years":[2010,2060],"subdivisions":{"BR":"Brava","BV":"Boa Vista","CA":"Santa Catarina","CF":"Santa Catarina do Fogo","CR":"Santa Cruz","MA":"Maio","MO":"Mosteiros","PA":"Paul","PN":"Porto Novo","PR":"Praia","RB":"Ribeira Brava","RG":"Ribeira Grande","RS":"Ribeira Grande de Santiago","SD":"São Domingos","SF":"São Filipe","SL":"Sal","SM":"São Miguel","SO":"São Lourenço dos Órgãos","SS":"São Salvador do Mundo","SV":"São Vicente","TA":"Tarrafal","TS":"Tarrafal de São Nicolau"},"holidays":[{"id":"new_year_s_day","name":"Ano Novo","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"democracy_and_freedom_day","name":"Dia da Liberdade e da Democracia","en":"Democracy and Freedom Day","category":"public","dates":"d|0113:2010-2060"},{"id":"tarrafal_de_santiago_municipality_day","name":"Dia do Município do Tarrafal de Santiago","en":"Tarrafal de Santiago Municipality Day","category":"public","regional":{"TA":"d|0115:2010-2060"}},{"id":"santo_antao_island_day","name":"Dia da Ilha de Santo Antão","en":"Santo Antão Island Day","category":"public","regional":{"PA":"d|0117:2010-2060","PN":"d|0117:2010-2060","RG":"d|0117:2010-2060"}},{"id":"national_heroes_day","name":"Dia da Nacionalidade e dos Heróis Nacionais","en":"National Heroes Day","category":"public","dates":"d|0120:2010-2060"},{"id":"sao_vicente_municipality_day","name":"Dia do Município de São Vicente","en":"São Vicente Municipality Day","category":"public","regional":{"SV":"d|0122:2010-2060"}},{"id":"ribeira_grande_de_santiago_municipality_day","name":"Dia do Município de Ribeira Grande de Santiago","en":"Ribeira Grande de Santiago Municipality Day","category":"public","regional":{"RS":"d|0131:2010-2060"}},{"id":"carnival_tuesday","name":"Terça-feira de Carnaval","en":"Carnival Tuesday","category":"public","regional":{"SV":"d|0206:2035,2046|0209:2016,2027|0210:2032,2043,2054|0211:2059|0212:2013|0213:2018,2024,2029|0214:2040,2051|0215:2056|0216:2010,2021|0217:2015,2026,2037|0218:2042,2048,2053|0221:2012,2023,2034,2045|0222:2039,2050|0225:2020,2031|0226:2036,2047,2058|0228:2017|0229:2028|0301:2022,2033,2044|0302:2049,2055,2060|0304:2014,2025|0305:2019,2030,2041,2052|0306:2057|0308:2011|0309:2038"}},{"id":"ash_wednesday","name":"Quarta-feira de Cinzas","en":"Ash Wednesday","category":"public","dates":"d|0207:2035,2046|0210:2016,2027|0211:2032,2043,2054|0212:2059|0213:2013|0214:2018,2024,2029|0215:2040,2051|0216:2056|0217:2010,2021|0218:2015,2026,2037|0219:2042,2048,2053|0222:2012,2023,2034,2045|0223:2039,2050|0226:2020,2031|0227:2036,2047,2058|0301:2017,2028|0302:2022,2033,2044|0303:2049,2055,2060|0305:2014,2025|0306:2019,2030,2041,2052|0307:2057|0309:2011|0310:2038"},{"id":"sao_domingos_municipality_day","name":"Dia do Município de São Domingos","en":"São Domingos Municipality Day","category":"public","regional":{"SD":"d|0313:2010-2060"}},{"id":"good_friday","name":"Sexta-feira Santa","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_sunday","name":"Páscoa","en":"Easter Sunday","category":"public","dates":"d|0325:2035,2046|0327:2016|0328:2027,2032|0329:2043,2054|0330:2059|0331:2013,2024|0401:2018,2029,2040|0402:2051,2056|0404:2010,2021|0405:2015,2026,2037,2048|0406:2042,2053|0408:2012|0409:2023,2034,2045|0410:2039,2050|0412:2020|0413:2031,2036|0414:2047,2058|0416:2017,2028|0417:2022,2033,2044|0418:2049,2055,2060|0420:2014,2025|0421:2019,2030,2041,2052|0422:2057|0424:2011|0425:2038"},{"id":"sao_filipe_municipality_day","name":"Dia do Município de São Filipe","en":"São Filipe Municipality Day","category":"public","regional":{"SF":"d|0501:2010-2060"}},{"id":"worker_s_day","name":"Dia do Trabalhador","en":"Worker's Day","category":"public","dates":"d|0501:2010-2060"},{"id":"ribeira_grande_municipality_day","name":"Dia do Município de Ribeira Grande","en":"Ribeira Grande Municipality Day","category":"public","regional":{"RG":"d|0507:2010-2060"}},{"id":"sao_lourenco_dos_orgaos_municipality_day","name":"Dia do Município de São Lourenço dos Órgãos","en":"São Lourenço dos Órgãos Municipality Day","category":"public","regional":{"SO":"d|0509:2010-2060"}},{"id":"praia_municipality_day","name":"Dia do Município da Praia","en":"Praia Municipality Day","category":"public","regional":{"PR":"d|0519:2010-2060"}},{"id":"international_children_s_day","name":"Dia Mundial da Criança","en":"International Children's Day","category":"public","dates":"d|0601:2010-2060"},{"id":"paul_municipality_day","name":"Dia do Município do Paúl","en":"Paúl Municipality Day","category":"public","regional":{"PA":"d|0613:2010-2060"}},{"id":"brava_municipality_day","name":"Dia do Município da Brava","en":"Brava Municipality Day","category":"public","regional":{"BR":"d|0624:2010-2060"}},{"id":"boa_vista_municipality_day","name":"Dia do Município da Boa Vista","en":"Boa Vista Municipality Day","category":"public","regional":{"BV":"d|0704:2010-2060"}},{"id":"national_independence_day","name":"Dia da Independência Nacional","en":"National Independence Day","category":"public","dates":"d|0705:2010-2060"},{"id":"sao_salvador_do_mundo_municipality_day","name":"Dia do Município de São Salvador do Mundo","en":"São Salvador do Mundo Municipality Day","category":"public","regional":{"SS":"d|0719:2010-2060"}},{"id":"santa_cruz_municipality_day","name":"Dia do Município de Santa Cruz","en":"Santa Cruz Municipality Day","category":"public","regional":{"CR":"d|0725:2010-2060"}},{"id":"tarrafal_de_sao_nicolau_municipality_day","name":"Dia do Município do Tarrafal de São Nicolau","en":"Tarrafal de São Nicolau Municipality Day","category":"public","regional":{"TS":"d|0802:2010-2060"}},{"id":"assumption_day","name":"Dia da Assunção","en":"Assumption Day","category":"public","dates":"d|0815:2010-2060"},{"id":"mosteiros_municipality_day","name":"Dia do Município dos Mosteiros","en":"Mosteiros Municipality Day","category":"public","regional":{"MO":"d|0815:2010-2060"}},{"id":"porto_novo_municipality_day","name":"Dia do Município do Porto Novo","en":"Porto Novo Municipality Day","category":"public","regional":{"PN":"d|0902:2010-2060"}},{"id":"maio_municipality_day","name":"Dia do Município do Maio","en":"Maio Municipality Day","category":"public","regional":{"MA":"d|0908:2010-2060"}},{"id":"sal_municipality_day","name":"Dia do Município do Sal","en":"Sal Municipality Day","category":"public","regional":{"SL":"d|0915:2010-2060"}},{"id":"sao_miguel_municipality_day","name":"Dia do Município de São Miguel","en":"São Miguel Municipality Day","category":"public","regional":{"SM":"d|0929:2010-2060"}},{"id":"all_saints_day","name":"Dia de Todos os Santos","en":"All Saints' Day","category":"public","dates":"d|1101:2010-2060"},{"id":"santa_catarina_de_santiago_municipality_day","name":"Dia do Município de Santa Catarina de Santiago","en":"Santa Catarina de Santiago Municipality Day","category":"public","regional":{"CA":"d|1125:2010-2060"}},{"id":"santa_catarina_do_fogo_municipality_day","name":"Dia do Município de Santa Catarina do Fogo","en":"Santa Catarina do Fogo Municipality Day","category":"public","regional":{"CF":"d|1125:2010-2060"}},{"id":"ribeira_brava_municipality_day","name":"Dia do Município de Ribeira Brava","en":"Ribeira Brava Municipality Day","category":"public","regional":{"RB":"d|1206:2010-2060"}},{"id":"christmas_day","name":"Dia do Natal","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"},{"id":"holy_thursday_optional","name":"Quinta-Feira Santa","en":"Holy Thursday","category":"optional","dates":"d|0322:2035,2046|0324:2016|0325:2027,2032|0326:2043,2054|0327:2059|0328:2013,2024|0329:2018,2029,2040|0330:2051,2056|0401:2010,2021|0402:2015,2026,2037,2048|0403:2042,2053|0405:2012|0406:2023,2034,2045|0407:2039,2050|0409:2020|0410:2031,2036|0411:2047,2058|0413:2017,2028|0414:2022,2033,2044|0415:2049,2055,2060|0417:2014,2025|0418:2019,2030,2041,2052|0419:2057|0421:2011|0422:2038"},{"id":"mother_s_day_optional","name":"Dia das Mães","en":"Mother's Day","category":"optional","dates":"d|0508:2011,2016,2022,2033,2039,2044,2050|0509:2010,2021,2027,2032,2038,2049,2055,2060|0510:2015,2020,2026,2037,2043,2048,2054|0511:2014,2025,2031,2036,2042,2053,2059|0512:2013,2019,2024,2030,2041,2047,2052,2058|0513:2012,2018,2029,2035,2040,2046,2057|0514:2017,2023,2028,2034,2045,2051,2056"},{"id":"father_s_day_optional","name":"Dia dos Pais","en":"Father's Day","category":"optional","dates":"d|0615:2014,2025,2031,2036,2042,2053,2059|0616:2013,2019,2024,2030,2041,2047,2052,2058|0617:2012,2018,2029,2035,2040,2046,2057|0618:2017,2023,2028,2034,2045,2051,2056|0619:2011,2016,2022,2033,2039,2044,2050|0620:2010,2021,2027,2032,2038,2049,2055,2060|0621:2015,2020,2026,2037,2043,2048,2054"}]} diff --git a/app/Support/Holidays/data/CW.json b/app/Support/Holidays/data/CW.json new file mode 100644 index 000000000..463745372 --- /dev/null +++ b/app/Support/Holidays/data/CW.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"CW","language":"pap_CW","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Aña Nobo","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"carnival_monday","name":"Dialuna despues di Carnaval Grandi","en":"Carnival Monday","category":"public","dates":"d|0205:2035,2046|0208:2016,2027|0209:2032,2043,2054|0210:2059|0211:2013|0212:2018,2024,2029|0213:2040,2051|0214:2056|0215:2010,2021|0216:2015,2026,2037|0217:2042,2048,2053|0220:2012,2023,2034,2045|0221:2039,2050|0224:2020,2031|0225:2036,2047,2058|0227:2017|0228:2022,2028,2033|0229:2044|0301:2049,2055,2060|0303:2014,2025|0304:2019,2030,2041,2052|0305:2057|0307:2011|0308:2038"},{"id":"good_friday","name":"Bièrnèsantu","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_sunday","name":"Pasku di Resurekshon","en":"Easter Sunday","category":"public","dates":"d|0325:2035,2046|0327:2016|0328:2027,2032|0329:2043,2054|0330:2059|0331:2013,2024|0401:2018,2029,2040|0402:2051,2056|0404:2010,2021|0405:2015,2026,2037,2048|0406:2042,2053|0408:2012|0409:2023,2034,2045|0410:2039,2050|0412:2020|0413:2031,2036|0414:2047,2058|0416:2017,2028|0417:2022,2033,2044|0418:2049,2055,2060|0420:2014,2025|0421:2019,2030,2041,2052|0422:2057|0424:2011|0425:2038"},{"id":"easter_monday","name":"Di dos dia di Pasku di Resurekshon","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"king_s_day","name":"Dia di Rey","en":"King's Day","category":"public","dates":"d|0426:2014,2025,2031,2036,2042,2053,2059|0427:2015-2024,2026-2030,2032-2035,2037-2041,2043-2052,2054-2058,2060"},{"id":"labor_day","name":"Dia di Obrero","en":"Labor Day","category":"public","dates":"d|0501:2010,2012-2015,2017-2021,2023-2032,2034-2038,2040-2043,2045-2049,2051-2060|0502:2011,2016,2022,2033,2039,2044,2050"},{"id":"ascension_day","name":"Dia di Asenshon","en":"Ascension Day","category":"public","dates":"d|0503:2035,2046|0505:2016|0506:2027,2032|0507:2043,2054|0508:2059|0509:2013,2024|0510:2018,2029,2040|0511:2051,2056|0513:2010,2021|0514:2015,2026,2037,2048|0515:2042,2053|0517:2012|0518:2023,2034,2045|0519:2039,2050|0521:2020|0522:2031,2036|0523:2047,2058|0525:2017,2028|0526:2022,2033,2044|0527:2049,2055,2060|0529:2014,2025|0530:2019,2030,2041,2052|0531:2057|0602:2011|0603:2038"},{"id":"national_anthem_and_flag_day","name":"Dia di Himno i Bandera","en":"National Anthem and Flag Day","category":"public","dates":"d|0702:2010-2060"},{"id":"curacao_day","name":"Dia di Pais Kòrsou","en":"Curaçao Day","category":"public","dates":"d|1010:2010-2060"},{"id":"christmas_day","name":"Pasku di Nasementu","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"},{"id":"second_day_of_christmas","name":"Di dos dia di Pasku di Nasementu","en":"Second Day of Christmas","category":"public","dates":"d|1226:2010-2060"},{"id":"new_year_s_eve_half_day","name":"Vispu di Aña Nobo","en":"New Year's Eve","category":"half_day","dates":"d|1231:2010-2060"}]} diff --git a/app/Support/Holidays/data/CX.json b/app/Support/Holidays/data/CX.json new file mode 100644 index 000000000..e854fc1b3 --- /dev/null +++ b/app/Support/Holidays/data/CX.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"CX","language":"en_CX","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051|0103:2011,2022,2028,2033,2039,2050,2056"},{"id":"eid_al_fitr","name":"Hari Raya Puasa","en":"Eid al-Fitr","category":"public","dates":"y|2010:0910|2011:0830|2012:0819-0820|2013:0808|2014:0728|2015:0717|2016:0706|2017:0624,0626|2018:0615|2019:0605|2020:0524-0525|2021:0513|2022:0502|2023:0422,0424|2024:0410|2025:0331|2026:0320|2027:0309|2028:0226,0228|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102-0103,1223|2034:1212|2035:1201,1203|2036:1119|2037:1108-1109|2038:1029|2039:1019|2040:1007-1008|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712-0713|2049:0701|2050:0620|2051:0610,0612|2052:0529|2053:0519|2054:0509,0511|2055:0428|2056:0416-0417|2057:0405|2058:0325|2059:0315,0317|2060:0304","estimated":"y|2011:0830|2012:0819|2015:0717|2018:0615|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304"},{"id":"eid_al_adha","name":"Hari Raya Haji","en":"Eid al-Adha","category":"public","dates":"y|2010:1116|2011:1106-1107|2012:1026|2013:1015|2014:1005,1007|2015:0923|2016:0913|2017:0901|2018:0821|2019:0811-0812|2020:0731|2021:0720|2022:0709,0711|2023:0628|2024:0617|2025:0606-0607|2026:0527|2027:0516-0517|2028:0505|2029:0424|2030:0413,0415|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218-0219|2036:0207|2037:0126|2038:0116,0118|2039:0105,1226|2040:1214|2041:1204|2042:1123-1124|2043:1112|2044:1031|2045:1021,1023|2046:1010|2047:0930|2048:0919,0921|2049:0908|2050:0828-0829|2051:0817|2052:0805|2053:0726,0728|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601,0603|2059:0522|2060:0510","estimated":"y|2011:1106|2012:1026|2015:0923|2018:0821|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510"},{"id":"chinese_new_year","name":"Chinese New Year","en":"Chinese New Year","category":"public","dates":"d|0122:2042|0123:2012,2023,2031,2042,2050|0124:2012,2023,2031,2039,2050,2058|0125:2039,2050,2058|0126:2028,2047|0127:2028,2047|0128:2017,2020,2036,2047,2055|0129:2017,2020,2025,2036,2047,2055|0130:2017,2025,2044|0131:2014,2017,2033,2044|0201:2014,2022,2033,2041,2044,2052|0202:2022,2041,2044,2049,2052,2060|0203:2011,2014,2030,2049,2060|0204:2011,2030,2038,2041,2057|0205:2019,2030,2038,2057|0206:2019,2027,2046,2057|0207:2027,2046|0208:2016,2027,2035,2054|0209:2016,2027,2035,2054|0210:2013,2024,2043,2054|0211:2013,2024,2032,2043,2051|0212:2013,2021,2024,2032,2040,2051,2059|0213:2021,2024,2029,2040,2051,2059|0214:2029,2040,2048,2051|0215:2010,2021,2037,2048,2056|0216:2010,2018,2037,2056|0217:2018,2026,2037,2045,2048|0218:2026,2045|0219:2015,2018,2034,2053|0220:2015,2034,2045,2053|0221:2034"},{"id":"australia_day","name":"Australia Day","en":"Australia Day","category":"public","dates":"d|0126:2010-2060|0127:2014,2020,2025,2031,2042,2048,2053,2059|0128:2013,2019,2030,2036,2041,2047,2058"},{"id":"labor_day","name":"Labour Day","en":"Labor Day","category":"public","dates":"d|0315:2027,2032,2038,2049,2055,2060|0316:2015,2020,2026,2037,2043,2048,2054|0317:2031,2036,2042,2053,2059|0318:2013,2019,2024,2030,2041,2047,2052,2058|0319:2012,2018,2029,2035,2040,2046,2057|0320:2017,2023,2028,2034,2045,2051,2056|0321:2011,2016,2022,2033,2039,2044,2050|0322:2010,2021|0324:2014,2025"},{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"anzac_day","name":"ANZAC Day","en":"ANZAC Day","category":"public","dates":"d|0425:2010-2060|0426:2010,2021,2027,2032,2038,2049,2055,2060|0427:2015,2020,2026,2037,2043,2048,2054"},{"id":"territory_day","name":"Territory Day","en":"Territory Day","category":"public","dates":"d|1001:2012,2018,2029,2035,2040,2046,2057|1002:2017,2023,2028,2034,2045,2051,2056|1003:2011,2016,2022,2033,2039,2044,2050|1004:2010,2021,2027,2032,2038,2049,2055,2060|1005:2015,2020,2026,2037,2043,2048,2054|1006:2014,2025,2031,2036,2042,2053,2059|1007:2013,2019,2024,2030,2041,2047,2052,2058"},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060|1226:2011,2016,2022,2033,2039,2044,2050|1227:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"boxing_day","name":"Boxing Day","en":"Boxing Day","category":"public","dates":"d|1226:2010-2060|1227:2011,2016,2022,2033,2039,2044,2050|1228:2010,2015,2020-2021,2026-2027,2032,2037-2038,2043,2048-2049,2054-2055,2060"}]} diff --git a/app/Support/Holidays/data/CY.json b/app/Support/Holidays/data/CY.json new file mode 100644 index 000000000..d4b5b38b1 --- /dev/null +++ b/app/Support/Holidays/data/CY.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"CY","language":"el","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Πρωτοχρονιά","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"epiphany","name":"Ημέρα των Θεοφανίων","en":"Epiphany","category":"public","dates":"d|0106:2010-2060"},{"id":"green_monday","name":"Καθαρά Δευτέρα","en":"Green Monday","category":"public","dates":"d|0215:2010|0216:2037|0217:2048|0219:2018,2029|0220:2034,2045|0221:2056|0223:2015,2026|0224:2031,2042,2053|0225:2058|0227:2012,2017,2023|0228:2028,2039,2050|0301:2055|0302:2020|0303:2014,2025,2036|0304:2041,2047,2052|0307:2011,2022,2033,2044|0308:2038,2049,2060|0311:2019,2030|0312:2035,2046,2057|0314:2016|0315:2021,2027,2032|0316:2043,2054|0317:2059|0318:2013,2024|0319:2040|0320:2051"},{"id":"greek_independence_day","name":"Ημέρα της Ελληνικής Ανεξαρτησίας","en":"Greek Independence Day","category":"public","dates":"d|0325:2010-2060"},{"id":"cyprus_national_day","name":"Εθνική Ημέρα Κύπρου","en":"Cyprus National Day","category":"public","dates":"d|0401:2010-2060"},{"id":"good_friday","name":"Μεγάλη Παρασκευή","en":"Good Friday","category":"public","dates":"d|0402:2010|0403:2037,2048|0406:2018,2029|0407:2034,2045,2056|0410:2015,2026|0411:2031,2042,2053|0412:2058|0413:2012|0414:2017,2023,2028|0415:2039,2050|0416:2055|0417:2020|0418:2014,2025,2036|0419:2041,2047,2052|0422:2011,2022,2033,2044|0423:2038,2049,2060|0426:2019,2030|0427:2035,2046,2057|0429:2016|0430:2021,2027,2032|0501:2043,2054|0502:2059|0503:2013,2024|0504:2040|0505:2051"},{"id":"easter_sunday","name":"Κυριακή του Πάσχα","en":"Easter Sunday","category":"public","dates":"d|0404:2010|0405:2037,2048|0408:2018,2029|0409:2034,2045,2056|0412:2015,2026|0413:2031,2042,2053|0414:2058|0415:2012|0416:2017,2023,2028|0417:2039,2050|0418:2055|0419:2020|0420:2014,2025,2036|0421:2041,2047,2052|0424:2011,2022,2033,2044|0425:2038,2049,2060|0428:2019,2030|0429:2035,2046,2057|0501:2016|0502:2021,2027,2032|0503:2043,2054|0504:2059|0505:2013,2024|0506:2040|0507:2051"},{"id":"easter_monday","name":"Δευτέρα της Διακαινησίμου","en":"Easter Monday","category":"public","dates":"d|0405:2010|0406:2037,2048|0409:2018,2029|0410:2034,2045,2056|0413:2015,2026|0414:2031,2042,2053|0415:2058|0416:2012|0417:2017,2023,2028|0418:2039,2050|0419:2055|0420:2020|0421:2014,2025,2036|0422:2041,2047,2052|0425:2011,2022,2033,2044|0426:2038,2049,2060|0429:2019,2030|0430:2035,2046,2057|0502:2016|0503:2021,2027,2032|0504:2043,2054|0505:2059|0506:2013,2024|0507:2040|0508:2051"},{"id":"labor_day","name":"Πρωτομαγιά","en":"Labor Day","category":"public","dates":"d|0501:2010-2060"},{"id":"pentecost_monday","name":"Δευτέρα του Αγίου Πνεύματος","en":"Pentecost Monday","category":"public","dates":"d|0524:2010|0525:2037,2048|0528:2018,2029|0529:2034,2045,2056|0601:2015,2026|0602:2031,2042,2053|0603:2058|0604:2012|0605:2017,2023,2028|0606:2039,2050|0607:2055|0608:2020|0609:2014,2025,2036|0610:2041,2047,2052|0613:2011,2022,2033,2044|0614:2038,2049,2060|0617:2019,2030|0618:2035,2046,2057|0620:2016|0621:2021,2027,2032|0622:2043,2054|0623:2059|0624:2013,2024|0625:2040|0626:2051"},{"id":"dormition_of_the_mother_of_god","name":"Κοίμηση της Θεοτόκου","en":"Dormition of the Mother of God","category":"public","dates":"d|0815:2010-2060"},{"id":"cyprus_independence_day","name":"Ημέρα της Κυπριακής Ανεξαρτησίας","en":"Cyprus Independence Day","category":"public","dates":"d|1001:2010-2060"},{"id":"ohi_day","name":"Ημέρα του Όχι","en":"Ohi Day","category":"public","dates":"d|1028:2010-2060"},{"id":"christmas_day","name":"Χριστούγεννα","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"},{"id":"day_after_christmas","name":"Επομένη Χριστουγέννων","en":"Day After Christmas","category":"public","dates":"d|1226:2010-2060"},{"id":"holy_saturday_optional","name":"Μεγάλο Σάββατο","en":"Holy Saturday","category":"optional","dates":"d|0403:2010|0404:2037,2048|0407:2018,2029|0408:2034,2045,2056|0411:2015,2026|0412:2031,2042,2053|0413:2058|0414:2012|0415:2017,2023,2028|0416:2039,2050|0417:2055|0418:2020|0419:2014,2025,2036|0420:2041,2047,2052|0423:2011,2022,2033,2044|0424:2038,2049,2060|0427:2019,2030|0428:2035,2046,2057|0430:2016|0501:2021,2027,2032|0502:2043,2054|0503:2059|0504:2013,2024|0505:2040|0506:2051"},{"id":"easter_tuesday_bank","name":"Τρίτη της Διακαινησίμου","en":"Easter Tuesday","category":"bank","dates":"d|0406:2010|0407:2037,2048|0410:2018,2029|0411:2034,2045,2056|0414:2015,2026|0415:2031,2042,2053|0416:2058|0417:2012|0418:2017,2023,2028|0419:2039,2050|0420:2055|0421:2020|0422:2014,2025,2036|0423:2041,2047,2052|0426:2011,2022,2033,2044|0427:2038,2049,2060|0430:2019,2030|0501:2035,2046,2057|0503:2016|0504:2021,2027,2032|0505:2043,2054|0506:2059|0507:2013,2024|0508:2040|0509:2051"},{"id":"christmas_eve_optional","name":"Παραμονή Χριστουγέννων","en":"Christmas Eve","category":"optional","dates":"d|1224:2010-2060"}]} diff --git a/app/Support/Holidays/data/CZ.json b/app/Support/Holidays/data/CZ.json new file mode 100644 index 000000000..1f860d3db --- /dev/null +++ b/app/Support/Holidays/data/CZ.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"CZ","language":"cs","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"independent_czech_state_restoration_day","name":"Den obnovy samostatného českého státu","en":"Independent Czech State Restoration Day","category":"public","dates":"d|0101:2010-2060"},{"id":"new_year_s_day","name":"Nový rok","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"good_friday","name":"Velký pátek","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2024|0330:2018,2029,2040|0331:2051,2056|0402:2021|0403:2026,2037,2048|0404:2042,2053|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2025|0419:2019,2030,2041,2052|0420:2057|0423:2038"},{"id":"easter_monday","name":"Velikonoční pondělí","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"labor_day","name":"Svátek práce","en":"Labor Day","category":"public","dates":"d|0501:2010-2060"},{"id":"victory_day","name":"Den vítězství","en":"Victory Day","category":"public","dates":"d|0508:2010-2060"},{"id":"saints_cyril_and_methodius_day","name":"Den slovanských věrozvěstů Cyrila a Metoděje","en":"Saints Cyril and Methodius Day","category":"public","dates":"d|0705:2010-2060"},{"id":"jan_hus_day","name":"Den upálení mistra Jana Husa","en":"Jan Hus Day","category":"public","dates":"d|0706:2010-2060"},{"id":"statehood_day","name":"Den české státnosti","en":"Statehood Day","category":"public","dates":"d|0928:2010-2060"},{"id":"independent_czechoslovak_state_day","name":"Den vzniku samostatného československého státu","en":"Independent Czechoslovak State Day","category":"public","dates":"d|1028:2010-2060"},{"id":"struggle_for_freedom_and_democracy_day_and_inter","name":"Den boje za svobodu a demokracii a Mezinárodní den studentstva","en":"Struggle for Freedom and Democracy Day and International Students' Day","category":"public","dates":"d|1117:2019-2060"},{"id":"christmas_eve","name":"Štědrý den","en":"Christmas Eve","category":"public","dates":"d|1224:2010-2060"},{"id":"christmas_day","name":"1. svátek vánoční","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"},{"id":"second_day_of_christmas","name":"2. svátek vánoční","en":"Second Day of Christmas","category":"public","dates":"d|1226:2010-2060"}]} diff --git a/app/Support/Holidays/data/DE.json b/app/Support/Holidays/data/DE.json new file mode 100644 index 000000000..39c92ea80 --- /dev/null +++ b/app/Support/Holidays/data/DE.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"DE","language":"de","years":[2010,2060],"subdivisions":{"BB":"Brandenburg","BE":"Berlin","BW":"Baden-Württemberg","BY":"Bayern","HB":"Bremen","HE":"Hessen","HH":"Hamburg","MV":"Mecklenburg-Vorpommern","NI":"Niedersachsen","NW":"Nordrhein-Westfalen","RP":"Rheinland-Pfalz","SH":"Schleswig-Holstein","SL":"Saarland","SN":"Sachsen","ST":"Sachsen-Anhalt","TH":"Thüringen","Augsburg":"Augsburg"},"holidays":[{"id":"new_year_s_day","name":"Neujahr","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"epiphany","name":"Heilige Drei Könige","en":"Epiphany","category":"public","regional":{"Augsburg":"d|0106:2010-2060","BW":"d|0106:2010-2060","BY":"d|0106:2010-2060","ST":"d|0106:2010-2060"}},{"id":"women_s_day","name":"Frauentag","en":"Women's Day","category":"public","regional":{"BE":"d|0308:2019-2060","MV":"d|0308:2023-2060"}},{"id":"good_friday","name":"Karfreitag","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_sunday","name":"Ostersonntag","en":"Easter Sunday","category":"public","regional":{"BB":"d|0325:2035,2046|0327:2016|0328:2027,2032|0329:2043,2054|0330:2059|0331:2013,2024|0401:2018,2029,2040|0402:2051,2056|0404:2010,2021|0405:2015,2026,2037,2048|0406:2042,2053|0408:2012|0409:2023,2034,2045|0410:2039,2050|0412:2020|0413:2031,2036|0414:2047,2058|0416:2017,2028|0417:2022,2033,2044|0418:2049,2055,2060|0420:2014,2025|0421:2019,2030,2041,2052|0422:2057|0424:2011|0425:2038"}},{"id":"easter_monday","name":"Ostermontag","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"labor_day","name":"Erster Mai","en":"Labor Day","category":"public","dates":"d|0501:2010-2060"},{"id":"ascension_day","name":"Christi Himmelfahrt","en":"Ascension Day","category":"public","dates":"d|0503:2035,2046|0505:2016|0506:2027,2032|0507:2043,2054|0508:2059|0509:2013,2024|0510:2018,2029,2040|0511:2051,2056|0513:2010,2021|0514:2015,2026,2037,2048|0515:2042,2053|0517:2012|0518:2023,2034,2045|0519:2039,2050|0521:2020|0522:2031,2036|0523:2047,2058|0525:2017,2028|0526:2022,2033,2044|0527:2049,2055,2060|0529:2014,2025|0530:2019,2030,2041,2052|0531:2057|0602:2011|0603:2038"},{"id":"pentecost","name":"Pfingstsonntag","en":"Pentecost","category":"public","regional":{"BB":"d|0513:2035,2046|0515:2016|0516:2027,2032|0517:2043,2054|0518:2059|0519:2013,2024|0520:2018,2029,2040|0521:2051,2056|0523:2010,2021|0524:2015,2026,2037,2048|0525:2042,2053|0527:2012|0528:2023,2034,2045|0529:2039,2050|0531:2020|0601:2031,2036|0602:2047,2058|0604:2017,2028|0605:2022,2033,2044|0606:2049,2055,2060|0608:2014,2025|0609:2019,2030,2041,2052|0610:2057|0612:2011|0613:2038"}},{"id":"pentecost_monday","name":"Pfingstmontag","en":"Pentecost Monday","category":"public","dates":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038"},{"id":"corpus_christi","name":"Fronleichnam","en":"Corpus Christi","category":"public","regional":{"Augsburg":"d|0524:2035,2046|0526:2016|0527:2027,2032|0528:2043,2054|0529:2059|0530:2013,2024|0531:2018,2029,2040|0601:2051,2056|0603:2010,2021|0604:2015,2026,2037,2048|0605:2042,2053|0607:2012|0608:2023,2034,2045|0609:2039,2050|0611:2020|0612:2031,2036|0613:2047,2058|0615:2017,2028|0616:2022,2033,2044|0617:2049,2055,2060|0619:2014,2025|0620:2019,2030,2041,2052|0621:2057|0623:2011|0624:2038","BW":"d|0524:2035,2046|0526:2016|0527:2027,2032|0528:2043,2054|0529:2059|0530:2013,2024|0531:2018,2029,2040|0601:2051,2056|0603:2010,2021|0604:2015,2026,2037,2048|0605:2042,2053|0607:2012|0608:2023,2034,2045|0609:2039,2050|0611:2020|0612:2031,2036|0613:2047,2058|0615:2017,2028|0616:2022,2033,2044|0617:2049,2055,2060|0619:2014,2025|0620:2019,2030,2041,2052|0621:2057|0623:2011|0624:2038","BY":"d|0524:2035,2046|0526:2016|0527:2027,2032|0528:2043,2054|0529:2059|0530:2013,2024|0531:2018,2029,2040|0601:2051,2056|0603:2010,2021|0604:2015,2026,2037,2048|0605:2042,2053|0607:2012|0608:2023,2034,2045|0609:2039,2050|0611:2020|0612:2031,2036|0613:2047,2058|0615:2017,2028|0616:2022,2033,2044|0617:2049,2055,2060|0619:2014,2025|0620:2019,2030,2041,2052|0621:2057|0623:2011|0624:2038","HE":"d|0524:2035,2046|0526:2016|0527:2027,2032|0528:2043,2054|0529:2059|0530:2013,2024|0531:2018,2029,2040|0601:2051,2056|0603:2010,2021|0604:2015,2026,2037,2048|0605:2042,2053|0607:2012|0608:2023,2034,2045|0609:2039,2050|0611:2020|0612:2031,2036|0613:2047,2058|0615:2017,2028|0616:2022,2033,2044|0617:2049,2055,2060|0619:2014,2025|0620:2019,2030,2041,2052|0621:2057|0623:2011|0624:2038","NW":"d|0524:2035,2046|0526:2016|0527:2027,2032|0528:2043,2054|0529:2059|0530:2013,2024|0531:2018,2029,2040|0601:2051,2056|0603:2010,2021|0604:2015,2026,2037,2048|0605:2042,2053|0607:2012|0608:2023,2034,2045|0609:2039,2050|0611:2020|0612:2031,2036|0613:2047,2058|0615:2017,2028|0616:2022,2033,2044|0617:2049,2055,2060|0619:2014,2025|0620:2019,2030,2041,2052|0621:2057|0623:2011|0624:2038","RP":"d|0524:2035,2046|0526:2016|0527:2027,2032|0528:2043,2054|0529:2059|0530:2013,2024|0531:2018,2029,2040|0601:2051,2056|0603:2010,2021|0604:2015,2026,2037,2048|0605:2042,2053|0607:2012|0608:2023,2034,2045|0609:2039,2050|0611:2020|0612:2031,2036|0613:2047,2058|0615:2017,2028|0616:2022,2033,2044|0617:2049,2055,2060|0619:2014,2025|0620:2019,2030,2041,2052|0621:2057|0623:2011|0624:2038","SL":"d|0524:2035,2046|0526:2016|0527:2027,2032|0528:2043,2054|0529:2059|0530:2013,2024|0531:2018,2029,2040|0601:2051,2056|0603:2010,2021|0604:2015,2026,2037,2048|0605:2042,2053|0607:2012|0608:2023,2034,2045|0609:2039,2050|0611:2020|0612:2031,2036|0613:2047,2058|0615:2017,2028|0616:2022,2033,2044|0617:2049,2055,2060|0619:2014,2025|0620:2019,2030,2041,2052|0621:2057|0623:2011|0624:2038"}},{"id":"75th_anniversary_of_the_east_german_uprising_of_","name":"75. Jahrestag des Aufstandes vom 17. Juni 1953","en":"75th anniversary of the East German uprising of 1953","category":"public","regional":{"BE":"d|0617:2028"}},{"id":"augsburg_peace_festival","name":"Augsburger Hohes Friedensfest","en":"Augsburg Peace Festival","category":"public","regional":{"Augsburg":"d|0808:2010-2060"}},{"id":"assumption_day","name":"Mariä Himmelfahrt","en":"Assumption Day","category":"public","regional":{"Augsburg":"d|0815:2010-2060","SL":"d|0815:2010-2060"}},{"id":"world_children_s_day","name":"Weltkindertag","en":"World Children's Day","category":"public","regional":{"TH":"d|0920:2019-2060"}},{"id":"german_unity_day","name":"Tag der Deutschen Einheit","en":"German Unity Day","category":"public","dates":"d|1003:2010-2060"},{"id":"reformation_day","name":"Reformationstag","en":"Reformation Day","category":"public","dates":"d|1031:2017","regional":{"BB":"d|1031:2010-2016,2018-2060","HB":"d|1031:2018-2060","HH":"d|1031:2018-2060","MV":"d|1031:2010-2016,2018-2060","NI":"d|1031:2018-2060","SH":"d|1031:2018-2060","SN":"d|1031:2010-2016,2018-2060","ST":"d|1031:2010-2016,2018-2060","TH":"d|1031:2010-2016,2018-2060"}},{"id":"all_saints_day","name":"Allerheiligen","en":"All Saints' Day","category":"public","regional":{"Augsburg":"d|1101:2010-2060","BW":"d|1101:2010-2060","BY":"d|1101:2010-2060","NW":"d|1101:2010-2060","RP":"d|1101:2010-2060","SL":"d|1101:2010-2060"}},{"id":"repentance_and_prayer_day","name":"Buß- und Bettag","en":"Repentance and Prayer Day","category":"public","regional":{"SN":"d|1116:2011,2016,2022,2033,2039,2044,2050|1117:2010,2021,2027,2032,2038,2049,2055,2060|1118:2015,2020,2026,2037,2043,2048,2054|1119:2014,2025,2031,2036,2042,2053,2059|1120:2013,2019,2024,2030,2041,2047,2052,2058|1121:2012,2018,2029,2035,2040,2046,2057|1122:2017,2023,2028,2034,2045,2051,2056"}},{"id":"christmas_day","name":"Erster Weihnachtstag","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"},{"id":"second_day_of_christmas","name":"Zweiter Weihnachtstag","en":"Second Day of Christmas","category":"public","dates":"d|1226:2010-2060"},{"id":"christmas_break_school","name":"Weihnachtsferien","en":"Christmas Break","category":"school","regional":{"Augsburg":"d|0101:2010-2029|0102:2010-2029|0103:2010-2029|0104:2010-2024,2026-2029|0105:2010-2013,2015-2019,2021-2024,2026-2029|0106:2011,2021-2023,2027-2028|0107:2011,2021-2023,2027-2028|0108:2021-2022,2027|0109:2021|1222:2018,2025|1223:2013,2017-2020,2023-2025,2028|1224:2010,2012-2028|1225:2010,2012-2028|1226:2010,2012-2028|1227:2010-2028|1228:2010-2028|1229:2010-2028|1230:2010-2028|1231:2010-2028","BB":"d|0101:2010-2021,2023-2024,2026-2027,2029|0102:2010,2012-2021,2023-2024,2026-2027,2029|0103:2012-2014,2017,2019-2020,2023-2024|0104:2013,2019,2024|0105:2019,2024|1221:2017,2020|1222:2014,2017-2018,2020,2022,2025,2028|1223:2010-2011,2013-2028|1224:2010-2028|1225:2010-2028|1226:2010-2028|1227:2010-2028|1228:2010-2028|1229:2010-2028|1230:2010-2028|1231:2010-2028","BE":"d|0101:2010-2021,2023-2024,2026-2027,2029|0102:2010,2012-2021,2023-2024,2026-2027,2029|0103:2012-2014,2017,2019-2020,2024|0104:2013,2019-2020,2024|0105:2019,2024|1221:2017,2020|1222:2014,2017-2018,2020,2022,2025,2027-2028|1223:2010-2011,2013-2020,2022-2028|1224:2010-2028|1225:2010-2028|1226:2010-2028|1227:2010-2028|1228:2010-2028|1229:2010-2028|1230:2010-2028|1231:2010-2028","BW":"d|0101:2010-2029|0102:2010-2029|0103:2010-2029|0104:2010-2029|0105:2010-2013,2015-2019,2021-2024,2026-2029|0106:2010-2011,2016-2017,2021-2023,2027-2028|0107:2010-2011,2016-2017,2021-2023,2027-2028|0108:2010-2011,2016,2021-2022,2027-2028|0109:2010,2016,2021,2027|1221:2022|1222:2014,2017,2022,2025|1223:2010-2011,2013-2017,2019-2028|1224:2010-2028|1225:2010-2028|1226:2010-2028|1227:2010-2028|1228:2010-2028|1229:2010-2028|1230:2010-2028|1231:2010-2028","BY":"d|0101:2010-2029|0102:2010-2029|0103:2010-2029|0104:2010-2024,2026-2029|0105:2010-2013,2015-2019,2021-2024,2026-2029|0106:2011,2021-2023,2027-2028|0107:2011,2021-2023,2027-2028|0108:2021-2022,2027|0109:2021|1222:2018,2025|1223:2013,2017-2020,2023-2025,2028|1224:2010,2012-2028|1225:2010,2012-2028|1226:2010,2012-2028|1227:2010-2028|1228:2010-2028|1229:2010-2028|1230:2010-2028|1231:2010-2028","HB":"d|0101:2010-2029|0102:2010-2029|0103:2010-2029|0104:2010-2013,2015-2029|0105:2010-2011,2013,2015-2018,2020-2024,2026-2029|0106:2010,2016-2018,2020-2023,2027-2029|0107:2021-2022,2027-2028|0108:2021-2022,2027-2028|0109:2027|1221:2016,2019|1222:2010,2014,2016-2017,2019,2025|1223:2010-2011,2013-2017,2019-2027|1224:2010-2027|1225:2010-2027|1226:2010-2027|1227:2010-2028|1228:2010-2028|1229:2010-2028|1230:2010-2028|1231:2010-2028","HE":"y|2010:0101-0109,1220-1231|2011:0101-0107,1221-1231|2012:0101-0106,1224-1231|2013:0101-0112,1223-1231|2014:0101-0111,1222-1231|2015:0101-0110,1223-1231|2016:0101-0109,1222-1231|2017:0101-0107,1224-1231|2018:0101-0113,1224-1231|2019:0101-0112,1223-1231|2020:0101-0111,1221-1231|2021:0101-0109,1223-1231|2022:0101-0108,1222-1231|2023:0101-0107,1227-1231|2024:0101-0113,1223-1231|2025:0101-0110,1222-1231|2026:0101-0110,1223-1231|2027:0101-0112,1223-1231|2028:0101-0111,1227-1231|2029:0101-0112","HH":"y|2010:1223-1231|2011:0101-0103,1227-1231|2012:0101-0106,1221-1231|2013:0101-0104,1219-1231|2014:0101-0103,1222-1231|2015:0101-0106,1221-1231|2016:0101,1227-1231|2017:0101-0106,1222-1231|2018:0101-0105,1220-1231|2019:0101-0104,1220-1231|2020:0101-0103,1221-1231|2021:0101-0104,1223-1231|2022:0101-0104,1223-1231|2023:0101-0106,1222-1231|2024:0101-0105,1220-1231|2025:0101-0103,1217-1231|2026:0101-0102,1221-1231|2027:0101,1220-1231|2028:1218-1229","MV":"d|0101:2010,2012-2021,2023-2029|0102:2010,2012-2021,2023-2029|0103:2012-2014,2018-2020,2024-2026,2028|0104:2013,2019-2020,2025,2028|0105:2019,2025|0106:2025|1220:2025|1221:2012,2015,2017,2020,2023,2025-2026|1222:2012,2014-2017,2020-2023,2025-2028|1223:2010-2017,2019-2028|1224:2010-2028|1225:2010-2028|1226:2010-2028|1227:2010-2028|1228:2010-2028|1229:2010-2028|1230:2010-2028|1231:2010-2028","NI":"d|0101:2010-2029|0102:2010-2029|0103:2010-2029|0104:2010-2013,2015-2029|0105:2010-2011,2013,2015-2018,2020-2024,2026-2029|0106:2010,2016-2017,2020-2023,2027-2029|0107:2021-2022,2027-2028|0108:2021,2027-2028|0109:2027|1221:2016|1222:2010,2014,2016-2017,2025|1223:2010-2011,2013-2017,2019-2022,2024-2027|1224:2010-2022,2024-2027|1225:2010-2022,2024-2027|1226:2010-2022,2024-2027|1227:2010-2028|1228:2010-2028|1229:2010-2028|1230:2010-2028|1231:2010-2028","NW":"d|0101:2010-2029|0102:2010-2029|0103:2010-2029|0104:2010-2029|0105:2010-2012,2014-2018,2020-2029|0106:2010-2012,2014-2018,2020-2023,2025-2028|0107:2011,2014,2022,2028|0108:2011,2022,2028|1221:2012,2018,2023,2028|1222:2012,2014,2018,2023,2025,2028|1223:2011-2016,2018-2020,2022-2026,2028|1224:2010-2016,2018-2028|1225:2010-2016,2018-2028|1226:2010-2016,2018-2028|1227:2010-2028|1228:2010-2028|1229:2010-2028|1230:2010-2028|1231:2010-2028","RP":"y|2010:0101-0105,1223-1231|2011:0101-0107,1222-1231|2012:0101-0106,1220-1231|2013:0101-0104,1223-1231|2014:0101-0107,1222-1231|2015:0101-0107,1223-1231|2016:0101-0108,1222-1231|2017:0101-0106,1222-1231|2018:0101-0109,1220-1231|2019:0101-0104,1223-1231|2020:0101-0106,1221-1231|2021:1223-1231|2022:1223-1231|2023:0101-0102,1227-1231|2024:0101-0105,1223-1231|2025:0101-0108,1222-1231|2026:0101-0107,1223-1231|2027:0101-0108,1223-1231|2028:0101-0107,1221-1231|2029:0101-0108","SH":"d|0101:2010-2029|0102:2010-2029|0103:2010-2029|0104:2010-2029|0105:2010-2018,2020-2029|0106:2010-2012,2014-2018,2020-2028|0107:2011,2022-2023,2025,2028|0108:2022,2028|1219:2024-2025|1220:2024-2025|1221:2015,2017-2018,2020,2024-2026,2028|1222:2014-2015,2017-2018,2020,2024-2026,2028|1223:2010-2011,2013-2022,2024-2028|1224:2010-2022,2024-2028|1225:2010-2022,2024-2028|1226:2010-2022,2024-2028|1227:2010-2028|1228:2010-2028|1229:2010-2028|1230:2010-2028|1231:2010-2028","SL":"y|2010:0101-0102,1220-1231|2011:1223-1231|2012:0101-0104,1224-1231|2013:0101-0105,1220-1231|2014:0101-0104,1222-1231|2015:0101-0107,1221-1231|2016:0101-0102,1219-1231|2017:1221-1231|2018:0101-0105,1220-1231|2019:0101-0104,1223-1231|2020:0101-0103,1221-1231|2021:1223-1231|2022:0101-0103,1222-1231|2023:0101-0104,1221-1231|2024:0101-0102,1223-1231|2025:0101-0103,1222-1231|2026:0101-0102,1221-1231|2027:1220-1231|2028:1220-1231|2029:0101-0102","SN":"d|0101:2010-2029|0102:2010,2012-2021,2023-2027,2029|0103:2014-2015,2019-2020,2025,2029|0104:2019|1221:2013,2015,2019|1222:2012-2015,2018-2019,2022,2025|1223:2010-2028|1224:2010-2028|1225:2010-2028|1226:2010-2028|1227:2010-2028|1228:2010-2028|1229:2010-2028|1230:2010-2028|1231:2010-2028","ST":"d|0101:2010-2027,2029|0102:2010-2027,2029|0103:2010-2016,2018-2026|0104:2010-2013,2015-2016,2019-2023,2025-2026|0105:2010-2012,2015-2016,2021-2023,2026|0106:2012,2022|0107:2012,2022|0108:2022|1219:2012,2016,2018|1220:2012,2016,2018,2027|1221:2012-2013,2015-2018,2020,2022-2023,2026-2028|1222:2010-2018,2020-2023,2025-2028|1223:2010-2028|1224:2010-2028|1225:2010-2028|1226:2010-2028|1227:2010-2028|1228:2010-2028|1229:2010-2028|1230:2010-2028|1231:2010-2028","TH":"d|0101:2010,2013-2016,2018-2021,2023-2027,2029|0102:2010,2013-2016,2018-2021,2023-2027,2029|0103:2013-2015,2018-2020,2023-2026,2029|0104:2013-2014,2018-2019,2024,2029|0105:2013,2018,2024,2029|1221:2018-2019|1222:2014,2017-2019,2022-2023,2025|1223:2010-2011,2013-2028|1224:2010-2028|1225:2010-2028|1226:2010-2028|1227:2010-2028|1228:2010-2028|1229:2010-2028|1230:2010-2028|1231:2010-2028"}},{"id":"winter_break_school","name":"Winterferien","en":"Winter Break","category":"school","regional":{"Augsburg":"y|2010:0215-0220|2011:0307-0311|2012:0220-0224|2013:0211-0215|2015:0216-0220|2016:0208-0212|2017:0227-0303|2018:0212-0216","BB":"y|2010:0201-0206|2011:0131-0205|2012:0130-0204|2013:0204-0209|2014:0203-0208|2015:0202-0207|2016:0201-0206|2017:0130-0204|2018:0205-0210|2019:0204-0209|2020:0203-0208|2021:0201-0206|2022:0131-0205|2023:0130-0203|2024:0205-0209|2025:0203-0208|2026:0202-0207|2027:0201-0206|2028:0131-0205|2029:0129-0203","BE":"y|2010:0201-0206|2011:0131-0205|2012:0130-0204|2013:0204-0209|2014:0203-0208|2015:0202-0207|2016:0201-0206|2017:0130-0204|2018:0205-0210|2019:0204-0209|2020:0203-0208|2021:0201-0206|2022:0129-0205|2023:0130-0204|2024:0205-0210|2025:0203-0208|2026:0202-0207|2027:0201-0206|2028:0131-0205|2029:0129-0203,0309","BY":"y|2010:0215-0220|2011:0307-0311|2012:0220-0224|2013:0211-0215|2015:0216-0220|2016:0208-0212|2017:0227-0303|2018:0212-0216","HB":"d|0128:2016|0129:2016|0130:2012,2014,2017,2023|0131:2011-2014,2017,2019,2022-2023,2028|0201:2010-2011,2013,2018-2019,2021-2022,2024,2027-2029|0202:2010,2015,2018,2021,2024,2026-2027,2029|0203:2015,2020,2025-2026|0204:2020,2025","HH":"d|0127:2023|0128:2022,2028|0129:2010,2016,2021,2027|0130:2012,2015,2017,2026|0131:2011,2014,2020,2025|0201:2013,2019|0202:2018,2024,2029","MV":"y|2010:0206-0220|2011:0207-0219|2012:0206-0217|2013:0204-0215|2014:0203-0215|2015:0202-0214|2016:0201-0213|2017:0206-0218|2018:0205-0216|2019:0204-0215|2020:0210-0221|2021:0206-0219|2022:0205-0218|2023:0206-0218|2024:0205-0216|2025:0203-0214|2026:0209-0220|2027:0208-0219|2028:0205-0218|2029:0205-0216","NI":"d|0128:2016|0129:2016|0130:2012,2014,2017,2023|0131:2011-2014,2017,2019,2022-2023,2028|0201:2010-2011,2013,2018-2019,2021-2022,2024,2027-2029|0202:2010,2015,2018,2021,2024,2026-2027,2029|0203:2015,2020,2025-2026|0204:2020,2025","RP":"y|2019:0225-0301|2020:0217-0221|2022:0221-0225","SL":"y|2010:0215-0220|2011:0307-0312|2012:0220-0225|2013:0211-0216|2014:0303-0308|2015:0216-0221|2016:0208-0213|2017:0227-0304|2018:0212-0217|2019:0225-0305|2020:0217-0225|2021:0215-0219|2022:0221-0301|2023:0220-0224|2024:0212-0216|2025:0224-0304|2026:0216-0220|2027:0208-0212|2028:0221-0229|2029:0212-0216","SN":"y|2010:0208-0220|2011:0212-0226|2012:0213-0225|2013:0204-0215|2014:0217-0301|2015:0209-0221|2016:0208-0220|2017:0213-0224|2018:0212-0223|2019:0218-0302|2020:0210-0222|2021:0208-0220|2022:0212-0226|2023:0213-0224|2024:0212-0223|2025:0217-0301|2026:0209-0221|2027:0208-0219|2028:0214-0226|2029:0205-0216","ST":"y|2010:0208-0213|2011:0205-0212|2012:0204-0211|2013:0201-0208|2014:0201-0212|2015:0202-0214|2016:0201-0210|2017:0204-0211|2018:0205-0209|2019:0211-0215|2020:0210-0214|2021:0208-0213|2022:0212-0219|2023:0206-0211|2024:0205-0210|2025:0127-0131|2026:0131-0206|2027:0201-0206|2028:0207-0212|2029:0205-0210","TH":"y|2010:0201-0206|2011:0131-0205|2012:0206-0211|2013:0218-0223|2014:0217-0222|2015:0202-0207|2016:0201-0206|2017:0206-0211|2018:0205-0209|2019:0211-0215|2020:0210-0214|2021:0208-0213|2022:0212-0219|2023:0213-0217|2024:0212-0216|2025:0203-0208|2026:0216-0221|2027:0201-0206|2028:0207-0212|2029:0212-0217"}},{"id":"easter_spring_break_school","name":"Oster-/Frühjahrsferien","en":"Easter/Spring Break","category":"school","regional":{"Augsburg":"y|2010:0329-0410|2011:0418-0430|2012:0402-0414|2013:0325-0406|2014:0303-0307,0414-0426|2015:0330-0411|2016:0321-0401|2017:0410-0422|2018:0326-0407|2019:0304-0308,0415-0427|2020:0224-0228,0406-0418|2021:0215-0219,0329-0410|2022:0228-0304,0411-0423|2023:0220-0224,0403-0415|2024:0212-0216,0325-0406|2025:0303-0307,0414-0425|2026:0216-0220,0330-0410|2027:0208-0212,0322-0402|2028:0228-0303,0410-0421|2029:0212-0216,0326-0406","BB":"y|2010:0331-0410|2011:0420-0430|2012:0404-0414|2013:0327-0406|2014:0416-0426|2015:0401-0411|2016:0323-0402|2017:0412-0422|2018:0326-0406|2019:0415-0426|2020:0406-0417|2021:0329-0409|2022:0411-0423|2023:0403-0414|2024:0325-0405|2025:0414-0425|2026:0330-0410|2027:0322-0403|2028:0410-0422|2029:0326-0406","BE":"y|2010:0331-0410|2011:0418-0430|2012:0402-0414|2013:0325-0406|2014:0414-0426|2015:0330-0411|2016:0321-0402|2017:0410-0418|2018:0326-0406|2019:0415-0426|2020:0406-0417|2021:0329-0410|2022:0307,0411-0423|2023:0403-0414|2024:0325-0405|2025:0414-0425,0502|2026:0330-0410|2027:0322-0402|2028:0410-0422|2029:0326-0406,0430","BW":"y|2010:0406-0410|2011:0426-0430|2012:0402-0413|2013:0325-0405|2014:0414-0425|2015:0330-0410|2016:0329-0402|2017:0410-0421|2018:0326-0406|2019:0415-0427|2020:0406-0418|2021:0401,0406-0410|2022:0414,0419-0423|2023:0406,0411-0415|2024:0323-0405|2025:0414-0426|2026:0330-0411|2027:0325,0330-0403|2028:0413,0418-0422|2029:0326-0407","BY":"y|2010:0329-0410|2011:0418-0430|2012:0402-0414|2013:0325-0406|2014:0303-0307,0414-0426|2015:0330-0411|2016:0321-0401|2017:0410-0422|2018:0326-0407|2019:0304-0308,0415-0427|2020:0224-0228,0406-0418|2021:0215-0219,0329-0410|2022:0228-0304,0411-0423|2023:0220-0224,0403-0415|2024:0212-0216,0325-0406|2025:0303-0307,0414-0425|2026:0216-0220,0330-0410|2027:0208-0212,0322-0402|2028:0228-0303,0410-0421|2029:0212-0216,0326-0406","HB":"y|2010:0319-0406|2011:0416-0430|2012:0326-0411|2013:0316-0402|2014:0403-0422|2015:0325-0410|2016:0318-0402|2017:0410-0422|2018:0319-0403|2019:0406-0423|2020:0328-0414|2021:0327-0410|2022:0404-0419|2023:0327-0411|2024:0318-0328|2025:0407-0419,0430,0502|2026:0323-0407|2027:0322-0403|2028:0410-0422|2029:0319-0403,0430","HE":"y|2010:0329-0410|2011:0418-0430|2012:0402-0414|2013:0325-0406|2014:0414-0426|2015:0330-0411|2016:0329-0409|2017:0403-0415|2018:0326-0407|2019:0415-0427|2020:0406-0418|2021:0406-0416|2022:0411-0423|2023:0403-0422|2024:0325-0413|2025:0407-0421|2026:0330-0410|2027:0322-0402|2028:0403-0414|2029:0329-0413","HH":"y|2010:0308-0320|2011:0307-0318|2012:0305-0316|2013:0304-0315|2014:0303-0314|2015:0302-0313|2016:0307-0318|2017:0306-0317|2018:0305-0316|2019:0304-0315|2020:0302-0313|2021:0301-0312|2022:0307-0318|2023:0306-0317|2024:0318-0328|2025:0310-0321,0502|2026:0302-0313|2027:0301-0312|2028:0306-0317|2029:0305-0316","MV":"y|2010:0329-0407|2011:0416-0427|2012:0402-0411|2013:0325-0403|2014:0414-0423|2015:0330-0408|2016:0321-0330|2017:0410-0419|2018:0326-0404|2019:0415-0424|2020:0406-0415|2021:0329-0407|2022:0411-0420|2023:0403-0412|2024:0325-0403|2025:0414-0423|2026:0330-0408|2027:0324-0402|2028:0412-0421|2029:0309,0328-0406,0430","NI":"y|2010:0319-0406|2011:0416-0430|2012:0326-0411|2013:0316-0402|2014:0403-0422|2015:0325-0410|2016:0318-0402|2017:0410-0422|2018:0319-0403|2019:0408-0423|2020:0330-0414|2021:0329-0409|2022:0404-0419|2023:0327-0411|2024:0318-0328|2025:0407-0419,0430,0502|2026:0323-0407|2027:0322-0403|2028:0410-0422|2029:0319-0403,0430","NW":"y|2010:0327-0410|2011:0418-0430|2012:0402-0414|2013:0325-0406|2014:0414-0426|2015:0330-0411|2016:0321-0402|2017:0410-0422|2018:0326-0407|2019:0415-0427|2020:0406-0418|2021:0329-0410|2022:0411-0423|2023:0403-0415|2024:0325-0406|2025:0414-0426|2026:0330-0411|2027:0322-0403|2028:0410-0422|2029:0326-0407","RP":"y|2010:0326-0409|2011:0418-0429|2012:0329-0413|2013:0320-0405|2014:0411-0425|2015:0326-0410|2016:0318-0401|2017:0410-0421|2018:0326-0406|2019:0423-0430|2020:0409-0417|2021:0329-0406|2022:0413-0422|2023:0403-0406|2024:0325-0402|2025:0414-0425|2026:0330-0410|2027:0322-0402|2028:0410-0421|2029:0326-0406","SH":"y|2010:0403-0417|2011:0415-0430|2012:0330-0413|2013:0325-0409|2014:0416-0502|2015:0401-0417|2016:0324-0409|2017:0407-0421|2018:0329-0413|2019:0404-0418|2020:0330-0417|2021:0401-0416|2022:0404-0416|2023:0406-0422|2024:0402-0419|2025:0411-0425|2026:0326-0410|2027:0330-0410|2028:0403-0415|2029:0323-0406","SL":"y|2010:0329-0410|2011:0418-0430|2012:0402-0414|2013:0325-0406|2014:0414-0426|2015:0330-0411|2016:0329-0409|2017:0410-0422|2018:0326-0406|2019:0417-0426|2020:0414-0424|2021:0329-0407|2022:0414-0422|2023:0403-0412|2024:0325-0405|2025:0414-0425|2026:0407-0417|2027:0330-0409|2028:0412-0421|2029:0326-0406","SN":"y|2010:0401-0410|2011:0422-0430|2012:0406-0414|2013:0329-0406|2014:0418-0426|2015:0402-0411|2016:0325-0402|2017:0413-0422|2018:0329-0406|2019:0419-0426|2020:0410-0418|2021:0402-0410|2022:0415-0423|2023:0407-0415|2024:0328-0405|2025:0418-0425|2026:0403-0410|2027:0326-0402|2028:0414-0422|2029:0329-0406","ST":"y|2010:0329-0409|2011:0418-0427|2012:0402-0405|2013:0325-0330|2014:0414-0417|2015:0402|2016:0324|2017:0410-0413|2018:0326-0331,0430|2019:0418-0430|2020:0406-0411|2021:0329-0403|2022:0411-0416|2023:0403-0408|2024:0325-0330|2025:0407-0419|2026:0330-0404|2027:0322-0327|2028:0410-0422|2029:0326-0331","TH":"y|2010:0329-0409|2011:0418-0430|2012:0402-0413|2013:0325-0406|2014:0419-0502|2015:0330-0411|2016:0324-0402|2017:0410-0421|2018:0326-0407|2019:0415-0427|2020:0406-0418|2021:0329-0410|2022:0411-0423|2023:0403-0415|2024:0325-0406|2025:0407-0419|2026:0407-0417|2027:0322-0403|2028:0403-0415|2029:0326-0407"}},{"id":"ascension_whit_break_school","name":"Himmelfahrts-/Pfingstferien","en":"Ascension/Whit Break","category":"school","regional":{"Augsburg":"y|2010:0525-0605|2011:0614-0625|2012:0529-0609|2013:0521-0531|2014:0610-0621|2015:0526-0605|2016:0517-0528|2017:0606-0616|2018:0522-0602|2019:0611-0621|2020:0602-0613|2021:0525-0604|2022:0607-0618|2023:0530-0609|2024:0521-0601|2025:0610-0620|2026:0526-0605|2027:0518-0528|2028:0606-0616|2029:0522-0601","BB":"y|2011:0603|2012:0430,0518|2013:0510|2014:0502,0530|2015:0515|2016:0506,0517|2025:0610|2026:0526|2027:0518|2029:0522","BE":"y|2010:0514,0525|2011:0603,0629|2012:0430,0518|2013:0510,0521|2014:0502,0530|2015:0515,0526|2016:0506,0517|2017:0524,0526,0606-0609|2018:0430,0511,0522|2019:0531,0611|2020:0522|2021:0514|2022:0527,0607|2023:0519,0530|2024:0510|2025:0530,0610|2026:0515,0526|2027:0507,0518-0519|2028:0526,0601-0602|2029:0511,0522-0525","BW":"y|2010:0525-0605|2011:0614-0625|2012:0529-0609|2013:0521-0601|2014:0610-0621|2015:0526-0606|2016:0517-0528|2017:0606-0616|2018:0522-0602|2019:0611-0621|2020:0602-0613|2021:0525-0605|2022:0607-0618|2023:0530-0609|2024:0521-0531|2025:0610-0620|2026:0526-0605|2027:0518-0529|2028:0606-0617|2029:0522-0601","BY":"y|2010:0525-0605|2011:0614-0625|2012:0529-0609|2013:0521-0531|2014:0610-0621|2015:0526-0605|2016:0517-0528|2017:0606-0616|2018:0522-0602|2019:0611-0621|2020:0602-0613|2021:0525-0604|2022:0607-0618|2023:0530-0609|2024:0521-0601|2025:0610-0620|2026:0526-0605|2027:0518-0528|2028:0606-0616|2029:0522-0601","HB":"y|2010:0525|2011:0614|2012:0529|2013:0521|2014:0610|2015:0526|2016:0517|2017:0606|2018:0430,0511,0522|2019:0531,0611|2020:0522,0602|2021:0514,0525|2022:0527,0607|2023:0519,0530|2024:0510,0521|2025:0530,0610|2026:0515,0526|2027:0507,0518|2028:0526,0606|2029:0511,0522","HH":"y|2010:0514-0522|2011:0426-0429,0603|2012:0430-0504,0518|2013:0502-0510|2014:0428-0502,0530|2015:0511-0515|2016:0506,0517-0520|2017:0522-0526|2018:0430,0507-0511|2019:0513-0517,0531|2020:0518-0522|2021:0510-0514|2022:0523-0527|2023:0515-0519|2024:0510,0521-0524|2025:0526-0530|2026:0511-0515|2027:0507-0514|2028:0522-0526|2029:0511-0518","MV":"y|2010:0521-0522|2011:0610-0614|2012:0525-0529|2013:0517-0521|2014:0606-0610|2015:0522-0526|2016:0514-0517|2017:0602-0606|2018:0511,0518-0522|2019:0531,0607-0611|2020:0522,0529-0602|2021:0514,0521-0525|2022:0527,0603-0607|2023:0519,0526-0530|2024:0510,0517-0521|2025:0530,0606-0610|2026:0515,0522-0526|2027:0507,0514-0518|2028:0526,0602-0606|2029:0511,0518-0522","NI":"y|2010:0514,0525|2011:0603,0614|2012:0430,0518,0529|2013:0510,0521|2014:0502,0530,0610|2015:0515,0526|2016:0506|2017:0526,0606|2018:0430,0511,0522|2019:0531,0611|2020:0522,0602|2021:0514,0525|2022:0527,0607|2023:0519,0530|2024:0510,0521|2025:0530,0610|2026:0515,0526|2027:0507,0518|2028:0526,0606|2029:0511,0522","NW":"d|0517:2016|0518:2027|0521:2013,2024|0522:2018,2029|0523:2018|0524:2018|0525:2010,2018,2021|0526:2015,2026|0529:2012|0530:2023|0602:2020|0606:2017|0610:2014,2025|0611:2019","RP":"y|2021:0525-0602|2023:0530-0607|2024:0521-0529","SH":"d|0506:2016|0507:2027|0510:2013,2024|0511:2018,2024,2029|0514:2021|0515:2015,2021,2026|0518:2012|0519:2023|0520:2023|0522:2020|0526:2017,2028|0527:2022|0528:2022|0530:2014,2025|0531:2019|0603:2011|0604:2011","SL":"y|2021:0525-0528|2022:0607-0610|2023:0530-0602|2024:0521-0524|2029:0522-0525","SN":"y|2010:0514|2011:0603|2012:0518|2013:0510,0518-0522|2014:0530|2015:0515|2016:0506|2017:0526|2018:0511,0519-0522|2019:0531|2020:0522|2021:0514|2022:0527|2023:0519|2024:0510,0518-0521|2025:0530|2026:0515|2027:0507,0515-0518|2028:0526|2029:0511,0519-0522","ST":"y|2010:0514-0522|2011:0614-0618|2012:0518-0525|2013:0510-0518|2014:0530-0607|2015:0515-0523|2016:0506-0514|2017:0526|2018:0511-0519|2019:0531-0601|2020:0518-0530|2021:0510-0522|2022:0523-0528|2023:0515-0519|2024:0521-0524|2025:0530|2026:0526-0529|2027:0515-0522|2028:0603-0610|2029:0430,0511-0525","TH":"y|2011:0611-0614|2012:0525-0529|2013:0510|2014:0530|2015:0515|2016:0506|2017:0526|2018:0511|2019:0531|2020:0522|2021:0514|2022:0527|2023:0519|2024:0510|2025:0530|2026:0515|2027:0507|2028:0526|2029:0511"}},{"id":"corpus_christi_catholic","name":"Fronleichnam","en":"Corpus Christi","category":"catholic","regional":{"SN":"d|0524:2035,2046|0526:2016|0527:2027,2032|0528:2043,2054|0529:2059|0530:2013,2024|0531:2018,2029,2040|0601:2051,2056|0603:2010,2021|0604:2015,2026,2037,2048|0605:2042,2053|0607:2012|0608:2023,2034,2045|0609:2039,2050|0611:2020|0612:2031,2036|0613:2047,2058|0615:2017,2028|0616:2022,2033,2044|0617:2049,2055,2060|0619:2014,2025|0620:2019,2030,2041,2052|0621:2057|0623:2011|0624:2038","TH":"d|0524:2035,2046|0526:2016|0527:2027,2032|0528:2043,2054|0529:2059|0530:2013,2024|0531:2018,2029,2040|0601:2051,2056|0603:2010,2021|0604:2015,2026,2037,2048|0605:2042,2053|0607:2012|0608:2023,2034,2045|0609:2039,2050|0611:2020|0612:2031,2036|0613:2047,2058|0615:2017,2028|0616:2022,2033,2044|0617:2049,2055,2060|0619:2014,2025|0620:2019,2030,2041,2052|0621:2057|0623:2011|0624:2038"}},{"id":"summer_break_school","name":"Sommerferien","en":"Summer Break","category":"school","regional":{"Augsburg":"y|2010:0802-0913|2011:0730-0912|2012:0801-0912|2013:0731-0911|2014:0730-0915|2015:0801-0914|2016:0730-0912|2017:0729-0911|2018:0730-0910|2019:0729-0909|2020:0727-0907|2021:0730-0913|2022:0801-0912|2023:0731-0911|2024:0729-0909|2025:0801,0804-0915|2026:0803-0914|2027:0802-0913|2028:0731-0911|2029:0730-0910","BB":"y|2010:0708-0821|2011:0630-0813|2012:0621-0803|2013:0620-0802|2014:0710-0822|2015:0716-0828|2016:0721-0903|2017:0720-0901|2018:0705-0818|2019:0620-0803|2020:0625-0808|2021:0624-0807|2022:0707-0820|2023:0713-0826|2024:0718-0831|2025:0724-0906|2026:0709-0822|2027:0701-0814|2028:0629-0812|2029:0628-0811","BE":"y|2010:0707-0821|2011:0630-0812|2012:0620-0803|2013:0619-0802|2014:0709-0822|2015:0716-0828|2016:0720-0902|2017:0720-0901|2018:0705-0817|2019:0620-0802|2020:0625-0807|2021:0624-0806|2022:0707-0819|2023:0713-0825|2024:0718-0830|2025:0724-0906|2026:0709-0822|2027:0701-0814|2028:0701-0812|2029:0701-0811","BW":"y|2010:0729-0911|2011:0728-0910|2012:0726-0908|2013:0725-0907|2014:0731-0913|2015:0730-0912|2016:0728-0910|2017:0727-0909|2018:0726-0908|2019:0729-0910|2020:0730-0912|2021:0729-0911|2022:0728-0910|2023:0727-0909|2024:0725-0907|2025:0731-0913|2026:0730-0912|2027:0729-0911|2028:0727-0909|2029:0726-0908","BY":"y|2010:0802-0913|2011:0730-0912|2012:0801-0912|2013:0731-0911|2014:0730-0915|2015:0801-0914|2016:0730-0912|2017:0729-0911|2018:0730-0910|2019:0729-0909|2020:0727-0907|2021:0730-0913|2022:0801-0912|2023:0731-0911|2024:0729-0909|2025:0801,0804-0915|2026:0803-0914|2027:0802-0913|2028:0731-0911|2029:0730-0910","HB":"y|2010:0624-0804|2011:0707-0817|2012:0723-0831|2013:0627-0807|2014:0731-0910|2015:0723-0902|2016:0623-0803|2017:0622-0802|2018:0628-0808|2019:0704-0814|2020:0716-0826|2021:0722-0901|2022:0714-0824|2023:0706-0816|2024:0624-0802|2025:0703-0813|2026:0702-0812|2027:0708-0818|2028:0720-0830|2029:0719-0829","HE":"y|2010:0705-0814|2011:0627-0805|2012:0702-0810|2013:0708-0816|2014:0728-0905|2015:0727-0904|2016:0718-0826|2017:0703-0811|2018:0625-0803|2019:0701-0809|2020:0706-0814|2021:0719-0827|2022:0725-0902|2023:0724-0901|2024:0715-0823|2025:0707-0815|2026:0629-0807|2027:0628-0806|2028:0703-0811|2029:0716-0824","HH":"y|2010:0708-0818|2011:0630-0810|2012:0621-0801|2013:0620-0731|2014:0710-0820|2015:0716-0826|2016:0721-0831|2017:0720-0830|2018:0705-0815|2019:0627-0807|2020:0625-0805|2021:0624-0804|2022:0707-0817|2023:0713-0823|2024:0718-0828|2025:0724-0903|2026:0709-0819|2027:0701-0811|2028:0703-0811|2029:0702-0810","MV":"y|2010:0712-0821|2011:0704-0813|2012:0623-0804|2013:0622-0803|2014:0714-0823|2015:0720-0829|2016:0725-0903|2017:0724-0902|2018:0709-0818|2019:0701-0810|2020:0622-0801|2021:0621-0731|2022:0704-0813|2023:0717-0826|2024:0722-0831|2025:0728-0906|2026:0713-0822|2027:0705-0814|2028:0626-0805|2029:0618-0728","NI":"y|2010:0624-0804|2011:0707-0817|2012:0723-0831|2013:0627-0807|2014:0731-0910|2015:0723-0902|2016:0623-0803|2017:0622-0802|2018:0628-0808|2019:0704-0814|2020:0716-0826|2021:0722-0901|2022:0714-0824|2023:0706-0816|2024:0624-0802|2025:0703-0813|2026:0702-0812|2027:0708-0818|2028:0720-0830|2029:0719-0829","NW":"y|2010:0715-0827|2011:0725-0906|2012:0709-0821|2013:0722-0903|2014:0707-0819|2015:0629-0811|2016:0711-0823|2017:0717-0829|2018:0716-0828|2019:0715-0827|2020:0629-0811|2021:0705-0817|2022:0627-0809|2023:0622-0804|2024:0708-0820|2025:0714-0826|2026:0720-0901|2027:0719-0831|2028:0710-0822|2029:0702-0814","RP":"y|2010:0705-0813|2011:0627-0805|2012:0702-0810|2013:0708-0816|2014:0728-0905|2015:0727-0904|2016:0718-0826|2017:0703-0811|2018:0625-0803|2019:0701-0809|2020:0706-0814|2021:0719-0827|2022:0725-0902|2023:0724-0901|2024:0715-0823|2025:0707-0815|2026:0629-0807|2027:0628-0806|2028:0703-0811|2029:0716-0824","SH":"y|2010:0712-0821|2011:0704-0813|2012:0625-0804|2013:0624-0803|2014:0714-0823|2015:0720-0829|2016:0725-0903|2017:0724-0902|2018:0709-0818|2019:0701-0810|2020:0629-0808|2021:0621-0731|2022:0704-0813|2023:0717-0826|2024:0722-0831|2025:0728-0906|2026:0704-0815|2027:0703-0814|2028:0624-0804|2029:0623-0803","SL":"y|2010:0705-0814|2011:0624-0806|2012:0702-0814|2013:0708-0817|2014:0728-0906|2015:0727-0905|2016:0718-0827|2017:0703-0814|2018:0625-0803|2019:0701-0809|2020:0706-0814|2021:0719-0827|2022:0725-0902|2023:0724-0901|2024:0715-0823|2025:0707-0814|2026:0629-0807|2027:0628-0806|2028:0703-0811|2029:0716-0824","SN":"y|2010:0628-0806|2011:0711-0819|2012:0723-0831|2013:0715-0823|2014:0721-0829|2015:0713-0821|2016:0627-0805|2017:0626-0804|2018:0702-0810|2019:0708-0816|2020:0720-0828|2021:0726-0903|2022:0718-0826|2023:0710-0818|2024:0620-0802|2025:0628-0808|2026:0704-0814|2027:0710-0820|2028:0722-0901|2029:0721-0831","ST":"y|2010:0624-0804|2011:0711-0824|2012:0723-0905|2013:0715-0828|2014:0721-0903|2015:0713-0826|2016:0627-0810|2017:0626-0809|2018:0628-0808|2019:0704-0814|2020:0716-0826|2021:0722-0901|2022:0714-0824|2023:0706-0816|2024:0624-0803|2025:0628-0808|2026:0704-0814|2027:0710-0820|2028:0722-0901|2029:0721-0831","TH":"y|2010:0624-0804|2011:0711-0819|2012:0723-0831|2013:0715-0823|2014:0721-0829|2015:0713-0821|2016:0627-0810|2017:0626-0809|2018:0702-0811|2019:0708-0817|2020:0720-0829|2021:0726-0904|2022:0718-0827|2023:0710-0819|2024:0620-0731|2025:0628-0808|2026:0704-0814|2027:0710-0820|2028:0722-0901|2029:0721-0831"}},{"id":"assumption_day_catholic","name":"Mariä Himmelfahrt","en":"Assumption Day","category":"catholic","regional":{"BY":"d|0815:2010-2060"}},{"id":"autumn_break_school","name":"Herbstferien","en":"Autumn Break","category":"school","regional":{"Augsburg":"y|2010:1102-1105|2011:1031-1105|2012:1029-1103|2013:1028-1031|2014:1027-1031|2015:1102-1107|2016:1031-1104|2017:1030-1103|2018:1029-1102|2019:1028-1031|2020:1031-1106|2021:1102-1105|2022:1031-1104|2023:1030-1103|2024:1028-1031|2025:1103-1107|2026:1102-1106|2027:1102-1105|2028:1030-1103","BB":"y|2010:1011-1023|2011:1004-1014|2012:1001-1013|2013:0930-1012,1101|2014:1020-1101|2015:1019-1030|2016:1017-1028|2017:1023-1104|2018:1022-1102|2019:1004-1018|2020:1012-1024|2021:1011-1023|2022:1024-1105|2023:1023-1104|2024:1021-1102|2025:1020-1101|2026:1019-1030|2027:1011-1023|2028:1002-1014","BE":"y|2010:1011-1023|2011:1004-1014|2012:1001-1013|2013:0930-1012|2014:1020-1101|2015:1019-1031|2016:1017-1028|2017:1002,1023-1104|2018:1022-1102|2019:1004,1007-1019|2020:1012-1024|2021:1011-1023|2022:1024-1105|2023:1002,1023-1104|2024:1004,1021-1102|2025:1020-1101|2026:1019-1031|2027:1011-1023|2028:1002-1014","BW":"y|2010:1102-1106|2011:1102-1104|2012:1029-1102|2013:1028-1030|2014:1027-1030|2015:1102-1106|2016:1102-1104|2017:1030-1103|2018:1029-1102|2019:1028-1031|2020:1026-1031|2021:1031,1102-1106|2022:1031,1102-1104|2023:1030-1103|2024:1028-1031|2025:1027-1031|2026:1026-1031|2027:1102-1106|2028:1030-1103","BY":"y|2010:1102-1105|2011:1031-1105|2012:1029-1103|2013:1028-1031|2014:1027-1031|2015:1102-1107|2016:1031-1104|2017:1030-1103|2018:1029-1102|2019:1028-1031|2020:1031-1106|2021:1102-1105|2022:1031-1104|2023:1030-1103|2024:1028-1031|2025:1103-1107|2026:1102-1106|2027:1102-1105|2028:1030-1103","HB":"y|2010:1009-1023|2011:1017-1029|2012:1022-1103|2013:1004-1018|2014:1027-1108|2015:1019-1031|2016:1004-1015|2017:1002-1014,1030|2018:1001-1013|2019:1004-1018|2020:1012-1024|2021:1018-1030|2022:1017-1029|2023:1002,1016-1030|2024:1004-1019,1101|2025:1013-1025|2026:1012-1024|2027:1018-1030|2028:1002,1023-1104","HE":"y|2010:1011-1022|2011:1010-1022|2012:1015-1027|2013:1014-1026|2014:1020-1101|2015:1019-1031|2016:1017-1029|2017:1009-1021|2018:1001-1013|2019:0930-1012|2020:1005-1017|2021:1011-1023|2022:1024-1029|2023:1023-1028|2024:1014-1025|2025:1006-1018|2026:1005-1017|2027:1004-1016|2028:1009-1020","HH":"y|2010:1004-1015|2011:1004-1014|2012:1001-1012|2013:0930-1011|2014:1013-1024|2015:1019-1030|2016:1017-1028|2017:1002,1016-1027|2018:1001-1012|2019:1004-1018,1101|2020:1005-1016|2021:1004-1015|2022:1010-1021|2023:1002,1016-1027|2024:1004,1021-1101|2025:1020-1031|2026:1019-1030|2027:1011-1022|2028:1002-1013,1030","MV":"y|2010:1018-1023|2011:1017-1021|2012:1001-1005|2013:1014-1019|2014:1020-1025|2015:1024-1030|2016:1024-1028|2017:1002,1023-1030|2018:1008-1013,1101-1102|2019:1004-1012,1101|2020:1005-1010,1102-1103|2021:1002-1009,1101-1102|2022:1010-1014,1101-1102|2023:1009-1014,1030-1101|2024:1004,1021-1026,1101|2025:1002,1020-1024,1103|2026:1015-1024|2027:1014-1023|2028:1002,1023-1028,1030","NI":"y|2010:1009-1023|2011:1017-1029|2012:1022-1103|2013:1004-1018|2014:1027-1108|2015:1019-1031|2016:1004-1015|2017:1002-1013,1030|2018:1001-1012|2019:1004-1018|2020:1012-1023|2021:1018-1029|2022:1017-1028|2023:1002,1016-1030|2024:1004-1019,1101|2025:1013-1025|2026:1012-1024|2027:1016-1030|2028:1002,1023-1104","NW":"y|2010:1011-1023|2011:1024-1105|2012:1008-1020|2013:1021-1102|2014:1006-1018|2015:1005-1017|2016:1010-1021|2017:1023-1104|2018:1015-1027|2019:1014-1026|2020:1012-1024|2021:1011-1023|2022:1004-1015|2023:1002-1014|2024:1014-1026|2025:1013-1025|2026:1017-1031|2027:1023-1106|2028:1023-1104","RP":"y|2010:1011-1022|2011:1004-1014|2012:1001-1012|2013:1004-1018|2014:1020-1031|2015:1019-1030|2016:1010-1021|2017:1002-1013|2018:1001-1012|2019:0930-1011|2020:1012-1023|2021:1011-1022|2022:1017-1031|2023:1016-1027|2024:1014-1025|2025:1013-1024|2026:1005-1016|2027:1004-1015|2028:1009-1020","SH":"y|2010:1011-1023|2011:1010-1022|2012:1004-1019|2013:1004-1018|2014:1013-1025|2015:1019-1031|2016:1017-1029|2017:1016-1027|2018:1001-1019|2019:1004-1018|2020:1005-1017|2021:1004-1016|2022:1010-1021|2023:1016-1027|2024:1021-1101|2025:1020-1030|2026:1012-1024|2027:1011-1023|2028:1016-1030","SL":"y|2010:1011-1023|2011:1004-1015|2012:1022-1103|2013:1021-1102|2014:1020-1031|2015:1019-1031|2016:1010-1022|2017:1002-1014|2018:1001-1012|2019:1007-1018|2020:1012-1023|2021:1018-1029|2022:1024-1104|2023:1023-1103|2024:1014-1025|2025:1013-1024|2026:1005-1016|2027:1004-1015|2028:1009-1020","SN":"y|2010:1004-1016|2011:1017-1028|2012:1022-1102|2013:1021-1101|2014:1020-1031|2015:1012-1024|2016:1003-1015|2017:1002-1014,1030|2018:1008-1020|2019:1014-1025|2020:1019-1031|2021:1018-1030|2022:1017-1029|2023:1002-1014,1030|2024:1007-1019|2025:1006-1018|2026:1012-1024|2027:1011-1023|2028:1023-1103","ST":"y|2010:1018-1023|2011:1017-1022|2012:1029-1102|2013:1021-1025|2014:1027-1030|2015:1017-1024|2016:1004-1015|2017:1002-1013,1030|2018:1001-1012|2019:1004-1011,1101|2020:1019-1024|2021:1025-1030|2022:1024-1104|2023:1002,1016-1030|2024:0930-1012,1101|2025:1013-1025|2026:1019-1030|2027:1018-1023|2028:1002,1030-1103","TH":"y|2010:1009-1023|2011:1017-1028|2012:1022-1103|2013:1021-1102|2014:1006-1018|2015:1005-1017|2016:1010-1022|2017:1002-1014|2018:1001-1013|2019:1007-1019|2020:1017-1030|2021:1025-1106|2022:1017-1029|2023:1002-1014|2024:0930-1012|2025:1006-1018|2026:1012-1024|2027:1009-1023|2028:1023-1103"}}]} diff --git a/app/Support/Holidays/data/DJ.json b/app/Support/Holidays/data/DJ.json new file mode 100644 index 000000000..abbfded18 --- /dev/null +++ b/app/Support/Holidays/data/DJ.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"DJ","language":"fr","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"isra_and_mi_raj","name":"Al Isra et Al Mirague","en":"Isra' and Mi'raj","category":"public","dates":"y|2010:0709|2011:0629|2012:0617|2013:0606|2014:0526|2015:0516|2016:0504|2017:0424|2018:0413|2019:0403|2020:0322|2021:0311|2022:0228|2023:0218|2024:0208|2025:0127|2026:0116|2027:0105,1225|2028:1214|2029:1203|2030:1123|2031:1112|2032:1101|2033:1021|2034:1010|2035:0929|2036:0918|2037:0907|2038:0828|2039:0817|2040:0805|2041:0725|2042:0715|2043:0704|2044:0623|2045:0613|2046:0602|2047:0522|2048:0510|2049:0429|2050:0419|2051:0409|2052:0328|2053:0318|2054:0307|2055:0224|2056:0213|2057:0201|2058:0122|2059:0112|2060:0101,1220","estimated":"y|2010:0709|2011:0629|2012:0617|2013:0606|2014:0526|2015:0516|2016:0504|2017:0424|2018:0413|2019:0403|2020:0322|2021:0311|2022:0228|2023:0218|2024:0208|2025:0127|2026:0116|2027:0105,1225|2028:1214|2029:1203|2030:1123|2031:1112|2032:1101|2033:1021|2034:1010|2035:0929|2036:0918|2037:0907|2038:0828|2039:0817|2040:0805|2041:0725|2042:0715|2043:0704|2044:0623|2045:0613|2046:0602|2047:0522|2048:0510|2049:0429|2050:0419|2051:0409|2052:0328|2053:0318|2054:0307|2055:0224|2056:0213|2057:0201|2058:0122|2059:0112|2060:0101,1220"},{"id":"new_year_s_day","name":"Nouvel an","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"eid_al_fitr","name":"Eid al-Fitr","en":"Eid al-Fitr","category":"public","dates":"y|2010:0910|2011:0830|2012:0819|2013:0808|2014:0728|2015:0717|2016:0706|2017:0625|2018:0615|2019:0604|2020:0524|2021:0513|2022:0502|2023:0421|2024:0410|2025:0330|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304","estimated":"y|2010:0910|2011:0830|2012:0819|2013:0808|2014:0728|2015:0717|2016:0706|2017:0625|2018:0615|2019:0604|2020:0524|2021:0513|2022:0502|2023:0421|2024:0410|2025:0330|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304"},{"id":"eid_al_fitr_holiday","name":"Eid al-Fitr deuxième jour","en":"Eid al-Fitr Holiday","category":"public","dates":"y|2010:0911|2011:0831|2012:0820|2013:0809|2014:0729|2015:0718|2016:0707|2017:0626|2018:0616|2019:0605|2020:0525|2021:0514|2022:0503|2023:0422|2024:0411|2025:0331|2026:0321|2027:0310|2028:0227|2029:0215|2030:0205|2031:0125|2032:0115|2033:0103,1224|2034:1213|2035:1202|2036:1120|2037:1109|2038:1030|2039:1020|2040:1008|2041:0927|2042:0916|2043:0905|2044:0825|2045:0815|2046:0804|2047:0725|2048:0713|2049:0702|2050:0621|2051:0611|2052:0530|2053:0520|2054:0510|2055:0429|2056:0417|2057:0406|2058:0326|2059:0316|2060:0305","estimated":"y|2010:0911|2011:0831|2012:0820|2013:0809|2014:0729|2015:0718|2016:0707|2017:0626|2018:0616|2019:0605|2020:0525|2021:0514|2022:0503|2023:0422|2024:0411|2025:0331|2026:0321|2027:0310|2028:0227|2029:0215|2030:0205|2031:0125|2032:0115|2033:0103,1224|2034:1213|2035:1202|2036:1120|2037:1109|2038:1030|2039:1020|2040:1008|2041:0927|2042:0916|2043:0905|2044:0825|2045:0815|2046:0804|2047:0725|2048:0713|2049:0702|2050:0621|2051:0611|2052:0530|2053:0520|2054:0510|2055:0429|2056:0417|2057:0406|2058:0326|2059:0316|2060:0305"},{"id":"prophet_muhammad_s_birthday","name":"Anniversaire du prophète Muhammad","en":"Prophet Muhammad's Birthday","category":"public","dates":"y|2010:0226|2011:0215|2012:0204|2013:0124|2014:0113|2015:0103,1223|2016:1211|2017:1130|2018:1120|2019:1109|2020:1029|2021:1018|2022:1008|2023:0927|2024:0915|2025:0904|2026:0825|2027:0814|2028:0803|2029:0724|2030:0713|2031:0702|2032:0620|2033:0609|2034:0530|2035:0520|2036:0508|2037:0428|2038:0417|2039:0406|2040:0325|2041:0315|2042:0304|2043:0222|2044:0211|2045:0130|2046:0119|2047:0108,1229|2048:1218|2049:1207|2050:1126|2051:1116|2052:1104|2053:1024|2054:1013|2055:1003|2056:0922|2057:0911|2058:0831|2059:0820|2060:0808","estimated":"y|2010:0226|2011:0215|2012:0204|2013:0124|2014:0113|2015:0103,1223|2016:1211|2017:1130|2018:1120|2019:1109|2020:1029|2021:1018|2022:1008|2023:0927|2024:0915|2025:0904|2026:0825|2027:0814|2028:0803|2029:0724|2030:0713|2031:0702|2032:0620|2033:0609|2034:0530|2035:0520|2036:0508|2037:0428|2038:0417|2039:0406|2040:0325|2041:0315|2042:0304|2043:0222|2044:0211|2045:0130|2046:0119|2047:0108,1229|2048:1218|2049:1207|2050:1126|2051:1116|2052:1104|2053:1024|2054:1013|2055:1003|2056:0922|2057:0911|2058:0831|2059:0820|2060:0808"},{"id":"day_of_arafah","name":"Arafat","en":"Day of Arafah","category":"public","dates":"y|2010:1115|2011:1105|2012:1025|2013:1014|2014:1003|2015:0922|2016:0910|2017:0831|2018:0820|2019:0810|2020:0730|2021:0719|2022:0708|2023:0627|2024:0615|2025:0605|2026:0526|2027:0515|2028:0504|2029:0423|2030:0412|2031:0401|2032:0321|2033:0310|2034:0228|2035:0217|2036:0206|2037:0125|2038:0115|2039:0104,1225|2040:1213|2041:1203|2042:1122|2043:1111|2044:1030|2045:1020|2046:1009|2047:0929|2048:0918|2049:0907|2050:0827|2051:0816|2052:0804|2053:0725|2054:0714|2055:0704|2056:0622|2057:0611|2058:0531|2059:0521|2060:0509","estimated":"y|2010:1115|2011:1105|2012:1025|2013:1014|2014:1003|2015:0922|2016:0910|2017:0831|2018:0820|2019:0810|2020:0730|2021:0719|2022:0708|2023:0627|2024:0615|2025:0605|2026:0526|2027:0515|2028:0504|2029:0423|2030:0412|2031:0401|2032:0321|2033:0310|2034:0228|2035:0217|2036:0206|2037:0125|2038:0115|2039:0104,1225|2040:1213|2041:1203|2042:1122|2043:1111|2044:1030|2045:1020|2046:1009|2047:0929|2048:0918|2049:0907|2050:0827|2051:0816|2052:0804|2053:0725|2054:0714|2055:0704|2056:0622|2057:0611|2058:0531|2059:0521|2060:0509"},{"id":"islamic_new_year","name":"Nouvel an musulman","en":"Islamic New Year","category":"public","dates":"y|2010:1207|2011:1126|2012:1115|2013:1104|2014:1025|2015:1014|2016:1002|2017:0921|2018:0911|2019:0831|2020:0820|2021:0809|2022:0730|2023:0719|2024:0707|2025:0626|2026:0616|2027:0606|2028:0525|2029:0514|2030:0503|2031:0423|2032:0411|2033:0401|2034:0321|2035:0311|2036:0228|2037:0216|2038:0205|2039:0126|2040:0115|2041:0104,1224|2042:1214|2043:1203|2044:1121|2045:1110|2046:1031|2047:1020|2048:1009|2049:0928|2050:0917|2051:0906|2052:0826|2053:0815|2054:0805|2055:0725|2056:0714|2057:0703|2058:0622|2059:0611|2060:0531","estimated":"y|2010:1207|2011:1126|2012:1115|2013:1104|2014:1025|2015:1014|2016:1002|2017:0921|2018:0911|2019:0831|2020:0820|2021:0809|2022:0730|2023:0719|2024:0707|2025:0626|2026:0616|2027:0606|2028:0525|2029:0514|2030:0503|2031:0423|2032:0411|2033:0401|2034:0321|2035:0311|2036:0228|2037:0216|2038:0205|2039:0126|2040:0115|2041:0104,1224|2042:1214|2043:1203|2044:1121|2045:1110|2046:1031|2047:1020|2048:1009|2049:0928|2050:0917|2051:0906|2052:0826|2053:0815|2054:0805|2055:0725|2056:0714|2057:0703|2058:0622|2059:0611|2060:0531"},{"id":"eid_al_adha","name":"Eid al-Adha","en":"Eid al-Adha","category":"public","dates":"y|2010:1116|2011:1106|2012:1026|2013:1015|2014:1004|2015:0923|2016:0911|2017:0901|2018:0821|2019:0811|2020:0731|2021:0720|2022:0709|2023:0628|2024:0616|2025:0606|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510","estimated":"y|2010:1116|2011:1106|2012:1026|2013:1015|2014:1004|2015:0923|2016:0911|2017:0901|2018:0821|2019:0811|2020:0731|2021:0720|2022:0709|2023:0628|2024:0616|2025:0606|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510"},{"id":"eid_al_adha_holiday","name":"Eid al-Adha deuxième jour","en":"Eid al-Adha Holiday","category":"public","dates":"y|2010:1117|2011:1107|2012:1027|2013:1016|2014:1005|2015:0924|2016:0912|2017:0902|2018:0822|2019:0812|2020:0801|2021:0721|2022:0710|2023:0629|2024:0617|2025:0607|2026:0528|2027:0517|2028:0506|2029:0425|2030:0414|2031:0403|2032:0323|2033:0312|2034:0302|2035:0219|2036:0208|2037:0127|2038:0117|2039:0106,1227|2040:1215|2041:1205|2042:1124|2043:1113|2044:1101|2045:1022|2046:1011|2047:1001|2048:0920|2049:0909|2050:0829|2051:0818|2052:0806|2053:0727|2054:0716|2055:0706|2056:0624|2057:0613|2058:0602|2059:0523|2060:0511","estimated":"y|2010:1117|2011:1107|2012:1027|2013:1016|2014:1005|2015:0924|2016:0912|2017:0902|2018:0822|2019:0812|2020:0801|2021:0721|2022:0710|2023:0629|2024:0617|2025:0607|2026:0528|2027:0517|2028:0506|2029:0425|2030:0414|2031:0403|2032:0323|2033:0312|2034:0302|2035:0219|2036:0208|2037:0127|2038:0117|2039:0106,1227|2040:1215|2041:1205|2042:1124|2043:1113|2044:1101|2045:1022|2046:1011|2047:1001|2048:0920|2049:0909|2050:0829|2051:0818|2052:0806|2053:0727|2054:0716|2055:0706|2056:0624|2057:0613|2058:0602|2059:0523|2060:0511"},{"id":"labor_day","name":"Fête du travail","en":"Labor Day","category":"public","dates":"d|0501:2010-2060"},{"id":"independence_day","name":"Fête de l'indépendance","en":"Independence Day","category":"public","dates":"d|0627:2010-2060"},{"id":"independence_day_holiday","name":"Fête de l'indépendance deuxième jour","en":"Independence Day Holiday","category":"public","dates":"d|0628:2010-2060"},{"id":"christmas_day","name":"Noël","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"}]} diff --git a/app/Support/Holidays/data/DK.json b/app/Support/Holidays/data/DK.json new file mode 100644 index 000000000..bd29844dd --- /dev/null +++ b/app/Support/Holidays/data/DK.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"DK","language":"da","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Nytårsdag","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"maundy_thursday","name":"Skærtorsdag","en":"Maundy Thursday","category":"public","dates":"d|0322:2035,2046|0324:2016|0325:2027,2032|0326:2043,2054|0327:2059|0328:2013,2024|0329:2018,2029,2040|0330:2051,2056|0401:2010,2021|0402:2015,2026,2037,2048|0403:2042,2053|0405:2012|0406:2023,2034,2045|0407:2039,2050|0409:2020|0410:2031,2036|0411:2047,2058|0413:2017,2028|0414:2022,2033,2044|0415:2049,2055,2060|0417:2014,2025|0418:2019,2030,2041,2052|0419:2057|0421:2011|0422:2038"},{"id":"good_friday","name":"Langfredag","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_sunday","name":"Påskedag","en":"Easter Sunday","category":"public","dates":"d|0325:2035,2046|0327:2016|0328:2027,2032|0329:2043,2054|0330:2059|0331:2013,2024|0401:2018,2029,2040|0402:2051,2056|0404:2010,2021|0405:2015,2026,2037,2048|0406:2042,2053|0408:2012|0409:2023,2034,2045|0410:2039,2050|0412:2020|0413:2031,2036|0414:2047,2058|0416:2017,2028|0417:2022,2033,2044|0418:2049,2055,2060|0420:2014,2025|0421:2019,2030,2041,2052|0422:2057|0424:2011|0425:2038"},{"id":"easter_monday","name":"Anden påskedag","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"ascension_day","name":"Kristi himmelfartsdag","en":"Ascension Day","category":"public","dates":"d|0503:2035,2046|0505:2016|0506:2027,2032|0507:2043,2054|0508:2059|0509:2013,2024|0510:2018,2029,2040|0511:2051,2056|0513:2010,2021|0514:2015,2026,2037,2048|0515:2042,2053|0517:2012|0518:2023,2034,2045|0519:2039,2050|0521:2020|0522:2031,2036|0523:2047,2058|0525:2017,2028|0526:2022,2033,2044|0527:2049,2055,2060|0529:2014,2025|0530:2019,2030,2041,2052|0531:2057|0602:2011|0603:2038"},{"id":"pentecost","name":"Pinsedag","en":"Pentecost","category":"public","dates":"d|0513:2035,2046|0515:2016|0516:2027,2032|0517:2043,2054|0518:2059|0519:2013,2024|0520:2018,2029,2040|0521:2051,2056|0523:2010,2021|0524:2015,2026,2037,2048|0525:2042,2053|0527:2012|0528:2023,2034,2045|0529:2039,2050|0531:2020|0601:2031,2036|0602:2047,2058|0604:2017,2028|0605:2022,2033,2044|0606:2049,2055,2060|0608:2014,2025|0609:2019,2030,2041,2052|0610:2057|0612:2011|0613:2038"},{"id":"pentecost_monday","name":"Anden pinsedag","en":"Pentecost Monday","category":"public","dates":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038"},{"id":"christmas_day","name":"Juledag","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"},{"id":"second_day_of_christmas","name":"Anden juledag","en":"Second Day of Christmas","category":"public","dates":"d|1226:2010-2060"},{"id":"workers_day_optional","name":"Arbejdernes kampdag","en":"Workers' Day","category":"optional","dates":"d|0501:2010-2060"},{"id":"constitution_day_optional","name":"Grundlovsdag","en":"Constitution Day","category":"optional","dates":"d|0605:2010-2060"},{"id":"christmas_eve_optional","name":"Juleaftensdag","en":"Christmas Eve","category":"optional","dates":"d|1224:2010-2060"},{"id":"new_year_s_eve_optional","name":"Nytårsaften","en":"New Year's Eve","category":"optional","dates":"d|1231:2010-2060"}]} diff --git a/app/Support/Holidays/data/DM.json b/app/Support/Holidays/data/DM.json new file mode 100644 index 000000000..71c105e03 --- /dev/null +++ b/app/Support/Holidays/data/DM.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"DM","language":null,"years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051"},{"id":"carnival_monday","name":"Carnival Monday","en":"Carnival Monday","category":"public","dates":"d|0205:2035,2046|0208:2016,2027|0209:2032,2043,2054|0210:2059|0211:2013|0212:2018,2024,2029|0213:2040,2051|0214:2056|0215:2010,2021|0216:2015,2026,2037|0217:2042,2048,2053|0220:2012,2023,2034,2045|0221:2039,2050|0224:2020,2031|0225:2036,2047,2058|0227:2017|0228:2022,2028,2033|0229:2044|0301:2049,2055,2060|0303:2014,2025|0304:2019,2030,2041,2052|0305:2057|0307:2011|0308:2038"},{"id":"carnival_tuesday","name":"Carnival Tuesday","en":"Carnival Tuesday","category":"public","dates":"d|0206:2035,2046|0209:2016,2027|0210:2032,2043,2054|0211:2059|0212:2013|0213:2018,2024,2029|0214:2040,2051|0215:2056|0216:2010,2021|0217:2015,2026,2037|0218:2042,2048,2053|0221:2012,2023,2034,2045|0222:2039,2050|0225:2020,2031|0226:2036,2047,2058|0228:2017|0229:2028|0301:2022,2033,2044|0302:2049,2055,2060|0304:2014,2025|0305:2019,2030,2041,2052|0306:2057|0308:2011|0309:2038"},{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_monday","name":"Easter Monday","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"labour_day","name":"Labour Day","en":"Labour Day","category":"public","dates":"d|0501:2017,2023,2028,2034,2045,2051,2056|0502:2011,2016,2022,2033,2039,2044,2050|0503:2010,2021,2027,2032,2038,2049,2055,2060|0504:2015,2020,2026,2037,2043,2048,2054|0505:2014,2025,2031,2036,2042,2053,2059|0506:2013,2019,2024,2030,2041,2047,2052,2058|0507:2012,2018,2029,2035,2040,2046,2057"},{"id":"whit_monday","name":"Whit Monday","en":"Whit Monday","category":"public","dates":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038"},{"id":"emancipation_day","name":"Emancipation Day","en":"Emancipation Day","category":"public","dates":"d|0801:2011,2016,2022,2033,2039,2044,2050|0802:2010,2021,2027,2032,2038,2049,2055,2060|0803:2015,2020,2026,2037,2043,2048,2054|0804:2014,2025,2031,2036,2042,2053,2059|0805:2013,2019,2024,2030,2041,2047,2052,2058|0806:2012,2018,2029,2035,2040,2046,2057|0807:2017,2023,2028,2034,2045,2051,2056"},{"id":"independence_day","name":"Independence Day","en":"Independence Day","category":"public","dates":"d|1103:2010-2060|1105:2013,2019,2024,2030,2041,2047,2052,2058"},{"id":"national_day_of_community_service","name":"National Day of Community Service","en":"National Day of Community Service","category":"public","dates":"d|1104:2010-2060|1105:2012,2018,2029,2035,2040,2046,2057"},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060|1227:2011,2016,2022,2033,2039,2044,2050"},{"id":"boxing_day","name":"Boxing Day","en":"Boxing Day","category":"public","dates":"d|1226:2010-2060|1227:2010,2021,2027,2032,2038,2049,2055,2060"}]} diff --git a/app/Support/Holidays/data/DO.json b/app/Support/Holidays/data/DO.json new file mode 100644 index 000000000..b65667319 --- /dev/null +++ b/app/Support/Holidays/data/DO.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"DO","language":"es","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Año Nuevo","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"epiphany","name":"Día de los Santos Reyes","en":"Epiphany","category":"public","dates":"d|0104:2010,2016,2021,2027,2038,2044,2049,2055|0105:2015,2026,2032,2037,2043,2054,2060|0106:2013-2014,2018-2020,2024-2025,2029-2031,2035-2036,2041-2042,2046-2048,2052-2053,2057-2059|0109:2012,2017,2023,2034,2040,2045,2051|0110:2011,2022,2028,2033,2039,2050,2056"},{"id":"lady_of_altagracia","name":"Día de la Altagracia","en":"Lady of Altagracia","category":"public","dates":"d|0121:2010-2060"},{"id":"juan_pablo_duarte_day","name":"Día de Duarte","en":"Juan Pablo Duarte Day","category":"public","dates":"d|0124:2011,2022,2028,2033,2039,2050,2056|0125:2010,2016,2021,2027,2038,2044,2049,2055|0126:2013-2015,2019-2020,2025-2026,2030-2032,2036-2037,2041-2043,2047-2048,2053-2054,2058-2060|0129:2018,2024,2029,2035,2046,2052,2057|0130:2012,2017,2023,2034,2040,2045,2051"},{"id":"national_independence_day","name":"Día de la Independencia Nacional","en":"National Independence Day","category":"public","dates":"d|0227:2010-2060"},{"id":"good_friday","name":"Viernes Santo","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"labor_day","name":"Día del Trabajo","en":"Labor Day","category":"public","dates":"d|0429:2013,2019,2024,2030,2041,2047,2052,2058|0430:2012,2018,2029,2035,2040,2046,2057|0501:2010,2017,2021,2023,2027-2028,2032,2034,2038,2045,2049,2051,2055-2056,2060|0502:2011,2016,2022,2033,2039,2044,2050|0504:2015,2020,2026,2037,2043,2048,2054|0505:2014,2025,2031,2036,2042,2053,2059"},{"id":"corpus_christi","name":"Corpus Christi","en":"Corpus Christi","category":"public","dates":"d|0524:2035,2046|0526:2016|0527:2027,2032|0528:2043,2054|0529:2059|0530:2013,2024|0531:2018,2029,2040|0601:2051,2056|0603:2010,2021|0604:2015,2026,2037,2048|0605:2042,2053|0607:2012|0608:2023,2034,2045|0609:2039,2050|0611:2020|0612:2031,2036|0613:2047,2058|0615:2017,2028|0616:2022,2033,2044|0617:2049,2055,2060|0619:2014,2025|0620:2019,2030,2041,2052|0621:2057|0623:2011|0624:2038"},{"id":"restoration_day","name":"Día de la Restauración","en":"Restoration Day","category":"public","dates":"d|0814:2017,2023,2034,2045,2051|0815:2011,2022,2033,2039,2050|0816:2010,2012,2014-2016,2020-2021,2024-2028,2031-2032,2036-2038,2040,2042-2044,2048-2049,2052-2056,2059-2060|0819:2013,2019,2030,2041,2047,2058|0820:2018,2029,2035,2046,2057"},{"id":"our_lady_of_mercedes_day","name":"Día de las Mercedes","en":"Our Lady of Mercedes Day","category":"public","dates":"d|0924:2010-2060"},{"id":"constitution_day","name":"Día de la Constitución","en":"Constitution Day","category":"public","dates":"d|1104:2013,2019,2024,2030,2041,2047,2052,2058|1105:2012,2018,2029,2035,2040,2046,2057|1106:2010-2011,2016-2017,2021-2023,2027-2028,2032-2034,2038-2039,2044-2045,2049-2051,2055-2056,2060|1109:2015,2020,2026,2037,2043,2048,2054|1110:2014,2025,2031,2036,2042,2053,2059"},{"id":"christmas_day","name":"Día de Navidad","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"}]} diff --git a/app/Support/Holidays/data/DZ.json b/app/Support/Holidays/data/DZ.json new file mode 100644 index 000000000..f0906b96c --- /dev/null +++ b/app/Support/Holidays/data/DZ.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"DZ","language":"ar","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"رأس السنة الميلادية","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"ashura","name":"عاشورة","en":"Ashura","category":"public","dates":"y|2010:1216|2011:1205|2012:1124|2013:1113|2014:1103|2015:1023|2016:1011|2017:0930|2018:0920|2019:0909|2020:0829|2021:0818|2022:0808|2023:0728|2024:0716|2025:0705|2026:0625|2027:0615|2028:0603|2029:0523|2030:0512|2031:0502|2032:0420|2033:0410|2034:0330|2035:0320|2036:0308|2037:0225|2038:0214|2039:0204|2040:0124|2041:0113|2042:0102,1223|2043:1212|2044:1130|2045:1119|2046:1109|2047:1029|2048:1018|2049:1007|2050:0926|2051:0915|2052:0904|2053:0824|2054:0814|2055:0803|2056:0723|2057:0712|2058:0701|2059:0620|2060:0609","estimated":"y|2010:1216|2011:1205|2012:1124|2013:1113|2014:1103|2015:1023|2016:1011|2017:0930|2018:0920|2019:0909|2020:0829|2021:0818|2022:0808|2023:0728|2024:0716|2025:0705|2026:0625|2027:0615|2028:0603|2029:0523|2030:0512|2031:0502|2032:0420|2033:0410|2034:0330|2035:0320|2036:0308|2037:0225|2038:0214|2039:0204|2040:0124|2041:0113|2042:0102,1223|2043:1212|2044:1130|2045:1119|2046:1109|2047:1029|2048:1018|2049:1007|2050:0926|2051:0915|2052:0904|2053:0824|2054:0814|2055:0803|2056:0723|2057:0712|2058:0701|2059:0620|2060:0609"},{"id":"eid_al_fitr","name":"عيد الفطر","en":"Eid al-Fitr","category":"public","dates":"y|2010:0910-0911|2011:0830-0831|2012:0819-0820|2013:0808-0809|2014:0728-0729|2015:0717-0718|2016:0706-0707|2017:0625-0626|2018:0615-0616|2019:0604-0605|2020:0524-0525|2021:0513-0514|2022:0502-0503|2023:0421-0422|2024:0410-0412|2025:0330-0401|2026:0320-0322|2027:0309-0311|2028:0226-0228|2029:0214-0216|2030:0204-0206|2031:0124-0126|2032:0114-0116|2033:0102-0104,1223-1225|2034:1212-1214|2035:1201-1203|2036:1119-1121|2037:1108-1110|2038:1029-1031|2039:1019-1021|2040:1007-1009|2041:0926-0928|2042:0915-0917|2043:0904-0906|2044:0824-0826|2045:0814-0816|2046:0803-0805|2047:0724-0726|2048:0712-0714|2049:0701-0703|2050:0620-0622|2051:0610-0612|2052:0529-0531|2053:0519-0521|2054:0509-0511|2055:0428-0430|2056:0416-0418|2057:0405-0407|2058:0325-0327|2059:0315-0317|2060:0304-0306","estimated":"y|2010:0910-0911|2011:0830-0831|2012:0819-0820|2013:0808-0809|2014:0728-0729|2015:0717-0718|2016:0706-0707|2017:0625-0626|2018:0615-0616|2019:0604-0605|2020:0524-0525|2021:0513-0514|2022:0502-0503|2023:0421-0422|2024:0410-0412|2025:0330-0401|2026:0320-0322|2027:0309-0311|2028:0226-0228|2029:0214-0216|2030:0204-0206|2031:0124-0126|2032:0114-0116|2033:0102-0104,1223-1225|2034:1212-1214|2035:1201-1203|2036:1119-1121|2037:1108-1110|2038:1029-1031|2039:1019-1021|2040:1007-1009|2041:0926-0928|2042:0915-0917|2043:0904-0906|2044:0824-0826|2045:0814-0816|2046:0803-0805|2047:0724-0726|2048:0712-0714|2049:0701-0703|2050:0620-0622|2051:0610-0612|2052:0529-0531|2053:0519-0521|2054:0509-0511|2055:0428-0430|2056:0416-0418|2057:0405-0407|2058:0325-0327|2059:0315-0317|2060:0304-0306"},{"id":"prophet_s_birthday","name":"عيد المولد النبوي","en":"Prophet's Birthday","category":"public","dates":"y|2010:0226|2011:0215|2012:0204|2013:0124|2014:0113|2015:0103,1223|2016:1211|2017:1130|2018:1120|2019:1109|2020:1029|2021:1018|2022:1008|2023:0927|2024:0915|2025:0904|2026:0825|2027:0814|2028:0803|2029:0724|2030:0713|2031:0702|2032:0620|2033:0609|2034:0530|2035:0520|2036:0508|2037:0428|2038:0417|2039:0406|2040:0325|2041:0315|2042:0304|2043:0222|2044:0211|2045:0130|2046:0119|2047:0108,1229|2048:1218|2049:1207|2050:1126|2051:1116|2052:1104|2053:1024|2054:1013|2055:1003|2056:0922|2057:0911|2058:0831|2059:0820|2060:0808","estimated":"y|2010:0226|2011:0215|2012:0204|2013:0124|2014:0113|2015:0103,1223|2016:1211|2017:1130|2018:1120|2019:1109|2020:1029|2021:1018|2022:1008|2023:0927|2024:0915|2025:0904|2026:0825|2027:0814|2028:0803|2029:0724|2030:0713|2031:0702|2032:0620|2033:0609|2034:0530|2035:0520|2036:0508|2037:0428|2038:0417|2039:0406|2040:0325|2041:0315|2042:0304|2043:0222|2044:0211|2045:0130|2046:0119|2047:0108,1229|2048:1218|2049:1207|2050:1126|2051:1116|2052:1104|2053:1024|2054:1013|2055:1003|2056:0922|2057:0911|2058:0831|2059:0820|2060:0808"},{"id":"islamic_new_year","name":"رأس السنة الهجرية","en":"Islamic New Year","category":"public","dates":"y|2010:1207|2011:1126|2012:1115|2013:1104|2014:1025|2015:1014|2016:1002|2017:0921|2018:0911|2019:0831|2020:0820|2021:0809|2022:0730|2023:0719|2024:0707|2025:0626|2026:0616|2027:0606|2028:0525|2029:0514|2030:0503|2031:0423|2032:0411|2033:0401|2034:0321|2035:0311|2036:0228|2037:0216|2038:0205|2039:0126|2040:0115|2041:0104,1224|2042:1214|2043:1203|2044:1121|2045:1110|2046:1031|2047:1020|2048:1009|2049:0928|2050:0917|2051:0906|2052:0826|2053:0815|2054:0805|2055:0725|2056:0714|2057:0703|2058:0622|2059:0611|2060:0531","estimated":"y|2010:1207|2011:1126|2012:1115|2013:1104|2014:1025|2015:1014|2016:1002|2017:0921|2018:0911|2019:0831|2020:0820|2021:0809|2022:0730|2023:0719|2024:0707|2025:0626|2026:0616|2027:0606|2028:0525|2029:0514|2030:0503|2031:0423|2032:0411|2033:0401|2034:0321|2035:0311|2036:0228|2037:0216|2038:0205|2039:0126|2040:0115|2041:0104,1224|2042:1214|2043:1203|2044:1121|2045:1110|2046:1031|2047:1020|2048:1009|2049:0928|2050:0917|2051:0906|2052:0826|2053:0815|2054:0805|2055:0725|2056:0714|2057:0703|2058:0622|2059:0611|2060:0531"},{"id":"eid_al_adha","name":"عيد الأضحى","en":"Eid al-Adha","category":"public","dates":"y|2010:1116-1117|2011:1106-1107|2012:1026-1027|2013:1015-1016|2014:1004-1005|2015:0923-0924|2016:0911-0912|2017:0901-0902|2018:0821-0822|2019:0811-0812|2020:0731-0801|2021:0720-0721|2022:0709-0710|2023:0628-0630|2024:0616-0618|2025:0606-0608|2026:0527-0529|2027:0516-0518|2028:0505-0507|2029:0424-0426|2030:0413-0415|2031:0402-0404|2032:0322-0324|2033:0311-0313|2034:0301-0303|2035:0218-0220|2036:0207-0209|2037:0126-0128|2038:0116-0118|2039:0105-0107,1226-1228|2040:1214-1216|2041:1204-1206|2042:1123-1125|2043:1112-1114|2044:1031-1102|2045:1021-1023|2046:1010-1012|2047:0930-1002|2048:0919-0921|2049:0908-0910|2050:0828-0830|2051:0817-0819|2052:0805-0807|2053:0726-0728|2054:0715-0717|2055:0705-0707|2056:0623-0625|2057:0612-0614|2058:0601-0603|2059:0522-0524|2060:0510-0512","estimated":"y|2010:1116-1117|2011:1106-1107|2012:1026-1027|2013:1015-1016|2014:1004-1005|2015:0923-0924|2016:0911-0912|2017:0901-0902|2018:0821-0822|2019:0811-0812|2020:0731-0801|2021:0720-0721|2022:0709-0710|2023:0628-0630|2024:0616-0618|2025:0606-0608|2026:0527-0529|2027:0516-0518|2028:0505-0507|2029:0424-0426|2030:0413-0415|2031:0402-0404|2032:0322-0324|2033:0311-0313|2034:0301-0303|2035:0218-0220|2036:0207-0209|2037:0126-0128|2038:0116-0118|2039:0105-0107,1226-1228|2040:1214-1216|2041:1204-1206|2042:1123-1125|2043:1112-1114|2044:1031-1102|2045:1021-1023|2046:1010-1012|2047:0930-1002|2048:0919-0921|2049:0908-0910|2050:0828-0830|2051:0817-0819|2052:0805-0807|2053:0726-0728|2054:0715-0717|2055:0705-0707|2056:0623-0625|2057:0612-0614|2058:0601-0603|2059:0522-0524|2060:0510-0512"},{"id":"amazigh_new_year","name":"رأس السنة الأمازيغية","en":"Amazigh New Year","category":"public","dates":"d|0112:2018-2060"},{"id":"labor_day","name":"عيد العمال","en":"Labor Day","category":"public","dates":"d|0501:2010-2060"},{"id":"independence_day","name":"عيد الاستقلال","en":"Independence Day","category":"public","dates":"d|0705:2010-2060"},{"id":"revolution_day","name":"عيد الثورة","en":"Revolution Day","category":"public","dates":"d|1101:2010-2060"},{"id":"easter_monday_christian","name":"إثنين الفصح","en":"Easter Monday","category":"christian","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"pesach_hebrew","name":"عيد الفصح اليهودي","en":"Pesach","category":"hebrew","dates":"d|0326:2013|0327:2032|0328:2021,2051|0329:2040,2048,2059|0330:2010|0331:2018,2029,2037|0401:2056|0402:2026,2045|0403:2053|0404:2015,2034|0405:2042|0406:2023|0407:2012,2050|0408:2031|0409:2020,2039,2058|0411:2017,2028,2047|0412:2036,2044|0413:2025,2055|0414:2033,2052|0415:2014,2060|0416:2022,2041|0417:2049|0418:2030|0419:2011,2057|0420:2019,2038|0421:2046|0422:2027|0423:2016,2024,2054|0424:2035|0425:2043"},{"id":"ascension_day_christian","name":"عيد الصعود","en":"Ascension Day","category":"christian","dates":"d|0503:2035,2046|0505:2016|0506:2027,2032|0507:2043,2054|0508:2059|0509:2013,2024|0510:2018,2029,2040|0511:2051,2056|0513:2010,2021|0514:2015,2026,2037,2048|0515:2042,2053|0517:2012|0518:2023,2034,2045|0519:2039,2050|0521:2020|0522:2031,2036|0523:2047,2058|0525:2017,2028|0526:2022,2033,2044|0527:2049,2055,2060|0529:2014,2025|0530:2019,2030,2041,2052|0531:2057|0602:2011|0603:2038"},{"id":"pentecost_monday_christian","name":"إثنين العنصرة","en":"Pentecost Monday","category":"christian","dates":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038"},{"id":"assumption_day_christian","name":"عيد انتقال السيدة العذراء","en":"Assumption Day","category":"christian","dates":"d|0815:2010-2060"},{"id":"rosh_hashanah_hebrew","name":"رأس السنة العبرية","en":"Rosh Hashanah","category":"hebrew","dates":"d|0905:2013|0906:2032|0907:2021,2051|0908:2040,2048,2059|0909:2010|0910:2018,2029,2037|0911:2056|0912:2026,2045|0913:2053|0914:2015,2034|0915:2042|0916:2023|0917:2012,2050|0918:2031|0919:2020,2039,2058|0921:2017,2028,2047|0922:2036,2044|0923:2025,2055|0924:2033,2052|0925:2014,2060|0926:2022,2041|0927:2049|0928:2030|0929:2011,2057|0930:2019,2038|1001:2046|1002:2027|1003:2016,2024,2054|1004:2035|1005:2043"},{"id":"yom_kippur_hebrew","name":"عيد الغفران","en":"Yom Kippur","category":"hebrew","dates":"d|0914:2013|0915:2032|0916:2021,2051|0917:2040,2048,2059|0918:2010|0919:2018,2029,2037|0920:2056|0921:2026,2045|0922:2053|0923:2015,2034|0924:2042|0925:2023|0926:2012,2050|0927:2031|0928:2020,2039,2058|0930:2017,2028,2047|1001:2036,2044|1002:2025,2055|1003:2033,2052|1004:2014,2060|1005:2022,2041|1006:2049|1007:2030|1008:2011,2057|1009:2019,2038|1010:2046|1011:2027|1012:2016,2024,2054|1013:2035|1014:2043"},{"id":"christmas_day_christian","name":"عيد الميلاد","en":"Christmas Day","category":"christian","dates":"d|1225:2010-2060"}]} diff --git a/app/Support/Holidays/data/EC.json b/app/Support/Holidays/data/EC.json new file mode 100644 index 000000000..cbe8a5694 --- /dev/null +++ b/app/Support/Holidays/data/EC.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"EC","language":"es","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Año Nuevo","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2017,2023,2034,2040,2045,2051|1231:2021,2027,2032,2038,2049,2055,2060"},{"id":"carnival","name":"Carnaval","en":"Carnival","category":"public","dates":"d|0205:2035,2046|0206:2035,2046|0208:2016,2027|0209:2016,2027,2032,2043,2054|0210:2032,2043,2054,2059|0211:2013,2059|0212:2013,2018,2024,2029|0213:2018,2024,2029,2040,2051|0214:2040,2051,2056|0215:2010,2021,2056|0216:2010,2015,2021,2026,2037|0217:2015,2026,2037,2042,2048,2053|0218:2042,2048,2053|0220:2012,2023,2034,2045|0221:2012,2023,2034,2039,2045,2050|0222:2039,2050|0224:2020,2031|0225:2020,2031,2036,2047,2058|0226:2036,2047,2058|0227:2017|0228:2017,2022,2028,2033|0229:2028,2044|0301:2022,2033,2044,2049,2055,2060|0302:2049,2055,2060|0303:2014,2025|0304:2014,2019,2025,2030,2041,2052|0305:2019,2030,2041,2052,2057|0306:2057|0307:2011|0308:2011,2038|0309:2038"},{"id":"good_friday","name":"Viernes Santo","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"labor_day","name":"Día del Trabajo","en":"Labor Day","category":"public","dates":"d|0430:2018,2021,2027,2029,2032,2035,2038,2040,2046,2049,2055,2057,2060|0501:2010-2060|0502:2022,2025,2031,2033,2036,2039,2042,2044,2050,2053,2059|0503:2019,2024,2030,2041,2047,2052,2058"},{"id":"the_battle_of_pichincha","name":"Batalla de Pichincha","en":"The Battle of Pichincha","category":"public","dates":"d|0523:2022,2025,2031,2033,2036,2039,2042,2044,2050,2053,2059|0524:2010-2060|0525:2018,2020,2026,2029,2035,2037,2040,2043,2046,2048,2054,2057|0526:2017,2023,2028,2034,2045,2051,2056"},{"id":"declaration_of_independence_of_quito","name":"Primer Grito de Independencia","en":"Declaration of Independence of Quito","category":"public","dates":"d|0809:2019,2021,2024,2027,2030,2032,2038,2041,2047,2049,2052,2055,2058,2060|0810:2010-2060|0811:2017,2023,2025,2028,2031,2034,2036,2042,2045,2051,2053,2056,2059|0812:2022,2033,2039,2044,2050"},{"id":"independence_of_guayaquil","name":"Independencia de Guayaquil","en":"Independence of Guayaquil","category":"public","dates":"d|1008:2018,2021,2027,2029,2032,2035,2038,2040,2046,2049,2055,2057,2060|1009:2010-2060|1010:2022,2025,2031,2033,2036,2039,2042,2044,2050,2053,2059|1011:2019,2024,2030,2041,2047,2052,2058"},{"id":"all_souls_day","name":"Día de Difuntos","en":"All Souls' Day","category":"public","dates":"d|1101:2019,2021,2024,2027,2030,2032,2038,2041,2047,2049,2052,2055,2058,2060|1102:2010-2060|1104:2022,2025,2031,2033,2036,2039,2042,2044,2050,2053,2059"},{"id":"independence_of_cuenca","name":"Independencia de Cuenca","en":"Independence of Cuenca","category":"public","dates":"d|1103:2010-2060|1104:2019,2024,2030,2041,2047,2052,2058"},{"id":"christmas_day","name":"Navidad","en":"Christmas Day","category":"public","dates":"d|1224:2021,2027,2032,2038,2049,2055,2060|1225:2010-2060|1226:2022,2033,2039,2044,2050"}]} diff --git a/app/Support/Holidays/data/EE.json b/app/Support/Holidays/data/EE.json new file mode 100644 index 000000000..2e527d2c1 --- /dev/null +++ b/app/Support/Holidays/data/EE.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"EE","language":"et","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"uusaasta","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"independence_day","name":"iseseisvuspäev","en":"Independence Day","category":"public","dates":"d|0224:2010-2060"},{"id":"good_friday","name":"suur reede","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_sunday","name":"ülestõusmispühade 1. püha","en":"Easter Sunday","category":"public","dates":"d|0325:2035,2046|0327:2016|0328:2027,2032|0329:2043,2054|0330:2059|0331:2013,2024|0401:2018,2029,2040|0402:2051,2056|0404:2010,2021|0405:2015,2026,2037,2048|0406:2042,2053|0408:2012|0409:2023,2034,2045|0410:2039,2050|0412:2020|0413:2031,2036|0414:2047,2058|0416:2017,2028|0417:2022,2033,2044|0418:2049,2055,2060|0420:2014,2025|0421:2019,2030,2041,2052|0422:2057|0424:2011|0425:2038"},{"id":"may_day","name":"kevadpüha","en":"May Day","category":"public","dates":"d|0501:2010-2060"},{"id":"pentecost","name":"nelipühade 1. püha","en":"Pentecost","category":"public","dates":"d|0513:2035,2046|0515:2016|0516:2027,2032|0517:2043,2054|0518:2059|0519:2013,2024|0520:2018,2029,2040|0521:2051,2056|0523:2010,2021|0524:2015,2026,2037,2048|0525:2042,2053|0527:2012|0528:2023,2034,2045|0529:2039,2050|0531:2020|0601:2031,2036|0602:2047,2058|0604:2017,2028|0605:2022,2033,2044|0606:2049,2055,2060|0608:2014,2025|0609:2019,2030,2041,2052|0610:2057|0612:2011|0613:2038"},{"id":"victory_day","name":"võidupüha","en":"Victory Day","category":"public","dates":"d|0623:2010-2060"},{"id":"midsummer_day","name":"jaanipäev","en":"Midsummer Day","category":"public","dates":"d|0624:2010-2060"},{"id":"independence_restoration_day","name":"taasiseseisvumispäev","en":"Independence Restoration Day","category":"public","dates":"d|0820:2010-2060"},{"id":"christmas_eve","name":"jõululaupäev","en":"Christmas Eve","category":"public","dates":"d|1224:2010-2060"},{"id":"christmas_day","name":"esimene jõulupüha","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"},{"id":"second_day_of_christmas","name":"teine jõulupüha","en":"Second Day of Christmas","category":"public","dates":"d|1226:2010-2060"},{"id":"pre_holiday_day_half_day","name":"pühade-eelne päev","en":"Pre-holiday day","category":"half_day","dates":"d|0223:2010-2060|0622:2010-2060|1223:2010-2060|1231:2010-2060"}]} diff --git a/app/Support/Holidays/data/EG.json b/app/Support/Holidays/data/EG.json new file mode 100644 index 000000000..d143fec59 --- /dev/null +++ b/app/Support/Holidays/data/EG.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"EG","language":"ar_EG","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"eid_al_fitr","name":"عيد الفطر المبارك","en":"Eid al-Fitr","category":"public","dates":"y|2010:0910-0911|2011:0830-0831|2012:0819-0820|2013:0808-0809|2014:0728-0729|2015:0717-0718|2016:0706-0707|2017:0625-0626|2018:0615-0616|2019:0604-0605|2020:0524-0525|2021:0513-0514|2022:0501-0502,0504|2023:0421-0422,0424|2024:0410-0411|2025:0330-0331|2026:0320-0321|2027:0309-0310|2028:0226-0227|2029:0214-0215|2030:0204-0205|2031:0124-0125|2032:0114-0115|2033:0102-0103,1223-1224|2034:1212-1213|2035:1201-1202|2036:1119-1120|2037:1108-1109|2038:1029-1030|2039:1019-1020|2040:1007-1008|2041:0926-0927|2042:0915-0916|2043:0904-0905|2044:0824-0825|2045:0814-0815|2046:0803-0804|2047:0724-0725|2048:0712-0713|2049:0701-0702|2050:0620-0621|2051:0610-0611|2052:0529-0530|2053:0519-0520|2054:0509-0510|2055:0428-0429|2056:0416-0417|2057:0405-0406|2058:0325-0326|2059:0315-0316|2060:0304-0305","estimated":"y|2010:0910-0911|2011:0830-0831|2012:0819-0820|2013:0808-0809|2014:0728-0729|2015:0717-0718|2016:0706-0707|2017:0625-0626|2018:0615-0616|2019:0604-0605|2027:0309-0310|2028:0226-0227|2029:0214-0215|2030:0204-0205|2031:0124-0125|2032:0114-0115|2033:0102-0103,1223-1224|2034:1212-1213|2035:1201-1202|2036:1119-1120|2037:1108-1109|2038:1029-1030|2039:1019-1020|2040:1007-1008|2041:0926-0927|2042:0915-0916|2043:0904-0905|2044:0824-0825|2045:0814-0815|2046:0803-0804|2047:0724-0725|2048:0712-0713|2049:0701-0702|2050:0620-0621|2051:0610-0611|2052:0529-0530|2053:0519-0520|2054:0509-0510|2055:0428-0429|2056:0416-0417|2057:0405-0406|2058:0325-0326|2059:0315-0316|2060:0304-0305"},{"id":"prophet_s_birthday","name":"المولد النبوي الشريف","en":"Prophet's Birthday","category":"public","dates":"y|2010:0226|2011:0215|2012:0204|2013:0124|2014:0113|2015:0103,1223|2016:1211|2017:1130|2018:1120|2019:1109|2020:1029|2021:1018|2022:1008|2023:0928|2024:0915|2025:0904|2026:0825|2027:0814|2028:0803|2029:0724|2030:0713|2031:0702|2032:0620|2033:0609|2034:0530|2035:0520|2036:0508|2037:0428|2038:0417|2039:0406|2040:0325|2041:0315|2042:0304|2043:0222|2044:0211|2045:0130|2046:0119|2047:0108,1229|2048:1218|2049:1207|2050:1126|2051:1116|2052:1104|2053:1024|2054:1013|2055:1003|2056:0922|2057:0911|2058:0831|2059:0820|2060:0808","estimated":"y|2010:0226|2011:0215|2012:0204|2013:0124|2014:0113|2015:0103,1223|2016:1211|2017:1130|2018:1120|2019:1109|2026:0825|2027:0814|2028:0803|2029:0724|2030:0713|2031:0702|2032:0620|2033:0609|2034:0530|2035:0520|2036:0508|2037:0428|2038:0417|2039:0406|2040:0325|2041:0315|2042:0304|2043:0222|2044:0211|2045:0130|2046:0119|2047:0108,1229|2048:1218|2049:1207|2050:1126|2051:1116|2052:1104|2053:1024|2054:1013|2055:1003|2056:0922|2057:0911|2058:0831|2059:0820|2060:0808"},{"id":"day_of_arafah","name":"وقفة عيد الأضحى المبارك","en":"Day of Arafah","category":"public","dates":"y|2010:1115|2011:1105|2012:1025|2013:1014|2014:1003|2015:0922|2016:0910|2017:0831|2018:0820|2019:0810|2020:0730|2021:0719|2022:0709|2023:0627|2024:0615|2025:0605|2026:0526|2027:0515|2028:0504|2029:0423|2030:0412|2031:0401|2032:0321|2033:0310|2034:0228|2035:0217|2036:0206|2037:0125|2038:0115|2039:0104,1225|2040:1213|2041:1203|2042:1122|2043:1111|2044:1030|2045:1020|2046:1009|2047:0929|2048:0918|2049:0907|2050:0827|2051:0816|2052:0804|2053:0725|2054:0714|2055:0704|2056:0622|2057:0611|2058:0531|2059:0521|2060:0509","estimated":"y|2010:1115|2011:1105|2012:1025|2013:1014|2014:1003|2015:0922|2016:0910|2017:0831|2018:0820|2019:0810|2026:0526|2027:0515|2028:0504|2029:0423|2030:0412|2031:0401|2032:0321|2033:0310|2034:0228|2035:0217|2036:0206|2037:0125|2038:0115|2039:0104,1225|2040:1213|2041:1203|2042:1122|2043:1111|2044:1030|2045:1020|2046:1009|2047:0929|2048:0918|2049:0907|2050:0827|2051:0816|2052:0804|2053:0725|2054:0714|2055:0704|2056:0622|2057:0611|2058:0531|2059:0521|2060:0509"},{"id":"islamic_new_year","name":"رأس السنة الهجرية","en":"Islamic New Year","category":"public","dates":"y|2010:1207|2011:1126|2012:1115|2013:1104|2014:1025|2015:1014|2016:1002|2017:0921|2018:0911|2019:0831|2020:0820|2021:0812|2022:0730|2023:0720|2024:0711|2025:0626|2026:0618|2027:0610|2028:0525|2029:0517|2030:0503|2031:0424|2032:0415|2033:0401|2034:0323|2035:0315|2036:0228|2037:0219|2038:0205|2039:0127|2040:0119|2041:0104,1226|2042:1218|2043:1203|2044:1124|2045:1110|2046:1101|2047:1024|2048:1009|2049:0930|2050:0917|2051:0907|2052:0829|2053:0815|2054:0806|2055:0729|2056:0714|2057:0705|2058:0622|2059:0612|2060:0603","estimated":"d|0104:2041|0205:2038|0228:2036|0401:2033|0503:2030|0525:2028|0622:2058|0714:2056|0815:2053|0831:2019|0911:2018|0917:2050|0921:2017|1002:2016|1009:2048|1014:2015|1025:2014|1104:2013|1110:2045|1115:2012|1126:2011|1203:2043|1207:2010"},{"id":"eid_al_adha","name":"عيد الأضحى المبارك","en":"Eid al-Adha","category":"public","dates":"y|2010:1116-1118|2011:1106-1108|2012:1026-1028|2013:1015-1017|2014:1004-1006|2015:0923-0925|2016:0911-0913|2017:0901-0903|2018:0821-0823|2019:0811-0813|2020:0731-0802|2021:0717-0718,0720-0722|2022:0710-0714|2023:0628-0630,0703|2024:0616-0618|2025:0606-0608|2026:0527-0529|2027:0516-0518|2028:0505-0507|2029:0424-0426|2030:0413-0415|2031:0402-0404|2032:0322-0324|2033:0311-0313|2034:0301-0303|2035:0218-0220|2036:0207-0209|2037:0126-0128|2038:0116-0118|2039:0105-0107,1226-1228|2040:1214-1216|2041:1204-1206|2042:1123-1125|2043:1112-1114|2044:1031-1102|2045:1021-1023|2046:1010-1012|2047:0930-1002|2048:0919-0921|2049:0908-0910|2050:0828-0830|2051:0817-0819|2052:0805-0807|2053:0726-0728|2054:0715-0717|2055:0705-0707|2056:0623-0625|2057:0612-0614|2058:0601-0603|2059:0522-0524|2060:0510-0512","estimated":"y|2010:1116-1118|2011:1106-1108|2012:1026-1028|2013:1015-1017|2014:1004-1006|2015:0923-0925|2016:0911-0913|2017:0901-0903|2018:0821-0823|2019:0811-0813|2026:0527-0529|2027:0516-0518|2028:0505-0507|2029:0424-0426|2030:0413-0415|2031:0402-0404|2032:0322-0324|2033:0311-0313|2034:0301-0303|2035:0218-0220|2036:0207-0209|2037:0126-0128|2038:0116-0118|2039:0105-0107,1226-1228|2040:1214-1216|2041:1204-1206|2042:1123-1125|2043:1112-1114|2044:1031-1102|2045:1021-1023|2046:1010-1012|2047:0930-1002|2048:0919-0921|2049:0908-0910|2050:0828-0830|2051:0817-0819|2052:0805-0807|2053:0726-0728|2054:0715-0717|2055:0705-0707|2056:0623-0625|2057:0612-0614|2058:0601-0603|2059:0522-0524|2060:0510-0512"},{"id":"coptic_christmas_day","name":"عيد الميلاد المجيد","en":"Coptic Christmas Day","category":"public","dates":"d|0106:2022|0107:2010-2021,2024-2060|0108:2023"},{"id":"january_25th_revolution_and_national_police_day","name":"ثورة ٢٥ يناير وعيد الشرطة","en":"January 25th Revolution and National Police Day","category":"public","dates":"d|0125:2012-2020,2024-2025,2029-2031,2035-2036,2041-2042,2046-2048,2052-2053,2057-2059|0126:2023,2034,2040,2045,2051|0127:2022,2028,2033,2039,2050,2056|0128:2021,2027,2038,2044,2049,2055|0129:2026,2032,2037,2043,2054,2060"},{"id":"spring_festival","name":"عيد شم النسيم","en":"Spring Festival","category":"public","dates":"d|0405:2010|0406:2037,2048|0409:2018,2029|0410:2034,2045,2056|0413:2015,2026|0414:2031,2042,2053|0415:2058|0416:2012|0417:2017,2023,2028|0418:2039,2050|0419:2055|0420:2020|0421:2014,2025,2036|0422:2041,2047,2052|0425:2011,2022,2033,2044|0426:2038,2049,2060|0429:2019,2030|0430:2035,2046,2057|0502:2016|0503:2021,2027,2032|0504:2043,2054|0505:2059|0506:2013,2024|0507:2040|0508:2051"},{"id":"sinai_liberation_day","name":"عيد تحرير سيناء","en":"Sinai Liberation Day","category":"public","dates":"d|0425:2010-2020,2022,2024-2026,2030-2031,2036-2037,2041-2043,2047-2048,2052-2054,2058-2059|0426:2029,2035,2040,2046,2057|0427:2023,2028,2034,2045,2051,2056|0428:2033,2039,2044,2050|0429:2021,2027,2032,2038,2049,2055,2060"},{"id":"labor_day","name":"عيد العمال","en":"Labor Day","category":"public","dates":"d|0501:2010-2021,2025-2027,2031-2032,2036-2038,2042-2043,2048-2049,2053-2055,2059-2060|0502:2030,2041,2047,2052,2058|0503:2029,2035,2040,2046,2057|0504:2023,2028,2034,2045,2051,2056|0505:2022,2024,2033,2039,2044,2050"},{"id":"june_30_revolution_day","name":"عيد ثورة ٣٠ يونيو","en":"June 30 Revolution Day","category":"public","dates":"d|0630:2014-2019,2022-2024,2028-2029,2033-2035,2039-2040,2044-2046,2050-2051,2056-2057|0701:2021,2027,2032,2038,2049,2055,2060|0702:2020,2023,2026,2037,2043,2048,2054|0703:2025,2031,2036,2042,2053,2059|0704:2030,2041,2047,2052,2058"},{"id":"july_23_revolution_day","name":"عيد ثورة ٢٣ يوليو","en":"July 23 Revolution Day","category":"public","dates":"d|0723:2010-2023,2026-2027,2032-2033,2037-2039,2043-2044,2048-2050,2054-2055,2060|0724:2025,2031,2036,2042,2053,2059|0725:2024,2030,2041,2047,2052,2058|0726:2029,2035,2040,2046,2057|0727:2028,2034,2045,2051,2056"},{"id":"armed_forces_day","name":"عيد القوات المسلحة","en":"Armed Forces Day","category":"public","dates":"d|1006:2010-2019,2022-2024,2028-2029,2033-2035,2039-2040,2044-2046,2050-2051,2056-2057|1007:2021,2027,2032,2038,2049,2055,2060|1008:2020,2026,2037,2043,2048,2054|1009:2025,2031,2036,2042,2053,2059|1010:2030,2041,2047,2052,2058"},{"id":"eid_al_fitr_government","name":"عيد الفطر المبارك","en":"Eid al-Fitr","category":"government","dates":"y|2019:0603,0606|2020:0523,0526|2021:0512,0515|2022:0430,0503|2023:0420,0423|2024:0409,0412|2025:0329,0401|2026:0319,0322|2027:0308,0311|2028:0225,0228|2029:0213,0216|2030:0203,0206|2031:0123,0126|2032:0113,0116|2033:0101,0104,1222,1225|2034:1211,1214|2035:1130,1203|2036:1118,1121|2037:1107,1110|2038:1028,1031|2039:1018,1021|2040:1006,1009|2041:0925,0928|2042:0914,0917|2043:0903,0906|2044:0823,0826|2045:0813,0816|2046:0802,0805|2047:0723,0726|2048:0711,0714|2049:0630,0703|2050:0619,0622|2051:0609,0612|2052:0528,0531|2053:0518,0521|2054:0508,0511|2055:0427,0430|2056:0415,0418|2057:0404,0407|2058:0324,0327|2059:0314,0317|2060:0303,0306","estimated":"y|2019:0603,0606|2027:0308,0311|2028:0225,0228|2029:0213,0216|2030:0203,0206|2031:0123,0126|2032:0113,0116|2033:0101,0104,1222,1225|2034:1211,1214|2035:1130,1203|2036:1118,1121|2037:1107,1110|2038:1028,1031|2039:1018,1021|2040:1006,1009|2041:0925,0928|2042:0914,0917|2043:0903,0906|2044:0823,0826|2045:0813,0816|2046:0802,0805|2047:0723,0726|2048:0711,0714|2049:0630,0703|2050:0619,0622|2051:0609,0612|2052:0528,0531|2053:0518,0521|2054:0508,0511|2055:0427,0430|2056:0415,0418|2057:0404,0407|2058:0324,0327|2059:0314,0317|2060:0303,0306"},{"id":"eid_al_adha_government","name":"عيد الأضحى المبارك","en":"Eid al-Adha","category":"government","dates":"y|2018:0824|2019:0814|2020:0803|2021:0723|2022:0713|2023:0701|2024:0619|2025:0609|2026:0530|2027:0519|2028:0508|2029:0427|2030:0416|2031:0405|2032:0325|2033:0314|2034:0304|2035:0221|2036:0210|2037:0129|2038:0119|2039:0108,1229|2040:1217|2041:1207|2042:1126|2043:1115|2044:1103|2045:1024|2046:1013|2047:1003|2048:0922|2049:0911|2050:0831|2051:0820|2052:0808|2053:0729|2054:0718|2055:0708|2056:0626|2057:0615|2058:0604|2059:0525|2060:0513","estimated":"y|2018:0824|2019:0814|2026:0530|2027:0519|2028:0508|2029:0427|2030:0416|2031:0405|2032:0325|2033:0314|2034:0304|2035:0221|2036:0210|2037:0129|2038:0119|2039:0108,1229|2040:1217|2041:1207|2042:1126|2043:1115|2044:1103|2045:1024|2046:1013|2047:1003|2048:0922|2049:0911|2050:0831|2051:0820|2052:0808|2053:0729|2054:0718|2055:0708|2056:0626|2057:0615|2058:0604|2059:0525|2060:0513"},{"id":"taba_liberation_day_school","name":"عيد تحرير طابا","en":"Taba Liberation Day","category":"school","dates":"d|0319:2019-2060"},{"id":"evacuation_day_school","name":"عيد الجلاء","en":"Evacuation Day","category":"school","dates":"d|0618:2019-2060"}]} diff --git a/app/Support/Holidays/data/EH.json b/app/Support/Holidays/data/EH.json new file mode 100644 index 000000000..fb7807019 --- /dev/null +++ b/app/Support/Holidays/data/EH.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"EH","language":"ar","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"eid_al_fitr","name":"عيد الفطر المبارك","en":"Eid al-Fitr","category":"public","dates":"y|2010:0910|2011:0830|2012:0819|2013:0808|2014:0728|2015:0717|2016:0706|2017:0625|2018:0615|2019:0604|2020:0524|2021:0513|2022:0502|2023:0421|2024:0410|2025:0330|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304","estimated":"y|2010:0910|2011:0830|2012:0819|2013:0808|2014:0728|2015:0717|2016:0706|2017:0625|2018:0615|2019:0604|2020:0524|2021:0513|2022:0502|2023:0421|2024:0410|2025:0330|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304"},{"id":"prophet_s_birthday","name":"المولد النبوي الشريف","en":"Prophet's Birthday","category":"public","dates":"y|2010:0226|2011:0215|2012:0204|2013:0124|2014:0113|2015:0103,1223|2016:1211|2017:1130|2018:1120|2019:1109|2020:1029|2021:1018|2022:1008|2023:0927|2024:0915|2025:0904|2026:0825|2027:0814|2028:0803|2029:0724|2030:0713|2031:0702|2032:0620|2033:0609|2034:0530|2035:0520|2036:0508|2037:0428|2038:0417|2039:0406|2040:0325|2041:0315|2042:0304|2043:0222|2044:0211|2045:0130|2046:0119|2047:0108,1229|2048:1218|2049:1207|2050:1126|2051:1116|2052:1104|2053:1024|2054:1013|2055:1003|2056:0922|2057:0911|2058:0831|2059:0820|2060:0808","estimated":"y|2010:0226|2011:0215|2012:0204|2013:0124|2014:0113|2015:0103,1223|2016:1211|2017:1130|2018:1120|2019:1109|2020:1029|2021:1018|2022:1008|2023:0927|2024:0915|2025:0904|2026:0825|2027:0814|2028:0803|2029:0724|2030:0713|2031:0702|2032:0620|2033:0609|2034:0530|2035:0520|2036:0508|2037:0428|2038:0417|2039:0406|2040:0325|2041:0315|2042:0304|2043:0222|2044:0211|2045:0130|2046:0119|2047:0108,1229|2048:1218|2049:1207|2050:1126|2051:1116|2052:1104|2053:1024|2054:1013|2055:1003|2056:0922|2057:0911|2058:0831|2059:0820|2060:0808"},{"id":"islamic_new_year","name":"رأس السنة الهجرية","en":"Islamic New Year","category":"public","dates":"y|2010:1207|2011:1126|2012:1115|2013:1104|2014:1025|2015:1014|2016:1002|2017:0921|2018:0911|2019:0831|2020:0820|2021:0809|2022:0730|2023:0719|2024:0707|2025:0626|2026:0616|2027:0606|2028:0525|2029:0514|2030:0503|2031:0423|2032:0411|2033:0401|2034:0321|2035:0311|2036:0228|2037:0216|2038:0205|2039:0126|2040:0115|2041:0104,1224|2042:1214|2043:1203|2044:1121|2045:1110|2046:1031|2047:1020|2048:1009|2049:0928|2050:0917|2051:0906|2052:0826|2053:0815|2054:0805|2055:0725|2056:0714|2057:0703|2058:0622|2059:0611|2060:0531","estimated":"y|2010:1207|2011:1126|2012:1115|2013:1104|2014:1025|2015:1014|2016:1002|2017:0921|2018:0911|2019:0831|2020:0820|2021:0809|2022:0730|2023:0719|2024:0707|2025:0626|2026:0616|2027:0606|2028:0525|2029:0514|2030:0503|2031:0423|2032:0411|2033:0401|2034:0321|2035:0311|2036:0228|2037:0216|2038:0205|2039:0126|2040:0115|2041:0104,1224|2042:1214|2043:1203|2044:1121|2045:1110|2046:1031|2047:1020|2048:1009|2049:0928|2050:0917|2051:0906|2052:0826|2053:0815|2054:0805|2055:0725|2056:0714|2057:0703|2058:0622|2059:0611|2060:0531"},{"id":"eid_al_adha","name":"عيد الأضحى المبارك","en":"Eid al-Adha","category":"public","dates":"y|2010:1116|2011:1106|2012:1026|2013:1015|2014:1004|2015:0923|2016:0911|2017:0901|2018:0821|2019:0811|2020:0731|2021:0720|2022:0709|2023:0628|2024:0616|2025:0606|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510","estimated":"y|2010:1116|2011:1106|2012:1026|2013:1015|2014:1004|2015:0923|2016:0911|2017:0901|2018:0821|2019:0811|2020:0731|2021:0720|2022:0709|2023:0628|2024:0616|2025:0606|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510"},{"id":"proclamation_of_the_sadr","name":"إعلان الجمهورية العربية الصحراوية الديمقراطية","en":"Proclamation of the SADR","category":"public","dates":"d|0227:2010-2060"},{"id":"first_martyr","name":"يوم الشهيد الأول","en":"First Martyr","category":"public","dates":"d|0308:2010-2060"},{"id":"creation_of_the_polisario_front","name":"تأسيس الجبهة الشعبية لتحرير الساقية الحمراء ووادي الذهب","en":"Creation of the Polisario Front","category":"public","dates":"d|0510:2010-2060"},{"id":"commencement_of_the_armed_struggle","name":"اندلاع الكفاح المسلح","en":"Commencement of the Armed Struggle","category":"public","dates":"d|0520:2010-2060"},{"id":"martyrs_day","name":"يوم الشهداء","en":"Martyrs' Day","category":"public","dates":"d|0609:2010-2060"},{"id":"uprising_day","name":"يوم الانتفاضة","en":"Uprising Day","category":"public","dates":"d|0617:2010-2060"},{"id":"national_unity_day","name":"عيد الوحدة الوطنية","en":"National Unity Day","category":"public","dates":"d|1012:2010-2060"}]} diff --git a/app/Support/Holidays/data/ER.json b/app/Support/Holidays/data/ER.json new file mode 100644 index 000000000..cea37bb3f --- /dev/null +++ b/app/Support/Holidays/data/ER.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"ER","language":null,"years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"eid_al_fitr","name":"Eid al-Fitr","en":"Eid al-Fitr","category":"public","dates":"y|2010:0910|2011:0830|2012:0819|2013:0808|2014:0728|2015:0717|2016:0706|2017:0625|2018:0615|2019:0604|2020:0524|2021:0513|2022:0502|2023:0421|2024:0410|2025:0330|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304","estimated":"y|2010:0910|2011:0830|2012:0819|2013:0808|2014:0728|2015:0717|2016:0706|2017:0625|2018:0615|2019:0604|2020:0524|2021:0513|2022:0502|2023:0421|2024:0410|2025:0330|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304"},{"id":"prophet_s_birthday","name":"Prophet's Birthday","en":"Prophet's Birthday","category":"public","dates":"y|2010:0226|2011:0215|2012:0204|2013:0124|2014:0113|2015:0103,1223|2016:1211|2017:1130|2018:1120|2019:1109|2020:1029|2021:1018|2022:1008|2023:0927|2024:0915|2025:0904|2026:0825|2027:0814|2028:0803|2029:0724|2030:0713|2031:0702|2032:0620|2033:0609|2034:0530|2035:0520|2036:0508|2037:0428|2038:0417|2039:0406|2040:0325|2041:0315|2042:0304|2043:0222|2044:0211|2045:0130|2046:0119|2047:0108,1229|2048:1218|2049:1207|2050:1126|2051:1116|2052:1104|2053:1024|2054:1013|2055:1003|2056:0922|2057:0911|2058:0831|2059:0820|2060:0808","estimated":"y|2010:0226|2011:0215|2012:0204|2013:0124|2014:0113|2015:0103,1223|2016:1211|2017:1130|2018:1120|2019:1109|2020:1029|2021:1018|2022:1008|2023:0927|2024:0915|2025:0904|2026:0825|2027:0814|2028:0803|2029:0724|2030:0713|2031:0702|2032:0620|2033:0609|2034:0530|2035:0520|2036:0508|2037:0428|2038:0417|2039:0406|2040:0325|2041:0315|2042:0304|2043:0222|2044:0211|2045:0130|2046:0119|2047:0108,1229|2048:1218|2049:1207|2050:1126|2051:1116|2052:1104|2053:1024|2054:1013|2055:1003|2056:0922|2057:0911|2058:0831|2059:0820|2060:0808"},{"id":"eid_al_adha","name":"Eid al-Adha","en":"Eid al-Adha","category":"public","dates":"y|2010:1116|2011:1106|2012:1026|2013:1015|2014:1004|2015:0923|2016:0911|2017:0901|2018:0821|2019:0811|2020:0731|2021:0720|2022:0709|2023:0628|2024:0616|2025:0606|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510","estimated":"y|2010:1116|2011:1106|2012:1026|2013:1015|2014:1004|2015:0923|2016:0911|2017:0901|2018:0821|2019:0811|2020:0731|2021:0720|2022:0709|2023:0628|2024:0616|2025:0606|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510"},{"id":"orthodox_christmas","name":"Orthodox Christmas","en":"Orthodox Christmas","category":"public","dates":"d|0107:2010-2060"},{"id":"epiphany","name":"Epiphany","en":"Epiphany","category":"public","dates":"d|0119:2010-2011,2013-2015,2017-2019,2021-2023,2025-2027,2029-2031,2033-2035,2037-2039,2041-2043,2045-2047,2049-2051,2053-2055,2057-2059|0120:2012,2016,2020,2024,2028,2032,2036,2040,2044,2048,2052,2056,2060"},{"id":"women_s_day","name":"Women's Day","en":"Women's Day","category":"public","dates":"d|0308:2010-2060"},{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","dates":"d|0402:2010|0403:2037,2048|0406:2018,2029|0407:2034,2045,2056|0410:2015,2026|0411:2031,2042,2053|0412:2058|0413:2012|0414:2017,2023,2028|0415:2039,2050|0416:2055|0417:2020|0418:2014,2025,2036|0419:2041,2047,2052|0422:2011,2022,2033,2044|0423:2038,2049,2060|0426:2019,2030|0427:2035,2046,2057|0429:2016|0430:2021,2027,2032|0501:2043,2054|0502:2059|0503:2013,2024|0504:2040|0505:2051"},{"id":"orthodox_easter","name":"Orthodox Easter","en":"Orthodox Easter","category":"public","dates":"d|0404:2010|0405:2037,2048|0408:2018,2029|0409:2034,2045,2056|0412:2015,2026|0413:2031,2042,2053|0414:2058|0415:2012|0416:2017,2023,2028|0417:2039,2050|0418:2055|0419:2020|0420:2014,2025,2036|0421:2041,2047,2052|0424:2011,2022,2033,2044|0425:2038,2049,2060|0428:2019,2030|0429:2035,2046,2057|0501:2016|0502:2021,2027,2032|0503:2043,2054|0504:2059|0505:2013,2024|0506:2040|0507:2051"},{"id":"international_workers_day","name":"International Workers' Day","en":"International Workers' Day","category":"public","dates":"d|0501:2010-2060"},{"id":"independence_day","name":"Independence Day","en":"Independence Day","category":"public","dates":"d|0524:2010-2060"},{"id":"martyrs_day","name":"Martyrs' Day","en":"Martyrs' Day","category":"public","dates":"d|0620:2010-2060"},{"id":"revolution_day","name":"Revolution Day","en":"Revolution Day","category":"public","dates":"d|0901:2010-2060"},{"id":"ethiopian_new_year","name":"Ethiopian New Year","en":"Ethiopian New Year","category":"public","dates":"d|0911:2010,2012-2014,2016-2018,2020-2022,2024-2026,2028-2030,2032-2034,2036-2038,2040-2042,2044-2046,2048-2050,2052-2054,2056-2058,2060|0912:2011,2015,2019,2023,2027,2031,2035,2039,2043,2047,2051,2055,2059"},{"id":"finding_of_the_true_cross","name":"Finding of the True Cross","en":"Finding of the True Cross","category":"public","dates":"d|0927:2010,2012-2014,2016-2018,2020-2022,2024-2026,2028-2030,2032-2034,2036-2038,2040-2042,2044-2046,2048-2050,2052-2054,2056-2058,2060|0928:2011,2015,2019,2023,2027,2031,2035,2039,2043,2047,2051,2055,2059"},{"id":"fenkil_day_government","name":"Fenkil Day","en":"Fenkil Day","category":"government","dates":"d|0210:2010-2060"}]} diff --git a/app/Support/Holidays/data/ES.json b/app/Support/Holidays/data/ES.json new file mode 100644 index 000000000..736df806a --- /dev/null +++ b/app/Support/Holidays/data/ES.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"ES","language":"es","years":[2010,2060],"subdivisions":{"AN":"Andalucía","AR":"Aragón","AS":"Asturias","CB":"Cantabria","CE":"Ceuta","CL":"Castilla y León","CM":"Castilla-La Mancha","CN":"Canarias","CT":"Cataluña","EX":"Extremadura","GA":"Galicia","IB":"Islas Baleares","MC":"Murcia","MD":"Madrid","ML":"Melilla","NC":"Navarra","PV":"País Vasco","RI":"La Rioja","VC":"Valenciana","Barcelona":"Barcelona"},"holidays":[{"id":"new_year_s_day","name":"Año Nuevo","en":"New Year's Day","category":"public","dates":"d|0101:2010-2011,2013-2016,2018-2022,2024-2033,2035-2039,2041-2044,2046-2050,2052-2060"},{"id":"eid_al_fitr","name":"Fiesta de Culminación del Ramadán-Eidul Fitr","en":"Eid al-Fitr","category":"public","regional":{"CE":"y|2022:0503|2023:0422|2024:0410|2025:0331|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304","ML":"y|2022:0503|2023:0421|2024:0410|2025:0331|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304"},"estimated":"y|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304"},{"id":"monday_following_new_year_s_day","name":"Lunes siguiente a Año Nuevo","en":"Monday following New Year's Day","category":"public","regional":{"AN":"d|0102:2012,2017,2023,2034,2040,2045,2051","AR":"d|0102:2012,2017,2023,2034,2040,2045,2051","AS":"d|0102:2012,2017,2023,2034,2040,2045,2051","CE":"d|0102:2012","CL":"d|0102:2017,2023,2034,2040,2045,2051","EX":"d|0102:2012","MC":"d|0102:2017,2023,2034,2040,2045,2051","ML":"d|0102:2017"}},{"id":"eid_al_adha","name":"Fiesta del Sacrificio-Eidul Adha","en":"Eid al-Adha","category":"public","regional":{"CE":"y|2010:1117|2011:1107|2012:1027|2013:1015|2014:1006|2015:0925|2016:0912|2017:0901|2018:0822|2019:0812|2020:0731|2021:0720|2022:0709|2023:0629|2024:0617|2025:0606|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510","ML":"y|2010:1117|2011:1107|2012:1026|2013:1015|2014:1004|2015:0925|2016:0912|2017:0901|2018:0822|2019:0812|2020:0731|2021:0721|2022:0711|2023:0629|2024:0617|2025:0606|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510"},"estimated":"y|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510"},{"id":"epiphany","name":"Epifanía del Señor","en":"Epiphany","category":"public","dates":"d|0106:2010-2012,2014-2018,2020-2029,2031-2035,2037-2040,2042-2046,2048-2057,2059-2060"},{"id":"monday_following_epiphany","name":"Lunes siguiente a Epifanía del Señor","en":"Monday following Epiphany","category":"public","regional":{"AN":"d|0107:2013,2019,2030,2036,2041,2047,2058","AR":"d|0107:2013,2019,2030,2036,2041,2047,2058","AS":"d|0107:2013,2019,2030,2036,2041,2047,2058","CB":"d|0107:2013","CE":"d|0107:2013,2019,2030,2036,2041,2047,2058","CL":"d|0107:2013,2019,2030,2036,2041,2047,2058","CM":"d|0107:2013","CN":"d|0107:2013,2019,2030,2036,2041,2047,2058","EX":"d|0107:2013,2019,2030,2036,2041,2047,2058","MC":"d|0107:2013,2019,2030,2036,2041,2047,2058","MD":"d|0107:2013,2019,2030,2036,2041,2047,2058","ML":"d|0107:2013,2019,2030,2036,2041,2047,2058","NC":"d|0107:2013,2019,2030,2036,2041,2047,2058"}},{"id":"andalusia_day","name":"Día de Andalucía","en":"Andalusia Day","category":"public","regional":{"AN":"d|0228:2011-2015,2017-2020,2022-2026,2028-2037,2039-2043,2045-2048,2050-2054,2056-2060"}},{"id":"monday_following_andalusia_day","name":"Lunes siguiente a Día de Andalucía","en":"Monday following Andalusia Day","category":"public","regional":{"AN":"d|0229:2016,2044|0301:2010,2021,2027,2038,2049,2055"}},{"id":"day_of_the_balearic_islands","name":"Día de las Islas Baleares","en":"Day of the Balearic Islands","category":"public","regional":{"IB":"d|0301:2010-2014,2016-2019,2021-2025,2027-2036,2038-2042,2044-2047,2049-2053,2055-2060"}},{"id":"monday_following_day_of_the_balearic_islands","name":"Lunes siguiente a Día de las Islas Baleares","en":"Monday following Day of the Balearic Islands","category":"public","regional":{"IB":"d|0302:2026,2037,2043,2048,2054"}},{"id":"saint_joseph_s_day","name":"San José","en":"Saint Joseph's Day","category":"public","regional":{"CL":"d|0319:2010,2012","CM":"d|0319:2010-2011,2020","EX":"d|0319:2010,2021","GA":"d|0319:2010-2011,2019-2021,2026","MC":"d|0319:2010-2016,2018-2021,2024-2027,2029-2033,2035-2044,2046-2050,2052-2055,2057-2060","MD":"d|0319:2010,2012,2015,2021,2027,2032,2038,2040,2049,2055,2060","ML":"d|0319:2010-2016","NC":"d|0319:2010,2012,2014-2015,2019-2021,2026","PV":"d|0319:2010,2015,2019-2021,2026-2027,2030,2032,2036-2038,2041,2043,2047-2049,2054-2055,2058,2060","RI":"d|0319:2010,2012","VC":"d|0319:2010-2016,2018-2022,2024-2027,2029-2033,2035-2044,2046-2050,2052-2055,2057-2060"}},{"id":"monday_following_saint_joseph_s_day","name":"Lunes siguiente a San José","en":"Monday following Saint Joseph's Day","category":"public","regional":{"EX":"d|0320:2017","MD":"d|0320:2017,2023,2034,2045,2051"}},{"id":"maundy_thursday","name":"Jueves Santo","en":"Maundy Thursday","category":"public","regional":{"AN":"d|0322:2035,2046|0324:2016|0325:2027,2032|0326:2043,2054|0327:2059|0328:2013,2024|0329:2018,2029,2040|0330:2051,2056|0401:2010,2021|0402:2015,2026,2037,2048|0403:2042,2053|0405:2012|0406:2023,2034,2045|0407:2039,2050|0409:2020|0410:2031,2036|0411:2047,2058|0413:2017,2028|0414:2022,2033,2044|0415:2049,2055,2060|0417:2014,2025|0418:2019,2030,2041,2052|0419:2057|0421:2011|0422:2038","AR":"d|0322:2035,2046|0324:2016|0325:2027,2032|0326:2043,2054|0327:2059|0328:2013,2024|0329:2018,2029,2040|0330:2051,2056|0401:2010,2021|0402:2015,2026,2037,2048|0403:2042,2053|0405:2012|0406:2023,2034,2045|0407:2039,2050|0409:2020|0410:2031,2036|0411:2047,2058|0413:2017,2028|0414:2022,2033,2044|0415:2049,2055,2060|0417:2014,2025|0418:2019,2030,2041,2052|0419:2057|0421:2011|0422:2038","AS":"d|0322:2035,2046|0324:2016|0325:2027,2032|0326:2043,2054|0327:2059|0328:2013,2024|0329:2018,2029,2040|0330:2051,2056|0401:2010,2021|0402:2015,2026,2037,2048|0403:2042,2053|0405:2012|0406:2023,2034,2045|0407:2039,2050|0409:2020|0410:2031,2036|0411:2047,2058|0413:2017,2028|0414:2022,2033,2044|0415:2049,2055,2060|0417:2014,2025|0418:2019,2030,2041,2052|0419:2057|0421:2011|0422:2038","CB":"d|0322:2035,2046|0324:2016|0325:2027,2032|0326:2043,2054|0327:2059|0328:2013,2024|0329:2029,2040|0330:2051,2056|0401:2010,2021|0402:2015,2026,2037,2048|0403:2042,2053|0405:2012|0406:2023,2034,2045|0407:2039,2050|0409:2020|0410:2031,2036|0411:2047,2058|0413:2017,2028|0414:2022,2033,2044|0415:2049,2055,2060|0417:2014,2025|0418:2019,2030,2041,2052|0419:2057|0421:2011|0422:2038","CE":"d|0322:2035,2046|0324:2016|0325:2027,2032|0326:2043,2054|0327:2059|0328:2013,2024|0329:2018,2029,2040|0330:2051,2056|0401:2010,2021|0402:2015,2026,2037,2048|0403:2042,2053|0405:2012|0406:2023,2034,2045|0407:2039,2050|0409:2020|0410:2031,2036|0411:2047,2058|0413:2017,2028|0414:2022,2033,2044|0415:2049,2055,2060|0417:2014,2025|0418:2019,2030,2041,2052|0419:2057|0421:2011|0422:2038","CL":"d|0322:2035,2046|0324:2016|0325:2027,2032|0326:2043,2054|0327:2059|0328:2013,2024|0329:2018,2029,2040|0330:2051,2056|0401:2010,2021|0402:2015,2026,2037,2048|0403:2042,2053|0405:2012|0406:2023,2034,2045|0407:2039,2050|0409:2020|0410:2031,2036|0411:2047,2058|0413:2017,2028|0414:2022,2033,2044|0415:2049,2055,2060|0417:2014,2025|0418:2019,2030,2041,2052|0419:2057|0421:2011|0422:2038","CM":"d|0322:2035,2046|0324:2016|0325:2027,2032|0326:2043,2054|0327:2059|0328:2013,2024|0329:2018,2029,2040|0330:2051,2056|0401:2010,2021|0402:2015,2026,2037,2048|0403:2042,2053|0405:2012|0406:2023,2034,2045|0407:2039,2050|0409:2020|0410:2031,2036|0411:2047,2058|0413:2017,2028|0414:2022,2033,2044|0415:2049,2055,2060|0417:2014,2025|0418:2019,2030,2041,2052|0419:2057|0421:2011|0422:2038","CN":"d|0322:2035,2046|0324:2016|0325:2027,2032|0326:2043,2054|0327:2059|0328:2013,2024|0329:2018,2029,2040|0330:2051,2056|0401:2010,2021|0402:2015,2026,2037,2048|0403:2042,2053|0405:2012|0406:2023,2034,2045|0407:2039,2050|0409:2020|0410:2031,2036|0411:2047,2058|0413:2017,2028|0414:2022,2033,2044|0415:2049,2055,2060|0417:2014,2025|0418:2019,2030,2041,2052|0419:2057|0421:2011|0422:2038","EX":"d|0322:2035,2046|0324:2016|0325:2027,2032|0326:2043,2054|0327:2059|0328:2013,2024|0329:2018,2029,2040|0330:2051,2056|0401:2010,2021|0402:2015,2026,2037,2048|0403:2042,2053|0405:2012|0406:2023,2034,2045|0407:2039,2050|0409:2020|0410:2031,2036|0411:2047,2058|0413:2017,2028|0414:2022,2033,2044|0415:2049,2055,2060|0417:2014,2025|0418:2019,2030,2041,2052|0419:2057|0421:2011|0422:2038","GA":"d|0322:2035,2046|0324:2016|0325:2027,2032|0326:2043,2054|0327:2059|0328:2013,2024|0329:2018,2029,2040|0330:2051,2056|0401:2010,2021|0402:2015,2026,2037,2048|0403:2042,2053|0405:2012|0406:2023,2034,2045|0407:2039,2050|0409:2020|0410:2031,2036|0411:2047,2058|0413:2017,2028|0414:2022,2033,2044|0415:2049,2055,2060|0417:2014,2025|0418:2019,2030,2041,2052|0419:2057|0421:2011|0422:2038","IB":"d|0322:2035,2046|0324:2016|0325:2027,2032|0326:2043,2054|0327:2059|0328:2013,2024|0329:2018,2029,2040|0330:2051,2056|0401:2010,2021|0402:2015,2026,2037,2048|0403:2042,2053|0405:2012|0406:2023,2034,2045|0407:2039,2050|0409:2020|0410:2031,2036|0411:2047,2058|0413:2017,2028|0414:2022,2033,2044|0415:2049,2055,2060|0417:2014,2025|0418:2019,2030,2041,2052|0419:2057|0421:2011|0422:2038","MC":"d|0322:2035,2046|0324:2016|0325:2027,2032|0326:2043,2054|0327:2059|0328:2013,2024|0329:2018,2029,2040|0330:2051,2056|0401:2010,2021|0402:2015,2026,2037,2048|0403:2042,2053|0405:2012|0406:2023,2034,2045|0407:2039,2050|0409:2020|0410:2031,2036|0411:2047,2058|0413:2017,2028|0414:2022,2033,2044|0415:2049,2055,2060|0417:2014,2025|0418:2019,2030,2041,2052|0419:2057|0421:2011|0422:2038","MD":"d|0322:2035,2046|0324:2016|0325:2027,2032|0326:2043,2054|0327:2059|0328:2013,2024|0329:2018,2029,2040|0330:2051,2056|0401:2010,2021|0402:2015,2026,2037,2048|0403:2042,2053|0405:2012|0406:2023,2034,2045|0407:2039,2050|0409:2020|0410:2031,2036|0411:2047,2058|0413:2017,2028|0414:2022,2033,2044|0415:2049,2055,2060|0417:2014,2025|0418:2019,2030,2041,2052|0419:2057|0421:2011|0422:2038","ML":"d|0322:2035,2046|0324:2016|0325:2027,2032|0326:2043,2054|0327:2059|0328:2013,2024|0329:2018,2029,2040|0330:2051,2056|0401:2010,2021|0402:2015,2026,2037,2048|0403:2042,2053|0405:2012|0406:2023,2034,2045|0407:2039,2050|0409:2020|0410:2031,2036|0411:2047,2058|0413:2017,2028|0414:2022,2033,2044|0415:2049,2055,2060|0417:2014,2025|0418:2019,2030,2041,2052|0419:2057|0421:2011|0422:2038","NC":"d|0322:2035,2046|0324:2016|0325:2027,2032|0326:2043,2054|0327:2059|0328:2013,2024|0329:2018,2029,2040|0330:2051,2056|0401:2010,2021|0402:2015,2026,2037,2048|0403:2042,2053|0405:2012|0406:2023,2034,2045|0407:2039,2050|0409:2020|0410:2031,2036|0411:2047,2058|0413:2017,2028|0414:2022,2033,2044|0415:2049,2055,2060|0417:2014,2025|0418:2019,2030,2041,2052|0419:2057|0421:2011|0422:2038","PV":"d|0322:2035,2046|0324:2016|0325:2027,2032|0326:2043,2054|0327:2059|0328:2013,2024|0329:2018,2029,2040|0330:2051,2056|0401:2010,2021|0402:2015,2026,2037,2048|0403:2042,2053|0405:2012|0406:2023,2034,2045|0407:2039,2050|0409:2020|0410:2031,2036|0411:2047,2058|0413:2017,2028|0414:2022,2033,2044|0415:2049,2055,2060|0417:2014,2025|0418:2019,2030,2041,2052|0419:2057|0421:2011|0422:2038","RI":"d|0322:2035,2046|0324:2016|0325:2027,2032|0326:2043,2054|0327:2059|0328:2013,2024|0329:2018,2029,2040|0330:2051,2056|0401:2010,2021|0402:2015,2026,2037,2048|0403:2042,2053|0405:2012|0406:2023,2034,2045|0407:2039,2050|0409:2020|0410:2031,2036|0411:2047,2058|0413:2017,2028|0414:2022,2033,2044|0415:2049,2055,2060|0417:2014,2025|0418:2019,2030,2041,2052|0419:2057|0421:2011|0422:2038","VC":"d|0324:2016|0407:2039,2050|0413:2017|0414:2022,2033,2044|0421:2011"}},{"id":"good_friday","name":"Viernes Santo","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_monday","name":"Lunes de Pascua","en":"Easter Monday","category":"public","regional":{"Barcelona":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038","CB":"d|0401:2013,2024|0406:2015|0413:2020|0415:2047,2058|0422:2019,2030,2041,2052","CM":"d|0406:2015,2026|0413:2020|0421:2014|0422:2019","CT":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038","IB":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038","NC":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038","PV":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038","RI":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2029,2040|0403:2051,2056|0405:2021|0406:2015,2026,2037,2048|0407:2042,2053|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038","VC":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"}},{"id":"castile_and_leon_day","name":"Fiesta de Castilla y León","en":"Castile and León Day","category":"public","regional":{"CL":"d|0423:2010-2016,2018-2022,2024-2027,2029-2033,2035-2044,2046-2050,2052-2055,2057-2060"}},{"id":"saint_george_s_day","name":"Día de San Jorge","en":"Saint George's Day","category":"public","regional":{"AR":"d|0423:2010-2016,2018-2022,2024-2027,2029-2033,2035-2044,2046-2050,2052-2055,2057-2060"}},{"id":"monday_following_castile_and_leon_day","name":"Lunes siguiente a Fiesta de Castilla y León","en":"Monday following Castile and León Day","category":"public","regional":{"CL":"d|0424:2017,2028,2034,2045,2051,2056"}},{"id":"monday_following_saint_george_s_day","name":"Lunes siguiente a Día de San Jorge","en":"Monday following Saint George's Day","category":"public","regional":{"AR":"d|0424:2017,2023,2028,2034,2045,2051,2056"}},{"id":"labor_day","name":"Fiesta del Trabajo","en":"Labor Day","category":"public","dates":"d|0501:2010,2012-2015,2017-2021,2023-2032,2034-2038,2040-2043,2045-2049,2051-2060"},{"id":"madrid_day","name":"Fiesta de la Comunidad de Madrid","en":"Madrid Day","category":"public","regional":{"MD":"d|0502:2011-2020,2022-2026,2028-2031,2033-2037,2039-2048,2050-2054,2056-2059"}},{"id":"monday_following_labor_day","name":"Lunes siguiente a Fiesta del Trabajo","en":"Monday following Labor Day","category":"public","regional":{"AN":"d|0502:2011,2016,2022,2033,2039,2044,2050","AR":"d|0502:2011,2016,2022,2033,2039,2044,2050","AS":"d|0502:2011,2016,2022,2033,2039,2044,2050","CB":"d|0502:2011","CE":"d|0502:2011","CL":"d|0502:2016,2022,2033,2039,2044,2050","CN":"d|0502:2016","EX":"d|0502:2011,2016,2022,2033,2039,2044,2050","MC":"d|0502:2011,2022,2033,2039,2044,2050","VC":"d|0502:2011"}},{"id":"monday_following_madrid_day","name":"Lunes siguiente a Fiesta de la Comunidad de Madrid","en":"Monday following Madrid Day","category":"public","regional":{"MD":"d|0503:2021,2027,2032,2038,2049,2055,2060"}},{"id":"pentecost_monday","name":"Día de la Pascua Granada","en":"Pentecost Monday","category":"public","regional":{"Barcelona":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2021|0525:2026,2037,2048|0526:2042,2053|0528:2012|0529:2034,2045|0530:2039,2050|0601:2015,2020|0602:2031,2036|0603:2047,2058|0605:2017,2023,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038","CT":"d|0516:2016|0530:2039,2050|0606:2022,2033,2044|0613:2011"}},{"id":"galician_literature_day","name":"Día de las Letras Gallegas","en":"Galician Literature Day","category":"public","regional":{"GA":"d|0517:2010-2014,2016-2019,2021-2025,2027-2036,2038-2042,2044-2047,2049-2053,2055-2060"}},{"id":"corpus_christi","name":"Corpus Christi","en":"Corpus Christi","category":"public","regional":{"CM":"d|0524:2035,2046|0526:2016|0527:2027,2032|0528:2043,2054|0529:2059|0530:2013,2024|0531:2029,2040|0601:2051,2056|0603:2021|0604:2015,2026,2037,2048|0605:2042,2053|0607:2012|0608:2023,2034,2045|0609:2039,2050|0611:2020|0612:2031,2036|0613:2047,2058|0615:2017,2028|0616:2022,2033,2044|0617:2049,2055,2060|0619:2014,2025|0620:2019,2030,2041,2052|0621:2057|0623:2011|0624:2038","MD":"d|0603:2010|0619:2014|0623:2011"}},{"id":"day_of_the_canary_islands","name":"Día de Canarias","en":"Day of the Canary Islands","category":"public","regional":{"CN":"d|0530:2011-2020,2022-2026,2028-2031,2033-2037,2039-2048,2050-2054,2056-2059"}},{"id":"castilla_la_mancha_day","name":"Día de Castilla-La Mancha","en":"Castilla-La Mancha Day","category":"public","regional":{"CM":"d|0531:2010-2014,2016-2019,2021-2025,2027-2036,2038-2042,2044-2047,2049-2053,2055-2060"}},{"id":"monday_following_day_of_the_canary_islands","name":"Lunes siguiente a Día de Canarias","en":"Monday following Day of the Canary Islands","category":"public","regional":{"CN":"d|0531:2010,2027,2032,2038,2049,2055,2060"}},{"id":"la_rioja_day","name":"Día de La Rioja","en":"La Rioja Day","category":"public","regional":{"RI":"d|0609:2010-2012,2014-2018,2020-2023,2025-2029,2031-2040,2042-2046,2048-2051,2053-2057,2059-2060"}},{"id":"murcia_day","name":"Día de la Región de Murcia","en":"Murcia Day","category":"public","regional":{"MC":"d|0609:2010-2012,2014-2018,2020-2023,2025-2029,2031-2040,2042-2046,2048-2051,2053-2057,2059-2060"}},{"id":"monday_following_la_rioja_day","name":"Lunes siguiente a Día de La Rioja","en":"Monday following La Rioja Day","category":"public","regional":{"RI":"d|0610:2013,2019,2024,2030,2041,2047,2052,2058"}},{"id":"monday_following_murcia_day","name":"Lunes siguiente a Día de la Región de Murcia","en":"Monday following Murcia Day","category":"public","regional":{"MC":"d|0610:2019,2030,2041,2047,2052,2058"}},{"id":"saint_anthony_s_day","name":"San Antonio","en":"Saint Anthony's Day","category":"public","regional":{"CE":"d|0613:2011-2020,2022-2026,2028-2031,2033-2037,2039-2048,2050-2054,2056-2059"}},{"id":"monday_following_saint_anthony_s_day","name":"Lunes siguiente a San Antonio","en":"Monday following Saint Anthony's Day","category":"public","regional":{"CE":"d|0614:2010,2021,2027,2032,2038,2049,2055,2060"}},{"id":"saint_john_the_baptist","name":"San Juan","en":"Saint John the Baptist","category":"public","regional":{"Barcelona":"d|0624:2010-2011,2013-2017,2019-2028,2030-2034,2036-2039,2041-2045,2047-2056,2058-2060","CT":"d|0624:2010-2011,2013-2017,2019-2028,2030-2034,2036-2039,2041-2045,2047-2056,2058-2060","GA":"d|0624:2013,2016,2020,2022,2026","VC":"d|0624:2019-2060"}},{"id":"galician_national_day","name":"Día Nacional de Galicia","en":"Galician National Day","category":"public","regional":{"GA":"d|0725:2011-2020,2022-2026,2028-2031,2033-2037,2039-2048,2050-2054,2056-2059"}},{"id":"saint_james_day","name":"Santiago Apóstol","en":"Saint James' Day","category":"public","regional":{"CB":"d|0725:2012-2014,2019,2024,2030,2041,2047,2052,2058","CL":"d|0725:2011,2023","MD":"d|0725:2011,2016,2022,2024-2025,2033,2039,2044,2050","NC":"d|0725:2011,2013,2015-2017,2022-2025","PV":"d|0725:2011,2013,2015-2017,2019-2020,2022-2026,2028-2031,2033-2037,2039-2048,2050-2054,2056-2059","RI":"d|0725:2011,2016"}},{"id":"cantabria_institutions_day","name":"Día de las Instituciones de Cantabria","en":"Cantabria Institutions Day","category":"public","regional":{"CB":"d|0728:2010-2011,2014,2016-2018,2020-2023,2025-2029,2031-2040,2042-2046,2048-2051,2053-2057,2059-2060"}},{"id":"santa_maria_of_africa","name":"Nuestra Señora de África","en":"Santa Maria of Africa","category":"public","regional":{"CE":"d|0805:2022-2060"}},{"id":"assumption_day","name":"Asunción de la Virgen","en":"Assumption Day","category":"public","dates":"d|0815:2011-2020,2022-2026,2028-2031,2033-2037,2039-2048,2050-2054,2056-2059"},{"id":"monday_following_assumption_day","name":"Lunes siguiente a Asunción de la Virgen","en":"Monday following Assumption Day","category":"public","regional":{"AN":"d|0816:2010,2021,2027,2032,2038,2049,2055,2060","AR":"d|0816:2010,2021,2027,2032,2038,2049,2055,2060","AS":"d|0816:2010,2021,2027,2032,2038,2049,2055,2060","CL":"d|0816:2021,2027,2032,2038,2049,2055,2060","CN":"d|0816:2021,2027,2032,2038,2049,2055,2060"}},{"id":"ceuta_day","name":"Día de Ceuta","en":"Ceuta Day","category":"public","regional":{"CE":"d|0902:2010,2013-2014,2016-2017,2019-2024,2026-2028,2030-2034,2036-2039,2041-2045,2047-2056,2058-2060"}},{"id":"asturias_day","name":"Día de Asturias","en":"Asturias Day","category":"public","regional":{"AS":"d|0908:2010-2012,2014-2018,2020-2023,2025-2029,2031-2040,2042-2046,2048-2051,2053-2057,2059-2060"}},{"id":"day_of_our_lady_of_victory","name":"Día de Nuestra Señora la Virgen de la Victoria","en":"Day of Our Lady of Victory","category":"public","regional":{"ML":"d|0908:2010-2012,2014-2018,2020-2023,2025-2029,2031-2040,2042-2046,2048-2051,2053-2057,2059-2060"}},{"id":"extremadura_day","name":"Día de Extremadura","en":"Extremadura Day","category":"public","regional":{"EX":"d|0908:2010-2012,2014-2018,2020-2023,2025-2029,2031-2040,2042-2046,2048-2051,2053-2057,2059-2060"}},{"id":"monday_following_asturias_day","name":"Lunes siguiente a Día de Asturias","en":"Monday following Asturias Day","category":"public","regional":{"AS":"d|0909:2013,2019,2024,2030,2041,2047,2052,2058"}},{"id":"monday_following_day_of_our_lady_of_victory","name":"Lunes siguiente a Día de Nuestra Señora la Virgen de la Victoria","en":"Monday following Day of Our Lady of Victory","category":"public","regional":{"ML":"d|0909:2013,2019,2030,2041,2047,2052,2058"}},{"id":"monday_following_extremadura_day","name":"Lunes siguiente a Día de Extremadura","en":"Monday following Extremadura Day","category":"public","regional":{"EX":"d|0909:2013,2019,2030,2041,2047,2052,2058"}},{"id":"national_day_of_catalonia","name":"Fiesta Nacional de Cataluña","en":"National Day of Catalonia","category":"public","regional":{"Barcelona":"d|0911:2010,2012-2015,2017-2021,2023-2032,2034-2038,2040-2043,2045-2049,2051-2060","CT":"d|0911:2010,2012-2015,2017-2021,2023-2032,2034-2038,2040-2043,2045-2049,2051-2060"}},{"id":"our_lady_of_bien_aparecida","name":"La Bien Aparecida","en":"Our Lady of Bien Aparecida","category":"public","regional":{"CB":"d|0915:2010-2012,2014-2018,2020-2023,2025-2029,2031-2040,2042-2046,2048-2051,2053-2057,2059-2060"}},{"id":"melilla_day","name":"Día de Melilla","en":"Melilla Day","category":"public","regional":{"ML":"d|0917:2010-2016,2018-2022,2024-2027,2029-2033,2035-2044,2046-2050,2052-2055,2057-2060"}},{"id":"monday_following_melilla_day","name":"Lunes siguiente a Día de Melilla","en":"Monday following Melilla Day","category":"public","regional":{"ML":"d|0918:2017,2028,2034,2045,2051,2056"}},{"id":"our_lady_of_mercy","name":"Nuestra Señora de la Merced","en":"Our Lady of Mercy","category":"public","regional":{"Barcelona":"d|0924:2012-2016,2018-2022,2024-2027,2029-2033,2035-2044,2046-2050,2052-2055,2057-2060"}},{"id":"monday_following_our_lady_of_mercy","name":"Lunes siguiente a Nuestra Señora de la Merced","en":"Monday following Our Lady of Mercy","category":"public","regional":{"Barcelona":"d|0925:2017,2023,2028,2034,2045,2051,2056|0926:2022"}},{"id":"valencian_community_day","name":"Día de la Comunidad Valenciana","en":"Valencian Community Day","category":"public","regional":{"VC":"d|1009:2010,2012-2015,2017-2021,2023-2032,2034-2038,2040-2043,2045-2049,2051-2060"}},{"id":"national_day","name":"Fiesta Nacional de España","en":"National Day","category":"public","dates":"d|1012:2010-2013,2015-2024,2026-2030,2032-2035,2037-2041,2043-2052,2054-2058,2060"},{"id":"monday_following_national_day","name":"Lunes siguiente a Fiesta Nacional de España","en":"Monday following National Day","category":"public","regional":{"AN":"d|1013:2014,2025,2031,2036,2042,2053,2059","AR":"d|1013:2014,2025,2031,2036,2042,2053,2059","AS":"d|1013:2014,2025,2031,2036,2042,2053,2059","CE":"d|1013:2014","CL":"d|1013:2014,2025,2031,2036,2042,2053,2059","EX":"d|1013:2014,2025,2031,2036,2042,2053,2059"}},{"id":"all_saints_day","name":"Todos los Santos","en":"All Saints' Day","category":"public","dates":"d|1101:2010-2014,2016-2019,2021-2025,2027-2036,2038-2042,2044-2047,2049-2053,2055-2060"},{"id":"monday_following_all_saints_day","name":"Lunes siguiente a Todos los Santos","en":"Monday following All Saints' Day","category":"public","regional":{"AN":"d|1102:2015,2020,2026,2037,2043,2048,2054","AR":"d|1102:2015,2020,2026,2037,2043,2048,2054","AS":"d|1102:2015,2020,2026,2037,2043,2048,2054","CB":"d|1102:2015","CE":"d|1102:2015","CL":"d|1102:2015,2020,2026,2037,2043,2048,2054","CM":"d|1102:2026,2037,2043,2048,2054","CN":"d|1102:2015,2026,2037,2043,2048,2054","EX":"d|1102:2015,2020,2026,2037,2043,2048,2054","GA":"d|1102:2015","IB":"d|1102:2015","MD":"d|1102:2020,2026,2037,2043,2048,2054","NC":"d|1102:2026,2037,2043,2048,2054"}},{"id":"saint_francis_xavier_s_day","name":"San Francisco Javier","en":"Saint Francis Xavier's Day","category":"public","regional":{"NC":"d|1203:2010-2016,2018-2022,2024-2027,2029-2033,2035-2044,2046-2050,2052-2055,2057-2060"}},{"id":"monday_following_saint_francis_xavier_s_day","name":"Lunes siguiente a San Francisco Javier","en":"Monday following Saint Francis Xavier's Day","category":"public","regional":{"NC":"d|1204:2017,2023,2028,2034,2045,2051,2056"}},{"id":"constitution_day","name":"Día de la Constitución Española","en":"Constitution Day","category":"public","dates":"d|1206:2010-2014,2016-2019,2021-2025,2027-2036,2038-2042,2044-2047,2049-2053,2055-2060"},{"id":"monday_following_constitution_day","name":"Lunes siguiente a Día de la Constitución Española","en":"Monday following Constitution Day","category":"public","regional":{"AN":"d|1207:2015,2020,2026,2037,2043,2048,2054","AR":"d|1207:2015,2020,2026,2037,2043,2048,2054","AS":"d|1207:2015,2020,2026,2037,2043,2048,2054","CB":"d|1207:2026,2037,2043,2048,2054","CE":"d|1207:2015,2020","CL":"d|1207:2015,2020,2026,2037,2043,2048,2054","CM":"d|1207:2015","CN":"d|1207:2020","EX":"d|1207:2015,2020,2026,2037,2043,2048,2054","IB":"d|1207:2015,2020","MC":"d|1207:2015,2020,2026,2037,2043,2048,2054","MD":"d|1207:2020,2026,2037,2043,2048,2054","ML":"d|1207:2015,2020,2026,2037,2043,2048,2054","NC":"d|1207:2020","RI":"d|1207:2015,2020,2026,2037,2043,2048,2054","VC":"d|1207:2015"}},{"id":"immaculate_conception","name":"Inmaculada Concepción","en":"Immaculate Conception","category":"public","dates":"d|1208:2010-2012,2014-2018,2020-2023,2025-2029,2031-2040,2042-2046,2048-2051,2053-2057,2059-2060"},{"id":"monday_following_immaculate_conception","name":"Lunes siguiente a Inmaculada Concepción","en":"Monday following Immaculate Conception","category":"public","regional":{"AN":"d|1209:2013,2019,2024,2030,2041,2047,2052,2058","AR":"d|1209:2013,2019,2024,2030,2041,2047,2052,2058","AS":"d|1209:2013,2019,2024,2030,2041,2047,2052,2058","CB":"d|1209:2019","CE":"d|1209:2013","CL":"d|1209:2013,2019,2024,2030,2041,2047,2052,2058","EX":"d|1209:2013,2019,2024,2030,2041,2047,2052,2058","MC":"d|1209:2013,2024,2030,2041,2047,2052,2058","MD":"d|1209:2019","ML":"d|1209:2019,2024,2030,2041,2047,2052,2058","RI":"d|1209:2013,2019"}},{"id":"christmas_day","name":"Natividad del Señor","en":"Christmas Day","category":"public","dates":"d|1225:2010,2012-2015,2017-2021,2023-2032,2034-2038,2040-2043,2045-2049,2051-2060"},{"id":"monday_following_christmas_day","name":"Lunes siguiente a Natividad del Señor","en":"Monday following Christmas Day","category":"public","regional":{"AN":"d|1226:2011,2016,2022,2033,2039,2044,2050","AR":"d|1226:2011,2016,2022,2033,2039,2044,2050","AS":"d|1226:2011,2016,2022,2033,2039,2044,2050","CB":"d|1226:2016,2022,2033,2039,2044,2050","CE":"d|1226:2011,2016","CL":"d|1226:2011,2016,2022,2033,2039,2044,2050","CM":"d|1226:2016,2022,2033,2039,2044,2050","CN":"d|1226:2011,2022,2033,2039,2044,2050","EX":"d|1226:2011,2016,2022,2033,2039,2044,2050","IB":"d|1226:2011,2016,2022,2033,2039,2044,2050","MC":"d|1226:2016,2022,2033,2039,2044,2050","MD":"d|1226:2016,2022,2033,2039,2044,2050","ML":"d|1226:2011,2016,2022,2033,2039,2044,2050","NC":"d|1226:2011,2016,2022,2033,2039,2044,2050","RI":"d|1226:2022,2033,2039,2044,2050","VC":"d|1226:2016"}},{"id":"saint_stephen_s_day","name":"San Esteban","en":"Saint Stephen's Day","category":"public","regional":{"Barcelona":"d|1226:2011-2020,2022-2026,2028-2031,2033-2037,2039-2048,2050-2054,2056-2059","CT":"d|1226:2011-2020,2022-2026,2028-2031,2033-2037,2039-2048,2050-2054,2056-2059","IB":"d|1226:2013-2014,2019-2020,2025-2026"}}]} diff --git a/app/Support/Holidays/data/ET.json b/app/Support/Holidays/data/ET.json new file mode 100644 index 000000000..c6c94ecb8 --- /dev/null +++ b/app/Support/Holidays/data/ET.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"ET","language":"am","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"eid_al_fitr","name":"የኢድ አልፈጥር","en":"Eid al-Fitr","category":"public","dates":"y|2010:0910|2011:0830|2012:0819|2013:0808|2014:0728|2015:0717|2016:0706|2017:0625|2018:0615|2019:0604|2020:0524|2021:0513|2022:0502|2023:0421|2024:0410|2025:0330|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304","estimated":"y|2010:0910|2011:0830|2012:0819|2013:0808|2014:0728|2015:0717|2016:0706|2017:0625|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304"},{"id":"prophet_s_birthday","name":"የመውሊድ በዓል","en":"Prophet's Birthday","category":"public","dates":"y|2010:0226|2011:0215|2012:0204|2013:0124|2014:0113|2015:0103,1223|2016:1211|2017:1130|2018:1121|2019:1110|2020:1029|2021:1018|2022:1008|2023:0927|2024:0915|2025:0905|2026:0825|2027:0814|2028:0803|2029:0724|2030:0713|2031:0702|2032:0620|2033:0609|2034:0530|2035:0520|2036:0508|2037:0428|2038:0417|2039:0406|2040:0325|2041:0315|2042:0304|2043:0222|2044:0211|2045:0130|2046:0119|2047:0108,1229|2048:1218|2049:1207|2050:1126|2051:1116|2052:1104|2053:1024|2054:1013|2055:1003|2056:0922|2057:0911|2058:0831|2059:0820|2060:0808","estimated":"y|2010:0226|2011:0215|2012:0204|2013:0124|2014:0113|2015:0103,1223|2016:1211|2017:1130|2026:0825|2027:0814|2028:0803|2029:0724|2030:0713|2031:0702|2032:0620|2033:0609|2034:0530|2035:0520|2036:0508|2037:0428|2038:0417|2039:0406|2040:0325|2041:0315|2042:0304|2043:0222|2044:0211|2045:0130|2046:0119|2047:0108,1229|2048:1218|2049:1207|2050:1126|2051:1116|2052:1104|2053:1024|2054:1013|2055:1003|2056:0922|2057:0911|2058:0831|2059:0820|2060:0808"},{"id":"eid_al_adha","name":"የኢድ አልአድሃ (አረፋ)","en":"Eid al-Adha","category":"public","dates":"y|2010:1116|2011:1106|2012:1026|2013:1015|2014:1004|2015:0923|2016:0911|2017:0901|2018:0822|2019:0811|2020:0731|2021:0720|2022:0709|2023:0628|2024:0616|2025:0606|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510","estimated":"y|2010:1116|2011:1106|2012:1026|2013:1015|2014:1004|2015:0923|2016:0911|2017:0901|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510"},{"id":"christmas_day","name":"የገና ወይም የልደት በዓል","en":"Christmas Day","category":"public","dates":"d|0107:2010-2060"},{"id":"epiphany","name":"የጥምቀት በዓል","en":"Epiphany","category":"public","dates":"d|0119:2010-2011,2013-2015,2017-2019,2021-2023,2025-2027,2029-2031,2033-2035,2037-2039,2041-2043,2045-2047,2049-2051,2053-2055,2057-2059|0120:2012,2016,2020,2024,2028,2032,2036,2040,2044,2048,2052,2056,2060"},{"id":"adwa_victory_day","name":"የአድዋ ድል በዓል","en":"Adwa Victory Day","category":"public","dates":"d|0302:2010-2060"},{"id":"good_friday","name":"የስቅለት በዓል","en":"Good Friday","category":"public","dates":"d|0402:2010|0403:2037,2048|0406:2018,2029|0407:2034,2045,2056|0410:2015,2026|0411:2031,2042,2053|0412:2058|0413:2012|0414:2017,2023,2028|0415:2039,2050|0416:2055|0417:2020|0418:2014,2025,2036|0419:2041,2047,2052|0422:2011,2022,2033,2044|0423:2038,2049,2060|0426:2019,2030|0427:2035,2046,2057|0429:2016|0430:2021,2027,2032|0501:2043,2054|0502:2059|0503:2013,2024|0504:2040|0505:2051"},{"id":"easter_sunday","name":"የትንሳኤ(ፋሲካ) በዓል","en":"Easter Sunday","category":"public","dates":"d|0404:2010|0405:2037,2048|0408:2018,2029|0409:2034,2045,2056|0412:2015,2026|0413:2031,2042,2053|0414:2058|0415:2012|0416:2017,2023,2028|0417:2039,2050|0418:2055|0419:2020|0420:2014,2025,2036|0421:2041,2047,2052|0424:2011,2022,2033,2044|0425:2038,2049,2060|0428:2019,2030|0429:2035,2046,2057|0501:2016|0502:2021,2027,2032|0503:2043,2054|0504:2059|0505:2013,2024|0506:2040|0507:2051"},{"id":"international_workers_day","name":"የዓለም የሠራተኞች (የላብአደሮች) ቀን","en":"International Workers' Day","category":"public","dates":"d|0501:2010-2060"},{"id":"ethiopian_patriots_victory_day","name":"የአርበኞች (የድል) ቀን በዓል","en":"Ethiopian Patriots' Victory Day","category":"public","dates":"d|0505:2010-2060"},{"id":"ethiopian_new_year","name":"የዘመን መለወጫ (እንቁጣጣሽ) በዓል","en":"Ethiopian New Year","category":"public","dates":"d|0911:2010,2012-2014,2016-2018,2020-2022,2024-2026,2028-2030,2032-2034,2036-2038,2040-2042,2044-2046,2048-2050,2052-2054,2056-2058,2060|0912:2011,2015,2019,2023,2027,2031,2035,2039,2043,2047,2051,2055,2059"},{"id":"finding_of_true_cross","name":"የመስቀል በዓል","en":"Finding of True Cross","category":"public","dates":"d|0927:2010,2012-2014,2016-2018,2020-2022,2024-2026,2028-2030,2032-2034,2036-2038,2040-2042,2044-2046,2048-2050,2052-2054,2056-2058,2060|0928:2011,2015,2019,2023,2027,2031,2035,2039,2043,2047,2051,2055,2059"}]} diff --git a/app/Support/Holidays/data/FI.json b/app/Support/Holidays/data/FI.json new file mode 100644 index 000000000..a0c9afeee --- /dev/null +++ b/app/Support/Holidays/data/FI.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"FI","language":"fi","years":[2010,2060],"subdivisions":{"01":"Ahvenanmaan maakunta","02":"Etelä-Karjala","03":"Etelä-Pohjanmaa","04":"Etelä-Savo","05":"Kainuu","06":"Kanta-Häme","07":"Keski-Pohjanmaa","08":"Keski-Suomi","09":"Kymenlaakso","10":"Lappi","11":"Pirkanmaa","12":"Pohjanmaa","13":"Pohjois-Karjala","14":"Pohjois-Pohjanmaa","15":"Pohjois-Savo","16":"Päijät-Häme","17":"Satakunta","18":"Uusimaa","19":"Varsinais-Suomi"},"holidays":[{"id":"new_year_s_day","name":"Uudenvuodenpäivä","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"epiphany","name":"Loppiainen","en":"Epiphany","category":"public","dates":"d|0106:2010-2060"},{"id":"good_friday","name":"Pitkäperjantai","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_sunday","name":"Pääsiäispäivä","en":"Easter Sunday","category":"public","dates":"d|0325:2035,2046|0327:2016|0328:2027,2032|0329:2043,2054|0330:2059|0331:2013,2024|0401:2018,2029,2040|0402:2051,2056|0404:2010,2021|0405:2015,2026,2037,2048|0406:2042,2053|0408:2012|0409:2023,2034,2045|0410:2039,2050|0412:2020|0413:2031,2036|0414:2047,2058|0416:2017,2028|0417:2022,2033,2044|0418:2049,2055,2060|0420:2014,2025|0421:2019,2030,2041,2052|0422:2057|0424:2011|0425:2038"},{"id":"easter_monday","name":"Toinen pääsiäispäivä","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"may_day","name":"Vappu","en":"May Day","category":"public","dates":"d|0501:2010-2060"},{"id":"ascension_day","name":"Helatorstai","en":"Ascension Day","category":"public","dates":"d|0503:2035,2046|0505:2016|0506:2027,2032|0507:2043,2054|0508:2059|0509:2013,2024|0510:2018,2029,2040|0511:2051,2056|0513:2010,2021|0514:2015,2026,2037,2048|0515:2042,2053|0517:2012|0518:2023,2034,2045|0519:2039,2050|0521:2020|0522:2031,2036|0523:2047,2058|0525:2017,2028|0526:2022,2033,2044|0527:2049,2055,2060|0529:2014,2025|0530:2019,2030,2041,2052|0531:2057|0602:2011|0603:2038"},{"id":"pentecost","name":"Helluntaipäivä","en":"Pentecost","category":"public","dates":"d|0513:2035,2046|0515:2016|0516:2027,2032|0517:2043,2054|0518:2059|0519:2013,2024|0520:2018,2029,2040|0521:2051,2056|0523:2010,2021|0524:2015,2026,2037,2048|0525:2042,2053|0527:2012|0528:2023,2034,2045|0529:2039,2050|0531:2020|0601:2031,2036|0602:2047,2058|0604:2017,2028|0605:2022,2033,2044|0606:2049,2055,2060|0608:2014,2025|0609:2019,2030,2041,2052|0610:2057|0612:2011|0613:2038"},{"id":"aland_s_autonomy_day","name":"Ahvenanmaan itsehallintopäivä","en":"Åland's Autonomy Day","category":"public","regional":{"01":"d|0609:2010-2060"}},{"id":"midsummer_eve","name":"Juhannusaatto","en":"Midsummer Eve","category":"public","dates":"d|0619:2015,2020,2026,2037,2043,2048,2054|0620:2014,2025,2031,2036,2042,2053,2059|0621:2013,2019,2024,2030,2041,2047,2052,2058|0622:2012,2018,2029,2035,2040,2046,2057|0623:2017,2023,2028,2034,2045,2051,2056|0624:2011,2016,2022,2033,2039,2044,2050|0625:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"midsummer_day","name":"Juhannuspäivä","en":"Midsummer Day","category":"public","dates":"d|0620:2015,2020,2026,2037,2043,2048,2054|0621:2014,2025,2031,2036,2042,2053,2059|0622:2013,2019,2024,2030,2041,2047,2052,2058|0623:2012,2018,2029,2035,2040,2046,2057|0624:2017,2023,2028,2034,2045,2051,2056|0625:2011,2016,2022,2033,2039,2044,2050|0626:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"all_saints_day","name":"Pyhäinpäivä","en":"All Saints' Day","category":"public","dates":"d|1031:2015,2020,2026,2037,2043,2048,2054|1101:2014,2025,2031,2036,2042,2053,2059|1102:2013,2019,2024,2030,2041,2047,2052,2058|1103:2012,2018,2029,2035,2040,2046,2057|1104:2017,2023,2028,2034,2045,2051,2056|1105:2011,2016,2022,2033,2039,2044,2050|1106:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"independence_day","name":"Itsenäisyyspäivä","en":"Independence Day","category":"public","dates":"d|1206:2010-2060"},{"id":"christmas_eve","name":"Jouluaatto","en":"Christmas Eve","category":"public","dates":"d|1224:2010-2060"},{"id":"christmas_day","name":"Joulupäivä","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"},{"id":"second_day_of_christmas","name":"Tapaninpäivä","en":"Second Day of Christmas","category":"public","dates":"d|1226:2010-2060"},{"id":"runeberg_day_unofficial","name":"Runebergin päivä","en":"Runeberg Day","category":"unofficial","dates":"d|0205:2010-2060"},{"id":"minna_canth_day_day_of_equality_unofficial","name":"Minna Canthin päivä, tasa-arvon päivä","en":"Minna Canth Day, Day of Equality","category":"unofficial","dates":"d|0319:2010-2060"},{"id":"mikael_agricola_day_day_of_the_finnish_language_unofficial","name":"Mikael Agricolan päivä, suomen kielen päivä","en":"Mikael Agricola Day, Day of the Finnish Language","category":"unofficial","dates":"d|0409:2010-2060"},{"id":"national_war_veterans_day_unofficial","name":"Kansallinen veteraanipäivä","en":"National War Veterans' Day","category":"unofficial","dates":"d|0427:2010-2060"},{"id":"europe_day_unofficial","name":"Eurooppa-päivä","en":"Europe Day","category":"unofficial","dates":"d|0509:2019-2060"},{"id":"j_v_snellman_day_day_of_finnish_heritage_unofficial","name":"J.V. Snellmanin päivä, suomalaisuuden päivä","en":"J. V. Snellman Day, Day of Finnish Heritage","category":"unofficial","dates":"d|0512:2010-2060"},{"id":"remembrance_day_unofficial","name":"Kaatuneitten muistopäivä","en":"Remembrance Day","category":"unofficial","dates":"d|0515:2011,2016,2022,2033,2039,2044,2050|0516:2010,2021,2027,2032,2038,2049,2055,2060|0517:2015,2020,2026,2037,2043,2048,2054|0518:2014,2025,2031,2036,2042,2053,2059|0519:2013,2019,2024,2030,2041,2047,2052,2058|0520:2012,2018,2029,2035,2040,2046,2057|0521:2017,2023,2028,2034,2045,2051,2056"},{"id":"eino_leino_day_day_of_summer_and_poetry_unofficial","name":"Eino Leinon päivä, runon ja suven päivä","en":"Eino Leino Day, Day of Summer and Poetry","category":"unofficial","dates":"d|0706:2010-2060"},{"id":"finland_s_nature_day_unofficial","name":"Suomen luonnon päivä","en":"Finland's Nature Day","category":"unofficial","dates":"d|0825:2029,2035,2040,2046,2057|0826:2023,2028,2034,2045,2051,2056|0827:2033,2039,2044,2050|0828:2027,2032,2038,2049,2055,2060|0829:2026,2037,2043,2048,2054|0830:2025,2031,2036,2042,2053,2059|0831:2024,2030,2041,2047,2052,2058"},{"id":"miina_sillanpaa_day_day_of_civic_participation_unofficial","name":"Miina Sillanpään ja kansalaisvaikuttamisen päivä","en":"Miina Sillanpää Day, Day of Civic Participation","category":"unofficial","dates":"d|1001:2016-2060"},{"id":"aleksis_kivi_day_day_of_finnish_literature_unofficial","name":"Aleksis Kiven päivä, suomalaisen kirjallisuuden päivä","en":"Aleksis Kivi Day, Day of Finnish Literature","category":"unofficial","dates":"d|1010:2010-2060"},{"id":"united_nations_day_unofficial","name":"YK:n päivä","en":"United Nations Day","category":"unofficial","dates":"d|1024:2010-2060"},{"id":"finnish_swedish_heritage_day_svenska_dagen_unofficial","name":"Ruotsalaisuuden päivä, Kustaa Aadolfin päivä","en":"Finnish Swedish Heritage Day, svenska dagen","category":"unofficial","dates":"d|1106:2010-2060"},{"id":"day_of_children_s_rights_unofficial","name":"Lapsen oikeuksien päivä","en":"Day of Children's Rights","category":"unofficial","dates":"d|1120:2020-2060"},{"id":"jean_sibelius_day_day_of_finnish_music_unofficial","name":"Jean Sibeliuksen päivä, suomalaisen musiikin päivä","en":"Jean Sibelius Day, Day of Finnish Music","category":"unofficial","dates":"d|1208:2011-2060"}]} diff --git a/app/Support/Holidays/data/FJ.json b/app/Support/Holidays/data/FJ.json new file mode 100644 index 000000000..aba1d5e2e --- /dev/null +++ b/app/Support/Holidays/data/FJ.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"FJ","language":null,"years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2016-2060|0102:2017,2023,2034,2040,2045,2051|0103:2022,2028,2033,2039,2050,2056"},{"id":"prophet_mohammed_s_birthday","name":"Prophet Mohammed's Birthday","en":"Prophet Mohammed's Birthday","category":"public","dates":"y|2016:1212|2017:1202,1204|2018:1119|2019:1109,1111|2020:1031,1102|2021:1018|2022:1007|2023:0930,1002|2024:0916|2025:0906,0908|2026:0825|2027:0814,0816|2028:0803|2029:0724|2030:0713,0715|2031:0702|2032:0620-0621|2033:0609|2034:0530|2035:0520-0521|2036:0508|2037:0428|2038:0417,0419|2039:0406|2040:0325-0326|2041:0315|2042:0304|2043:0222-0223|2044:0211|2045:0130|2046:0119|2047:0108,1229-1230|2048:1218|2049:1207|2050:1126,1128|2051:1116|2052:1104|2053:1024|2054:1013|2055:1003-1004|2056:0922|2057:0911|2058:0831,0902|2059:0820|2060:0808-0809","estimated":"y|2026:0825|2027:0814|2028:0803|2029:0724|2030:0713|2031:0702|2032:0620|2033:0609|2034:0530|2035:0520|2036:0508|2037:0428|2038:0417|2039:0406|2040:0325|2041:0315|2042:0304|2043:0222|2044:0211|2045:0130|2046:0119|2047:0108,1229|2048:1218|2049:1207|2050:1126|2051:1116|2052:1104|2053:1024|2054:1013|2055:1003|2056:0922|2057:0911|2058:0831|2059:0820|2060:0808"},{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2024|0330:2018,2029,2040|0331:2051,2056|0402:2021|0403:2026,2037,2048|0404:2042,2053|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2025|0419:2019,2030,2041,2052|0420:2057|0423:2038"},{"id":"easter_saturday","name":"Easter Saturday","en":"Easter Saturday","category":"public","dates":"d|0324:2035,2046|0326:2016|0327:2027,2032|0328:2043,2054|0329:2059|0330:2024|0331:2018,2029,2040|0401:2051,2056|0403:2021|0404:2026,2037,2048|0405:2042,2053|0408:2023,2034,2045|0409:2039,2050|0411:2020|0412:2031,2036|0413:2047,2058|0415:2017,2028|0416:2022,2033,2044|0417:2049,2055,2060|0419:2025|0420:2019,2030,2041,2052|0421:2057|0424:2038"},{"id":"easter_monday","name":"Easter Monday","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2024|0402:2018,2029,2040|0403:2051,2056|0405:2021|0406:2026,2037,2048|0407:2042,2053|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2025|0422:2019,2030,2041,2052|0423:2057|0426:2038"},{"id":"girmit_day","name":"Girmit Day","en":"Girmit Day","category":"public","dates":"d|0511:2026,2037,2043,2048,2054|0512:2025,2031,2036,2042,2053,2059|0513:2024,2030,2041,2047,2052,2058|0514:2029,2035,2040,2046,2057|0515:2023,2028,2034,2045,2051,2056|0516:2033,2039,2044,2050|0517:2027,2032,2038,2049,2055,2060"},{"id":"ratu_sir_lala_sukuna_day","name":"Ratu Sir Lala Sukuna Day","en":"Ratu Sir Lala Sukuna Day","category":"public","dates":"d|0525:2029,2035,2040,2046,2057|0526:2028,2034,2045,2051,2056|0527:2033,2039,2044,2050|0528:2027,2032,2038,2049,2055,2060|0529:2023,2026,2037,2043,2048,2054|0530:2025,2031,2036,2042,2053,2059|0531:2024,2030,2041,2047,2052,2058"},{"id":"fiji_day","name":"Fiji Day","en":"Fiji Day","category":"public","dates":"d|1010:2016-2060"},{"id":"diwali","name":"Diwali","en":"Diwali","category":"public","dates":"y|2016:1031|2017:1019|2018:1107|2019:1028|2020:1114,1116|2021:1104|2022:1025|2023:1113|2024:1101|2025:1021|2026:1108-1109|2027:1029|2028:1017|2029:1105|2030:1026,1028|2031:1114|2032:1102|2033:1022,1024|2034:1110|2035:1030","estimated":"d|1017:2028|1022:2033|1026:2030|1029:2027|1030:2035|1102:2032|1105:2029|1108:2026|1110:2034|1114:2031"},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1225:2016-2060|1227:2016,2021-2022,2027,2032-2033,2038-2039,2044,2049-2050,2055,2060"},{"id":"boxing_day","name":"Boxing Day","en":"Boxing Day","category":"public","dates":"d|1226:2016-2060|1228:2020-2021,2026-2027,2032,2037-2038,2043,2048-2049,2054-2055,2060"}]} diff --git a/app/Support/Holidays/data/FK.json b/app/Support/Holidays/data/FK.json new file mode 100644 index 000000000..ce305521d --- /dev/null +++ b/app/Support/Holidays/data/FK.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"FK","language":"en_GB","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051|0103:2011,2022,2028,2033,2039,2050,2056"},{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"liberation_day","name":"Liberation Day","en":"Liberation Day","category":"public","dates":"d|0614:2010-2060|0615:2015,2020,2026,2037,2043,2048,2054|0616:2014,2025,2031,2036,2042,2053,2059"},{"id":"peat_cutting_day","name":"Peat Cutting Day","en":"Peat Cutting Day","category":"public","dates":"d|1001:2012,2018,2029,2035,2040,2046,2057|1002:2017,2023,2028,2034,2045,2051,2056|1003:2011,2016,2022,2033,2039,2044,2050|1004:2010,2021,2027,2032,2038,2049,2055,2060|1005:2015,2020,2026,2037,2043,2048,2054|1006:2014,2025,2031,2036,2042,2053,2059|1007:2013,2019,2024,2030,2041,2047,2052,2058"},{"id":"king_s_birthday","name":"HM The King's Birthday","en":"King's Birthday","category":"public","dates":"d|1114:2022-2060|1115:2027,2032,2038,2049,2055,2060|1116:2026,2037,2043,2048,2054"},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060|1228:2010-2011,2016,2021-2022,2027,2032-2033,2038-2039,2044,2049-2050,2055,2060"},{"id":"boxing_day","name":"Boxing Day","en":"Boxing Day","category":"public","dates":"d|1226:2010-2060|1228:2015,2020,2026,2037,2043,2048,2054|1229:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"christmas_holiday","name":"Christmas Holiday","en":"Christmas Holiday","category":"public","dates":"d|1227:2010-2060|1229:2014-2015,2020,2025-2026,2031,2036-2037,2042-2043,2048,2053-2054,2059"},{"id":"government_holiday_government","name":"Government Holiday","en":"Government Holiday","category":"government","dates":"d|1228:2012,2017-2018,2023,2028-2029,2034-2035,2040,2045-2046,2051,2056-2057|1229:2011,2016-2017,2022-2023,2028,2033-2034,2039,2044-2045,2050-2051,2056|1230:2010-2011,2013-2016,2019-2022,2024-2027,2030-2033,2036-2039,2041-2044,2047-2050,2052-2055,2058-2060|1231:2010,2012-2015,2018-2021,2024-2027,2029-2032,2035-2038,2040-2043,2046-2049,2052-2055,2057-2060"}]} diff --git a/app/Support/Holidays/data/FM.json b/app/Support/Holidays/data/FM.json new file mode 100644 index 000000000..90d716bdb --- /dev/null +++ b/app/Support/Holidays/data/FM.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"FM","language":"en_FM","years":[2010,2060],"subdivisions":{"KSA":"Kosrae","PNI":"Pohnpei","TRK":"Chuuk","YAP":"Yap"},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051|1231:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"kosrae_state_constitution_day","name":"Kosrae State Constitution Day","en":"Kosrae State Constitution Day","category":"public","regional":{"KSA":"d|0110:2014,2020,2025,2031,2042,2048,2053,2059|0111:2010-2060|0112:2015,2026,2032,2037,2043,2054,2060"}},{"id":"yap_day","name":"Yap Day","en":"Yap Day","category":"public","regional":{"YAP":"d|0228:2014,2025,2031,2042,2053,2059|0229:2036|0301:2010-2060|0302:2015,2020,2026,2037,2043,2048,2054"}},{"id":"women_s_day","name":"Women's Day","en":"Women's Day","category":"public","regional":{"PNI":"d|0307:2025,2031,2036,2042,2053,2059|0308:2022-2060|0309:2026,2037,2043,2048,2054"}},{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","regional":{"KSA":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038","PNI":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"}},{"id":"micronesian_culture_and_tradition_day","name":"Micronesian Culture and Tradition Day","en":"Micronesian Culture and Tradition Day","category":"public","dates":"d|0330:2012,2018,2029,2035,2040,2046,2057|0331:2011-2060|0401:2013,2019,2024,2030,2041,2047,2052,2058"},{"id":"pohnpei_cultural_day","name":"Pohnpei Cultural Day","en":"Pohnpei Cultural Day","category":"public","regional":{"PNI":"d|0330:2012,2018,2029,2035,2040,2046,2057|0331:2010-2060|0401:2013,2019,2024,2030,2041,2047,2052,2058"}},{"id":"federated_states_of_micronesia_day","name":"Federated States of Micronesia Day","en":"Federated States of Micronesia Day","category":"public","dates":"d|0509:2014,2025,2031,2036,2042,2053,2059|0510:2010-2060|0511:2015,2020,2026,2037,2043,2048,2054"},{"id":"gospel_day","name":"Gospel Day","en":"Gospel Day","category":"public","regional":{"KSA":"d|0820:2010,2021,2027,2032,2038,2049,2055,2060|0821:2010-2060|0822:2011,2016,2022,2033,2039,2044,2050"}},{"id":"kosrae_liberation_day","name":"Kosrae Liberation Day","en":"Kosrae Liberation Day","category":"public","regional":{"KSA":"d|0907:2012,2018,2029,2035,2040,2046,2057|0908:2010-2060|0909:2013,2019,2024,2030,2041,2047,2052,2058"}},{"id":"liberation_day","name":"Liberation Day","en":"Liberation Day","category":"public","regional":{"PNI":"d|0910:2010,2021,2027,2032,2038,2049,2055,2060|0911:2010-2060|0912:2011,2016,2022,2033,2039,2044,2050"}},{"id":"state_charter_day","name":"State Charter Day","en":"State Charter Day","category":"public","regional":{"TRK":"d|0925:2015,2020,2026,2037,2043,2048,2054|0926:2010-2060|0927:2010,2021,2027,2032,2038,2049,2055,2060"}},{"id":"chuuk_state_constitution_day","name":"Chuuk State Constitution Day","en":"Chuuk State Constitution Day","category":"public","regional":{"TRK":"d|0930:2011,2016,2022,2033,2039,2044,2050|1001:2010-2060|1002:2017,2023,2028,2034,2045,2051,2056"}},{"id":"united_nations_day","name":"United Nations Day","en":"United Nations Day","category":"public","dates":"d|1023:2015,2020,2026,2037,2043,2048,2054|1024:2010-2060|1025:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"independence_day","name":"Independence Day","en":"Independence Day","category":"public","dates":"d|1102:2012,2018,2029,2035,2040,2046,2057|1103:2010-2060|1104:2013,2019,2024,2030,2041,2047,2052,2058"},{"id":"self_government_day","name":"Self Government Day","en":"Self Government Day","category":"public","regional":{"KSA":"d|1102:2012,2018,2029,2035,2040,2046,2057|1103:2010-2060|1104:2013,2019,2024,2030,2041,2047,2052,2058"}},{"id":"pohnpei_constitution_day","name":"Pohnpei Constitution Day","en":"Pohnpei Constitution Day","category":"public","regional":{"PNI":"d|1107:2014,2025,2031,2036,2042,2053,2059|1108:2010-2060|1109:2015,2020,2026,2037,2043,2048,2054"}},{"id":"fsm_veterans_of_foreign_wars_day","name":"FSM Veterans of Foreign Wars Day","en":"FSM Veterans of Foreign Wars Day","category":"public","dates":"d|1110:2017,2023,2028,2034,2045,2051,2056|1111:2010-2060|1112:2012,2018,2029,2035,2040,2046,2057"},{"id":"presidents_day","name":"Presidents Day","en":"Presidents Day","category":"public","dates":"d|1122:2024,2030,2041,2047,2052,2058|1123:2021-2060|1124:2025,2031,2036,2042,2053,2059"},{"id":"thanksgiving_day","name":"Thanksgiving Day","en":"Thanksgiving Day","category":"public","regional":{"KSA":"d|1122:2012,2018,2029,2035,2040,2046,2057|1123:2017,2023,2028,2034,2045,2051,2056|1124:2011,2016,2022,2033,2039,2044,2050|1125:2021,2027,2032,2038,2049,2055,2060|1126:2015,2020,2026,2037,2043,2048,2054|1127:2014,2025,2031,2036,2042,2053,2059|1128:2013,2019,2024,2030,2041,2047,2052,2058"}},{"id":"kosrae_disability_day","name":"Kosrae Disability Day","en":"Kosrae Disability Day","category":"public","regional":{"KSA":"d|1202:2033,2039,2044,2050|1203:2024-2060|1204:2028,2034,2045,2051,2056"}},{"id":"yap_state_constitution_day","name":"Yap State Constitution Day","en":"Yap State Constitution Day","category":"public","regional":{"YAP":"d|1223:2011,2016,2022,2033,2039,2044,2050|1224:2010-2060|1225:2017,2023,2028,2034,2045,2051,2056"}},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1224:2010,2021,2027,2032,2038,2049,2055,2060|1225:2010-2060|1226:2011,2016,2022,2033,2039,2044,2050"}]} diff --git a/app/Support/Holidays/data/FO.json b/app/Support/Holidays/data/FO.json new file mode 100644 index 000000000..a9f5f0e09 --- /dev/null +++ b/app/Support/Holidays/data/FO.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"FO","language":"fo","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Nýggjársdagur","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"maundy_thursday","name":"Skírhósdagur","en":"Maundy Thursday","category":"public","dates":"d|0322:2035,2046|0324:2016|0325:2027,2032|0326:2043,2054|0327:2059|0328:2013,2024|0329:2018,2029,2040|0330:2051,2056|0401:2010,2021|0402:2015,2026,2037,2048|0403:2042,2053|0405:2012|0406:2023,2034,2045|0407:2039,2050|0409:2020|0410:2031,2036|0411:2047,2058|0413:2017,2028|0414:2022,2033,2044|0415:2049,2055,2060|0417:2014,2025|0418:2019,2030,2041,2052|0419:2057|0421:2011|0422:2038"},{"id":"good_friday","name":"Langifríggjadagur","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_sunday","name":"Páskadagur","en":"Easter Sunday","category":"public","dates":"d|0325:2035,2046|0327:2016|0328:2027,2032|0329:2043,2054|0330:2059|0331:2013,2024|0401:2018,2029,2040|0402:2051,2056|0404:2010,2021|0405:2015,2026,2037,2048|0406:2042,2053|0408:2012|0409:2023,2034,2045|0410:2039,2050|0412:2020|0413:2031,2036|0414:2047,2058|0416:2017,2028|0417:2022,2033,2044|0418:2049,2055,2060|0420:2014,2025|0421:2019,2030,2041,2052|0422:2057|0424:2011|0425:2038"},{"id":"easter_monday","name":"Annar páskadagur","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"great_prayer_day","name":"Dýri biðidagur","en":"Great Prayer Day","category":"public","dates":"d|0420:2035,2046|0422:2016|0423:2027,2032|0424:2043,2054|0425:2059|0426:2013,2024|0427:2018,2029,2040|0428:2051,2056|0430:2010,2021|0501:2015,2026,2037,2048|0502:2042,2053|0504:2012|0505:2023,2034,2045|0506:2039,2050|0508:2020|0509:2031,2036|0510:2047,2058|0512:2017,2028|0513:2022,2033,2044|0514:2049,2055,2060|0516:2014,2025|0517:2019,2030,2041,2052|0518:2057|0520:2011|0521:2038"},{"id":"ascension_day","name":"Kristi himmalsferðardagur","en":"Ascension Day","category":"public","dates":"d|0503:2035,2046|0505:2016|0506:2027,2032|0507:2043,2054|0508:2059|0509:2013,2024|0510:2018,2029,2040|0511:2051,2056|0513:2010,2021|0514:2015,2026,2037,2048|0515:2042,2053|0517:2012|0518:2023,2034,2045|0519:2039,2050|0521:2020|0522:2031,2036|0523:2047,2058|0525:2017,2028|0526:2022,2033,2044|0527:2049,2055,2060|0529:2014,2025|0530:2019,2030,2041,2052|0531:2057|0602:2011|0603:2038"},{"id":"pentecost","name":"Hvítusunnudagur","en":"Pentecost","category":"public","dates":"d|0513:2035,2046|0515:2016|0516:2027,2032|0517:2043,2054|0518:2059|0519:2013,2024|0520:2018,2029,2040|0521:2051,2056|0523:2010,2021|0524:2015,2026,2037,2048|0525:2042,2053|0527:2012|0528:2023,2034,2045|0529:2039,2050|0531:2020|0601:2031,2036|0602:2047,2058|0604:2017,2028|0605:2022,2033,2044|0606:2049,2055,2060|0608:2014,2025|0609:2019,2030,2041,2052|0610:2057|0612:2011|0613:2038"},{"id":"pentecost_monday","name":"Annar hvítusunnudagur","en":"Pentecost Monday","category":"public","dates":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038"},{"id":"saint_olaf_s_day","name":"Ólavsøkudagur","en":"Saint Olaf's Day","category":"public","dates":"d|0729:2010-2060"},{"id":"christmas_eve","name":"Jólaaftan","en":"Christmas Eve","category":"public","dates":"d|1224:2010-2060"},{"id":"christmas_day","name":"Jóladagur","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"},{"id":"second_day_of_christmas","name":"Annar jóladagur","en":"Second Day of Christmas","category":"public","dates":"d|1226:2010-2060"},{"id":"new_year_s_eve","name":"Nýggjársaftan","en":"New Year's Eve","category":"public","dates":"d|1231:2010-2060"},{"id":"national_flag_day_half_day","name":"Flaggdagur","en":"National Flag Day","category":"half_day","dates":"d|0425:2010-2060"},{"id":"constitution_day_half_day","name":"Grundlógardagur","en":"Constitution Day","category":"half_day","dates":"d|0605:2010-2060"},{"id":"saint_olaf_s_eve_half_day","name":"Ólavsøkuaftan","en":"Saint Olaf's Eve","category":"half_day","dates":"d|0728:2010-2060"}]} diff --git a/app/Support/Holidays/data/FR.json b/app/Support/Holidays/data/FR.json new file mode 100644 index 000000000..dcbfa3085 --- /dev/null +++ b/app/Support/Holidays/data/FR.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"FR","language":"fr","years":[2010,2060],"subdivisions":{"57":"Moselle","6AE":"Alsace","971":"GP","972":"MQ","973":"GY","974":"RE","976":"YT","BL":"Saint-Barthélemy","MF":"Saint-Martin","NC":"Nouvelle-Calédonie","PF":"Polynésie Française","PM":"Saint-Pierre-et-Miquelon","TF":"Terres australes françaises","WF":"Wallis-et-Futuna"},"holidays":[{"id":"new_year_s_day","name":"Jour de l'an","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"mi_careme","name":"Mi-Carême","en":"Mi-Careme","category":"public","regional":{"971":"d|0301:2035,2046|0303:2016|0304:2027,2032|0305:2043,2054|0306:2059|0307:2013,2024|0308:2018,2029,2040|0309:2051,2056|0311:2010,2021|0312:2015,2026,2037,2048|0313:2042,2053|0315:2012|0316:2023,2034,2045|0317:2039,2050|0319:2020|0320:2031,2036|0321:2047,2058|0323:2017,2028|0324:2022,2033,2044|0325:2049,2055,2060|0327:2014,2025|0328:2019,2030,2041,2052|0329:2057|0331:2011|0401:2038"}},{"id":"missionary_day","name":"Arrivée de l'Évangile","en":"Missionary Day","category":"public","regional":{"PF":"d|0305:2010-2060"}},{"id":"good_friday","name":"Vendredi saint","en":"Good Friday","category":"public","regional":{"57":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038","6AE":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038","971":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038","972":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038","PF":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"}},{"id":"easter_monday","name":"Lundi de Pâques","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"abolition_of_slavery","name":"Abolition de l'esclavage","en":"Abolition of Slavery","category":"public","regional":{"971":"d|0527:2010-2060","972":"d|0522:2010-2060","973":"d|0610:2010-2060","974":"d|1220:2010-2060","976":"d|0427:2010-2060","BL":"d|1009:2012-2060","MF":"d|0528:2012-2060"}},{"id":"feast_of_saint_peter_chanel","name":"Saint Pierre Chanel","en":"Feast of Saint Peter Chanel","category":"public","regional":{"WF":"d|0428:2010-2060"}},{"id":"labor_day","name":"Fête du Travail","en":"Labor Day","category":"public","dates":"d|0501:2010-2060"},{"id":"ascension_day","name":"Ascension","en":"Ascension Day","category":"public","dates":"d|0503:2035,2046|0505:2016|0506:2027,2032|0507:2043,2054|0508:2059|0509:2013,2024|0510:2018,2029,2040|0511:2051,2056|0513:2010,2021|0514:2015,2026,2037,2048|0515:2042,2053|0517:2012|0518:2023,2034,2045|0519:2039,2050|0521:2020|0522:2031,2036|0523:2047,2058|0525:2017,2028|0526:2022,2033,2044|0527:2049,2055,2060|0529:2014,2025|0530:2019,2030,2041,2052|0531:2057|0602:2011|0603:2038"},{"id":"victory_day","name":"Fête de la Victoire","en":"Victory Day","category":"public","dates":"d|0508:2010-2060"},{"id":"pentecost_monday","name":"Lundi de Pentecôte","en":"Pentecost Monday","category":"public","dates":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038"},{"id":"saints_peter_and_paul_day","name":"Saints Pierre et Paul","en":"Saints Peter and Paul Day","category":"public","regional":{"WF":"d|0629:2010-2060"}},{"id":"national_day","name":"Fête nationale","en":"National Day","category":"public","dates":"d|0714:2010-2060"},{"id":"victor_schoelcher_day","name":"Fête de Victor Schoelcher","en":"Victor Schoelcher Day","category":"public","regional":{"971":"d|0721:2010-2060","972":"d|0721:2010-2060","MF":"d|0721:2012-2060"}},{"id":"territory_day","name":"Fête du Territoire","en":"Territory Day","category":"public","regional":{"WF":"d|0729:2010-2060"}},{"id":"assumption_day","name":"Assomption","en":"Assumption Day","category":"public","dates":"d|0815:2010-2060"},{"id":"citizenship_day","name":"Fête de la Citoyenneté","en":"Citizenship Day","category":"public","regional":{"NC":"d|0924:2010-2060"}},{"id":"all_saints_day","name":"Toussaint","en":"All Saints' Day","category":"public","dates":"d|1101:2010-2060"},{"id":"armistice_day","name":"Armistice","en":"Armistice Day","category":"public","dates":"d|1111:2010-2060"},{"id":"matari_i","name":"Matāri'i","en":"Matāri'i","category":"public","regional":{"PF":"d|1120:2025-2060"}},{"id":"christmas_day","name":"Noël","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"},{"id":"saint_stephen_s_day","name":"Saint-Étienne","en":"Saint Stephen's Day","category":"public","regional":{"57":"d|1226:2010-2060","6AE":"d|1226:2010-2060"}}]} diff --git a/app/Support/Holidays/data/GA.json b/app/Support/Holidays/data/GA.json new file mode 100644 index 000000000..5c5de2434 --- /dev/null +++ b/app/Support/Holidays/data/GA.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"GA","language":"fr","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Jour de l'an","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"eid_al_fitr","name":"Fin du Ramadan","en":"Eid al-Fitr","category":"public","dates":"y|2010:0910|2011:0831|2012:0819|2013:0808|2014:0729|2015:0718|2016:0707|2017:0626|2018:0615|2019:0604|2020:0524|2021:0513|2022:0502|2023:0421|2024:0410|2025:0330|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304","estimated":"y|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304"},{"id":"eid_al_adha","name":"Fête du sacrifice","en":"Eid al-Adha","category":"public","dates":"y|2010:1117|2011:1107|2012:1026|2013:1015|2014:1005|2015:0924|2016:0913|2017:0902|2018:0822|2019:0811|2020:0731|2021:0720|2022:0709|2023:0628|2024:0616|2025:0606|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510","estimated":"y|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510"},{"id":"easter_sunday","name":"Pâques","en":"Easter Sunday","category":"public","dates":"d|0325:2035,2046|0327:2016|0328:2027,2032|0329:2043,2054|0330:2059|0331:2013,2024|0401:2018,2029,2040|0402:2051,2056|0404:2010,2021|0405:2015,2026,2037,2048|0406:2042,2053|0408:2012|0409:2023,2034,2045|0410:2039,2050|0412:2020|0413:2031,2036|0414:2047,2058|0416:2017,2028|0417:2022,2033,2044|0418:2049,2055,2060|0420:2014,2025|0421:2019,2030,2041,2052|0422:2057|0424:2011|0425:2038"},{"id":"easter_monday","name":"Lundi de Pâques","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"women_s_rights_day","name":"Journée des droits de la femme","en":"Women's Rights Day","category":"public","dates":"d|0417:2015-2060"},{"id":"labor_day","name":"Fête du Travail","en":"Labor Day","category":"public","dates":"d|0501:2010-2060"},{"id":"ascension_day","name":"Ascension","en":"Ascension Day","category":"public","dates":"d|0503:2035,2046|0506:2027,2032|0507:2043,2054|0508:2059|0509:2024|0510:2018,2029,2040|0511:2051,2056|0513:2021|0514:2026,2037,2048|0515:2042,2053|0518:2023,2034,2045|0519:2039,2050|0521:2020|0522:2031,2036|0523:2047,2058|0525:2028|0526:2022,2033,2044|0527:2049,2055,2060|0529:2025|0530:2019,2030,2041,2052|0531:2057|0603:2038"},{"id":"pentecost","name":"Pentecôte","en":"Pentecost","category":"public","dates":"d|0513:2035,2046|0515:2016|0516:2027,2032|0517:2043,2054|0518:2059|0519:2013,2024|0520:2018,2029,2040|0521:2051,2056|0523:2010,2021|0524:2015,2026,2037,2048|0525:2042,2053|0527:2012|0528:2023,2034,2045|0529:2039,2050|0531:2020|0601:2031,2036|0602:2047,2058|0604:2017,2028|0605:2022,2033,2044|0606:2049,2055,2060|0608:2014,2025|0609:2019,2030,2041,2052|0610:2057|0612:2011|0613:2038"},{"id":"pentecost_monday","name":"Lundi de Pentecôte","en":"Pentecost Monday","category":"public","dates":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038"},{"id":"assumption_day","name":"Assomption de Marie","en":"Assumption Day","category":"public","dates":"d|0815:2010-2060"},{"id":"independence_day","name":"Jour de l'indépendance","en":"Independence Day","category":"public","dates":"d|0816:2010-2060"},{"id":"independence_day_holiday","name":"Fête de l'indépendance","en":"Independence Day Holiday","category":"public","dates":"d|0817:2010-2060"},{"id":"liberation_day","name":"Journée de la Libération","en":"Liberation Day","category":"public","dates":"d|0830:2024-2060"},{"id":"all_saints_day","name":"Toussaint","en":"All Saints' Day","category":"public","dates":"d|1101:2010-2060"},{"id":"christmas_day","name":"Noël","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"}]} diff --git a/app/Support/Holidays/data/GB.json b/app/Support/Holidays/data/GB.json new file mode 100644 index 000000000..f925fdb99 --- /dev/null +++ b/app/Support/Holidays/data/GB.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"GB","language":"en_GB","years":[2010,2060],"subdivisions":{"ENG":"England","NIR":"Northern Ireland","SCT":"Scotland","WLS":"Wales"},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051|0103:2011,2022,2028,2033,2039,2050,2056"},{"id":"new_year_holiday","name":"New Year Holiday","en":"New Year Holiday","category":"public","regional":{"SCT":"d|0102:2010-2060|0103:2012,2017,2023,2034,2040,2045,2051|0104:2010-2011,2016,2021-2022,2027-2028,2033,2038-2039,2044,2049-2050,2055-2056"}},{"id":"saint_patrick_s_day","name":"Saint Patrick's Day","en":"Saint Patrick's Day","category":"public","regional":{"NIR":"d|0317:2010-2060|0318:2013,2019,2024,2030,2041,2047,2052,2058|0319:2012,2018,2029,2035,2040,2046,2057"}},{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_monday","name":"Easter Monday","en":"Easter Monday","category":"public","regional":{"ENG":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038","NIR":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038","WLS":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"}},{"id":"may_day","name":"May Day","en":"May Day","category":"public","dates":"d|0501:2017,2023,2028,2034,2045,2051,2056|0502:2011,2016,2022,2033,2039,2044,2050|0503:2010,2021,2027,2032,2038,2049,2055,2060|0504:2015,2026,2037,2043,2048,2054|0505:2014,2025,2031,2036,2042,2053,2059|0506:2013,2019,2024,2030,2041,2047,2052,2058|0507:2012,2018,2029,2035,2040,2046,2057|0508:2020"},{"id":"spring_bank_holiday","name":"Spring Bank Holiday","en":"Spring Bank Holiday","category":"public","dates":"d|0525:2015,2020,2026,2037,2043,2048,2054|0526:2014,2025,2031,2036,2042,2053,2059|0527:2013,2019,2024,2030,2041,2047,2052,2058|0528:2018,2029,2035,2040,2046,2057|0529:2017,2023,2028,2034,2045,2051,2056|0530:2011,2016,2033,2039,2044,2050|0531:2010,2021,2027,2032,2038,2049,2055,2060|0602:2022|0604:2012"},{"id":"scotland_s_participation_in_the_fifa_world_cup_f","name":"Scotland's participation in the FIFA World Cup final","en":"Scotland's participation in the FIFA World Cup final","category":"public","regional":{"SCT":"d|0615:2026"}},{"id":"battle_of_the_boyne","name":"Battle of the Boyne","en":"Battle of the Boyne","category":"public","regional":{"NIR":"d|0712:2010-2060|0713:2015,2020,2026,2037,2043,2048,2054|0714:2014,2025,2031,2036,2042,2053,2059"}},{"id":"summer_bank_holiday","name":"Summer Bank Holiday","en":"Summer Bank Holiday","category":"public","regional":{"SCT":"d|0801:2011,2016,2022,2033,2039,2044,2050|0802:2010,2021,2027,2032,2038,2049,2055,2060|0803:2015,2020,2026,2037,2043,2048,2054|0804:2014,2025,2031,2036,2042,2053,2059|0805:2013,2019,2024,2030,2041,2047,2052,2058|0806:2012,2018,2029,2035,2040,2046,2057|0807:2017,2023,2028,2034,2045,2051,2056"}},{"id":"late_summer_bank_holiday","name":"Late Summer Bank Holiday","en":"Late Summer Bank Holiday","category":"public","regional":{"ENG":"d|0825:2014,2025,2031,2036,2042,2053,2059|0826:2013,2019,2024,2030,2041,2047,2052,2058|0827:2012,2018,2029,2035,2040,2046,2057|0828:2017,2023,2028,2034,2045,2051,2056|0829:2011,2016,2022,2033,2039,2044,2050|0830:2010,2021,2027,2032,2038,2049,2055,2060|0831:2015,2020,2026,2037,2043,2048,2054","NIR":"d|0825:2014,2025,2031,2036,2042,2053,2059|0826:2013,2019,2024,2030,2041,2047,2052,2058|0827:2012,2018,2029,2035,2040,2046,2057|0828:2017,2023,2028,2034,2045,2051,2056|0829:2011,2016,2022,2033,2039,2044,2050|0830:2010,2021,2027,2032,2038,2049,2055,2060|0831:2015,2020,2026,2037,2043,2048,2054","WLS":"d|0825:2014,2025,2031,2036,2042,2053,2059|0826:2013,2019,2024,2030,2041,2047,2052,2058|0827:2012,2018,2029,2035,2040,2046,2057|0828:2017,2023,2028,2034,2045,2051,2056|0829:2011,2016,2022,2033,2039,2044,2050|0830:2010,2021,2027,2032,2038,2049,2055,2060|0831:2015,2020,2026,2037,2043,2048,2054"}},{"id":"saint_andrew_s_day","name":"Saint Andrew's Day","en":"Saint Andrew's Day","category":"public","regional":{"SCT":"d|1130:2010-2060|1201:2014,2025,2031,2036,2042,2053,2059|1202:2013,2019,2024,2030,2041,2047,2052,2058"}},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060|1227:2010-2011,2016,2021-2022,2027,2032-2033,2038-2039,2044,2049-2050,2055,2060"},{"id":"boxing_day","name":"Boxing Day","en":"Boxing Day","category":"public","dates":"d|1226:2010-2060|1228:2010,2015,2020-2021,2026-2027,2032,2037-2038,2043,2048-2049,2054-2055,2060"}]} diff --git a/app/Support/Holidays/data/GD.json b/app/Support/Holidays/data/GD.json new file mode 100644 index 000000000..e02d3ebb1 --- /dev/null +++ b/app/Support/Holidays/data/GD.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"GD","language":"en_GD","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051"},{"id":"independence_day","name":"Independence Day","en":"Independence Day","category":"public","dates":"d|0207:2010-2060|0208:2010,2016,2021,2027,2038,2044,2049,2055"},{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_monday","name":"Easter Monday","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"labor_day","name":"Labour Day","en":"Labor Day","category":"public","dates":"d|0501:2010-2060|0502:2011,2016,2022,2033,2039,2044,2050"},{"id":"pentecost_monday","name":"Whit Monday","en":"Pentecost Monday","category":"public","dates":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038"},{"id":"corpus_christi","name":"Corpus Christi","en":"Corpus Christi","category":"public","dates":"d|0524:2035,2046|0526:2016|0527:2027,2032|0528:2043,2054|0529:2059|0530:2013,2024|0531:2018,2029,2040|0601:2051,2056|0603:2010,2021|0604:2015,2026,2037,2048|0605:2042,2053|0607:2012|0608:2023,2034,2045|0609:2039,2050|0611:2020|0612:2031,2036|0613:2047,2058|0615:2017,2028|0616:2022,2033,2044|0617:2049,2055,2060|0619:2014,2025|0620:2019,2030,2041,2052|0621:2057|0623:2011|0624:2038"},{"id":"emancipation_day","name":"Emancipation Day","en":"Emancipation Day","category":"public","dates":"d|0801:2011,2016,2022,2025-2060|0802:2010,2021,2027,2032,2038,2049,2055,2060|0803:2015,2020|0804:2014|0805:2013,2019,2024|0806:2012,2018|0807:2017,2023"},{"id":"carnival_monday","name":"Carnival Monday","en":"Carnival Monday","category":"public","dates":"d|0808:2011,2016,2022,2033,2039,2044,2050|0809:2010,2021,2027,2032,2038,2049,2055,2060|0810:2015,2020,2026,2037,2043,2048,2054|0811:2014,2025,2031,2036,2042,2053,2059|0812:2013,2019,2024,2030,2041,2047,2052,2058|0813:2012,2018,2029,2035,2040,2046,2057|0814:2017,2023,2028,2034,2045,2051,2056"},{"id":"carnival_tuesday","name":"Carnival Tuesday","en":"Carnival Tuesday","category":"public","dates":"d|0809:2011,2016,2022,2033,2039,2044,2050|0810:2010,2021,2027,2032,2038,2049,2055,2060|0811:2015,2020,2026,2037,2043,2048,2054|0812:2014,2025,2031,2036,2042,2053,2059|0813:2013,2019,2024,2030,2041,2047,2052,2058|0814:2012,2018,2029,2035,2040,2046,2057|0815:2017,2023,2028,2034,2045,2051,2056"},{"id":"national_heroes_day","name":"National Heroes' Day","en":"National Heroes' Day","category":"public","dates":"d|1019:2023-2060|1020:2025,2031,2036,2042,2053,2059"},{"id":"thanksgiving_day","name":"Thanksgiving Day","en":"Thanksgiving Day","category":"public","dates":"d|1025:2010-2060|1026:2015,2020,2026,2037,2043,2048,2054"},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060|1226:2011,2016,2022,2033,2039,2044,2050"},{"id":"boxing_day","name":"Boxing Day","en":"Boxing Day","category":"public","dates":"d|1226:2010-2060|1227:2010,2021,2027,2032,2038,2049,2055,2060"}]} diff --git a/app/Support/Holidays/data/GE.json b/app/Support/Holidays/data/GE.json new file mode 100644 index 000000000..99b484c27 --- /dev/null +++ b/app/Support/Holidays/data/GE.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"GE","language":"ka","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"ახალი წელი","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2010-2060"},{"id":"christmas_day","name":"ქრისტეშობა","en":"Christmas Day","category":"public","dates":"d|0107:2010-2060"},{"id":"epiphany","name":"ნათლისღება","en":"Epiphany","category":"public","dates":"d|0119:2010-2060"},{"id":"mother_s_day","name":"დედის დღე","en":"Mother's Day","category":"public","dates":"d|0303:2010-2060"},{"id":"international_women_s_day","name":"ქალთა საერთაშორისო დღე","en":"International Women's Day","category":"public","dates":"d|0308:2010-2060"},{"id":"good_friday","name":"წითელი პარასკევი","en":"Good Friday","category":"public","dates":"d|0402:2010|0403:2037,2048|0406:2018,2029|0407:2034,2045,2056|0410:2015,2026|0411:2031,2042,2053|0412:2058|0413:2012|0414:2017,2023,2028|0415:2039,2050|0416:2055|0417:2020|0418:2014,2025,2036|0419:2041,2047,2052|0422:2011,2022,2033,2044|0423:2038,2049,2060|0426:2019,2030|0427:2035,2046,2057|0429:2016|0430:2021,2027,2032|0501:2043,2054|0502:2059|0503:2013,2024|0504:2040|0505:2051"},{"id":"holy_saturday","name":"დიდი შაბათი","en":"Holy Saturday","category":"public","dates":"d|0403:2010|0404:2037,2048|0407:2018,2029|0408:2034,2045,2056|0411:2015,2026|0412:2031,2042,2053|0413:2058|0414:2012|0415:2017,2023,2028|0416:2039,2050|0417:2055|0418:2020|0419:2014,2025,2036|0420:2041,2047,2052|0423:2011,2022,2033,2044|0424:2038,2049,2060|0427:2019,2030|0428:2035,2046,2057|0430:2016|0501:2021,2027,2032|0502:2043,2054|0503:2059|0504:2013,2024|0505:2040|0506:2051"},{"id":"easter_sunday","name":"აღდგომა","en":"Easter Sunday","category":"public","dates":"d|0404:2010|0405:2037,2048|0408:2018,2029|0409:2034,2045,2056|0412:2015,2026|0413:2031,2042,2053|0414:2058|0415:2012|0416:2017,2023,2028|0417:2039,2050|0418:2055|0419:2020|0420:2014,2025,2036|0421:2041,2047,2052|0424:2011,2022,2033,2044|0425:2038,2049,2060|0428:2019,2030|0429:2035,2046,2057|0501:2016|0502:2021,2027,2032|0503:2043,2054|0504:2059|0505:2013,2024|0506:2040|0507:2051"},{"id":"easter_monday","name":"შავი ორშაბათი","en":"Easter Monday","category":"public","dates":"d|0405:2010|0406:2037,2048|0409:2018,2029|0410:2034,2045,2056|0413:2015,2026|0414:2031,2042,2053|0415:2058|0416:2012|0417:2017,2023,2028|0418:2039,2050|0419:2055|0420:2020|0421:2014,2025,2036|0422:2041,2047,2052|0425:2011,2022,2033,2044|0426:2038,2049,2060|0429:2019,2030|0430:2035,2046,2057|0502:2016|0503:2021,2027,2032|0504:2043,2054|0505:2059|0506:2013,2024|0507:2040|0508:2051"},{"id":"national_unity_day","name":"ეროვნული ერთიანობის დღე","en":"National Unity Day","category":"public","dates":"d|0409:2010-2060"},{"id":"day_of_victory_over_fascism","name":"ფაშიზმზე გამარჯვების დღე","en":"Day of Victory over Fascism","category":"public","dates":"d|0509:2010-2060"},{"id":"saint_andrew_s_day","name":"წმინდა ანდრია პირველწოდებულის დღე","en":"Saint Andrew's Day","category":"public","dates":"d|0512:2010-2060"},{"id":"day_of_family_sanctity_and_respect_for_parents","name":"ოჯახის სიწმინდისა და მშობლების პატივისცემის დღე","en":"Day of Family Sanctity and Respect for Parents","category":"public","dates":"d|0517:2025-2060"},{"id":"independence_day","name":"დამოუკიდებლობის დღე","en":"Independence Day","category":"public","dates":"d|0526:2010-2060"},{"id":"dormition_of_the_mother_of_god","name":"მარიამობა","en":"Dormition of the Mother of God","category":"public","dates":"d|0828:2010-2060"},{"id":"holiday_of_svetitskhovloba_robe_of_jesus","name":"მცხეთობის","en":"Holiday of Svetitskhovloba, Robe of Jesus","category":"public","dates":"d|1014:2010-2060"},{"id":"saint_george_s_day","name":"გიორგობა","en":"Saint George's Day","category":"public","dates":"d|1123:2010-2060"}]} diff --git a/app/Support/Holidays/data/GF.json b/app/Support/Holidays/data/GF.json new file mode 100644 index 000000000..6fb49a7cc --- /dev/null +++ b/app/Support/Holidays/data/GF.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"GF","language":"fr","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Jour de l'an","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"easter_monday","name":"Lundi de Pâques","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"labor_day","name":"Fête du Travail","en":"Labor Day","category":"public","dates":"d|0501:2010-2060"},{"id":"ascension_day","name":"Ascension","en":"Ascension Day","category":"public","dates":"d|0503:2035,2046|0505:2016|0506:2027,2032|0507:2043,2054|0508:2059|0509:2013,2024|0510:2018,2029,2040|0511:2051,2056|0513:2010,2021|0514:2015,2026,2037,2048|0515:2042,2053|0517:2012|0518:2023,2034,2045|0519:2039,2050|0521:2020|0522:2031,2036|0523:2047,2058|0525:2017,2028|0526:2022,2033,2044|0527:2049,2055,2060|0529:2014,2025|0530:2019,2030,2041,2052|0531:2057|0602:2011|0603:2038"},{"id":"victory_day","name":"Fête de la Victoire","en":"Victory Day","category":"public","dates":"d|0508:2010-2060"},{"id":"pentecost_monday","name":"Lundi de Pentecôte","en":"Pentecost Monday","category":"public","dates":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038"},{"id":"abolition_of_slavery","name":"Abolition de l'esclavage","en":"Abolition of Slavery","category":"public","dates":"d|0610:2010-2060"},{"id":"national_day","name":"Fête nationale","en":"National Day","category":"public","dates":"d|0714:2010-2060"},{"id":"assumption_day","name":"Assomption","en":"Assumption Day","category":"public","dates":"d|0815:2010-2060"},{"id":"all_saints_day","name":"Toussaint","en":"All Saints' Day","category":"public","dates":"d|1101:2010-2060"},{"id":"armistice_day","name":"Armistice","en":"Armistice Day","category":"public","dates":"d|1111:2010-2060"},{"id":"christmas_day","name":"Noël","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"}]} diff --git a/app/Support/Holidays/data/GG.json b/app/Support/Holidays/data/GG.json new file mode 100644 index 000000000..4de74022b --- /dev/null +++ b/app/Support/Holidays/data/GG.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"GG","language":null,"years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051|0103:2011,2022,2028,2033,2039,2050,2056"},{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_monday","name":"Easter Monday","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"may_day_bank_holiday","name":"May Day Bank Holiday","en":"May Day Bank Holiday","category":"public","dates":"d|0501:2017,2023,2028,2034,2045,2051,2056|0502:2011,2016,2022,2033,2039,2044,2050|0503:2010,2021,2027,2032,2038,2049,2055,2060|0504:2015,2026,2037,2043,2048,2054|0505:2014,2025,2031,2036,2042,2053,2059|0506:2013,2019,2024,2030,2041,2047,2052,2058|0507:2012,2018,2029,2035,2040,2046,2057|0508:2020"},{"id":"liberation_day","name":"Liberation Day","en":"Liberation Day","category":"public","dates":"d|0509:2011-2060|0510:2010"},{"id":"spring_bank_holiday","name":"Spring Bank Holiday","en":"Spring Bank Holiday","category":"public","dates":"d|0525:2015,2020,2026,2037,2043,2048,2054|0526:2014,2025,2031,2036,2042,2053,2059|0527:2013,2019,2024,2030,2041,2047,2052,2058|0528:2018,2029,2035,2040,2046,2057|0529:2017,2023,2028,2034,2045,2051,2056|0530:2011,2016,2033,2039,2044,2050|0531:2010,2021,2027,2032,2038,2049,2055,2060|0602:2022|0604:2012"},{"id":"summer_bank_holiday","name":"Summer Bank Holiday","en":"Summer Bank Holiday","category":"public","dates":"d|0825:2014,2025,2031,2036,2042,2053,2059|0826:2013,2019,2024,2030,2041,2047,2052,2058|0827:2012,2018,2029,2035,2040,2046,2057|0828:2017,2023,2028,2034,2045,2051,2056|0829:2011,2016,2022,2033,2039,2044,2050|0830:2010,2021,2027,2032,2038,2049,2055,2060|0831:2015,2020,2026,2037,2043,2048,2054"},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060|1227:2010-2011,2016,2021-2022,2027,2032-2033,2038-2039,2044,2049-2050,2055,2060"},{"id":"boxing_day","name":"Boxing Day","en":"Boxing Day","category":"public","dates":"d|1226:2010-2060|1228:2010,2015,2020-2021,2026-2027,2032,2037-2038,2043,2048-2049,2054-2055,2060"}]} diff --git a/app/Support/Holidays/data/GH.json b/app/Support/Holidays/data/GH.json new file mode 100644 index 000000000..7100229a5 --- /dev/null +++ b/app/Support/Holidays/data/GH.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"GH","language":null,"years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"eid_ul_fitr","name":"Eid-ul-Fitr","en":"Eid-ul-Fitr","category":"public","dates":"y|2010:0910|2011:0830|2012:0819|2013:0808|2014:0728|2015:0717|2016:0706|2017:0626|2018:0615|2019:0605|2020:0524|2021:0513|2022:0503|2023:0422|2024:0411|2025:0330|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304","estimated":"y|2010:0910|2011:0830|2012:0819|2013:0808|2014:0728|2015:0717|2016:0706|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304"},{"id":"public_holiday","name":"Public Holiday","en":"Public Holiday","category":"public","dates":"y|2017:0703|2018:0702|2019:0805,0812,0923|2020:0525,1228|2021:0308,0503,1227-1228|2022:0103,0307,0502,0711,1227|2023:0102,0109,0424|2024:0108,0617,0805,0923|2025:0331-0401,0704,0922|2026:0109,0703"},{"id":"shaqq_day","name":"Shaqq Day","en":"Shaqq Day","category":"public","dates":"y|2026:0321|2027:0310|2028:0227|2029:0215|2030:0205|2031:0125|2032:0115|2033:0103,1224|2034:1213|2035:1202|2036:1120|2037:1109|2038:1030|2039:1020|2040:1008|2041:0927|2042:0916|2043:0905|2044:0825|2045:0815|2046:0804|2047:0725|2048:0713|2049:0702|2050:0621|2051:0611|2052:0530|2053:0520|2054:0510|2055:0429|2056:0417|2057:0406|2058:0326|2059:0316|2060:0305","estimated":"y|2027:0310|2028:0227|2029:0215|2030:0205|2031:0125|2032:0115|2033:0103,1224|2034:1213|2035:1202|2036:1120|2037:1109|2038:1030|2039:1020|2040:1008|2041:0927|2042:0916|2043:0905|2044:0825|2045:0815|2046:0804|2047:0725|2048:0713|2049:0702|2050:0621|2051:0611|2052:0530|2053:0520|2054:0510|2055:0429|2056:0417|2057:0406|2058:0326|2059:0316|2060:0305"},{"id":"eid_ul_adha","name":"Eid-ul-Adha","en":"Eid-ul-Adha","category":"public","dates":"y|2010:1116|2011:1106|2012:1026|2013:1015|2014:1004|2015:0923|2016:0911|2017:0901|2018:0821|2019:0811|2020:0731|2021:0720|2022:0709|2023:0628|2024:0616|2025:0606|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510","estimated":"y|2010:1116|2011:1106|2012:1026|2013:1015|2014:1004|2015:0923|2016:0911|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510"},{"id":"constitution_day","name":"Constitution Day","en":"Constitution Day","category":"public","dates":"d|0107:2019-2060"},{"id":"independence_day","name":"Independence Day","en":"Independence Day","category":"public","dates":"d|0306:2010-2060"},{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_monday","name":"Easter Monday","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"labour_day","name":"Labour Day","en":"Labour Day","category":"public","dates":"d|0501:2026-2060"},{"id":"republic_day","name":"Republic Day","en":"Republic Day","category":"public","dates":"d|0701:2010-2018,2025-2060"},{"id":"founder_s_day","name":"Founder's Day","en":"Founder's Day","category":"public","dates":"d|0921:2010-2018,2025-2060"},{"id":"farmers_day","name":"Farmers' Day","en":"Farmers' Day","category":"public","dates":"d|1201:2017,2023,2028,2034,2045,2051,2056|1202:2011,2016,2022,2033,2039,2044,2050|1203:2010,2021,2027,2032,2038,2049,2055,2060|1204:2015,2026,2037,2043,2048,2054|1205:2014,2025,2031,2036,2042,2053,2059|1206:2013,2019,2024,2030,2041,2047,2052,2058|1207:2012,2018,2020,2029,2035,2040,2046,2057"},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"},{"id":"boxing_day","name":"Boxing Day","en":"Boxing Day","category":"public","dates":"d|1226:2010-2060"}]} diff --git a/app/Support/Holidays/data/GI.json b/app/Support/Holidays/data/GI.json new file mode 100644 index 000000000..3fee4b932 --- /dev/null +++ b/app/Support/Holidays/data/GI.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"GI","language":"en_GB","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051|0103:2011,2022,2028,2033,2039,2050,2056"},{"id":"winter_midterm_bank_holiday","name":"Winter Midterm Bank Holiday","en":"Winter Midterm Bank Holiday","category":"public","dates":"d|0212:2024|0215:2027,2038,2044,2049,2055|0216:2026,2032,2037,2043,2054,2060|0217:2025,2031,2042,2048,2053,2059|0218:2030,2036,2041,2047,2058|0219:2029,2035,2046,2052,2057|0220:2023,2034,2040,2045,2051|0221:2028,2033,2039,2050,2056"},{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_monday","name":"Easter Monday","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"workers_memorial_day","name":"Workers' Memorial Day","en":"Workers' Memorial Day","category":"public","dates":"d|0426:2013|0427:2015|0428:2014,2016-2060|0429:2019,2024,2030,2041,2047,2052,2058|0430:2018,2029,2035,2040,2046,2057"},{"id":"may_day","name":"May Day","en":"May Day","category":"public","dates":"d|0501:2010-2060|0502:2011,2016,2022,2033,2039,2044,2050|0503:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"spring_bank_holiday","name":"Spring Bank Holiday","en":"Spring Bank Holiday","category":"public","dates":"d|0525:2015,2020,2026,2037,2043,2048,2054|0526:2014,2025,2031,2036,2042,2053,2059|0527:2013,2019,2024,2030,2041,2047,2052,2058|0528:2018,2029,2035,2040,2046,2057|0529:2017,2023,2028,2034,2045,2051,2056|0530:2011,2016,2033,2039,2044,2050|0531:2010,2021,2027,2032,2038,2049,2055,2060|0602:2022|0604:2012"},{"id":"king_s_birthday","name":"King's Birthday","en":"King's Birthday","category":"public","dates":"d|0610:2030,2041,2047,2052,2058|0611:2029,2035,2040,2046,2057|0612:2028,2034,2045,2051,2056|0613:2033,2039,2044,2050|0614:2027,2032,2038,2049,2055,2060|0615:2026,2037,2043,2048,2054|0616:2025,2031,2036,2042,2053,2059|0617:2024|0619:2023"},{"id":"late_summer_bank_holiday","name":"Late Summer Bank Holiday","en":"Late Summer Bank Holiday","category":"public","dates":"d|0825:2014,2025,2031,2036,2042,2053,2059|0826:2013,2019,2024,2030,2041,2047,2052,2058|0827:2012,2018,2029,2035,2040,2046,2057|0828:2017,2023,2028,2034,2045,2051,2056|0829:2011,2016,2022,2033,2039,2044,2050|0830:2010,2021,2027,2032,2038,2049,2055,2060|0831:2015,2020,2026,2037,2043,2048,2054"},{"id":"gibraltar_national_day","name":"Gibraltar National Day","en":"Gibraltar National Day","category":"public","dates":"d|0904:2017|0905:2016|0910:2010-2015,2018-2060|0911:2023,2028,2034,2045,2051,2056|0912:2011,2022,2033,2039,2044,2050"},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060|1227:2010-2011,2016,2021-2022,2027,2032-2033,2038-2039,2044,2049-2050,2055,2060"},{"id":"boxing_day","name":"Boxing Day","en":"Boxing Day","category":"public","dates":"d|1226:2010-2060|1228:2010,2015,2020-2021,2026-2027,2032,2037-2038,2043,2048-2049,2054-2055,2060"}]} diff --git a/app/Support/Holidays/data/GL.json b/app/Support/Holidays/data/GL.json new file mode 100644 index 000000000..4f05b91e5 --- /dev/null +++ b/app/Support/Holidays/data/GL.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"GL","language":"kl","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Ukiortaaq","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"maundy_thursday","name":"Sisamanngortoq illernartoq","en":"Maundy Thursday","category":"public","dates":"d|0322:2035,2046|0324:2016|0325:2027,2032|0326:2043,2054|0327:2059|0328:2013,2024|0329:2018,2029,2040|0330:2051,2056|0401:2010,2021|0402:2015,2026,2037,2048|0403:2042,2053|0405:2012|0406:2023,2034,2045|0407:2039,2050|0409:2020|0410:2031,2036|0411:2047,2058|0413:2017,2028|0414:2022,2033,2044|0415:2049,2055,2060|0417:2014,2025|0418:2019,2030,2041,2052|0419:2057|0421:2011|0422:2038"},{"id":"good_friday","name":"Tallimanngorneq tannaartoq","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_sunday","name":"Poorskip ullua","en":"Easter Sunday","category":"public","dates":"d|0325:2035,2046|0327:2016|0328:2027,2032|0329:2043,2054|0330:2059|0331:2013,2024|0401:2018,2029,2040|0402:2051,2056|0404:2010,2021|0405:2015,2026,2037,2048|0406:2042,2053|0408:2012|0409:2023,2034,2045|0410:2039,2050|0412:2020|0413:2031,2036|0414:2047,2058|0416:2017,2028|0417:2022,2033,2044|0418:2049,2055,2060|0420:2014,2025|0421:2019,2030,2041,2052|0422:2057|0424:2011|0425:2038"},{"id":"easter_monday","name":"Poorskip-aappaa","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"great_prayer_day","name":"Ulloq qinuffiusoq","en":"Great Prayer Day","category":"public","dates":"d|0420:2035,2046|0422:2016|0423:2027,2032|0424:2043,2054|0425:2059|0426:2013,2024|0427:2018,2029,2040|0428:2051,2056|0430:2010,2021|0501:2015,2026,2037,2048|0502:2042,2053|0504:2012|0505:2023,2034,2045|0506:2039,2050|0508:2020|0509:2031,2036|0510:2047,2058|0512:2017,2028|0513:2022,2033,2044|0514:2049,2055,2060|0516:2014,2025|0517:2019,2030,2041,2052|0518:2057|0520:2011|0521:2038"},{"id":"ascension_day","name":"Qilaliarfik","en":"Ascension Day","category":"public","dates":"d|0503:2035,2046|0505:2016|0506:2027,2032|0507:2043,2054|0508:2059|0509:2013,2024|0510:2018,2029,2040|0511:2051,2056|0513:2010,2021|0514:2015,2026,2037,2048|0515:2042,2053|0517:2012|0518:2023,2034,2045|0519:2039,2050|0521:2020|0522:2031,2036|0523:2047,2058|0525:2017,2028|0526:2022,2033,2044|0527:2049,2055,2060|0529:2014,2025|0530:2019,2030,2041,2052|0531:2057|0602:2011|0603:2038"},{"id":"pentecost","name":"Piinsip ullua","en":"Pentecost","category":"public","dates":"d|0513:2035,2046|0515:2016|0516:2027,2032|0517:2043,2054|0518:2059|0519:2013,2024|0520:2018,2029,2040|0521:2051,2056|0523:2010,2021|0524:2015,2026,2037,2048|0525:2042,2053|0527:2012|0528:2023,2034,2045|0529:2039,2050|0531:2020|0601:2031,2036|0602:2047,2058|0604:2017,2028|0605:2022,2033,2044|0606:2049,2055,2060|0608:2014,2025|0609:2019,2030,2041,2052|0610:2057|0612:2011|0613:2038"},{"id":"pentecost_monday","name":"Piinsip-aappaa","en":"Pentecost Monday","category":"public","dates":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038"},{"id":"christmas_day","name":"Juullip ullua","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"},{"id":"second_day_of_christmas","name":"Juullip-aappaa","en":"Second Day of Christmas","category":"public","dates":"d|1226:2010-2060"},{"id":"epiphany_optional","name":"Kunngit pingasut ulluat","en":"Epiphany","category":"optional","dates":"d|0106:2010-2060"},{"id":"international_workers_day_optional","name":"Sulisartut ulluat","en":"International Workers' Day","category":"optional","dates":"d|0501:2010-2060"},{"id":"national_day_optional","name":"Ullortuneq","en":"National Day","category":"optional","dates":"d|0621:2010-2060"},{"id":"christmas_eve_optional","name":"Juulliaraq","en":"Christmas Eve","category":"optional","dates":"d|1224:2010-2060"},{"id":"new_year_s_eve_optional","name":"Ukiortaami","en":"New Year's Eve","category":"optional","dates":"d|1231:2010-2060"}]} diff --git a/app/Support/Holidays/data/GM.json b/app/Support/Holidays/data/GM.json new file mode 100644 index 000000000..b5eac2970 --- /dev/null +++ b/app/Support/Holidays/data/GM.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"GM","language":"en_GM","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2023,2034,2040,2045,2051|0103:2022,2028,2033,2039,2050,2056"},{"id":"ashura","name":"Yawmul Ashura","en":"Ashura","category":"public","dates":"y|2010:1216|2011:1205|2012:1124|2013:1113|2014:1103|2015:1024|2016:1012|2017:1001|2018:0920|2019:0910|2020:0829|2021:0819|2022:0808|2023:0728|2024:0716|2025:0705,0707|2026:0625|2027:0615|2028:0603,0605|2029:0523|2030:0512-0513|2031:0502|2032:0420|2033:0410-0411|2034:0330|2035:0320|2036:0308,0310|2037:0225|2038:0214-0215|2039:0204|2040:0124|2041:0113-0114|2042:0102,1223|2043:1212,1214|2044:1130|2045:1119-1120|2046:1109|2047:1029|2048:1018-1019|2049:1007|2050:0926|2051:0915|2052:0904|2053:0824-0825|2054:0814|2055:0803|2056:0723-0724|2057:0712|2058:0701|2059:0620|2060:0609","estimated":"y|2010:1216|2011:1205|2012:1124|2013:1113|2014:1103|2026:0625|2027:0615|2028:0603|2029:0523|2030:0512|2031:0502|2032:0420|2033:0410|2034:0330|2035:0320|2036:0308|2037:0225|2038:0214|2039:0204|2040:0124|2041:0113|2042:0102,1223|2043:1212|2044:1130|2045:1119|2046:1109|2047:1029|2048:1018|2049:1007|2050:0926|2051:0915|2052:0904|2053:0824|2054:0814|2055:0803|2056:0723|2057:0712|2058:0701|2059:0620|2060:0609"},{"id":"eid_al_fitr","name":"Koriteh","en":"Eid al-Fitr","category":"public","dates":"y|2010:0910|2011:0830|2012:0819|2013:0808|2014:0728|2015:0718|2016:0707|2017:0626|2018:0615|2019:0604|2020:0523|2021:0513-0514|2022:0502-0503|2023:0421-0422,0424|2024:0410-0411|2025:0330-0331|2026:0320-0321,0323|2027:0309-0310|2028:0226-0228|2029:0214-0215|2030:0204-0205|2031:0124-0125,0127|2032:0114-0115|2033:0102-0103,1223-1224,1226|2034:1212-1213|2035:1201-1203|2036:1119-1120|2037:1108-1109|2038:1029-1030,1101|2039:1019-1020|2040:1007-1008|2041:0926-0927|2042:0915-0916|2043:0904-0905,0907|2044:0824-0825|2045:0814-0815|2046:0803-0804,0806|2047:0724-0725|2048:0712-0713|2049:0701-0702|2050:0620-0621|2051:0610-0612|2052:0529-0530|2053:0519-0520|2054:0509-0511|2055:0428-0429|2056:0416-0417|2057:0405-0406|2058:0325-0326|2059:0315-0317|2060:0304-0305","estimated":"y|2010:0910|2011:0830|2012:0819|2013:0808|2014:0728|2026:0320-0321|2027:0309-0310|2028:0226-0227|2029:0214-0215|2030:0204-0205|2031:0124-0125|2032:0114-0115|2033:0102-0103,1223-1224|2034:1212-1213|2035:1201-1202|2036:1119-1120|2037:1108-1109|2038:1029-1030|2039:1019-1020|2040:1007-1008|2041:0926-0927|2042:0915-0916|2043:0904-0905|2044:0824-0825|2045:0814-0815|2046:0803-0804|2047:0724-0725|2048:0712-0713|2049:0701-0702|2050:0620-0621|2051:0610-0611|2052:0529-0530|2053:0519-0520|2054:0509-0510|2055:0428-0429|2056:0416-0417|2057:0405-0406|2058:0325-0326|2059:0315-0316|2060:0304-0305"},{"id":"prophet_s_birthday","name":"Mawlid Nabi","en":"Prophet's Birthday","category":"public","dates":"y|2010:0226|2011:0215|2012:0204|2013:0124|2014:0113|2015:0103,1223|2016:1212|2017:1201|2018:1121|2019:1109|2020:1029|2021:1019|2022:1008,1010|2023:0928|2024:0916|2025:0904|2026:0825|2027:0814,0816|2028:0803|2029:0724|2030:0713,0715|2031:0702|2032:0620-0621|2033:0609|2034:0530|2035:0520-0521|2036:0508|2037:0428|2038:0417,0419|2039:0406|2040:0325-0326|2041:0315|2042:0304|2043:0222-0223|2044:0211|2045:0130|2046:0119|2047:0108,1229-1230|2048:1218|2049:1207|2050:1126,1128|2051:1116|2052:1104|2053:1024|2054:1013|2055:1003-1004|2056:0922|2057:0911|2058:0831,0902|2059:0820|2060:0808-0809","estimated":"y|2010:0226|2011:0215|2012:0204|2013:0124|2014:0113|2015:0103,1223|2025:0904|2026:0825|2027:0814|2028:0803|2029:0724|2030:0713|2031:0702|2032:0620|2033:0609|2034:0530|2035:0520|2036:0508|2037:0428|2038:0417|2039:0406|2040:0325|2041:0315|2042:0304|2043:0222|2044:0211|2045:0130|2046:0119|2047:0108,1229|2048:1218|2049:1207|2050:1126|2051:1116|2052:1104|2053:1024|2054:1013|2055:1003|2056:0922|2057:0911|2058:0831|2059:0820|2060:0808"},{"id":"eid_al_adha","name":"Tobaski","en":"Eid al-Adha","category":"public","dates":"y|2010:1116|2011:1106|2012:1026|2013:1015|2014:1004|2015:0924|2016:0913|2017:0902|2018:0822|2019:0811|2020:0731|2021:0720-0721|2022:0709-0711|2023:0628-0629|2024:0616-0617|2025:0606-0607,0609|2026:0527-0528|2027:0516-0517|2028:0505-0506,0508|2029:0424-0425|2030:0413-0415|2031:0402-0403|2032:0322-0323|2033:0311-0312,0314|2034:0301-0302|2035:0218-0219|2036:0207-0208|2037:0126-0127|2038:0116-0118|2039:0105-0106,1226-1227|2040:1214-1215,1217|2041:1204-1205|2042:1123-1124|2043:1112-1113|2044:1031-1101|2045:1021-1023|2046:1010-1011|2047:0930-1001|2048:0919-0921|2049:0908-0909|2050:0828-0829|2051:0817-0818|2052:0805-0806|2053:0726-0728|2054:0715-0716|2055:0705-0706|2056:0623-0624,0626|2057:0612-0613|2058:0601-0603|2059:0522-0523|2060:0510-0511","estimated":"y|2010:1116|2011:1106|2012:1026|2013:1015|2014:1004|2026:0527-0528|2027:0516-0517|2028:0505-0506|2029:0424-0425|2030:0413-0414|2031:0402-0403|2032:0322-0323|2033:0311-0312|2034:0301-0302|2035:0218-0219|2036:0207-0208|2037:0126-0127|2038:0116-0117|2039:0105-0106,1226-1227|2040:1214-1215|2041:1204-1205|2042:1123-1124|2043:1112-1113|2044:1031-1101|2045:1021-1022|2046:1010-1011|2047:0930-1001|2048:0919-0920|2049:0908-0909|2050:0828-0829|2051:0817-0818|2052:0805-0806|2053:0726-0727|2054:0715-0716|2055:0705-0706|2056:0623-0624|2057:0612-0613|2058:0601-0602|2059:0522-0523|2060:0510-0511"},{"id":"laylat_al_qadr","name":"Lialat-Ul-Qadr","en":"Laylat al-Qadr","category":"public","dates":"y|2010:0906|2011:0827|2012:0815|2013:0804|2014:0724|2015:0714|2016:0703|2017:0622|2018:0611|2019:0601|2020:0520|2021:0509-0510|2022:0429|2023:0418|2024:0406,0408|2025:0327|2026:0316|2027:0306,0308|2028:0223|2029:0211-0212|2030:0131|2031:0121|2032:0110,0112,1230|2033:1219|2034:1208|2035:1127|2036:1115,1117|2037:1105|2038:1026|2039:1015,1017|2040:1003|2041:0923|2042:0912|2043:0901|2044:0821-0822|2045:0810|2046:0731|2047:0720,0722|2048:0708|2049:0628|2050:0617|2051:0606|2052:0526-0527|2053:0516|2054:0505|2055:0424,0426|2056:0412|2057:0401-0402|2058:0322|2059:0312|2060:0229-0301","estimated":"y|2010:0906|2011:0827|2012:0815|2013:0804|2014:0724|2026:0316|2027:0306|2028:0223|2029:0211|2030:0131|2031:0121|2032:0110,1230|2033:1219|2034:1208|2035:1127|2036:1115|2037:1105|2038:1026|2039:1015|2040:1003|2041:0923|2042:0912|2043:0901|2044:0821|2045:0810|2046:0731|2047:0720|2048:0708|2049:0628|2050:0617|2051:0606|2052:0526|2053:0516|2054:0505|2055:0424|2056:0412|2057:0401|2058:0322|2059:0312|2060:0229"},{"id":"independence_day","name":"Independence Day","en":"Independence Day","category":"public","dates":"d|0218:2010-2060|0219:2024,2029,2035,2046,2052,2057|0220:2023,2034,2040,2045,2051","estimated":"y|2035:0218-0219"},{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_monday","name":"Easter Monday","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"labor_day","name":"Labour Day","en":"Labor Day","category":"public","dates":"d|0501:2010-2060|0502:2022,2033,2039,2044,2050|0503:2021,2027,2032,2038,2049,2055,2060"},{"id":"africa_liberation_day","name":"Africa Liberation Day","en":"Africa Liberation Day","category":"public","dates":"d|0525:2010-2060|0526:2025,2031,2036,2042,2053,2059|0527:2024,2030,2041,2047,2052,2058"},{"id":"july_22_revolution_day","name":"July 22 Revolution Day","en":"July 22 Revolution Day","category":"public","dates":"d|0722:2010-2060|0723:2029,2035,2040,2046,2057|0724:2023,2028,2034,2045,2051,2056"},{"id":"assumption_day","name":"Feast of the Assumption","en":"Assumption Day","category":"public","dates":"d|0815:2010-2060|0816:2021,2027,2032,2038,2049,2055,2060|0817:2026,2037,2043,2048,2054"},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"},{"id":"boxing_day","name":"Boxing Day","en":"Boxing Day","category":"public","dates":"d|1226:2010-2060|1227:2021,2027,2032,2038,2049,2055,2060|1228:2026,2037,2043,2048,2054"}]} diff --git a/app/Support/Holidays/data/GN.json b/app/Support/Holidays/data/GN.json new file mode 100644 index 000000000..9a85a1ee4 --- /dev/null +++ b/app/Support/Holidays/data/GN.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"GN","language":"fr","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Fête du Nouvel an","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"day_after_the_new_year_s_day","name":"Lendemain de la Fête du Nouvel an","en":"Day after the New Year's Day","category":"public","dates":"d|0102:2023,2034,2040,2045,2051"},{"id":"eid_al_fitr","name":"Jour de l'Aïd el-Fitr","en":"Eid al-Fitr","category":"public","dates":"y|2010:0910|2011:0830|2012:0819|2013:0808|2014:0728|2015:0718|2016:0707|2017:0626|2018:0615|2019:0604|2020:0524|2021:0513|2022:0502|2023:0421|2024:0410|2025:0331|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304","estimated":"y|2010:0910|2011:0830|2012:0819|2013:0808|2014:0728|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304"},{"id":"day_after_the_eid_al_fitr","name":"Lendemain de la Jour de l'Aïd el-Fitr","en":"Day after the Eid al-Fitr","category":"public","dates":"d|0103:2033|0417:2056|0713:2048|1008:2040|1109:2037","estimated":"d|0103:2033|0417:2056|0713:2048|1008:2040|1109:2037"},{"id":"eid_al_adha","name":"Jour de la Tabaski","en":"Eid al-Adha","category":"public","dates":"y|2010:1116|2011:1106|2012:1026|2013:1015|2014:1004|2015:0924|2016:0913|2017:0902|2018:0822|2019:0811|2020:0731|2021:0720|2022:0710|2023:0628|2024:0616|2025:0607|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510","estimated":"y|2010:1116|2011:1106|2012:1026|2013:1015|2014:1004|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510"},{"id":"day_after_eid_al_adha","name":"Lendemain de la Tabaski","en":"Day after Eid al-Adha","category":"public","dates":"y|2023:0629|2024:0617|2025:0608|2026:0528|2027:0517|2028:0506|2029:0425|2030:0414|2031:0403|2032:0323|2033:0312|2034:0302|2035:0219|2036:0208|2037:0127|2038:0117|2039:0106,1227|2040:1215|2041:1205|2042:1124|2043:1113|2044:1101|2045:1022|2046:1011|2047:1001|2048:0920|2049:0909|2050:0829|2051:0818|2052:0806|2053:0727|2054:0716|2055:0706|2056:0624|2057:0613|2058:0602|2059:0523|2060:0511","estimated":"y|2026:0528|2027:0517|2028:0506|2029:0425|2030:0414|2031:0403|2032:0323|2033:0312|2034:0302|2035:0219|2036:0208|2037:0127|2038:0117|2039:0106,1227|2040:1215|2041:1205|2042:1124|2043:1113|2044:1101|2045:1022|2046:1011|2047:1001|2048:0920|2049:0909|2050:0829|2051:0818|2052:0806|2053:0727|2054:0716|2055:0706|2056:0624|2057:0613|2058:0602|2059:0523|2060:0511"},{"id":"day_after_prophet_s_birthday","name":"Lendemain de la nuit du Maoloud","en":"Day after Prophet's Birthday","category":"public","dates":"y|2010:0226|2011:0215|2012:0204|2013:0124|2014:0113|2015:1224|2016:1212|2017:1201|2018:1120|2019:1110|2020:1029|2021:1018|2022:1008|2023:0927|2024:0915|2025:0905|2026:0825|2027:0814|2028:0803|2029:0724|2030:0713|2031:0702|2032:0620|2033:0609|2034:0530|2035:0520|2036:0508|2037:0428|2038:0417|2039:0406|2040:0325|2041:0315|2042:0304|2043:0222|2044:0211|2045:0130|2046:0119|2047:0108,1229|2048:1218|2049:1207|2050:1126|2051:1116|2052:1104|2053:1024|2054:1013|2055:1003|2056:0922|2057:0911|2058:0831|2059:0820|2060:0808","estimated":"y|2010:0226|2011:0215|2012:0204|2013:0124|2014:0113|2026:0825|2027:0814|2028:0803|2029:0724|2030:0713|2031:0702|2032:0620|2033:0609|2034:0530|2035:0520|2036:0508|2037:0428|2038:0417|2039:0406|2040:0325|2041:0315|2042:0304|2043:0222|2044:0211|2045:0130|2046:0119|2047:0108,1229|2048:1218|2049:1207|2050:1126|2051:1116|2052:1104|2053:1024|2054:1013|2055:1003|2056:0922|2057:0911|2058:0831|2059:0820|2060:0808"},{"id":"day_after_night_of_power","name":"Lendemain de la nuit Lailatoul Qadr","en":"Day after Night of Power","category":"public","dates":"y|2010:0906|2011:0827|2012:0815|2013:0804|2014:0724|2015:0714|2016:0703|2017:0622|2018:0612|2019:0601|2020:0520|2021:0509|2022:0429|2023:0418|2024:0406|2025:0327|2026:0316|2027:0306|2028:0223|2029:0211|2030:0131|2031:0121|2032:0110,1230|2033:1219|2034:1208|2035:1127|2036:1115|2037:1105|2038:1026|2039:1015|2040:1003|2041:0923|2042:0912|2043:0901|2044:0821|2045:0810|2046:0731|2047:0720|2048:0708|2049:0628|2050:0617|2051:0606|2052:0526|2053:0516|2054:0505|2055:0424|2056:0412|2057:0401|2058:0322|2059:0312|2060:0229","estimated":"y|2010:0906|2011:0827|2012:0815|2013:0804|2014:0724|2026:0316|2027:0306|2028:0223|2029:0211|2030:0131|2031:0121|2032:0110,1230|2033:1219|2034:1208|2035:1127|2036:1115|2037:1105|2038:1026|2039:1015|2040:1003|2041:0923|2042:0912|2043:0901|2044:0821|2045:0810|2046:0731|2047:0720|2048:0708|2049:0628|2050:0617|2051:0606|2052:0526|2053:0516|2054:0505|2055:0424|2056:0412|2057:0401|2058:0322|2059:0312|2060:0229"},{"id":"easter_monday","name":"Lundi de Pâques","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"labor_day","name":"Fête du Travail","en":"Labor Day","category":"public","dates":"d|0501:2010-2060"},{"id":"africa_day","name":"Anniversaire de l'Union Africaine","en":"Africa Day","category":"public","dates":"d|0525:2010-2060"},{"id":"assumption_day","name":"Assomption","en":"Assumption Day","category":"public","dates":"d|0815:2010-2060"},{"id":"independence_day","name":"Fête anniversaire de l'indépendance de la Guinée","en":"Independence Day","category":"public","dates":"d|1002:2010-2060"},{"id":"day_after_the_independence_day","name":"Lendemain de la Fête anniversaire de l'indépendance de la Guinée","en":"Day after the Independence Day","category":"public","dates":"d|1003:2033,2039,2044,2050"},{"id":"christmas_day","name":"Fête de Noël","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"}]} diff --git a/app/Support/Holidays/data/GP.json b/app/Support/Holidays/data/GP.json new file mode 100644 index 000000000..efb698582 --- /dev/null +++ b/app/Support/Holidays/data/GP.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"GP","language":"fr","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Jour de l'an","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"mi_careme","name":"Mi-Carême","en":"Mi-Careme","category":"public","dates":"d|0301:2035,2046|0303:2016|0304:2027,2032|0305:2043,2054|0306:2059|0307:2013,2024|0308:2018,2029,2040|0309:2051,2056|0311:2010,2021|0312:2015,2026,2037,2048|0313:2042,2053|0315:2012|0316:2023,2034,2045|0317:2039,2050|0319:2020|0320:2031,2036|0321:2047,2058|0323:2017,2028|0324:2022,2033,2044|0325:2049,2055,2060|0327:2014,2025|0328:2019,2030,2041,2052|0329:2057|0331:2011|0401:2038"},{"id":"good_friday","name":"Vendredi saint","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_monday","name":"Lundi de Pâques","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"labor_day","name":"Fête du Travail","en":"Labor Day","category":"public","dates":"d|0501:2010-2060"},{"id":"ascension_day","name":"Ascension","en":"Ascension Day","category":"public","dates":"d|0503:2035,2046|0505:2016|0506:2027,2032|0507:2043,2054|0508:2059|0509:2013,2024|0510:2018,2029,2040|0511:2051,2056|0513:2010,2021|0514:2015,2026,2037,2048|0515:2042,2053|0517:2012|0518:2023,2034,2045|0519:2039,2050|0521:2020|0522:2031,2036|0523:2047,2058|0525:2017,2028|0526:2022,2033,2044|0527:2049,2055,2060|0529:2014,2025|0530:2019,2030,2041,2052|0531:2057|0602:2011|0603:2038"},{"id":"victory_day","name":"Fête de la Victoire","en":"Victory Day","category":"public","dates":"d|0508:2010-2060"},{"id":"pentecost_monday","name":"Lundi de Pentecôte","en":"Pentecost Monday","category":"public","dates":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038"},{"id":"abolition_of_slavery","name":"Abolition de l'esclavage","en":"Abolition of Slavery","category":"public","dates":"d|0527:2010-2060"},{"id":"national_day","name":"Fête nationale","en":"National Day","category":"public","dates":"d|0714:2010-2060"},{"id":"victor_schoelcher_day","name":"Fête de Victor Schoelcher","en":"Victor Schoelcher Day","category":"public","dates":"d|0721:2010-2060"},{"id":"assumption_day","name":"Assomption","en":"Assumption Day","category":"public","dates":"d|0815:2010-2060"},{"id":"all_saints_day","name":"Toussaint","en":"All Saints' Day","category":"public","dates":"d|1101:2010-2060"},{"id":"armistice_day","name":"Armistice","en":"Armistice Day","category":"public","dates":"d|1111:2010-2060"},{"id":"christmas_day","name":"Noël","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"}]} diff --git a/app/Support/Holidays/data/GQ.json b/app/Support/Holidays/data/GQ.json new file mode 100644 index 000000000..8dbfd036f --- /dev/null +++ b/app/Support/Holidays/data/GQ.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"GQ","language":"es","years":[2010,2060],"subdivisions":{"AN":"Annobón","BN":"Bioko Norte","BS":"Bioko Sur","CS":"Centro Sur","DJ":"Djibloho","KN":"Kié-Ntem","LI":"Litoral","WN":"Wele-Nzas"},"holidays":[{"id":"new_year_s_day","name":"Año Nuevo","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051|0103:2011,2022,2028,2033,2039,2050,2056"},{"id":"international_women_s_day","name":"Día Internacional de la Mujer","en":"International Women's Day","category":"public","dates":"d|0308:2010-2060"},{"id":"maundy_thursday","name":"Jueves Santo","en":"Maundy Thursday","category":"public","dates":"d|0322:2035,2046|0324:2016|0325:2027,2032|0326:2043,2054|0327:2059|0328:2013,2024|0329:2018,2029,2040|0330:2051,2056|0401:2010,2021|0402:2015,2026,2037,2048|0403:2042,2053|0405:2012|0406:2023,2034,2045|0407:2039,2050|0409:2020|0410:2031,2036|0411:2047,2058|0413:2017,2028|0414:2022,2033,2044|0415:2049,2055,2060|0417:2014,2025|0418:2019,2030,2041,2052|0419:2057|0421:2011|0422:2038"},{"id":"good_friday","name":"Viernes Santo","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"international_labor_day","name":"Día Internacional del Trabajo","en":"International Labor Day","category":"public","dates":"d|0501:2010-2060|0502:2011,2016,2022,2033,2039,2044,2050|0503:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"corpus_christi","name":"Corpus Christi","en":"Corpus Christi","category":"public","dates":"d|0524:2035,2046|0526:2016|0527:2027,2032|0528:2043,2054|0529:2059|0530:2013,2024|0531:2018,2029,2040|0601:2051,2056|0603:2010,2021|0604:2015,2026,2037,2048|0605:2042,2053|0607:2012|0608:2023,2034,2045|0609:2039,2050|0611:2020|0612:2031,2036|0613:2047,2058|0615:2017,2028|0616:2022,2033,2044|0617:2049,2055,2060|0619:2014,2025|0620:2019,2030,2041,2052|0621:2057|0623:2011|0624:2038"},{"id":"african_liberation_day","name":"Día de la liberación Africana","en":"African Liberation Day","category":"public","dates":"d|0525:2010-2060"},{"id":"president_s_day","name":"Natalicio de Su Excelencia el Presidente de la República","en":"President's Day","category":"public","dates":"d|0605:2010-2060|0606:2011,2016,2022,2033,2039,2044,2050|0607:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"patron_saint_festival_of_annobon","name":"Fiesta Patronal de Annobón","en":"Patron Saint Festival of Annobón","category":"public","regional":{"AN":"d|0613:2010-2060"}},{"id":"armed_forces_day","name":"Día de las Fuerzas Armadas","en":"Armed Forces Day","category":"public","dates":"d|0803:2010-2060|0804:2014,2025,2031,2036,2042,2053,2059|0805:2013,2019,2024,2030,2041,2047,2052,2058"},{"id":"constitution_day","name":"Día de la Constitución","en":"Constitution Day","category":"public","dates":"d|0815:2010-2060|0816:2010,2021,2027,2032,2038,2049,2055,2060|0817:2015,2020,2026,2037,2043,2048,2054"},{"id":"national_independence_day","name":"Día de la Independencia Nacional","en":"National Independence Day","category":"public","dates":"d|1012:2010-2060|1013:2014,2025,2031,2036,2042,2053,2059|1014:2013,2019,2024,2030,2041,2047,2052,2058"},{"id":"immaculate_conception","name":"Festividad de la Inmaculada Concepción de María","en":"Immaculate Conception","category":"public","dates":"d|1208:2010-2060|1209:2013,2019,2024,2030,2041,2047,2052,2058|1210:2012,2018,2029,2035,2040,2046,2057"},{"id":"christmas_day","name":"Día de Navidad","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060|1226:2011,2016,2022,2033,2039,2044,2050|1227:2010,2021,2027,2032,2038,2049,2055,2060"}]} diff --git a/app/Support/Holidays/data/GR.json b/app/Support/Holidays/data/GR.json new file mode 100644 index 000000000..d02990b88 --- /dev/null +++ b/app/Support/Holidays/data/GR.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"GR","language":"el","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Πρωτοχρονιά","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"epiphany","name":"Θεοφάνεια","en":"Epiphany","category":"public","dates":"d|0106:2010-2060"},{"id":"green_monday","name":"Καθαρά Δευτέρα","en":"Green Monday","category":"public","dates":"d|0215:2010|0216:2037|0217:2048|0219:2018,2029|0220:2034,2045|0221:2056|0223:2015,2026|0224:2031,2042,2053|0225:2058|0227:2012,2017,2023|0228:2028,2039,2050|0301:2055|0302:2020|0303:2014,2025,2036|0304:2041,2047,2052|0307:2011,2022,2033,2044|0308:2038,2049,2060|0311:2019,2030|0312:2035,2046,2057|0314:2016|0315:2021,2027,2032|0316:2043,2054|0317:2059|0318:2013,2024|0319:2040|0320:2051"},{"id":"independence_day","name":"Εικοστή Πέμπτη Μαρτίου","en":"Independence Day","category":"public","dates":"d|0325:2010-2060"},{"id":"good_friday","name":"Μεγάλη Παρασκευή","en":"Good Friday","category":"public","dates":"d|0402:2010|0403:2037,2048|0406:2018,2029|0407:2034,2045,2056|0410:2015,2026|0411:2031,2042,2053|0412:2058|0413:2012|0414:2017,2023,2028|0415:2039,2050|0416:2055|0417:2020|0418:2014,2025,2036|0419:2041,2047,2052|0422:2011,2022,2033,2044|0423:2038,2049,2060|0426:2019,2030|0427:2035,2046,2057|0429:2016|0430:2021,2027,2032|0501:2043,2054|0502:2059|0503:2013,2024|0504:2040|0505:2051"},{"id":"easter_monday","name":"Δευτέρα του Πάσχα","en":"Easter Monday","category":"public","dates":"d|0405:2010|0406:2037,2048|0409:2018,2029|0410:2034,2045,2056|0413:2015,2026|0414:2031,2042,2053|0415:2058|0416:2012|0417:2017,2023,2028|0418:2039,2050|0419:2055|0420:2020|0421:2014,2025,2036|0422:2041,2047,2052|0425:2011,2022,2033,2044|0426:2038,2049,2060|0429:2019,2030|0430:2035,2046,2057|0502:2016|0503:2021,2027,2032|0504:2043,2054|0505:2059|0506:2013,2024|0507:2040|0508:2051"},{"id":"labor_day","name":"Εργατική Πρωτομαγιά","en":"Labor Day","category":"public","dates":"d|0501:2010-2023,2025-2060|0502:2022,2033,2039,2044,2050|0503:2038,2049,2055,2060|0504:2021,2027,2032|0507:2024"},{"id":"pentecost_monday","name":"Δευτέρα του Αγίου Πνεύματος","en":"Pentecost Monday","category":"public","dates":"d|0524:2010|0525:2037,2048|0528:2018,2029|0529:2034,2045,2056|0601:2015,2026|0602:2031,2042,2053|0603:2058|0604:2012|0605:2017,2023,2028|0606:2039,2050|0607:2055|0608:2020|0609:2014,2025,2036|0610:2041,2047,2052|0613:2011,2022,2033,2044|0614:2038,2049,2060|0617:2019,2030|0618:2035,2046,2057|0620:2016|0621:2021,2027,2032|0622:2043,2054|0623:2059|0624:2013,2024|0625:2040|0626:2051"},{"id":"dormition_of_the_mother_of_god","name":"Κοίμηση της Θεοτόκου","en":"Dormition of the Mother of God","category":"public","dates":"d|0815:2010-2060"},{"id":"ohi_day","name":"Ημέρα του Όχι","en":"Ohi Day","category":"public","dates":"d|1028:2010-2060"},{"id":"christmas_day","name":"Χριστούγεννα","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"},{"id":"glorifying_mother_of_god","name":"Σύναξη της Υπεραγίας Θεοτόκου","en":"Glorifying Mother of God","category":"public","dates":"d|1226:2010-2060"},{"id":"christmas_eve_half_day","name":"Παραμονή Χριστουγέννων","en":"Christmas Eve","category":"half_day","dates":"d|1224:2010-2060"},{"id":"new_year_s_eve_half_day","name":"Παραμονή Πρωτοχρονιάς","en":"New Year's Eve","category":"half_day","dates":"d|1231:2010-2060"}]} diff --git a/app/Support/Holidays/data/GS.json b/app/Support/Holidays/data/GS.json new file mode 100644 index 000000000..d7fc1f628 --- /dev/null +++ b/app/Support/Holidays/data/GS.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"GS","language":"en_GS","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2012-2060|0102:2012,2017,2023,2034,2040,2045,2051|0103:2022,2028,2033,2039,2050,2056"},{"id":"possession_day","name":"Possession Day","en":"Possession Day","category":"public","dates":"d|0117:2012-2060|0118:2016,2021,2027,2038,2044,2049,2055|0119:2015,2026,2032,2037,2043,2054,2060"},{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0423:2038"},{"id":"liberation_day","name":"Liberation Day","en":"Liberation Day","category":"public","dates":"d|0425:2016-2060|0426:2012-2015,2021,2027,2032,2038,2049,2055,2060|0427:2015,2020,2026,2037,2043,2048,2054|0428:2014"},{"id":"shackleton_day","name":"Shackleton Day","en":"Shackleton Day","category":"public","dates":"d|0519:2023|0520:2015-2060|0521:2018,2029,2035,2040,2046,2057|0522:2017,2028,2034,2045,2051,2056"},{"id":"mid_winter_day","name":"Mid-winter Day","en":"Mid-winter Day","category":"public","dates":"d|0620:2025|0621:2012-2060|0622:2015,2020,2026,2037,2043,2048,2054|0623:2014,2031,2036,2042,2053,2059"},{"id":"environment_day","name":"Environment Day","en":"Environment Day","category":"public","dates":"d|1029:2021|1030:2020-2060|1031:2022,2033,2039,2044,2050|1101:2027,2032,2038,2049,2055,2060"},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1225:2012-2060|1226:2016,2022,2033,2039,2044,2050|1227:2021,2027,2032,2038,2049,2055,2060"},{"id":"boxing_day","name":"Boxing Day","en":"Boxing Day","category":"public","dates":"d|1226:2012-2060|1227:2016,2022,2033,2039,2044,2050|1228:2015,2020-2021,2026-2027,2032,2037-2038,2043,2048-2049,2054-2055,2060"}]} diff --git a/app/Support/Holidays/data/GT.json b/app/Support/Holidays/data/GT.json new file mode 100644 index 000000000..e2c9203d0 --- /dev/null +++ b/app/Support/Holidays/data/GT.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"GT","language":"es","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Año Nuevo","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"maundy_thursday","name":"Jueves Santo","en":"Maundy Thursday","category":"public","dates":"d|0322:2035,2046|0324:2016|0325:2027,2032|0326:2043,2054|0327:2059|0328:2013,2024|0329:2018,2029,2040|0330:2051,2056|0401:2010,2021|0402:2015,2026,2037,2048|0403:2042,2053|0405:2012|0406:2023,2034,2045|0407:2039,2050|0409:2020|0410:2031,2036|0411:2047,2058|0413:2017,2028|0414:2022,2033,2044|0415:2049,2055,2060|0417:2014,2025|0418:2019,2030,2041,2052|0419:2057|0421:2011|0422:2038"},{"id":"good_friday","name":"Viernes Santo","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"holy_saturday","name":"Sábado Santo","en":"Holy Saturday","category":"public","dates":"d|0324:2035,2046|0326:2016|0327:2027,2032|0328:2043,2054|0329:2059|0330:2013,2024|0331:2018,2029,2040|0401:2051,2056|0403:2010,2021|0404:2015,2026,2037,2048|0405:2042,2053|0407:2012|0408:2023,2034,2045|0409:2039,2050|0411:2020|0412:2031,2036|0413:2047,2058|0415:2017,2028|0416:2022,2033,2044|0417:2049,2055,2060|0419:2014,2025|0420:2019,2030,2041,2052|0421:2057|0423:2011|0424:2038"},{"id":"labor_day","name":"Día del Trabajo","en":"Labor Day","category":"public","dates":"d|0429:2019|0501:2010-2018,2020-2060"},{"id":"army_day","name":"Día del Ejército","en":"Army Day","category":"public","dates":"d|0628:2021,2027,2032,2038,2049,2055,2060|0629:2020,2026,2037,2043,2048,2054|0630:2010-2018,2025,2031,2036,2042,2053,2059|0701:2019,2024,2030,2041,2047,2052,2058|0702:2029,2035,2040,2046,2057|0703:2023,2028,2034,2045,2051,2056|0704:2022,2033,2039,2044,2050"},{"id":"assumption_day","name":"Día de la Asunción","en":"Assumption Day","category":"public","dates":"d|0815:2010-2060"},{"id":"independence_day","name":"Día de la Independencia","en":"Independence Day","category":"public","dates":"d|0915:2010-2060"},{"id":"revolution_day","name":"Día de la Revolución","en":"Revolution Day","category":"public","dates":"d|1020:2010-2017,2020-2060|1021:2019|1022:2018"},{"id":"all_saints_day","name":"Día de Todos los Santos","en":"All Saints' Day","category":"public","dates":"d|1101:2010-2060"},{"id":"christmas_day","name":"Día de Navidad","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"}]} diff --git a/app/Support/Holidays/data/GU.json b/app/Support/Holidays/data/GU.json new file mode 100644 index 000000000..5a71e1b01 --- /dev/null +++ b/app/Support/Holidays/data/GU.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"GU","language":"en_US","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051|1231:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"martin_luther_king_jr_day","name":"Martin Luther King Jr. Day","en":"Martin Luther King Jr. Day","category":"public","dates":"d|0115:2018,2024,2029,2035,2046,2052,2057|0116:2012,2017,2023,2034,2040,2045,2051|0117:2011,2022,2028,2033,2039,2050,2056|0118:2010,2016,2021,2027,2038,2044,2049,2055|0119:2015,2026,2032,2037,2043,2054,2060|0120:2014,2020,2025,2031,2042,2048,2053,2059|0121:2013,2019,2030,2036,2041,2047,2058"},{"id":"washington_s_birthday","name":"Washington's Birthday","en":"Washington's Birthday","category":"public","dates":"d|0215:2010,2016,2021,2027,2038,2044,2049,2055|0216:2015,2026,2032,2037,2043,2054,2060|0217:2014,2020,2025,2031,2042,2048,2053,2059|0218:2013,2019,2030,2036,2041,2047,2058|0219:2018,2024,2029,2035,2046,2052,2057|0220:2012,2017,2023,2034,2040,2045,2051|0221:2011,2022,2028,2033,2039,2050,2056"},{"id":"guam_discovery_day","name":"Guam Discovery Day","en":"Guam Discovery Day","category":"public","dates":"d|0301:2010,2021,2027,2032,2038,2049,2055,2060|0302:2015,2020,2026,2037,2043,2048,2054|0303:2014,2025,2031,2036,2042,2053,2059|0304:2013,2019,2024,2030,2041,2047,2052,2058|0305:2012,2018,2029,2035,2040,2046,2057|0306:2017,2023,2028,2034,2045,2051,2056|0307:2011,2016,2022,2033,2039,2044,2050"},{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"memorial_day","name":"Memorial Day","en":"Memorial Day","category":"public","dates":"d|0525:2015,2020,2026,2037,2043,2048,2054|0526:2014,2025,2031,2036,2042,2053,2059|0527:2013,2019,2024,2030,2041,2047,2052,2058|0528:2012,2018,2029,2035,2040,2046,2057|0529:2017,2023,2028,2034,2045,2051,2056|0530:2011,2016,2022,2033,2039,2044,2050|0531:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"juneteenth_national_independence_day","name":"Juneteenth National Independence Day","en":"Juneteenth National Independence Day","category":"public","dates":"d|0618:2021,2027,2032,2038,2049,2055,2060|0619:2021-2060|0620:2022,2033,2039,2044,2050"},{"id":"independence_day","name":"Independence Day","en":"Independence Day","category":"public","dates":"d|0703:2015,2020,2026,2037,2043,2048,2054|0704:2010-2060|0705:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"liberation_day_guam","name":"Liberation Day (Guam)","en":"Liberation Day (Guam)","category":"public","dates":"d|0721:2010-2060"},{"id":"labor_day","name":"Labor Day","en":"Labor Day","category":"public","dates":"d|0901:2014,2025,2031,2036,2042,2053,2059|0902:2013,2019,2024,2030,2041,2047,2052,2058|0903:2012,2018,2029,2035,2040,2046,2057|0904:2017,2023,2028,2034,2045,2051,2056|0905:2011,2016,2022,2033,2039,2044,2050|0906:2010,2021,2027,2032,2038,2049,2055,2060|0907:2015,2020,2026,2037,2043,2048,2054"},{"id":"all_souls_day","name":"All Souls' Day","en":"All Souls' Day","category":"public","dates":"d|1102:2010-2060"},{"id":"veterans_day","name":"Veterans Day","en":"Veterans Day","category":"public","dates":"d|1110:2017,2023,2028,2034,2045,2051,2056|1111:2010-2060|1112:2012,2018,2029,2035,2040,2046,2057"},{"id":"thanksgiving_day","name":"Thanksgiving Day","en":"Thanksgiving Day","category":"public","dates":"d|1122:2012,2018,2029,2035,2040,2046,2057|1123:2017,2023,2028,2034,2045,2051,2056|1124:2011,2016,2022,2033,2039,2044,2050|1125:2010,2021,2027,2032,2038,2049,2055,2060|1126:2015,2020,2026,2037,2043,2048,2054|1127:2014,2025,2031,2036,2042,2053,2059|1128:2013,2019,2024,2030,2041,2047,2052,2058"},{"id":"lady_of_camarin_day","name":"Lady of Camarin Day","en":"Lady of Camarin Day","category":"public","dates":"d|1208:2010-2060"},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1224:2010,2021,2027,2032,2038,2049,2055,2060|1225:2010-2060|1226:2011,2016,2022,2033,2039,2044,2050"},{"id":"valentine_s_day_unofficial","name":"Valentine's Day","en":"Valentine's Day","category":"unofficial","dates":"d|0214:2010-2060"},{"id":"saint_patrick_s_day_unofficial","name":"Saint Patrick's Day","en":"Saint Patrick's Day","category":"unofficial","dates":"d|0317:2010-2060"},{"id":"good_friday_unofficial","name":"Good Friday","en":"Good Friday","category":"unofficial","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_sunday_unofficial","name":"Easter Sunday","en":"Easter Sunday","category":"unofficial","dates":"d|0325:2035,2046|0327:2016|0328:2027,2032|0329:2043,2054|0330:2059|0331:2013,2024|0401:2018,2029,2040|0402:2051,2056|0404:2010,2021|0405:2015,2026,2037,2048|0406:2042,2053|0408:2012|0409:2023,2034,2045|0410:2039,2050|0412:2020|0413:2031,2036|0414:2047,2058|0416:2017,2028|0417:2022,2033,2044|0418:2049,2055,2060|0420:2014,2025|0421:2019,2030,2041,2052|0422:2057|0424:2011|0425:2038"},{"id":"mother_s_day_unofficial","name":"Mother's Day","en":"Mother's Day","category":"unofficial","dates":"d|0508:2011,2016,2022,2033,2039,2044,2050|0509:2010,2021,2027,2032,2038,2049,2055,2060|0510:2015,2020,2026,2037,2043,2048,2054|0511:2014,2025,2031,2036,2042,2053,2059|0512:2013,2019,2024,2030,2041,2047,2052,2058|0513:2012,2018,2029,2035,2040,2046,2057|0514:2017,2023,2028,2034,2045,2051,2056"},{"id":"father_s_day_unofficial","name":"Father's Day","en":"Father's Day","category":"unofficial","dates":"d|0615:2014,2025,2031,2036,2042,2053,2059|0616:2013,2019,2024,2030,2041,2047,2052,2058|0617:2012,2018,2029,2035,2040,2046,2057|0618:2017,2023,2028,2034,2045,2051,2056|0619:2011,2016,2022,2033,2039,2044,2050|0620:2010,2021,2027,2032,2038,2049,2055,2060|0621:2015,2020,2026,2037,2043,2048,2054"},{"id":"halloween_unofficial","name":"Halloween","en":"Halloween","category":"unofficial","dates":"d|1031:2010-2060"},{"id":"christmas_eve_unofficial","name":"Christmas Eve","en":"Christmas Eve","category":"unofficial","dates":"d|1224:2010-2060"},{"id":"new_year_s_eve_unofficial","name":"New Year's Eve","en":"New Year's Eve","category":"unofficial","dates":"d|1231:2010-2060"}]} diff --git a/app/Support/Holidays/data/GW.json b/app/Support/Holidays/data/GW.json new file mode 100644 index 000000000..76631bc7c --- /dev/null +++ b/app/Support/Holidays/data/GW.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"GW","language":"pt_GW","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Ano Novo","en":"New Year's Day","category":"public","dates":"d|0101:2023-2060"},{"id":"eid_al_fitr","name":"Korité","en":"Eid al-Fitr","category":"public","dates":"y|2023:0421|2024:0410|2025:0330|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304","estimated":"y|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304"},{"id":"eid_al_adha","name":"Tabaski","en":"Eid al-Adha","category":"public","dates":"y|2023:0628|2024:0616|2025:0606|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510","estimated":"y|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510"},{"id":"national_heroes_day","name":"Dia dos Heróis Nacionais","en":"National Heroes' Day","category":"public","dates":"d|0120:2023-2060"},{"id":"day_of_the_beginning_of_the_armed_struggle","name":"Dia do Início da Luta Armada","en":"Day of the Beginning of the Armed Struggle","category":"public","dates":"d|0123:2023-2060"},{"id":"international_women_s_day","name":"Dia Internacional da Mulher","en":"International Women's Day","category":"public","dates":"d|0308:2023-2060"},{"id":"easter_sunday","name":"Páscoa","en":"Easter Sunday","category":"public","dates":"d|0325:2035,2046|0328:2027,2032|0329:2043,2054|0330:2059|0331:2024|0401:2029,2040|0402:2051,2056|0405:2026,2037,2048|0406:2042,2053|0409:2023,2034,2045|0410:2039,2050|0413:2031,2036|0414:2047,2058|0416:2028|0417:2033,2044|0418:2049,2055,2060|0420:2025|0421:2030,2041,2052|0422:2057|0425:2038"},{"id":"worker_s_day","name":"Dia do Trabalhador","en":"Worker's Day","category":"public","dates":"d|0501:2023-2060"},{"id":"pidjiguiti_day","name":"Dia de Pidjiguiti","en":"Pidjiguiti Day","category":"public","dates":"d|0803:2023-2060"},{"id":"independence_day","name":"Dia da Independência","en":"Independence Day","category":"public","dates":"d|0924:2023-2060"},{"id":"christmas_day","name":"Dia de Natal","en":"Christmas Day","category":"public","dates":"d|1225:2023-2060"}]} diff --git a/app/Support/Holidays/data/GY.json b/app/Support/Holidays/data/GY.json new file mode 100644 index 000000000..829e6713c --- /dev/null +++ b/app/Support/Holidays/data/GY.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"GY","language":"en_GY","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051|0103:2011,2022,2028,2033,2039,2050,2056"},{"id":"prophet_s_birthday","name":"Youman Nabi","en":"Prophet's Birthday","category":"public","dates":"y|2010:0226|2011:0216|2012:0205-0206|2013:0124|2014:0114|2015:0103,1224|2016:1212|2017:1201|2018:1121|2019:1110-1111|2020:1029|2021:1019|2022:1009-1010|2023:0928|2024:0916|2025:0905|2026:0826|2027:0815-0816|2028:0804|2029:0725|2030:0714-0715|2031:0703|2032:0621|2033:0610|2034:0531|2035:0521|2036:0509|2037:0429|2038:0418-0419|2039:0407|2040:0326|2041:0316|2042:0305|2043:0223|2044:0212|2045:0131|2046:0120|2047:0109,1230|2048:1219|2049:1208|2050:1127-1128|2051:1117|2052:1105|2053:1025|2054:1014|2055:1004|2056:0923|2057:0912|2058:0901-0902|2059:0821|2060:0809","estimated":"y|2026:0826|2027:0815|2028:0804|2029:0725|2030:0714|2031:0703|2032:0621|2033:0610|2034:0531|2035:0521|2036:0509|2037:0429|2038:0418|2039:0407|2040:0326|2041:0316|2042:0305|2043:0223|2044:0212|2045:0131|2046:0120|2047:0109,1230|2048:1219|2049:1208|2050:1127|2051:1117|2052:1105|2053:1025|2054:1014|2055:1004|2056:0923|2057:0912|2058:0901|2059:0821|2060:0809"},{"id":"eid_al_adha","name":"Eid-Ul-Azha","en":"Eid al-Adha","category":"public","dates":"y|2010:1117|2011:1107|2012:1026|2013:1015|2014:1005-1006|2015:0924|2016:0913|2017:0902|2018:0822|2019:0811-0812|2020:0731|2021:0721|2022:0709|2023:0629|2024:0617|2025:0607|2026:0528|2027:0517|2028:0506|2029:0425|2030:0414-0415|2031:0403|2032:0323|2033:0312|2034:0302|2035:0219|2036:0208|2037:0127|2038:0117-0118|2039:0106,1227|2040:1215|2041:1205|2042:1124|2043:1113|2044:1101|2045:1022-1023|2046:1011|2047:1001|2048:0920-0921|2049:0909|2050:0829|2051:0818|2052:0806|2053:0727-0728|2054:0716|2055:0706|2056:0624|2057:0613|2058:0602-0603|2059:0523|2060:0511","estimated":"y|2026:0528|2027:0517|2028:0506|2029:0425|2030:0414|2031:0403|2032:0323|2033:0312|2034:0302|2035:0219|2036:0208|2037:0127|2038:0117|2039:0106,1227|2040:1215|2041:1205|2042:1124|2043:1113|2044:1101|2045:1022|2046:1011|2047:1001|2048:0920|2049:0909|2050:0829|2051:0818|2052:0806|2053:0727|2054:0716|2055:0706|2056:0624|2057:0613|2058:0602|2059:0523|2060:0511"},{"id":"republic_day","name":"Republic Day","en":"Republic Day","category":"public","dates":"d|0223:2010-2060","estimated":"d|0223:2043"},{"id":"holi","name":"Phagwah","en":"Holi","category":"public","dates":"d|0301:2010,2029|0302:2018|0303:2026|0305:2034|0306:2015,2034|0307:2023|0308:2012|0309:2031|0310:2020,2031|0311:2028|0312:2017|0313:2017|0314:2025|0316:2033|0317:2014|0318:2022|0320:2011,2030|0321:2011,2019|0322:2027|0323:2016|0324:2035|0325:2024|0327:2013,2032|0328:2021|0329:2021"},{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_monday","name":"Easter Monday","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"labor_day","name":"Labour Day","en":"Labor Day","category":"public","dates":"d|0501:2010-2060|0502:2011,2016,2022,2033,2039,2044,2050"},{"id":"arrival_day","name":"Arrival Day","en":"Arrival Day","category":"public","dates":"d|0505:2019-2060|0506:2019,2024,2030,2041,2047,2052,2058"},{"id":"independence_day","name":"Independence Day","en":"Independence Day","category":"public","dates":"d|0526:2016-2060"},{"id":"caricom_day","name":"CARICOM Day","en":"CARICOM Day","category":"public","dates":"d|0701:2019,2024,2030,2041,2047,2052,2058|0702:2018,2029,2035,2040,2046,2057|0703:2017,2023,2028,2034,2045,2051,2056|0704:2016,2022,2033,2039,2044,2050|0705:2021,2027,2032,2038,2049,2055,2060|0706:2020,2026,2037,2043,2048,2054|0707:2025,2031,2036,2042,2053,2059"},{"id":"emancipation_day","name":"Emancipation Day","en":"Emancipation Day","category":"public","dates":"d|0801:2016-2060|0802:2021,2027,2032,2038,2049,2055,2060"},{"id":"diwali","name":"Deepavali","en":"Diwali","category":"public","dates":"d|1017:2028|1019:2017|1020:2025|1022:2033|1023:2014|1024:2022|1026:2011,2030|1027:2019|1028:2019|1029:2027|1030:2016,2035|1031:2016,2024|1102:2032|1103:2013|1104:2013,2021|1105:2010,2029|1107:2018|1108:2026|1109:2026|1110:2034|1111:2015|1112:2023|1113:2012,2023|1114:2020,2031"},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060|1227:2010-2011,2016,2021-2022,2027,2032-2033,2038-2039,2044,2049-2050,2055,2060"},{"id":"day_after_christmas","name":"Day After Christmas","en":"Day After Christmas","category":"public","dates":"d|1226:2010-2060|1228:2010,2015,2020-2021,2026-2027,2032,2037-2038,2043,2048-2049,2054-2055,2060"}]} diff --git a/app/Support/Holidays/data/HK.json b/app/Support/Holidays/data/HK.json new file mode 100644 index 000000000..b0e1c5265 --- /dev/null +++ b/app/Support/Holidays/data/HK.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"HK","language":"zh_HK","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"一月一日","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051"},{"id":"chinese_new_year","name":"農曆年初一","en":"Chinese New Year","category":"public","dates":"d|0122:2042|0123:2012,2031|0124:2039,2058|0125:2020|0126:2028,2047|0128:2017,2036,2055|0129:2025|0130:2044|0131:2014,2033|0201:2022,2041,2052|0202:2049,2060|0203:2011|0204:2038|0205:2019|0206:2027,2046|0208:2016,2035|0210:2024,2043|0211:2032,2051|0212:2021,2059|0213:2029|0214:2048|0215:2056|0216:2018|0217:2026,2045|0219:2015,2053"},{"id":"the_second_day_of_chinese_new_year","name":"農曆年初二","en":"The second day of Chinese New Year","category":"public","dates":"d|0123:2023,2042|0124:2012,2031,2050|0125:2039,2058|0127:2028|0129:2036,2055|0130:2025|0201:2014,2033|0202:2022,2041,2052|0203:2049,2060|0204:2011,2030|0205:2038,2057|0206:2019|0207:2046|0209:2016,2035,2054|0211:2013,2043|0212:2032|0213:2021,2040,2059|0214:2029|0215:2010,2048|0216:2037,2056|0217:2018|0218:2026,2045|0220:2015,2034,2053"},{"id":"the_third_day_of_chinese_new_year","name":"農曆年初三","en":"The third day of Chinese New Year","category":"public","dates":"d|0124:2023,2042|0125:2012,2031,2050|0126:2039,2058|0127:2020|0128:2028,2047|0130:2017,2036,2055|0131:2025|0201:2044|0202:2033|0203:2022,2052|0204:2049,2060|0205:2011,2030|0206:2038,2057|0207:2019|0208:2027,2046|0210:2016,2035,2054|0212:2013,2024,2043|0213:2032,2051|0214:2040,2059|0215:2029|0216:2010|0217:2037,2056|0219:2026|0221:2015,2034,2053"},{"id":"the_fourth_day_of_chinese_new_year","name":"農曆年初四","en":"The fourth day of Chinese New Year","category":"public","dates":"d|0125:2023|0126:2050|0128:2020|0129:2047|0131:2017|0202:2044|0203:2014|0204:2041|0206:2030|0207:2057|0209:2027|0211:2054|0213:2013,2024|0214:2051|0215:2021,2040|0217:2048|0218:2037|0219:2018|0220:2045|0222:2034"},{"id":"good_friday","name":"耶穌受難節","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0326:2032|0327:2043,2054|0328:2059|0330:2029,2040|0331:2051,2056|0403:2037,2048|0404:2042,2053|0407:2034,2045|0408:2039,2050|0411:2031,2036|0412:2047,2058|0414:2028|0415:2033,2044|0416:2049,2055,2060|0419:2030,2041,2052|0420:2057|0423:2038"},{"id":"the_day_following_good_friday","name":"耶穌受難節翌日","en":"The day following Good Friday","category":"public","dates":"d|0324:2035,2046|0327:2032|0328:2043,2054|0329:2059|0331:2040|0401:2051,2056|0404:2037,2048|0405:2042,2053|0408:2034,2045|0409:2039,2050|0412:2031,2036|0413:2047,2058|0416:2033,2044|0417:2049,2055,2060|0420:2030,2041,2052|0421:2057|0424:2038"},{"id":"easter_monday","name":"復活節星期一","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0329:2027,2032|0330:2043,2054|0331:2059|0402:2029,2040|0403:2051,2056|0406:2026,2037,2048|0407:2042,2053|0410:2034,2045|0411:2039,2050|0414:2031,2036|0415:2047,2058|0417:2028|0418:2033,2044|0419:2049,2055,2060|0422:2030,2041,2052|0423:2057|0426:2038"},{"id":"tomb_sweeping_day","name":"清明節","en":"Tomb-Sweeping Day","category":"public","dates":"d|0404:2012-2013,2016-2017,2020-2021,2024-2025,2028-2029,2032-2033,2036-2037,2040-2042,2044-2046,2048-2050,2052-2054,2056-2058,2060|0405:2010-2011,2014-2015,2018-2019,2021-2023,2026-2027,2030-2032,2034-2035,2038-2039,2043,2047,2049,2051,2055,2059-2060|0406:2015,2043|0407:2026"},{"id":"the_buddha_s_birthday","name":"佛誕","en":"The Buddha's Birthday","category":"public","dates":"d|0430:2039,2058|0502:2028,2047|0503:2036|0504:2055|0505:2025,2044|0506:2033,2052|0507:2041,2060|0508:2022|0509:2022,2030,2049|0510:2049|0511:2038,2057|0513:2027,2046|0514:2046|0515:2024,2035,2054|0516:2032,2043|0517:2032,2051|0518:2040|0519:2059|0520:2029,2048|0521:2029|0522:2037,2056|0524:2026,2045|0525:2026,2034,2053|0526:2023,2042,2053|0528:2031,2050"},{"id":"labor_day","name":"勞動節","en":"Labor Day","category":"public","dates":"d|0501:2010-2060|0502:2011,2016,2022,2033,2039,2044,2050"},{"id":"dragon_boat_festival","name":"端午節","en":"Dragon Boat Festival","category":"public","dates":"d|0527:2039|0528:2028|0529:2028,2047|0530:2017,2036,2055|0531:2025,2044,2055|0601:2033,2052|0602:2014|0603:2022,2041,2060|0604:2049|0605:2030|0606:2011,2057|0607:2019,2038|0608:2046|0609:2016,2027|0610:2024,2035,2054|0611:2035,2043|0612:2013,2032|0613:2051|0614:2021,2040,2059|0615:2048|0616:2010,2029|0617:2056|0618:2018,2037|0619:2026,2045|0620:2015,2034,2053|0622:2023,2042|0623:2012,2042,2050|0624:2031|0625:2020,2058"},{"id":"hong_kong_s_a_r_establishment_day","name":"香港特別行政區成立紀念日","en":"Hong Kong S.A.R. Establishment Day","category":"public","dates":"d|0701:2010-2060|0702:2012,2018,2029,2035,2040,2046,2057"},{"id":"the_day_following_mid_autumn_festival","name":"中秋節翌日","en":"The Day following Mid-Autumn Festival","category":"public","dates":"d|0909:2014,2033|0910:2060|0911:2041|0913:2011,2030|0914:2019,2038,2057|0916:2016,2027|0917:2035,2054|0918:2024,2043|0920:2013,2032,2051|0921:2040|0922:2021,2059|0923:2010,2048|0925:2018,2037,2056|0926:2026,2045|0927:2053|0928:2015,2034|0929:2042|0930:2023|1001:2012,2050|1002:2020,2031|1003:2039,2058|1004:2028|1005:2017,2047|1006:2044,2055|1007:2025"},{"id":"the_second_day_following_mid_autumn_festival","name":"中秋節後第二日","en":"The Second Day following Mid-Autumn Festival","category":"public","dates":"d|0909:2052|0912:2022|0913:2049|0917:2046|0924:2029|1006:2036"},{"id":"double_ninth_festival","name":"重陽節","en":"Double Ninth Festival","category":"public","dates":"d|1001:2033|1002:2014,2060|1003:2041|1004:2022|1005:2011,2030,2049|1007:2019,2038,2057|1008:2027,2046,2057|1009:2016,2035,2054|1010:2016|1011:2024,2043|1012:2032,2043|1013:2013,2051|1014:2013,2021,2040,2059|1015:2040|1016:2010,2029,2048|1017:2018,2037,2056|1018:2026,2045|1019:2026|1020:2034,2053|1021:2015|1022:2042|1023:2012,2023|1024:2031,2050|1025:2020,2058|1026:2020,2028,2039|1027:2036,2047|1028:2017,2047,2055|1029:2025,2044|1030:2052"},{"id":"national_day","name":"國慶日","en":"National Day","category":"public","dates":"d|1001:2010-2060|1002:2012,2017,2023,2028,2034,2045,2051,2056|1003:2033,2050"},{"id":"christmas_day","name":"聖誕節","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060|1226:2011,2016,2022|1227:2033,2039,2044,2050"},{"id":"the_first_weekday_after_christmas_day","name":"聖誕節後第一個周日","en":"The first weekday after Christmas Day","category":"public","dates":"d|1226:2024-2060|1227:2027,2032,2038,2049,2055,2060"},{"id":"new_year_s_day_optional","name":"一月一日","en":"New Year's Day","category":"optional","dates":"d|0101:2010-2011,2013-2016,2018-2022,2024-2033,2035-2039,2041-2044,2046-2050,2052-2060"},{"id":"the_day_following_new_year_s_day_optional","name":"一月一日翌日","en":"The day following New Year's Day","category":"optional","dates":"d|0102:2012,2017,2023,2034,2040,2045,2051"},{"id":"chinese_new_year_optional","name":"農曆年初一","en":"Chinese New Year","category":"optional","dates":"d|0122:2042|0123:2012,2031|0124:2039,2058|0125:2020|0126:2028,2047|0128:2017,2036,2055|0129:2025|0130:2044|0131:2014,2033|0201:2022,2041,2052|0202:2049,2060|0203:2011|0204:2038|0205:2019|0206:2027,2046|0208:2016,2035|0210:2024,2043|0211:2032,2051|0212:2021,2059|0213:2029|0214:2048|0215:2056|0216:2018|0217:2026,2045|0219:2015,2053"},{"id":"the_second_day_of_chinese_new_year_optional","name":"農曆年初二","en":"The second day of Chinese New Year","category":"optional","dates":"d|0123:2023,2042|0124:2012,2031,2050|0125:2039,2058|0127:2028|0129:2036,2055|0130:2025|0201:2014,2033|0202:2022,2041,2052|0203:2049,2060|0204:2011,2030|0205:2038,2057|0206:2019|0207:2046|0209:2016,2035,2054|0211:2013,2043|0212:2032|0213:2021,2040,2059|0214:2029|0215:2010,2048|0216:2037,2056|0217:2018|0218:2026,2045|0220:2015,2034,2053"},{"id":"the_third_day_of_chinese_new_year_optional","name":"農曆年初三","en":"The third day of Chinese New Year","category":"optional","dates":"d|0124:2023,2042|0125:2012,2031,2050|0126:2039,2058|0127:2020|0128:2028,2047|0130:2017,2036,2055|0131:2025|0201:2044|0202:2033|0203:2022,2052|0204:2049,2060|0205:2011,2030|0206:2038,2057|0207:2019|0208:2027,2046|0210:2016,2035,2054|0212:2013,2024,2043|0213:2032,2051|0214:2040,2059|0215:2029|0216:2010|0217:2037,2056|0219:2026|0221:2015,2034,2053"},{"id":"the_fourth_day_of_chinese_new_year_optional","name":"農曆年初四","en":"The fourth day of Chinese New Year","category":"optional","dates":"d|0125:2023|0126:2050|0128:2020|0129:2047|0131:2017|0202:2044|0203:2014|0204:2041|0206:2030|0207:2057|0209:2027|0211:2054|0213:2013,2024|0214:2051|0215:2021,2040|0217:2048|0218:2037|0219:2018|0220:2045|0222:2034"},{"id":"good_friday_optional","name":"耶穌受難節","en":"Good Friday","category":"optional","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"the_day_following_good_friday_optional","name":"耶穌受難節翌日","en":"The day following Good Friday","category":"optional","dates":"d|0324:2035,2046|0326:2016|0327:2027,2032|0328:2043,2054|0329:2059|0330:2013,2024|0331:2018,2029,2040|0401:2051,2056|0403:2010,2021|0404:2015,2026,2037,2048|0405:2042,2053|0407:2012|0408:2023,2034,2045|0409:2039,2050|0411:2020|0412:2031,2036|0413:2047,2058|0415:2017,2028|0416:2022,2033,2044|0417:2049,2055,2060|0419:2014,2025|0420:2019,2030,2041,2052|0421:2057|0423:2011|0424:2038"},{"id":"easter_monday_optional","name":"復活節星期一","en":"Easter Monday","category":"optional","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010|0406:2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"tomb_sweeping_day_optional","name":"清明節","en":"Tomb-Sweeping Day","category":"optional","dates":"d|0404:2012-2013,2016-2017,2020,2024-2025,2028-2029,2033,2036-2037,2040-2042,2044-2046,2048,2050,2052-2054,2056-2058|0405:2011,2014,2018-2019,2022-2023,2027,2030-2031,2034-2035,2038-2039,2047,2051,2055,2059"},{"id":"the_day_following_tomb_sweeping_day_optional","name":"清明節翌日","en":"The day following Tomb-Sweeping Day","category":"optional","dates":"d|0405:2021,2032,2049,2060|0406:2010,2015,2026,2043"},{"id":"the_day_following_easter_monday_optional","name":"復活節星期一翌日","en":"The day following Easter Monday","category":"optional","dates":"d|0406:2021|0407:2015,2026"},{"id":"the_buddha_s_birthday_optional","name":"佛誕","en":"The Buddha's Birthday","category":"optional","dates":"d|0428:2012|0430:2020,2039,2058|0502:2028,2047|0503:2017,2036|0504:2055|0505:2025,2044|0506:2014,2033,2052|0507:2041,2060|0509:2030|0510:2011|0511:2038,2057|0513:2027|0514:2016|0515:2024,2035,2054|0516:2043|0517:2013,2051|0518:2040|0519:2021,2059|0520:2048|0521:2010|0522:2018,2037,2056|0524:2045|0525:2015,2034|0526:2023,2042|0528:2031,2050"},{"id":"labor_day_optional","name":"勞動節","en":"Labor Day","category":"optional","dates":"d|0501:2010,2012-2015,2017-2021,2023-2032,2034-2038,2040-2043,2045-2049,2051-2060"},{"id":"the_day_following_labor_day_optional","name":"勞動節翌日","en":"The day following Labor Day","category":"optional","dates":"d|0502:2011,2016,2022,2033,2039,2044,2050"},{"id":"the_day_following_the_buddha_s_birthday_optional","name":"佛誕翌日","en":"The day following the Buddha's Birthday","category":"optional","dates":"d|0509:2022|0510:2049|0513:2019|0514:2046|0517:2032|0521:2029|0525:2026|0526:2053"},{"id":"dragon_boat_festival_optional","name":"端午節","en":"Dragon Boat Festival","category":"optional","dates":"d|0527:2039|0529:2047|0530:2017,2036|0531:2025,2044|0601:2033,2052|0602:2014|0603:2022,2041,2060|0604:2049|0605:2030|0606:2011,2057|0607:2019,2038|0608:2046|0609:2016,2027|0610:2024,2054|0611:2043|0612:2013,2032|0613:2051|0614:2021,2040,2059|0615:2048|0616:2010,2029|0617:2056|0618:2018,2037|0619:2026,2045|0620:2015,2034,2053|0622:2023|0623:2012,2050|0624:2031|0625:2020,2058"},{"id":"the_day_following_dragon_boat_festival_optional","name":"端午節翌日","en":"The day following Dragon Boat Festival","category":"optional","dates":"d|0529:2028|0531:2055|0611:2035|0623:2042"},{"id":"hong_kong_s_a_r_establishment_day_optional","name":"香港特別行政區成立紀念日","en":"Hong Kong S.A.R. Establishment Day","category":"optional","dates":"d|0701:2010-2011,2013-2017,2019-2028,2030-2034,2036-2039,2041-2045,2047-2056,2058-2060"},{"id":"the_day_following_hong_kong_s_a_r_establishment__optional","name":"香港特別行政區成立紀念日翌日","en":"The day following Hong Kong S.A.R. Establishment Day","category":"optional","dates":"d|0702:2012,2018,2029,2035,2040,2046,2057"},{"id":"the_day_following_mid_autumn_festival_optional","name":"中秋節翌日","en":"The Day following Mid-Autumn Festival","category":"optional","dates":"d|0909:2014,2033|0910:2060|0911:2041|0913:2011,2030|0914:2019,2038,2057|0916:2016,2027|0917:2035,2054|0918:2024,2043|0920:2013,2032,2051|0921:2040|0922:2021,2059|0923:2010,2048|0925:2018,2037,2056|0926:2026,2045|0927:2053|0928:2015,2034|0929:2042|0930:2023|1001:2012,2050|1002:2020,2031|1003:2039,2058|1004:2028|1005:2017,2047|1006:2044,2055|1007:2025"},{"id":"the_second_day_following_mid_autumn_festival_optional","name":"中秋節後第二日","en":"The Second Day following Mid-Autumn Festival","category":"optional","dates":"d|0909:2052|0912:2022|0913:2049|0917:2046|0924:2029|1006:2036"},{"id":"double_ninth_festival_optional","name":"重陽節","en":"Double Ninth Festival","category":"optional","dates":"d|1001:2033|1002:2014,2060|1003:2041|1004:2022|1005:2011,2030,2049|1007:2019,2038|1008:2027,2046|1009:2035,2054|1011:2024|1012:2032|1013:2051|1014:2021,2059|1016:2010,2029,2048|1017:2018,2037,2056|1018:2045|1020:2034,2053|1021:2015|1022:2042|1023:2012,2023|1024:2031,2050|1025:2058|1026:2028,2039|1027:2036|1028:2017,2055|1029:2025,2044|1030:2052"},{"id":"national_day_optional","name":"國慶日","en":"National Day","category":"optional","dates":"d|1001:2010-2011,2013-2016,2018-2022,2024-2027,2029-2032,2035-2044,2046-2049,2052-2055,2057-2060"},{"id":"the_day_following_national_day_optional","name":"國慶日翌日","en":"The day following National Day","category":"optional","dates":"d|1002:2012,2017,2023,2028,2034,2045,2051,2056|1003:2033,2050"},{"id":"the_day_following_double_ninth_festival_optional","name":"重陽節翌日","en":"The day following Double Ninth Festival","category":"optional","dates":"d|1008:2057|1010:2016|1012:2043|1014:2013|1015:2040|1019:2026|1026:2020|1028:2047"},{"id":"christmas_day_optional","name":"聖誕節","en":"Christmas Day","category":"optional","dates":"d|1225:2010,2012-2015,2017-2021,2023-2032,2034-2038,2040-2043,2045-2049,2051-2060"},{"id":"the_first_weekday_after_christmas_day_optional","name":"聖誕節後第一個周日","en":"The first weekday after Christmas Day","category":"optional","dates":"d|1226:2011-2020,2022-2026,2028-2031,2033-2037,2039-2048,2050-2054,2056-2059|1227:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"the_second_weekday_after_christmas_day_optional","name":"聖誕節後第二個周日","en":"The second weekday after Christmas Day","category":"optional","dates":"d|1227:2011,2016,2022,2033,2039,2044,2050"}]} diff --git a/app/Support/Holidays/data/HM.json b/app/Support/Holidays/data/HM.json new file mode 100644 index 000000000..0be7bd12d --- /dev/null +++ b/app/Support/Holidays/data/HM.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"HM","language":null,"years":[2010,2060],"subdivisions":{},"holidays":[]} diff --git a/app/Support/Holidays/data/HN.json b/app/Support/Holidays/data/HN.json new file mode 100644 index 000000000..4de05ff69 --- /dev/null +++ b/app/Support/Holidays/data/HN.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"HN","language":"es","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Año Nuevo","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"maundy_thursday","name":"Jueves Santo","en":"Maundy Thursday","category":"public","dates":"d|0322:2035,2046|0324:2016|0325:2027,2032|0326:2043,2054|0327:2059|0328:2013,2024|0329:2018,2029,2040|0330:2051,2056|0401:2010,2021|0402:2015,2026,2037,2048|0403:2042,2053|0405:2012|0406:2023,2034,2045|0407:2039,2050|0409:2020|0410:2031,2036|0411:2047,2058|0413:2017,2028|0414:2022,2033,2044|0415:2049,2055,2060|0417:2014,2025|0418:2019,2030,2041,2052|0419:2057|0421:2011|0422:2038"},{"id":"good_friday","name":"Viernes Santo","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"holy_saturday","name":"Sábado de Gloria","en":"Holy Saturday","category":"public","dates":"d|0324:2035,2046|0326:2016|0327:2027,2032|0328:2043,2054|0329:2059|0330:2013,2024|0331:2018,2029,2040|0401:2051,2056|0403:2010,2021|0404:2015,2026,2037,2048|0405:2042,2053|0407:2012|0408:2023,2034,2045|0409:2039,2050|0411:2020|0412:2031,2036|0413:2047,2058|0415:2017,2028|0416:2022,2033,2044|0417:2049,2055,2060|0419:2014,2025|0420:2019,2030,2041,2052|0421:2057|0423:2011|0424:2038"},{"id":"panamerican_day","name":"Día de las Américas","en":"Panamerican Day","category":"public","dates":"d|0414:2010-2060"},{"id":"labor_day","name":"Día del Trabajo","en":"Labor Day","category":"public","dates":"d|0501:2010-2060"},{"id":"independence_day","name":"Día de la Independencia","en":"Independence Day","category":"public","dates":"d|0915:2010-2060"},{"id":"morazan_weekend","name":"Semana Morazánica","en":"Morazan Weekend","category":"public","dates":"d|1001:2025,2031,2036,2042,2053,2059|1002:2019,2024-2025,2030-2031,2036,2041-2042,2047,2052-2053,2058-2059|1003:2018-2019,2024-2025,2029-2031,2035-2036,2040-2042,2046-2047,2052-2053,2057-2059|1004:2017-2019,2023-2024,2028-2030,2034-2035,2040-2041,2045-2047,2051-2052,2056-2058|1005:2016-2018,2022-2023,2028-2029,2033-2035,2039-2040,2044-2046,2050-2051,2056-2057|1006:2016-2017,2021-2023,2027-2028,2032-2034,2038-2039,2044-2045,2049-2051,2055-2056,2060|1007:2015-2016,2020-2022,2026-2027,2032-2033,2037-2039,2043-2044,2048-2050,2054-2055,2060|1008:2015,2020-2021,2026-2027,2032,2037-2038,2043,2048-2049,2054-2055,2060|1009:2015,2020,2026,2037,2043,2048,2054"},{"id":"christmas_day","name":"Navidad","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"}]} diff --git a/app/Support/Holidays/data/HR.json b/app/Support/Holidays/data/HR.json new file mode 100644 index 000000000..29bc89f85 --- /dev/null +++ b/app/Support/Holidays/data/HR.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"HR","language":"hr","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Nova godina","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"epiphany","name":"Bogojavljenje ili Sveta tri kralja","en":"Epiphany","category":"public","dates":"d|0106:2010-2060"},{"id":"easter_sunday","name":"Uskrs","en":"Easter Sunday","category":"public","dates":"d|0325:2035,2046|0327:2016|0328:2027,2032|0329:2043,2054|0330:2059|0331:2013,2024|0401:2018,2029,2040|0402:2051,2056|0404:2010,2021|0405:2015,2026,2037,2048|0406:2042,2053|0408:2012|0409:2023,2034,2045|0410:2039,2050|0412:2020|0413:2031,2036|0414:2047,2058|0416:2017,2028|0417:2022,2033,2044|0418:2049,2055,2060|0420:2014,2025|0421:2019,2030,2041,2052|0422:2057|0424:2011|0425:2038"},{"id":"easter_monday","name":"Uskrsni ponedjeljak","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"labor_day","name":"Praznik rada","en":"Labor Day","category":"public","dates":"d|0501:2010-2060"},{"id":"corpus_christi","name":"Tijelovo","en":"Corpus Christi","category":"public","dates":"d|0524:2035,2046|0526:2016|0527:2027,2032|0528:2043,2054|0529:2059|0530:2013,2024|0531:2018,2029,2040|0601:2051,2056|0603:2010,2021|0604:2015,2026,2037,2048|0605:2042,2053|0607:2012|0608:2023,2034,2045|0609:2039,2050|0611:2020|0612:2031,2036|0613:2047,2058|0615:2017,2028|0616:2022,2033,2044|0617:2049,2055,2060|0619:2014,2025|0620:2019,2030,2041,2052|0621:2057|0623:2011|0624:2038"},{"id":"statehood_day","name":"Dan državnosti","en":"Statehood Day","category":"public","dates":"d|0530:2020-2060|0625:2010-2019"},{"id":"anti_fascist_struggle_day","name":"Dan antifašističke borbe","en":"Anti-Fascist Struggle Day","category":"public","dates":"d|0622:2010-2060"},{"id":"victory_and_homeland_thanksgiving_day_and_croati","name":"Dan pobjede i domovinske zahvalnosti i Dan hrvatskih branitelja","en":"Victory and Homeland Thanksgiving Day and Croatian Veterans Day","category":"public","dates":"d|0805:2010-2060"},{"id":"assumption_day","name":"Velika Gospa","en":"Assumption Day","category":"public","dates":"d|0815:2010-2060"},{"id":"all_saints_day","name":"Svi sveti","en":"All Saints' Day","category":"public","dates":"d|1101:2010-2060"},{"id":"remembrance_day","name":"Dan sjećanja na žrtve Domovinskog rata i Dan sjećanja na žrtvu Vukovara i Škabrnje","en":"Remembrance Day","category":"public","dates":"d|1118:2020-2060"},{"id":"christmas_day","name":"Božić","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"},{"id":"saint_stephen_s_day","name":"Sveti Stjepan","en":"Saint Stephen's Day","category":"public","dates":"d|1226:2010-2060"}]} diff --git a/app/Support/Holidays/data/HT.json b/app/Support/Holidays/data/HT.json new file mode 100644 index 000000000..749409b8d --- /dev/null +++ b/app/Support/Holidays/data/HT.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"HT","language":"fr_HT","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"national_independence_day","name":"Fête de l'Indépendance Nationale","en":"National Independence Day","category":"public","dates":"d|0101:2010-2060"},{"id":"new_year_s_day","name":"Nouvel An","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"ancestry_day","name":"Jour des Aïeux","en":"Ancestry Day","category":"public","dates":"d|0102:2010-2060"},{"id":"carnival","name":"Carnaval","en":"Carnival","category":"public","dates":"d|0204:2035,2046|0207:2016,2027|0208:2032,2043,2054|0209:2059|0210:2013|0211:2018,2024,2029|0212:2040,2051|0213:2056|0214:2010,2021|0215:2015,2026,2037|0216:2042,2048,2053|0219:2012,2023,2034,2045|0220:2039,2050|0223:2020,2031|0224:2036,2047,2058|0226:2017|0227:2022,2028,2033|0228:2044,2049,2055|0229:2060|0302:2014,2025|0303:2019,2030,2041,2052|0304:2057|0306:2011|0307:2038"},{"id":"shrove_monday","name":"Lundi Gras","en":"Shrove Monday","category":"public","dates":"d|0205:2035,2046|0208:2016,2027|0209:2032,2043,2054|0210:2059|0211:2013|0212:2018,2024,2029|0213:2040,2051|0214:2056|0215:2010,2021|0216:2015,2026,2037|0217:2042,2048,2053|0220:2012,2023,2034,2045|0221:2039,2050|0224:2020,2031|0225:2036,2047,2058|0227:2017|0228:2022,2028,2033|0229:2044|0301:2049,2055,2060|0303:2014,2025|0304:2019,2030,2041,2052|0305:2057|0307:2011|0308:2038"},{"id":"fat_tuesday","name":"Mardi Gras","en":"Fat Tuesday","category":"public","dates":"d|0206:2035,2046|0209:2016,2027|0210:2032,2043,2054|0211:2059|0212:2013|0213:2018,2024,2029|0214:2040,2051|0215:2056|0216:2010,2021|0217:2015,2026,2037|0218:2042,2048,2053|0221:2012,2023,2034,2045|0222:2039,2050|0225:2020,2031|0226:2036,2047,2058|0228:2017|0229:2028|0301:2022,2033,2044|0302:2049,2055,2060|0304:2014,2025|0305:2019,2030,2041,2052|0306:2057|0308:2011|0309:2038"},{"id":"good_friday","name":"Vendredi Saint","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_sunday","name":"Pâques","en":"Easter Sunday","category":"public","dates":"d|0325:2035,2046|0327:2016|0328:2027,2032|0329:2043,2054|0330:2059|0331:2013,2024|0401:2018,2029,2040|0402:2051,2056|0404:2010,2021|0405:2015,2026,2037,2048|0406:2042,2053|0408:2012|0409:2023,2034,2045|0410:2039,2050|0412:2020|0413:2031,2036|0414:2047,2058|0416:2017,2028|0417:2022,2033,2044|0418:2049,2055,2060|0420:2014,2025|0421:2019,2030,2041,2052|0422:2057|0424:2011|0425:2038"},{"id":"agriculture_and_labor_day","name":"Fête de l'Agriculture et du Travail","en":"Agriculture and Labor Day","category":"public","dates":"d|0501:2010-2060"},{"id":"flag_day_and_university_day","name":"Fête du Drapeau et de l'Université","en":"Flag Day and University Day","category":"public","dates":"d|0518:2010-2060"},{"id":"corpus_christi","name":"Fête-Dieu","en":"Corpus Christi","category":"public","dates":"d|0524:2035,2046|0526:2016|0527:2027,2032|0528:2043,2054|0529:2059|0530:2013,2024|0531:2018,2029,2040|0601:2051,2056|0603:2010,2021|0604:2015,2026,2037,2048|0605:2042,2053|0607:2012|0608:2023,2034,2045|0609:2039,2050|0611:2020|0612:2031,2036|0613:2047,2058|0615:2017,2028|0616:2022,2033,2044|0617:2049,2055,2060|0619:2014,2025|0620:2019,2030,2041,2052|0621:2057|0623:2011|0624:2038"},{"id":"assumption_day","name":"Assomption de Marie","en":"Assumption Day","category":"public","dates":"d|0815:2010-2060"},{"id":"death_of_dessalines","name":"Mort de Dessalines","en":"Death of Dessalines","category":"public","dates":"d|1017:2010-2060"},{"id":"all_saints_day","name":"La Toussaint","en":"All Saints' Day","category":"public","dates":"d|1101:2010-2060"},{"id":"day_of_the_dead","name":"Fête des Morts","en":"Day of the Dead","category":"public","dates":"d|1102:2010-2060"},{"id":"armed_forces_day","name":"Commémoration de la Bataille de Vertières","en":"Armed Forces Day","category":"public","dates":"d|1118:2010-2060"},{"id":"commemoration_of_the_battle_of_vertieres","name":"Jour des Forces Armées","en":"Commemoration of the Battle of Vertieres","category":"public","dates":"d|1118:2010-2060"},{"id":"christmas_day","name":"Noël","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"},{"id":"ash_wednesday_optional","name":"Mercredi des Cendres","en":"Ash Wednesday","category":"optional","dates":"d|0207:2035,2046|0210:2016,2027|0211:2032,2043,2054|0212:2059|0213:2013|0214:2018,2024,2029|0215:2040,2051|0216:2056|0217:2010,2021|0218:2015,2026,2037|0219:2042,2048,2053|0222:2012,2023,2034,2045|0223:2039,2050|0226:2020,2031|0227:2036,2047,2058|0301:2017,2028|0302:2022,2033,2044|0303:2049,2055,2060|0305:2014,2025|0306:2019,2030,2041,2052|0307:2057|0309:2011|0310:2038"},{"id":"maundy_thursday_optional","name":"Jeudi Saint","en":"Maundy Thursday","category":"optional","dates":"d|0322:2035,2046|0324:2016|0325:2027,2032|0326:2043,2054|0327:2059|0328:2013,2024|0329:2018,2029,2040|0330:2051,2056|0401:2010,2021|0402:2015,2026,2037,2048|0403:2042,2053|0405:2012|0406:2023,2034,2045|0407:2039,2050|0409:2020|0410:2031,2036|0411:2047,2058|0413:2017,2028|0414:2022,2033,2044|0415:2049,2055,2060|0417:2014,2025|0418:2019,2030,2041,2052|0419:2057|0421:2011|0422:2038"},{"id":"ascension_day_optional","name":"Ascension","en":"Ascension Day","category":"optional","dates":"d|0503:2035,2046|0505:2016|0506:2027,2032|0507:2043,2054|0508:2059|0509:2013,2024|0510:2018,2029,2040|0511:2051,2056|0513:2010,2021|0514:2015,2026,2037,2048|0515:2042,2053|0517:2012|0518:2023,2034,2045|0519:2039,2050|0521:2020|0522:2031,2036|0523:2047,2058|0525:2017,2028|0526:2022,2033,2044|0527:2049,2055,2060|0529:2014,2025|0530:2019,2030,2041,2052|0531:2057|0602:2011|0603:2038"},{"id":"national_sovereignty_day_optional","name":"Fête de la Souveraineté Nationale","en":"National Sovereignty Day","category":"optional","dates":"d|0523:2010-2060"},{"id":"feast_of_lady_of_perpetual_help_patroness_of_hai_optional","name":"Fête de Notre-Dame du Perpétuel Secours, patronne d'Haiti","en":"Feast of Lady of Perpetual Help, Patroness of Haiti","category":"optional","dates":"d|0627:2010-2060"},{"id":"birth_anniversary_of_jean_jacques_dessalines_optional","name":"Anniversaire de Naissance de Jean-Jacques Dessalines","en":"Birth Anniversary of Jean-Jacques Dessalines","category":"optional","dates":"d|0920:2010-2060"},{"id":"discovery_day_optional","name":"Jour de la Découverte","en":"Discovery Day","category":"optional","dates":"d|1205:2010-2060"}]} diff --git a/app/Support/Holidays/data/HU.json b/app/Support/Holidays/data/HU.json new file mode 100644 index 000000000..8b3a847d5 --- /dev/null +++ b/app/Support/Holidays/data/HU.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"HU","language":"hu","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Újév","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"day_off","name":"Pihenőnap","en":"Day off","category":"public","dates":"d|0102:2015,2026|0314:2011,2016,2022|0316:2012,2018|0430:2012,2018|0502:2014,2025|0819:2013,2019,2024|0821:2015,2020,2026|1022:2012,2018|1024:2014,2025|1031:2011,2016,2022|1102:2012,2018|1224:2010,2012-2015,2018-2021,2024-2026|1227:2013,2019,2024|1231:2012,2018"},{"id":"national_day","name":"Nemzeti ünnep","en":"National Day","category":"public","dates":"d|0315:2010-2060|1023:2010-2060"},{"id":"good_friday","name":"Nagypéntek","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0326:2027,2032|0327:2043,2054|0328:2059|0329:2024|0330:2018,2029,2040|0331:2051,2056|0402:2021|0403:2026,2037,2048|0404:2042,2053|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2025|0419:2019,2030,2041,2052|0420:2057|0423:2038"},{"id":"easter","name":"Húsvét","en":"Easter","category":"public","dates":"d|0325:2035,2046|0327:2016|0328:2027,2032|0329:2043,2054|0330:2059|0331:2013,2024|0401:2018,2029,2040|0402:2051,2056|0404:2010,2021|0405:2015,2026,2037,2048|0406:2042,2053|0408:2012|0409:2023,2034,2045|0410:2039,2050|0412:2020|0413:2031,2036|0414:2047,2058|0416:2017,2028|0417:2022,2033,2044|0418:2049,2055,2060|0420:2014,2025|0421:2019,2030,2041,2052|0422:2057|0424:2011|0425:2038"},{"id":"easter_monday","name":"Húsvét Hétfő","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"labor_day","name":"A Munka ünnepe","en":"Labor Day","category":"public","dates":"d|0501:2010-2060"},{"id":"pentecost","name":"Pünkösd","en":"Pentecost","category":"public","dates":"d|0513:2035,2046|0515:2016|0516:2027,2032|0517:2043,2054|0518:2059|0519:2013,2024|0520:2018,2029,2040|0521:2051,2056|0523:2010,2021|0524:2015,2026,2037,2048|0525:2042,2053|0527:2012|0528:2023,2034,2045|0529:2039,2050|0531:2020|0601:2031,2036|0602:2047,2058|0604:2017,2028|0605:2022,2033,2044|0606:2049,2055,2060|0608:2014,2025|0609:2019,2030,2041,2052|0610:2057|0612:2011|0613:2038"},{"id":"pentecost_monday","name":"Pünkösdhétfő","en":"Pentecost Monday","category":"public","dates":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038"},{"id":"state_foundation_day","name":"Az államalapítás ünnepe","en":"State Foundation Day","category":"public","dates":"d|0820:2010-2060"},{"id":"all_saints_day","name":"Mindenszentek","en":"All Saints' Day","category":"public","dates":"d|1101:2010-2060"},{"id":"christmas_day","name":"Karácsony","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"},{"id":"second_day_of_christmas","name":"Karácsony másnapja","en":"Second Day of Christmas","category":"public","dates":"d|1226:2010-2060"}]} diff --git a/app/Support/Holidays/data/ID.json b/app/Support/Holidays/data/ID.json new file mode 100644 index 000000000..ae39cc576 --- /dev/null +++ b/app/Support/Holidays/data/ID.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"ID","language":"id","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Tahun Baru Masehi","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"isra_and_mi_raj","name":"Isra Mikraj Nabi Muhammad","en":"Isra' and Mi'raj","category":"public","dates":"y|2010:0710|2011:0629|2012:0617|2013:0606|2014:0527|2015:0516|2016:0506|2017:0424|2018:0414|2019:0403|2020:0322|2021:0311|2022:0228|2023:0218|2024:0208|2025:0127|2026:0116|2027:0105,1226|2028:1214|2029:1204|2030:1123|2031:1113|2032:1101|2033:1021|2034:1010|2035:0930|2036:0918|2037:0908|2038:0829|2039:0818|2040:0806|2041:0726|2042:0715|2043:0705|2044:0624|2045:0613|2046:0603|2047:0523|2048:0511|2049:0430|2050:0420|2051:0409|2052:0329|2053:0318|2054:0307|2055:0224|2056:0213|2057:0202|2058:0123|2059:0112|2060:0102,1221","estimated":"y|2028:1214|2029:1204|2030:1123|2031:1113|2032:1101|2033:1021|2034:1010|2035:0930|2036:0918|2037:0908|2038:0829|2039:0818|2040:0806|2041:0726|2042:0715|2043:0705|2044:0624|2045:0613|2046:0603|2047:0523|2048:0511|2049:0430|2050:0420|2051:0409|2052:0329|2053:0318|2054:0307|2055:0224|2056:0213|2057:0202|2058:0123|2059:0112|2060:0102,1221"},{"id":"eid_al_fitr","name":"Hari Raya Idul Fitri","en":"Eid al-Fitr","category":"public","dates":"y|2010:0910|2011:0830|2012:0819|2013:0808|2014:0728|2015:0717|2016:0706|2017:0625|2018:0615|2019:0605|2020:0524|2021:0513|2022:0502|2023:0422|2024:0410|2025:0331|2026:0321|2027:0310|2028:0227|2029:0215|2030:0204|2031:0125|2032:0114|2033:0103,1223|2034:1212|2035:1201|2036:1119|2037:1109|2038:1030|2039:1019|2040:1008|2041:0927|2042:0916|2043:0905|2044:0825|2045:0814|2046:0804|2047:0724|2048:0713|2049:0702|2050:0621|2051:0610|2052:0530|2053:0520|2054:0509|2055:0428|2056:0416|2057:0405|2058:0326|2059:0315|2060:0304","estimated":"y|2027:0310|2028:0227|2029:0215|2030:0204|2031:0125|2032:0114|2033:0103,1223|2034:1212|2035:1201|2036:1119|2037:1109|2038:1030|2039:1019|2040:1008|2041:0927|2042:0916|2043:0905|2044:0825|2045:0814|2046:0804|2047:0724|2048:0713|2049:0702|2050:0621|2051:0610|2052:0530|2053:0520|2054:0509|2055:0428|2056:0416|2057:0405|2058:0326|2059:0315|2060:0304"},{"id":"prophet_s_birthday","name":"Maulid Nabi Muhammad","en":"Prophet's Birthday","category":"public","dates":"y|2010:0226|2011:0215|2012:0205|2013:0124|2014:0114|2015:0103,1224|2016:1212|2017:1201|2018:1120|2019:1109|2020:1029|2021:1020|2022:1008|2023:0928|2024:0916|2025:0905|2026:0825|2027:0815|2028:0804|2029:0724|2030:0713|2031:0702|2032:0621|2033:0610|2034:0530|2035:0520|2036:0509|2037:0428|2038:0417|2039:0406|2040:0325|2041:0315|2042:0305|2043:0222|2044:0212|2045:0131|2046:0120|2047:0109,1229|2048:1218|2049:1208|2050:1127|2051:1116|2052:1104|2053:1024|2054:1014|2055:1003|2056:0922|2057:0912|2058:0901|2059:0821|2060:0809","estimated":"y|2028:0804|2029:0724|2030:0713|2031:0702|2032:0621|2033:0610|2034:0530|2035:0520|2036:0509|2037:0428|2038:0417|2039:0406|2040:0325|2041:0315|2042:0305|2043:0222|2044:0212|2045:0131|2046:0120|2047:0109,1229|2048:1218|2049:1208|2050:1127|2051:1116|2052:1104|2053:1024|2054:1014|2055:1003|2056:0922|2057:0912|2058:0901|2059:0821|2060:0809"},{"id":"eid_al_fitr_second_day","name":"Hari kedua dari Hari Raya Idul Fitri","en":"Eid al-Fitr Second Day","category":"public","dates":"y|2010:0911|2011:0831|2012:0820|2013:0809|2014:0729|2015:0718|2016:0707|2017:0626|2018:0616|2019:0606|2020:0525|2021:0514|2022:0503|2023:0423|2024:0411|2025:0401|2026:0322|2027:0311|2028:0228|2029:0216|2030:0205|2031:0126|2032:0115|2033:0104,1224|2034:1213|2035:1202|2036:1120|2037:1110|2038:1031|2039:1020|2040:1009|2041:0928|2042:0917|2043:0906|2044:0826|2045:0815|2046:0805|2047:0725|2048:0714|2049:0703|2050:0622|2051:0611|2052:0531|2053:0521|2054:0510|2055:0429|2056:0417|2057:0406|2058:0327|2059:0316|2060:0305","estimated":"y|2027:0311|2028:0228|2029:0216|2030:0205|2031:0126|2032:0115|2033:0104,1224|2034:1213|2035:1202|2036:1120|2037:1110|2038:1031|2039:1020|2040:1009|2041:0928|2042:0917|2043:0906|2044:0826|2045:0815|2046:0805|2047:0725|2048:0714|2049:0703|2050:0622|2051:0611|2052:0531|2053:0521|2054:0510|2055:0429|2056:0417|2057:0406|2058:0327|2059:0316|2060:0305"},{"id":"islamic_new_year","name":"Tahun Baru Islam","en":"Islamic New Year","category":"public","dates":"y|2010:1207|2011:1127|2012:1115|2013:1105|2014:1025|2015:1014|2016:1002|2017:0921|2018:0911|2019:0901|2020:0820|2021:0811|2022:0730|2023:0719|2024:0707|2025:0627|2026:0616|2027:0606|2028:0526|2029:0515|2030:0504|2031:0423|2032:0412|2033:0401|2034:0322|2035:0311|2036:0229|2037:0217|2038:0206|2039:0126|2040:0116|2041:0104,1225|2042:1214|2043:1203|2044:1121|2045:1110|2046:1031|2047:1021|2048:1009|2049:0929|2050:0918|2051:0907|2052:0826|2053:0816|2054:0805|2055:0726|2056:0714|2057:0704|2058:0623|2059:0612|2060:0531","estimated":"y|2028:0526|2029:0515|2030:0504|2031:0423|2032:0412|2033:0401|2034:0322|2035:0311|2036:0229|2037:0217|2038:0206|2039:0126|2040:0116|2041:0104,1225|2042:1214|2043:1203|2044:1121|2045:1110|2046:1031|2047:1021|2048:1009|2049:0929|2050:0918|2051:0907|2052:0826|2053:0816|2054:0805|2055:0726|2056:0714|2057:0704|2058:0623|2059:0612|2060:0531"},{"id":"eid_al_adha","name":"Hari Raya Idul Adha","en":"Eid al-Adha","category":"public","dates":"y|2010:1117|2011:1106|2012:1026|2013:1015|2014:1005|2015:0924|2016:0912|2017:0901|2018:0822|2019:0811|2020:0731|2021:0720|2022:0710|2023:0629|2024:0617|2025:0606|2026:0527|2027:0517|2028:0505|2029:0425|2030:0414|2031:0403|2032:0322|2033:0312|2034:0301|2035:0219|2036:0208|2037:0127|2038:0116|2039:0106,1226|2040:1215|2041:1204|2042:1123|2043:1112|2044:1101|2045:1021|2046:1011|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0806|2053:0726|2054:0716|2055:0706|2056:0624|2057:0613|2058:0602|2059:0522|2060:0511","estimated":"y|2027:0517|2028:0505|2029:0425|2030:0414|2031:0403|2032:0322|2033:0312|2034:0301|2035:0219|2036:0208|2037:0127|2038:0116|2039:0106,1226|2040:1215|2041:1204|2042:1123|2043:1112|2044:1101|2045:1021|2046:1011|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0806|2053:0726|2054:0716|2055:0706|2056:0624|2057:0613|2058:0602|2059:0522|2060:0511"},{"id":"lunar_new_year","name":"Tahun Baru Imlek","en":"Lunar New Year","category":"public","dates":"d|0122:2023,2042|0123:2012,2031,2050|0124:2039,2058|0125:2020|0126:2028,2047|0128:2017,2036,2055|0129:2025|0130:2044|0131:2014,2033|0201:2022,2041,2052|0202:2049,2060|0203:2011,2030|0204:2038,2057|0205:2019|0206:2027,2046|0208:2016,2035,2054|0210:2024,2043|0211:2013,2032,2051|0212:2021,2040,2059|0213:2029|0214:2048|0215:2010,2037,2056|0216:2018|0217:2026,2045|0219:2015,2034,2053","estimated":"d|0122:2042|0123:2031,2050|0124:2039,2058|0126:2028,2047|0128:2036,2055|0130:2044|0131:2033|0201:2041,2052|0202:2049,2060|0203:2030|0204:2038,2057|0206:2046|0208:2035,2054|0210:2043|0211:2032,2051|0212:2040,2059|0213:2029|0214:2048|0215:2037,2056|0217:2045|0219:2034,2053"},{"id":"day_of_silence","name":"Hari Suci Nyepi","en":"Day of Silence","category":"public","dates":"d|0303:2022,2041|0305:2011,2030,2049|0306:2038|0307:2019|0308:2027,2046|0309:2016|0310:2035|0311:2024,2043|0312:2013,2032|0314:2021,2040|0315:2029,2048|0316:2010|0317:2018,2037|0319:2026,2045|0320:2034|0321:2015|0322:2023,2042|0323:2012|0324:2031,2050|0325:2020,2039|0326:2028|0327:2047|0328:2017,2036|0329:2025,2044|0331:2014,2033"},{"id":"good_friday","name":"Wafat Yesus Kristus","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_sunday","name":"Kebangkitan Yesus Kristus","en":"Easter Sunday","category":"public","dates":"d|0325:2035,2046|0328:2027,2032|0329:2043,2054|0330:2059|0331:2024|0401:2029,2040|0402:2051,2056|0405:2026,2037,2048|0406:2042,2053|0409:2034,2045|0410:2039,2050|0413:2031,2036|0414:2047,2058|0416:2028|0417:2033,2044|0418:2049,2055,2060|0420:2025|0421:2030,2041,2052|0422:2057|0425:2038"},{"id":"international_labor_day","name":"Hari Buruh Internasional","en":"International Labor Day","category":"public","dates":"d|0501:2014-2060"},{"id":"ascension_day","name":"Kenaikan Yesus Kristus","en":"Ascension Day","category":"public","dates":"d|0503:2035,2046|0505:2016|0506:2027,2032|0507:2043,2054|0508:2059|0509:2013,2024|0510:2018,2029,2040|0511:2051,2056|0513:2010,2021|0514:2015,2026,2037,2048|0515:2042,2053|0517:2012|0518:2023,2034,2045|0519:2039,2050|0521:2020|0522:2031,2036|0523:2047,2058|0525:2017,2028|0526:2022,2033,2044|0527:2049,2055,2060|0529:2014,2025|0530:2019,2030,2041,2052|0531:2057|0602:2011|0603:2038"},{"id":"vesak_day","name":"Hari Raya Waisak","en":"Vesak Day","category":"public","dates":"d|0506:2012|0507:2020,2039,2058|0509:2028,2047|0510:2036|0511:2017,2055|0512:2025,2044|0513:2033,2052|0514:2041,2060|0515:2014|0516:2022,2030,2049|0517:2011|0518:2038,2057|0519:2019|0520:2027,2046|0522:2016,2035,2054|0523:2024,2032,2043|0524:2051|0525:2013,2040|0526:2021,2059|0527:2029,2048|0528:2010|0529:2018,2037,2056|0531:2026,2045|0601:2034,2053|0602:2015,2042|0604:2023,2031,2050","estimated":"d|0507:2039,2058|0509:2028,2047|0510:2036|0511:2055|0512:2044|0513:2033,2052|0514:2041,2060|0516:2030,2049|0518:2038,2057|0520:2046|0522:2035,2054|0523:2032,2043|0524:2051|0525:2040|0526:2059|0527:2029,2048|0529:2037,2056|0531:2045|0601:2034,2053|0602:2042|0604:2031,2050"},{"id":"pancasila_day","name":"Hari Lahir Pancasila","en":"Pancasila Day","category":"public","dates":"d|0601:2016-2060"},{"id":"independence_day","name":"Hari Kemerdekaan Republik Indonesia","en":"Independence Day","category":"public","dates":"d|0817:2010-2060"},{"id":"christmas_day","name":"Hari Raya Natal","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"},{"id":"lunar_new_year_joint_holiday_government","name":"Cuti Bersama Tahun Baru Imlek","en":"Lunar New Year Joint Holiday","category":"government","dates":"d|0123:2023|0128:2025|0205:2027|0209:2024|0216:2026"},{"id":"eid_al_fitr_joint_holiday_government","name":"Cuti Bersama Hari Raya Idul Fitri","en":"Eid al-Fitr Joint Holiday","category":"government","dates":"y|2010:0909,0913|2011:0829,0901-0902|2012:0821-0822|2013:0805-0807|2014:0730-0801|2015:0716,0720-0721|2016:0704-0705,0708|2017:0623,0627-0630|2018:0611-0614,0618-0620|2019:0603-0604,0607|2020:1231|2021:0512|2022:0429,0504-0506|2023:0419-0421,0424-0425|2024:0408-0409,0412,0415|2025:0402-0404,0407|2026:0320,0323-0324|2027:0309,0312,0315"},{"id":"day_of_silence_joint_holiday_government","name":"Cuti Bersama Hari Suci Nyepi","en":"Day of Silence Joint Holiday","category":"government","dates":"d|0312:2024|0318:2026|0323:2023|0328:2025"},{"id":"good_friday_joint_holiday_government","name":"Cuti Bersama Wafat Yesus Kristus","en":"Good Friday Joint Holiday","category":"government","dates":"d|0325:2027"},{"id":"ascension_joint_holiday_government","name":"Cuti Bersama Kenaikan Yesus Kristus","en":"Ascension Joint Holiday","category":"government","dates":"d|0510:2024|0515:2026|0518:2012|0530:2025"},{"id":"vesak_joint_holiday_government","name":"Cuti Bersama Hari Raya Waisak","en":"Vesak Joint Holiday","category":"government","dates":"d|0513:2025|0516:2011|0519:2027|0524:2024|0602:2023"},{"id":"eid_al_adha_joint_holiday_government","name":"Cuti Bersama Hari Raya Idul Adha","en":"Eid al-Adha Joint Holiday","category":"government","dates":"y|2013:1014|2023:0628,0630|2024:0618|2025:0609|2026:0528|2027:0518"},{"id":"christmas_joint_holiday_government","name":"Cuti Bersama Hari Raya Natal","en":"Christmas Joint Holiday","category":"government","dates":"d|1224:2010,2012,2018-2020,2026-2027|1226:2011,2013-2014,2016-2017,2023-2025"}]} diff --git a/app/Support/Holidays/data/IE.json b/app/Support/Holidays/data/IE.json new file mode 100644 index 000000000..deef1b8a2 --- /dev/null +++ b/app/Support/Holidays/data/IE.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"IE","language":null,"years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"saint_brigid_s_day","name":"Saint Brigid's Day","en":"Saint Brigid's Day","category":"public","dates":"d|0201:2027,2030,2036,2038,2041,2044,2047,2049,2055,2058|0202:2026,2032,2037,2043,2054,2060|0203:2025,2031,2042,2048,2053,2059|0205:2024,2029,2035,2046,2052,2057|0206:2023,2034,2040,2045,2051|0207:2028,2033,2039,2050,2056"},{"id":"saint_patrick_s_day","name":"Saint Patrick's Day","en":"Saint Patrick's Day","category":"public","dates":"d|0317:2010-2060"},{"id":"easter_monday","name":"Easter Monday","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"may_day","name":"May Day","en":"May Day","category":"public","dates":"d|0501:2017,2023,2028,2034,2045,2051,2056|0502:2011,2016,2022,2033,2039,2044,2050|0503:2010,2021,2027,2032,2038,2049,2055,2060|0504:2015,2020,2026,2037,2043,2048,2054|0505:2014,2025,2031,2036,2042,2053,2059|0506:2013,2019,2024,2030,2041,2047,2052,2058|0507:2012,2018,2029,2035,2040,2046,2057"},{"id":"june_bank_holiday","name":"June Bank Holiday","en":"June Bank Holiday","category":"public","dates":"d|0601:2015,2020,2026,2037,2043,2048,2054|0602:2014,2025,2031,2036,2042,2053,2059|0603:2013,2019,2024,2030,2041,2047,2052,2058|0604:2012,2018,2029,2035,2040,2046,2057|0605:2017,2023,2028,2034,2045,2051,2056|0606:2011,2016,2022,2033,2039,2044,2050|0607:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"august_bank_holiday","name":"August Bank Holiday","en":"August Bank Holiday","category":"public","dates":"d|0801:2011,2016,2022,2033,2039,2044,2050|0802:2010,2021,2027,2032,2038,2049,2055,2060|0803:2015,2020,2026,2037,2043,2048,2054|0804:2014,2025,2031,2036,2042,2053,2059|0805:2013,2019,2024,2030,2041,2047,2052,2058|0806:2012,2018,2029,2035,2040,2046,2057|0807:2017,2023,2028,2034,2045,2051,2056"},{"id":"october_bank_holiday","name":"October Bank Holiday","en":"October Bank Holiday","category":"public","dates":"d|1025:2010,2021,2027,2032,2038,2049,2055,2060|1026:2015,2020,2026,2037,2043,2048,2054|1027:2014,2025,2031,2036,2042,2053,2059|1028:2013,2019,2024,2030,2041,2047,2052,2058|1029:2012,2018,2029,2035,2040,2046,2057|1030:2017,2023,2028,2034,2045,2051,2056|1031:2011,2016,2022,2033,2039,2044,2050"},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"},{"id":"saint_stephen_s_day","name":"Saint Stephen's Day","en":"Saint Stephen's Day","category":"public","dates":"d|1226:2010-2060"},{"id":"good_friday_optional","name":"Good Friday","en":"Good Friday","category":"optional","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"}]} diff --git a/app/Support/Holidays/data/IL.json b/app/Support/Holidays/data/IL.json new file mode 100644 index 000000000..52e043eb0 --- /dev/null +++ b/app/Support/Holidays/data/IL.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"IL","language":"he","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"pesach","name":"פסח","en":"Pesach","category":"public","dates":"d|0326:2013|0327:2032|0328:2021,2051|0329:2040,2048,2059|0330:2010|0331:2018,2029,2037|0401:2056|0402:2026,2045|0403:2053|0404:2015,2034|0405:2042|0406:2023|0407:2012,2050|0408:2031|0409:2020,2039,2058|0411:2017,2028,2047|0412:2036,2044|0413:2025,2055|0414:2033,2052|0415:2014,2060|0416:2022,2041|0417:2049|0418:2030|0419:2011,2057|0420:2019,2038|0421:2046|0422:2027|0423:2016,2024,2054|0424:2035|0425:2043"},{"id":"seventh_day_of_pesach","name":"שביעי של פסח","en":"Seventh day of Pesach","category":"public","dates":"d|0401:2013|0402:2032|0403:2021,2051|0404:2040,2048,2059|0405:2010|0406:2018,2029,2037|0407:2056|0408:2026,2045|0409:2053|0410:2015,2034|0411:2042|0412:2023|0413:2012,2050|0414:2031|0415:2020,2039,2058|0417:2017,2028,2047|0418:2036,2044|0419:2025,2055|0420:2033,2052|0421:2014,2060|0422:2022,2041|0423:2049|0424:2030|0425:2011,2057|0426:2019,2038|0427:2046|0428:2027|0429:2016,2024,2054|0430:2035|0501:2043"},{"id":"independence_day","name":"יום העצמאות","en":"Independence Day","category":"public","dates":"d|0415:2021,2032|0416:2013,2048|0417:2059|0418:2040,2051|0419:2018,2029|0420:2010,2045,2056|0421:2037|0422:2026|0423:2015,2053|0424:2042|0425:2034|0426:2012,2023|0427:2050|0428:2039|0429:2020,2031|0430:2058|0501:2025,2036,2047|0502:2017,2028,2052|0503:2044|0504:2033,2055|0505:2022,2060|0506:2014,2049|0507:2041|0508:2030|0509:2019,2057|0510:2011,2046|0511:2038|0512:2016,2027|0513:2054|0514:2024,2043|0515:2035"},{"id":"shavuot","name":"שבועות","en":"Shavuot","category":"public","dates":"d|0515:2013|0516:2032|0517:2021,2051|0518:2040,2048,2059|0519:2010|0520:2018,2029,2037|0521:2056|0522:2026,2045|0523:2053|0524:2015,2034|0525:2042|0526:2023|0527:2012,2050|0528:2031|0529:2020,2039,2058|0531:2017,2028,2047|0601:2036,2044|0602:2025,2055|0603:2033,2052|0604:2014,2060|0605:2022,2041|0606:2049|0607:2030|0608:2011,2057|0609:2019,2038|0610:2046|0611:2027|0612:2016,2024,2054|0613:2035|0614:2043"},{"id":"rosh_hashanah","name":"ראש השנה","en":"Rosh Hashanah","category":"public","dates":"d|0905:2013|0906:2013,2032|0907:2021,2032,2051|0908:2021,2040,2048,2051,2059|0909:2010,2040,2048,2059|0910:2010,2018,2029,2037|0911:2018,2029,2037,2056|0912:2026,2045,2056|0913:2026,2045,2053|0914:2015,2034,2053|0915:2015,2034,2042|0916:2023,2042|0917:2012,2023,2050|0918:2012,2031,2050|0919:2020,2031,2039,2058|0920:2020,2039,2058|0921:2017,2028,2047|0922:2017,2028,2036,2044,2047|0923:2025,2036,2044,2055|0924:2025,2033,2052,2055|0925:2014,2033,2052,2060|0926:2014,2022,2041,2060|0927:2022,2041,2049|0928:2030,2049|0929:2011,2030,2057|0930:2011,2019,2038,2057|1001:2019,2038,2046|1002:2027,2046|1003:2016,2024,2027,2054|1004:2016,2024,2035,2054|1005:2035,2043|1006:2043"},{"id":"yom_kippur","name":"יום כיפור","en":"Yom Kippur","category":"public","dates":"d|0914:2013|0915:2032|0916:2021,2051|0917:2040,2048,2059|0918:2010|0919:2018,2029,2037|0920:2056|0921:2026,2045|0922:2053|0923:2015,2034|0924:2042|0925:2023|0926:2012,2050|0927:2031|0928:2020,2039,2058|0930:2017,2028,2047|1001:2036,2044|1002:2025,2055|1003:2033,2052|1004:2014,2060|1005:2022,2041|1006:2049|1007:2030|1008:2011,2057|1009:2019,2038|1010:2046|1011:2027|1012:2016,2024,2054|1013:2035|1014:2043"},{"id":"sukkot","name":"סוכות","en":"Sukkot","category":"public","dates":"d|0919:2013|0920:2032|0921:2021,2051|0922:2040,2048,2059|0923:2010|0924:2018,2029,2037|0925:2056|0926:2026,2045|0927:2053|0928:2015,2034|0929:2042|0930:2023|1001:2012,2050|1002:2031|1003:2020,2039,2058|1005:2017,2028,2047|1006:2036,2044|1007:2025,2055|1008:2033,2052|1009:2014,2060|1010:2022,2041|1011:2049|1012:2030|1013:2011,2057|1014:2019,2038|1015:2046|1016:2027|1017:2016,2024,2054|1018:2035|1019:2043"},{"id":"simchat_torah_shemini_atzeret","name":"שמחת תורה/שמיני עצרת","en":"Simchat Torah / Shemini Atzeret","category":"public","dates":"d|0926:2013|0927:2032|0928:2021,2051|0929:2040,2048,2059|0930:2010|1001:2018,2029,2037|1002:2056|1003:2026,2045|1004:2053|1005:2015,2034|1006:2042|1007:2023|1008:2012,2050|1009:2031|1010:2020,2039,2058|1012:2017,2028,2047|1013:2036,2044|1014:2025,2055|1015:2033,2052|1016:2014,2060|1017:2022,2041|1018:2049|1019:2030|1020:2011,2057|1021:2019,2038|1022:2046|1023:2027|1024:2016,2024,2054|1025:2035|1026:2043"},{"id":"hanukkah_school","name":"חנוכה","en":"Hanukkah","category":"school","dates":"y|2010:1202-1209|2011:1221-1228|2012:1209-1216|2013:1128-1205|2014:1217-1224|2015:1207-1214|2016:1225-1231|2017:0101,1213-1220|2018:1203-1210|2019:1223-1230|2020:1211-1218|2021:1129-1206|2022:1219-1226|2023:1208-1215|2024:1226-1231|2025:0101-0102,1215-1222|2026:1205-1212|2027:1225-1231|2028:0101,1213-1220|2029:1202-1209|2030:1221-1228|2031:1210-1217|2032:1128-1205|2033:1217-1224|2034:1207-1214|2035:1226-1231|2036:0101-0102,1214-1221|2037:1203-1210|2038:1222-1229|2039:1212-1219|2040:1130-1207|2041:1218-1225|2042:1208-1215|2043:1227-1231|2044:0101-0103,1215-1222|2045:1204-1211|2046:1224-1231|2047:1213-1220|2048:1130-1207|2049:1220-1227|2050:1210-1217|2051:1129-1206|2052:1216-1223|2053:1206-1213|2054:1226-1231|2055:0101-0102,1215-1222|2056:1203-1210|2057:1222-1229|2058:1211-1218|2059:1130-1207|2060:1218-1225"},{"id":"ta_anit_ester_school","name":"תענית אסתר","en":"Ta'anit Ester","category":"school","dates":"d|0221:2013|0223:2051|0225:2010,2021,2032|0226:2037,2059|0227:2040,2048|0228:2018,2029|0301:2056|0302:2026,2034,2045|0303:2053|0304:2015|0305:2042|0306:2023,2031|0307:2012,2050,2058|0309:2017,2020,2028,2039|0310:2044|0311:2047,2055|0312:2036|0313:2014,2025|0314:2033,2041,2052|0315:2060|0316:2022|0317:2011,2049|0318:2030,2038|0319:2057|0320:2019|0321:2024,2046|0322:2027,2035|0323:2016,2054|0325:2043"},{"id":"purim_optional","name":"פורים","en":"Purim","category":"optional","dates":"d|0224:2013|0226:2021,2032,2051|0227:2059|0228:2010,2040,2048|0301:2018,2029,2037|0302:2056|0303:2026,2045|0304:2053|0305:2015,2034|0306:2042|0307:2023|0308:2012,2050|0309:2031|0310:2020,2039,2058|0312:2017,2028,2047|0313:2036,2044|0314:2025,2055|0315:2033,2052|0316:2014,2060|0317:2022,2041|0318:2049|0319:2030|0320:2011,2057|0321:2019,2038|0322:2046|0323:2027|0324:2016,2024,2054|0325:2035|0326:2043"},{"id":"purim_school","name":"פורים","en":"Purim","category":"school","dates":"d|0224:2013|0226:2021,2032,2051|0227:2059|0228:2010,2040,2048|0301:2018,2029,2037|0302:2056|0303:2026,2045|0304:2053|0305:2015,2034|0306:2042|0307:2023|0308:2012,2050|0309:2031|0310:2020,2039,2058|0312:2017,2028,2047|0313:2036,2044|0314:2025,2055|0315:2033,2052|0316:2014,2060|0317:2022,2041|0318:2049|0319:2030|0320:2011,2057|0321:2019,2038|0322:2046|0323:2027|0324:2016,2024,2054|0325:2035|0326:2043"},{"id":"pesach_holiday_optional","name":"חול המועד פסח","en":"Pesach holiday","category":"optional","dates":"y|2010:0331-0404|2011:0420-0424|2012:0408-0412|2013:0327-0331|2014:0416-0420|2015:0405-0409|2016:0424-0428|2017:0412-0416|2018:0401-0405|2019:0421-0425|2020:0410-0414|2021:0329-0402|2022:0417-0421|2023:0407-0411|2024:0424-0428|2025:0414-0418|2026:0403-0407|2027:0423-0427|2028:0412-0416|2029:0401-0405|2030:0419-0423|2031:0409-0413|2032:0328-0401|2033:0415-0419|2034:0405-0409|2035:0425-0429|2036:0413-0417|2037:0401-0405|2038:0421-0425|2039:0410-0414|2040:0330-0403|2041:0417-0421|2042:0406-0410|2043:0426-0430|2044:0413-0417|2045:0403-0407|2046:0422-0426|2047:0412-0416|2048:0330-0403|2049:0418-0422|2050:0408-0412|2051:0329-0402|2052:0415-0419|2053:0404-0408|2054:0424-0428|2055:0414-0418|2056:0402-0406|2057:0420-0424|2058:0410-0414|2059:0330-0403|2060:0416-0420"},{"id":"pesach_holiday_school","name":"חול המועד פסח","en":"Pesach holiday","category":"school","dates":"y|2010:0331-0404|2011:0420-0424|2012:0408-0412|2013:0327-0331|2014:0416-0420|2015:0405-0409|2016:0424-0428|2017:0412-0416|2018:0401-0405|2019:0421-0425|2020:0410-0414|2021:0329-0402|2022:0417-0421|2023:0407-0411|2024:0424-0428|2025:0414-0418|2026:0403-0407|2027:0423-0427|2028:0412-0416|2029:0401-0405|2030:0419-0423|2031:0409-0413|2032:0328-0401|2033:0415-0419|2034:0405-0409|2035:0425-0429|2036:0413-0417|2037:0401-0405|2038:0421-0425|2039:0410-0414|2040:0330-0403|2041:0417-0421|2042:0406-0410|2043:0426-0430|2044:0413-0417|2045:0403-0407|2046:0422-0426|2047:0412-0416|2048:0330-0403|2049:0418-0422|2050:0408-0412|2051:0329-0402|2052:0415-0419|2053:0404-0408|2054:0424-0428|2055:0414-0418|2056:0402-0406|2057:0420-0424|2058:0410-0414|2059:0330-0403|2060:0416-0420"},{"id":"remembrance_day_optional","name":"יום הזיכרון לחללי מערכות ישראל ונפגעי פעולות האיבה","en":"Remembrance Day","category":"optional","dates":"d|0414:2021,2032|0415:2013,2048|0416:2059|0417:2040,2051|0418:2018,2029|0419:2010,2045,2056|0420:2037|0421:2026|0422:2015,2053|0423:2042|0424:2034|0425:2012,2023|0426:2050|0427:2039|0428:2020,2031|0429:2058|0430:2025,2036,2047|0501:2017,2028,2052|0502:2044|0503:2033,2055|0504:2022,2060|0505:2014,2049|0506:2041|0507:2030|0508:2019,2057|0509:2011,2046|0510:2038|0511:2016,2027|0512:2054|0513:2024,2043|0514:2035"},{"id":"lag_baomer_school","name":"ל״ג בעומר","en":"Lag BaOmer","category":"school","dates":"d|0428:2013|0429:2032|0430:2021,2051|0501:2040,2048,2059|0502:2010|0503:2018,2029,2037|0504:2056|0505:2026,2045|0506:2053|0507:2015,2034|0508:2042|0509:2023|0510:2012,2050|0511:2031|0512:2020,2039,2058|0514:2017,2028,2047|0515:2036,2044|0516:2025,2055|0517:2033,2052|0518:2014,2060|0519:2022,2041|0520:2049|0521:2030|0522:2011,2057|0523:2019,2038|0524:2046|0525:2027|0526:2016,2024,2054|0527:2035|0528:2043"},{"id":"jerusalem_day_optional","name":"יום ירושלים","en":"Jerusalem Day","category":"optional","dates":"d|0508:2013|0509:2032|0510:2021,2051|0511:2040,2048,2059|0512:2010|0513:2018,2029,2037|0514:2056|0515:2026,2045|0516:2053|0517:2015,2034|0518:2042|0519:2023|0520:2012,2050|0521:2031|0522:2020,2039,2058|0524:2017,2028,2047|0525:2036,2044|0526:2025,2055|0527:2033,2052|0528:2014,2060|0529:2022,2041|0530:2049|0531:2030|0601:2011,2057|0602:2019,2038|0603:2046|0604:2027|0605:2016,2024,2054|0606:2035|0607:2043"},{"id":"tisha_b_av_optional","name":"תשעה באב","en":"Tisha B'Av","category":"optional","dates":"d|0716:2013|0718:2021,2032,2051|0719:2040,2048|0720:2010,2059|0721:2037|0722:2018,2029|0723:2026,2045,2056|0724:2053|0725:2034|0726:2015|0727:2023,2042|0728:2050|0729:2012,2031|0730:2020,2058|0731:2039|0801:2017,2028,2047|0802:2044|0803:2025,2036,2055|0804:2033,2052|0805:2014,2060|0806:2041|0807:2022|0808:2030,2049|0809:2011,2057|0810:2038|0811:2019|0812:2027,2046|0813:2024,2054|0814:2016,2035|0816:2043"},{"id":"sukkot_holiday_optional","name":"חול המועד סוכות","en":"Sukkot holiday","category":"optional","dates":"y|2010:0924-0928|2011:1014-1018|2012:1002-1006|2013:0920-0924|2014:1010-1014|2015:0929-1003|2016:1018-1022|2017:1006-1010|2018:0925-0929|2019:1015-1019|2020:1004-1008|2021:0922-0926|2022:1011-1015|2023:1001-1005|2024:1018-1022|2025:1008-1012|2026:0927-1001|2027:1017-1021|2028:1006-1010|2029:0925-0929|2030:1013-1017|2031:1003-1007|2032:0921-0925|2033:1009-1013|2034:0929-1003|2035:1019-1023|2036:1007-1011|2037:0925-0929|2038:1015-1019|2039:1004-1008|2040:0923-0927|2041:1011-1015|2042:0930-1004|2043:1020-1024|2044:1007-1011|2045:0927-1001|2046:1016-1020|2047:1006-1010|2048:0923-0927|2049:1012-1016|2050:1002-1006|2051:0922-0926|2052:1009-1013|2053:0928-1002|2054:1018-1022|2055:1008-1012|2056:0926-0930|2057:1014-1018|2058:1004-1008|2059:0923-0927|2060:1010-1014"},{"id":"sukkot_holiday_school","name":"חול המועד סוכות","en":"Sukkot holiday","category":"school","dates":"y|2010:0924-0928|2011:1014-1018|2012:1002-1006|2013:0920-0924|2014:1010-1014|2015:0929-1003|2016:1018-1022|2017:1006-1010|2018:0925-0929|2019:1015-1019|2020:1004-1008|2021:0922-0926|2022:1011-1015|2023:1001-1005|2024:1018-1022|2025:1008-1012|2026:0927-1001|2027:1017-1021|2028:1006-1010|2029:0925-0929|2030:1013-1017|2031:1003-1007|2032:0921-0925|2033:1009-1013|2034:0929-1003|2035:1019-1023|2036:1007-1011|2037:0925-0929|2038:1015-1019|2039:1004-1008|2040:0923-0927|2041:1011-1015|2042:0930-1004|2043:1020-1024|2044:1007-1011|2045:0927-1001|2046:1016-1020|2047:1006-1010|2048:0923-0927|2049:1012-1016|2050:1002-1006|2051:0922-0926|2052:1009-1013|2053:0928-1002|2054:1018-1022|2055:1008-1012|2056:0926-0930|2057:1014-1018|2058:1004-1008|2059:0923-0927|2060:1010-1014"},{"id":"sigd_optional","name":"סיגד","en":"Sigd","category":"optional","dates":"d|1102:2013|1103:2032|1104:2021,2051|1105:2040,2048,2059|1106:2010|1107:2018,2029,2037|1108:2056|1109:2026,2045|1110:2053|1111:2015,2034|1112:2042|1113:2023|1114:2012,2050|1115:2031|1116:2020,2039,2058|1118:2017,2028,2047|1119:2036,2044|1120:2025,2055|1121:2033,2052|1122:2014,2060|1123:2022,2041|1124:2049|1125:2030|1126:2011,2057|1127:2019,2038|1128:2046|1129:2027|1130:2016,2024,2054|1201:2035|1202:2043"}]} diff --git a/app/Support/Holidays/data/IM.json b/app/Support/Holidays/data/IM.json new file mode 100644 index 000000000..bf5381475 --- /dev/null +++ b/app/Support/Holidays/data/IM.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"IM","language":"en_GB","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051|0103:2011,2022,2028,2033,2039,2050,2056"},{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_monday","name":"Easter Monday","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"may_day","name":"May Day","en":"May Day","category":"public","dates":"d|0501:2017,2023,2028,2034,2045,2051,2056|0502:2011,2016,2022,2033,2039,2044,2050|0503:2010,2021,2027,2032,2038,2049,2055,2060|0504:2015,2026,2037,2043,2048,2054|0505:2014,2025,2031,2036,2042,2053,2059|0506:2013,2019,2024,2030,2041,2047,2052,2058|0507:2012,2018,2029,2035,2040,2046,2057|0508:2020"},{"id":"spring_bank_holiday","name":"Spring Bank Holiday","en":"Spring Bank Holiday","category":"public","dates":"d|0525:2015,2020,2026,2037,2043,2048,2054|0526:2014,2025,2031,2036,2042,2053,2059|0527:2013,2019,2024,2030,2041,2047,2052,2058|0528:2018,2029,2035,2040,2046,2057|0529:2017,2023,2028,2034,2045,2051,2056|0530:2011,2016,2033,2039,2044,2050|0531:2010,2021,2027,2032,2038,2049,2055,2060|0602:2022|0604:2012"},{"id":"tt_bank_holiday","name":"TT Bank Holiday","en":"TT Bank Holiday","category":"public","dates":"d|0601:2012,2018,2029,2035,2040,2046,2057|0602:2017,2023,2028,2034,2045,2051,2056|0603:2011,2016,2022,2033,2039,2044,2050|0604:2010,2021,2027,2032,2038,2049,2055,2060|0605:2015,2020,2026,2037,2043,2048,2054|0606:2014,2025,2031,2036,2042,2053,2059|0607:2013,2019,2024,2030,2041,2047,2052,2058"},{"id":"tynwald_day","name":"Tynwald Day","en":"Tynwald Day","category":"public","dates":"d|0705:2010-2013,2016-2019,2021-2024,2027-2030,2032-2035,2038-2041,2044-2047,2049-2052,2055-2058,2060|0706:2015,2020,2026,2037,2043,2048,2054|0707:2014,2025,2031,2036,2042,2053,2059"},{"id":"late_summer_bank_holiday","name":"Late Summer Bank Holiday","en":"Late Summer Bank Holiday","category":"public","dates":"d|0825:2014,2025,2031,2036,2042,2053,2059|0826:2013,2019,2024,2030,2041,2047,2052,2058|0827:2012,2018,2029,2035,2040,2046,2057|0828:2017,2023,2028,2034,2045,2051,2056|0829:2011,2016,2022,2033,2039,2044,2050|0830:2010,2021,2027,2032,2038,2049,2055,2060|0831:2015,2020,2026,2037,2043,2048,2054"},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060|1227:2010-2011,2016,2021-2022,2027,2032-2033,2038-2039,2044,2049-2050,2055,2060"},{"id":"boxing_day","name":"Boxing Day","en":"Boxing Day","category":"public","dates":"d|1226:2010-2060|1228:2010,2015,2020-2021,2026-2027,2032,2037-2038,2043,2048-2049,2054-2055,2060"}]} diff --git a/app/Support/Holidays/data/IN.json b/app/Support/Holidays/data/IN.json new file mode 100644 index 000000000..adb716c5d --- /dev/null +++ b/app/Support/Holidays/data/IN.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"IN","language":"en_IN","years":[2010,2060],"subdivisions":{"AN":"Andaman and Nicobar Islands","AP":"Andhra Pradesh","AR":"Arunachal Pradesh","AS":"Assam","BR":"Bihar","CG":"Chhattisgarh","CH":"Chandigarh","DH":"Dadra and Nagar Haveli and Daman and Diu","DL":"Delhi","GA":"Goa","GJ":"Gujarat","HP":"Himachal Pradesh","HR":"Haryana","JH":"Jharkhand","JK":"Jammu and Kashmir","KA":"Karnataka","KL":"Kerala","LA":"Ladakh","LD":"Lakshadweep","MH":"Maharashtra","ML":"Meghalaya","MN":"Manipur","MP":"Madhya Pradesh","MZ":"Mizoram","NL":"Nagaland","OD":"Odisha","PB":"Punjab","PY":"Puducherry","RJ":"Rajasthan","SK":"Sikkim","TN":"Tamil Nadu","TR":"Tripura","TS":"TG","UK":"Uttarakhand","UP":"Uttar Pradesh","WB":"West Bengal"},"holidays":[{"id":"guru_gobind_singh_s_birthday","name":"Guru Gobind Singh's Jayanti","en":"Guru Gobind Singh's Birthday","category":"public","regional":{"CH":"d|0102:2020|0104:2028|0105:2010,2012,2015,2017|0106:2025|0107:2014,2033|0109:2022|0110:2030|0113:2019|0115:2027,2029|0116:2016,2035|0117:2024,2034|0118:2013,2032|0120:2021|0131:2011|1225:2017|1227:2025|1229:2022|1231:2030","PB":"d|0102:2020|0104:2028|0105:2010,2012,2015,2017|0106:2025|0107:2014,2033|0109:2022|0110:2030|0113:2019|0115:2027,2029|0116:2016,2035|0117:2024,2034|0118:2013,2032|0120:2021|0131:2011|1225:2017|1227:2025|1229:2022|1231:2030"}},{"id":"isra_and_mi_raj","name":"Shab-I-Miraj","en":"Isra' and Mi'raj","category":"public","regional":{"JK":"y|2010:0710|2011:0630|2012:0618|2013:0607|2014:0527|2015:0517|2016:0505|2017:0425|2018:0414|2019:0404|2020:0323|2021:0312|2022:0301|2023:0219|2024:0209|2025:0128|2026:0117|2027:0106,1226|2028:1215|2029:1204|2030:1124|2031:1113|2032:1102|2033:1022|2034:1011|2035:0930|2036:0919|2037:0908|2038:0829|2039:0818|2040:0806|2041:0726|2042:0716|2043:0705|2044:0624|2045:0614|2046:0603|2047:0523|2048:0511|2049:0430|2050:0420|2051:0410|2052:0329|2053:0319|2054:0308|2055:0225|2056:0214|2057:0202|2058:0123|2059:0113|2060:0102,1221"},"estimated":"y|2010:0710|2011:0630|2012:0618|2013:0607|2014:0527|2015:0517|2016:0505|2017:0425|2018:0414|2019:0404|2020:0323|2021:0312|2022:0301|2023:0219|2024:0209|2025:0128|2026:0117|2027:0106,1226|2028:1215|2029:1204|2030:1124|2031:1113|2032:1102|2033:1022|2034:1011|2035:0930|2036:0919|2037:0908|2038:0829|2039:0818|2040:0806|2041:0726|2042:0716|2043:0705|2044:0624|2045:0614|2046:0603|2047:0523|2048:0511|2049:0430|2050:0420|2051:0410|2052:0329|2053:0319|2054:0308|2055:0225|2056:0214|2057:0202|2058:0123|2059:0113|2060:0102,1221"},{"id":"mannam_s_birthday","name":"Mannam's Jayanti","en":"Mannam's Birthday","category":"public","regional":{"KL":"d|0102:2010-2060"}},{"id":"post_new_year","name":"Post New Year","en":"Post New Year","category":"public","regional":{"MZ":"d|0102:2010-2060"}},{"id":"ashura","name":"Muharram","en":"Ashura","category":"public","dates":"y|2010:1217|2011:1206|2012:1125|2013:1114|2014:1104|2015:1024|2016:1012|2017:1001|2018:0921|2019:0910|2020:0830|2021:0819|2022:0809|2023:0729|2024:0717|2025:0706|2026:0626|2027:0616|2028:0604|2029:0524|2030:0513|2031:0503|2032:0421|2033:0411|2034:0331|2035:0321|2036:0309|2037:0226|2038:0215|2039:0205|2040:0125|2041:0114|2042:0103,1224|2043:1213|2044:1201|2045:1120|2046:1110|2047:1030|2048:1019|2049:1008|2050:0927|2051:0916|2052:0905|2053:0825|2054:0815|2055:0804|2056:0724|2057:0713|2058:0702|2059:0621|2060:0610","estimated":"y|2027:0616|2028:0604|2029:0524|2030:0513|2031:0503|2032:0421|2033:0411|2034:0331|2035:0321|2036:0309|2037:0226|2038:0215|2039:0205|2040:0125|2041:0114|2042:0103,1224|2043:1213|2044:1201|2045:1120|2046:1110|2047:1030|2048:1019|2049:1008|2050:0927|2051:0916|2052:0905|2053:0825|2054:0815|2055:0804|2056:0724|2057:0713|2058:0702|2059:0621|2060:0610"},{"id":"eid_al_fitr","name":"Id-ul-Fitr","en":"Eid al-Fitr","category":"public","dates":"y|2010:0911|2011:0831|2012:0820|2013:0809|2014:0729|2015:0718|2016:0706|2017:0626|2018:0616|2019:0605|2020:0525|2021:0514|2022:0503|2023:0422|2024:0411|2025:0331|2026:0321|2027:0310|2028:0227|2029:0215|2030:0205|2031:0125|2032:0115|2033:0103,1224|2034:1213|2035:1202|2036:1120|2037:1109|2038:1030|2039:1020|2040:1008|2041:0927|2042:0916|2043:0905|2044:0825|2045:0815|2046:0804|2047:0725|2048:0713|2049:0702|2050:0621|2051:0611|2052:0530|2053:0520|2054:0510|2055:0429|2056:0417|2057:0406|2058:0326|2059:0316|2060:0305","estimated":"y|2027:0310|2028:0227|2029:0215|2030:0205|2031:0125|2032:0115|2033:0103,1224|2034:1213|2035:1202|2036:1120|2037:1109|2038:1030|2039:1020|2040:1008|2041:0927|2042:0916|2043:0905|2044:0825|2045:0815|2046:0804|2047:0725|2048:0713|2049:0702|2050:0621|2051:0611|2052:0530|2053:0520|2054:0510|2055:0429|2056:0417|2057:0406|2058:0326|2059:0316|2060:0305"},{"id":"prophet_s_birthday","name":"Milad-un-Nabi","en":"Prophet's Birthday","category":"public","dates":"y|2010:0227|2011:0216|2012:0205|2013:0125|2014:0114|2015:0104,1224|2016:1213|2017:1202|2018:1121|2019:1110|2020:1030|2021:1019|2022:1009|2023:0928|2024:0916|2025:0905|2026:0826|2027:0815|2028:0804|2029:0725|2030:0714|2031:0703|2032:0621|2033:0610|2034:0531|2035:0521|2036:0509|2037:0429|2038:0418|2039:0407|2040:0326|2041:0316|2042:0305|2043:0223|2044:0212|2045:0131|2046:0120|2047:0109,1230|2048:1219|2049:1208|2050:1127|2051:1117|2052:1105|2053:1025|2054:1014|2055:1004|2056:0923|2057:0912|2058:0901|2059:0821|2060:0809","estimated":"y|2027:0815|2028:0804|2029:0725|2030:0714|2031:0703|2032:0621|2033:0610|2034:0531|2035:0521|2036:0509|2037:0429|2038:0418|2039:0407|2040:0326|2041:0316|2042:0305|2043:0223|2044:0212|2045:0131|2046:0120|2047:0109,1230|2048:1219|2049:1208|2050:1127|2051:1117|2052:1105|2053:1025|2054:1014|2055:1004|2056:0923|2057:0912|2058:0901|2059:0821|2060:0809"},{"id":"eid_al_adha","name":"Id-ul-Zuha (Bakrid)","en":"Eid al-Adha","category":"public","dates":"y|2010:1117|2011:1107|2012:1027|2013:1016|2014:1006|2015:0925|2016:0912|2017:0902|2018:0822|2019:0812|2020:0801|2021:0721|2022:0710|2023:0629|2024:0617|2025:0607|2026:0527|2027:0517|2028:0506|2029:0425|2030:0414|2031:0403|2032:0323|2033:0312|2034:0302|2035:0219|2036:0208|2037:0127|2038:0117|2039:0106,1227|2040:1215|2041:1205|2042:1124|2043:1113|2044:1101|2045:1022|2046:1011|2047:1001|2048:0920|2049:0909|2050:0829|2051:0818|2052:0806|2053:0727|2054:0716|2055:0706|2056:0624|2057:0613|2058:0602|2059:0523|2060:0511","estimated":"y|2027:0517|2028:0506|2029:0425|2030:0414|2031:0403|2032:0323|2033:0312|2034:0302|2035:0219|2036:0208|2037:0127|2038:0117|2039:0106,1227|2040:1215|2041:1205|2042:1124|2043:1113|2044:1101|2045:1022|2046:1011|2047:1001|2048:0920|2049:0909|2050:0829|2051:0818|2052:0806|2053:0727|2054:0716|2055:0706|2056:0624|2057:0613|2058:0602|2059:0523|2060:0511"},{"id":"missionary_day","name":"Missionary Day","en":"Missionary Day","category":"public","regional":{"MZ":"d|0111:2010-2060"}},{"id":"swami_vivekananda_s_birthday","name":"Swami Vivekananda's Jayanti","en":"Swami Vivekananda's Birthday","category":"public","regional":{"WB":"d|0112:2010-2060"}},{"id":"magh_bihu","name":"Magh Bihu","en":"Magh Bihu","category":"public","regional":{"AS":"d|0114:2010,2013-2014,2017-2018,2021-2022,2025-2026,2029-2030,2033-2034|0115:2011-2012,2015-2016,2019-2020,2023-2024,2027-2028,2031-2032,2035|0116:2023","DH":"d|0114:2010,2013-2014,2017-2018,2021-2023,2025-2026,2029-2030,2033-2034|0115:2011-2012,2015-2016,2019-2020,2023-2024,2027-2028,2031-2032,2035"}},{"id":"makar_sankranti","name":"Makar Sankranti","en":"Makar Sankranti","category":"public","regional":{"DH":"d|0114:2010,2013-2014,2017-2018,2021-2026,2029-2030,2033-2034|0115:2011-2012,2015-2016,2019-2020,2027-2028,2031-2032,2035","KA":"d|0114:2010,2013-2014,2017-2018,2021-2026,2029-2030,2033-2034|0115:2011-2012,2015-2016,2019-2020,2027-2028,2031-2032,2035","LD":"d|0114:2010,2013-2014,2017-2018,2021-2026,2029-2030,2033-2034|0115:2011-2012,2015-2016,2019-2020,2027-2028,2031-2032,2035","SK":"d|0114:2010,2013-2014,2017-2018,2021-2026,2029-2030,2033-2034|0115:2011-2012,2015-2016,2019-2020,2027-2028,2031-2032,2035"}},{"id":"pongal","name":"Pongal","en":"Pongal","category":"public","regional":{"DH":"d|0114:2010,2013-2014,2017-2018,2021-2022,2025-2026,2029-2030,2033-2034|0115:2011-2012,2015-2016,2019-2020,2023-2024,2027-2028,2031-2032,2035","PY":"d|0114:2010,2013-2014,2017-2018,2021-2022,2025,2029-2030,2033-2034|0115:2011-2012,2015-2016,2019-2020,2023-2024,2026-2028,2031-2032,2035","TN":"d|0114:2010,2013-2014,2017-2018,2021-2022,2025-2026,2029-2030,2033-2034|0115:2011-2012,2015-2016,2019-2020,2023-2024,2027-2028,2031-2032,2035"}},{"id":"uttarayan","name":"Uttarayan","en":"Uttarayan","category":"public","regional":{"GJ":"d|0114:2010,2013-2014,2017-2018,2021-2026,2029-2030,2033-2034|0115:2011-2012,2015-2016,2019-2020,2027-2028,2031-2032,2035"}},{"id":"sonam_lochhar","name":"Sonam Lochhar","en":"Sonam Lochhar","category":"public","regional":{"SK":"d|0115:2029|0116:2010|0118:2018|0119:2026|0121:2015,2034|0122:2023|0124:2012,2031|0125:2020|0128:2017|0130:2025|0131:2014,2033|0202:2022|0203:2030|0204:2011|0205:2019|0207:2027|0209:2016,2035|0210:2024|0211:2013|0212:2021,2032|0226:2028"}},{"id":"thiruvalluvar_day","name":"Thiruvalluvar Day","en":"Thiruvalluvar Day","category":"public","regional":{"PY":"d|0115:2010,2013-2014,2017-2018,2021-2022,2025,2029-2030,2033-2034|0116:2011-2012,2015-2016,2019-2020,2023-2024,2026-2028,2031-2032,2035","TN":"d|0115:2010,2013-2014,2017-2018,2021-2022,2025-2026,2029-2030,2033-2034|0116:2011-2012,2015-2016,2019-2020,2023-2024,2027-2028,2031-2032,2035"}},{"id":"uzhavar_thirunal","name":"Uzhavar Thirunal","en":"Uzhavar Thirunal","category":"public","regional":{"TN":"d|0116:2010,2013-2014,2017-2018,2021-2022,2025-2026,2029-2030,2033-2034|0117:2011-2012,2015-2016,2019-2020,2023-2024,2027-2028,2031-2032,2035"}},{"id":"sir_chottu_ram_s_birthday","name":"Sir Chottu Ram's Jayanti","en":"Sir Chottu Ram's Birthday","category":"public","regional":{"HR":"d|0119:2029|0120:2010|0122:2018|0123:2026|0124:2015,2034|0126:2023|0127:2031|0128:2012|0129:2020|0131:2028|0201:2017|0202:2025|0204:2014,2033|0205:2022|0207:2030|0208:2011|0209:2019|0211:2027|0212:2016,2035|0214:2013,2024|0215:2032|0216:2021"}},{"id":"netaji_subhas_chandra_bose_s_birthday","name":"Netaji Subhas Chandra Bose's Jayanti","en":"Netaji Subhas Chandra Bose's Birthday","category":"public","regional":{"AS":"d|0123:2010-2060","TR":"d|0123:2010-2060","WB":"d|0123:2010-2060"}},{"id":"statehood_day","name":"Statehood Day","en":"Statehood Day","category":"public","regional":{"AR":"d|0220:2010-2060","HP":"d|0125:2010-2060"}},{"id":"republic_day","name":"Republic Day","en":"Republic Day","category":"public","dates":"d|0126:2010-2060"},{"id":"guru_ravi_das_s_birthday","name":"Guru Ravi Das's Jayanti","en":"Guru Ravi Das's Birthday","category":"public","regional":{"HP":"d|0130:2010,2029|0131:2018|0201:2026|0203:2015,2034|0205:2023|0207:2012,2031|0209:2020|0210:2017,2028|0212:2025|0214:2014,2033|0216:2022|0218:2011,2030|0219:2019|0220:2027|0222:2016,2035|0224:2024|0225:2013|0226:2032|0227:2021"}},{"id":"maha_shivaratri","name":"Maha Shivaratri","en":"Maha Shivaratri","category":"public","dates":"d|0211:2029|0213:2018|0217:2034|0220:2012,2031|0223:2028|0224:2017|0226:2025|0227:2033|0301:2022|0302:2011,2030|0304:2019|0306:2027|0308:2035|0310:2032"},{"id":"chhatrapati_shivaji_maharaj_s_birthday","name":"Chhatrapati Shivaji Maharaj's Jayanti","en":"Chhatrapati Shivaji Maharaj's Birthday","category":"public","regional":{"MH":"d|0219:2010-2060"}},{"id":"akhil_sikkim_khas_chettri_bahun_kalyan_sangh_s_f","name":"Akhil Sikkim Khas Chettri Bahun Kalyan Sangh's Foundation Day","en":"Akhil Sikkim Khas Chettri Bahun Kalyan Sangh's Foundation Day","category":"public","regional":{"SK":"d|0226:2010-2060"}},{"id":"holika_dahan","name":"Holika Dahan","en":"Holika Dahan","category":"public","regional":{"RJ":"d|0228:2010,2029|0301:2018|0303:2026|0304:2034|0305:2015|0307:2012,2023|0308:2031|0309:2020|0310:2028|0312:2017|0313:2025|0315:2033|0316:2014|0317:2022|0319:2011,2030|0320:2019|0321:2027|0323:2016,2035|0324:2024|0326:2013,2032|0328:2021","UK":"d|0228:2010,2029|0301:2018|0303:2026|0304:2034|0305:2015|0307:2012,2023|0308:2031|0309:2020|0310:2028|0312:2017|0313:2025|0315:2033|0316:2014|0317:2022|0319:2011,2030|0320:2019|0321:2027|0323:2016,2035|0324:2024|0326:2013,2032|0328:2021"}},{"id":"holi","name":"Holi","en":"Holi","category":"public","dates":"d|0301:2010,2029|0302:2018|0304:2026|0305:2034|0306:2015|0308:2012,2023|0309:2031|0310:2020|0311:2028|0313:2017|0314:2025|0316:2033|0317:2014|0318:2022|0320:2030|0321:2019|0322:2027|0324:2016,2035|0325:2024|0327:2013,2032|0329:2021","regional":{"MH":"d|0303:2026|0320:2011"}},{"id":"chaitra_sukladi","name":"Chaitra Sukladi","en":"Chaitra Sukladi","category":"public","regional":{"DH":"d|0316:2010|0318:2018|0319:2026|0321:2015,2034|0322:2023|0323:2012|0324:2031|0325:2020|0327:2028|0328:2017|0330:2025|0331:2014,2033|0402:2022|0403:2030|0404:2011|0406:2019|0407:2027|0408:2016|0409:2024,2035|0411:2013,2032|0413:2021|0414:2029"}},{"id":"cheti_chand","name":"Cheti Chand","en":"Cheti Chand","category":"public","regional":{"DH":"d|0316:2010|0318:2018|0319:2026|0321:2015,2034|0322:2023|0323:2012|0324:2031|0325:2020|0327:2028|0328:2017|0330:2025|0331:2014,2033|0402:2022|0403:2030|0404:2011|0406:2019|0407:2027|0408:2016|0409:2024,2035|0411:2013,2032|0413:2021|0414:2029","GJ":"d|0316:2010|0318:2018|0319:2026|0321:2015,2034|0322:2023|0323:2012|0324:2031|0325:2020|0327:2028|0328:2017|0330:2025|0331:2014,2033|0402:2022|0403:2030|0404:2011|0406:2019|0407:2027|0408:2016|0409:2024,2035|0411:2013,2032|0413:2021|0414:2029","RJ":"d|0316:2010|0318:2018|0319:2026|0321:2015,2034|0322:2023|0323:2012|0324:2031|0325:2020|0327:2028|0328:2017|0330:2025|0331:2014,2033|0402:2022|0403:2030|0404:2011|0406:2019|0407:2027|0408:2016|0409:2024,2035|0411:2013,2032|0413:2021|0414:2029","UK":"d|0316:2010|0318:2018|0319:2026|0321:2015,2034|0322:2023|0323:2012|0324:2031|0325:2020|0327:2028|0328:2017|0330:2025|0331:2014,2033|0402:2022|0403:2030|0404:2011|0406:2019|0407:2027|0408:2016|0409:2024,2035|0411:2013,2032|0413:2021|0414:2029"}},{"id":"gudi_padwa","name":"Gudi Padwa","en":"Gudi Padwa","category":"public","regional":{"DH":"d|0316:2010|0318:2018|0319:2026|0321:2015,2034|0322:2023|0323:2012|0324:2031|0325:2020|0327:2028|0328:2017|0330:2025|0331:2014,2033|0402:2022|0403:2030|0404:2011|0406:2019|0407:2027|0408:2016|0409:2024,2035|0411:2013,2032|0413:2021|0414:2029","GA":"d|0316:2010|0318:2018|0319:2026|0321:2015,2034|0322:2023|0323:2012|0324:2031|0325:2020|0327:2028|0328:2017|0330:2025|0331:2014,2033|0402:2022|0403:2030|0404:2011|0406:2019|0407:2027|0408:2016|0409:2024,2035|0411:2013,2032|0413:2021|0414:2029","MH":"d|0316:2010|0318:2018|0319:2026|0321:2015,2034|0322:2023|0323:2012|0324:2031|0325:2020|0327:2028|0328:2017|0330:2025|0331:2014,2033|0402:2022|0403:2030|0404:2011|0406:2019|0407:2027|0408:2016|0409:2024,2035|0411:2013,2032|0413:2021|0414:2029"}},{"id":"ugadi","name":"Ugadi","en":"Ugadi","category":"public","regional":{"DH":"d|0316:2010|0318:2018|0319:2026|0321:2015,2034|0322:2023|0323:2012|0324:2031|0325:2020|0327:2028|0328:2017|0330:2025|0331:2014,2033|0402:2022|0403:2030|0404:2011|0406:2019|0407:2027|0408:2016|0409:2024,2035|0411:2013,2032|0413:2021|0414:2029","KA":"d|0316:2010|0318:2018|0319:2026|0321:2015,2034|0322:2023|0323:2012|0324:2031|0325:2020|0327:2028|0328:2017|0330:2025|0331:2014,2033|0402:2022|0403:2030|0404:2011|0406:2019|0407:2027|0408:2016|0409:2024,2035|0411:2013,2032|0413:2021|0414:2029"}},{"id":"1st_navratra","name":"1st Navratra","en":"1st Navratra","category":"public","regional":{"JK":"d|0317:2010|0318:2018|0320:2026|0321:2015,2034|0322:2023|0323:2012|0324:2031|0325:2020|0327:2028|0329:2017|0330:2025|0331:2014,2033|0402:2022|0404:2011,2030|0406:2019|0408:2016,2027|0409:2024,2035|0411:2013,2032|0413:2021|0415:2029"}},{"id":"nowruz","name":"Nauroz","en":"Nowruz","category":"public","regional":{"JK":"d|0320:2012,2016,2020,2024,2028-2029,2032-2033,2036-2037,2040-2041,2044-2045,2048-2049,2052-2053,2056-2057,2060|0321:2010-2011,2013-2015,2017-2019,2021-2023,2025-2027,2030-2031,2034-2035,2038-2039,2042-2043,2046-2047,2050-2051,2054-2055,2058-2059","LA":"d|0320:2012,2016,2020,2024,2028-2029,2032-2033,2036-2037,2040-2041,2044-2045,2048-2049,2052-2053,2056-2057,2060|0321:2010-2011,2013-2015,2017-2019,2021-2023,2025-2027,2030-2031,2034-2035,2038-2039,2042-2043,2046-2047,2050-2051,2054-2055,2058-2059"}},{"id":"veerangana_avantibai_s_martyrdom_day","name":"Veerangana Avantibai's Shaheedi Diwas","en":"Veerangana Avantibai's Martyrdom Day","category":"public","regional":{"MP":"d|0320:2010-2060"}},{"id":"maundy_thursday","name":"Maundy Thursday","en":"Maundy Thursday","category":"public","regional":{"KL":"d|0322:2035,2046|0324:2016|0325:2027,2032|0326:2043,2054|0327:2059|0328:2013,2024|0329:2018,2029,2040|0330:2051,2056|0401:2010,2021|0402:2015,2026,2037,2048|0403:2042,2053|0405:2012|0406:2023,2034,2045|0407:2039,2050|0409:2020|0410:2031,2036|0411:2047,2058|0413:2017,2028|0414:2022,2033,2044|0415:2049,2055,2060|0417:2014,2025|0418:2019,2030,2041,2052|0419:2057|0421:2011|0422:2038"}},{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"shaheed_e_azam_bhagat_singh_sukhdev_and_rajguru_","name":"Shaheed-e-Azam Bhagat Singh, Sukhdev and Rajguru's Shaheedi Diwas","en":"Shaheed-e-Azam Bhagat Singh, Sukhdev and Rajguru's Martyrdom Day","category":"public","regional":{"HR":"d|0323:2010-2060","PB":"d|0323:2010-2060"}},{"id":"ram_navami","name":"Ram Navami","en":"Ram Navami","category":"public","dates":"d|0324:2010|0326:2026|0328:2015,2034|0330:2023|0401:2031|0402:2020|0403:2028|0404:2017|0407:2033|0408:2014|0412:2011,2030|0413:2019|0415:2016,2027|0416:2035|0417:2024|0419:2013,2032|0421:2021","regional":{"OD":"d|0327:2026"}},{"id":"easter_sunday","name":"Easter Sunday","en":"Easter Sunday","category":"public","regional":{"KL":"d|0325:2035,2046|0327:2016|0328:2027,2032|0329:2043,2054|0330:2059|0331:2013,2024|0401:2018,2029,2040|0402:2051,2056|0404:2010,2021|0405:2015,2026,2037,2048|0406:2042,2053|0408:2012|0409:2023,2034,2045|0410:2039,2050|0412:2020|0413:2031,2036|0414:2047,2058|0416:2017,2028|0417:2022,2033,2044|0418:2049,2055,2060|0420:2014,2025|0421:2019,2030,2041,2052|0422:2057|0424:2011|0425:2038"}},{"id":"mahavira_s_birthday","name":"Mahavir Jayanti","en":"Mahavira's Birthday","category":"public","dates":"d|0329:2018|0331:2026|0401:2034|0402:2015|0404:2023|0405:2012,2031|0406:2020|0407:2028|0409:2017|0410:2025|0412:2033|0413:2014|0414:2022|0416:2011,2030|0417:2019|0418:2027|0420:2016,2035|0421:2024|0423:2032|0424:2013|0425:2021|0426:2029|0428:2010"},{"id":"guru_nabha_dass_s_birthday","name":"Guru Nabha Dass's Jayanti","en":"Guru Nabha Dass's Birthday","category":"public","regional":{"PB":"d|0408:2010-2060"}},{"id":"mahatma_jyotiba_phule_s_birthday","name":"Mahatma Jyotiba Phule's Jayanti","en":"Mahatma Jyotiba Phule's Birthday","category":"public","regional":{"RJ":"d|0411:2010-2060"},"estimated":"d|0411:2033"},{"id":"bahag_bihu","name":"Bahag Bihu","en":"Bahag Bihu","category":"public","regional":{"AR":"d|0414:2012,2016,2020,2024,2028,2032,2036,2040,2044,2048,2052,2056,2060|0415:2010-2011,2013-2015,2017-2019,2021-2023,2025-2027,2029-2031,2033-2035,2037-2039,2041-2043,2045-2047,2049-2051,2053-2055,2057-2059","AS":"d|0413:2012,2016,2020,2024,2028,2032,2036,2040,2044,2048,2052,2056,2060|0414:2010-2060|0415:2010-2060|0416:2010-2011,2013-2015,2017-2019,2021-2023,2025-2027,2029-2031,2033-2035,2037-2039,2041-2043,2045-2047,2049-2051,2053-2055,2057-2059"}},{"id":"baisakhi","name":"Baisakhi","en":"Baisakhi","category":"public","regional":{"JK":"d|0413:2012-2013,2016,2020,2024-2025,2028,2032|0414:2010-2011,2014-2015,2017-2019,2021-2023,2026-2027,2029-2031,2033-2035"}},{"id":"vaisakhi","name":"Vaisakhi","en":"Vaisakhi","category":"public","regional":{"HR":"d|0413:2012-2013,2016,2020,2024-2025,2028,2032|0414:2010-2011,2014-2015,2017-2019,2021-2023,2026-2027,2029-2031,2033-2035","PB":"d|0413:2012-2013,2016,2020,2024-2025,2028,2032|0414:2010-2011,2014-2015,2017-2019,2021-2023,2026-2027,2029-2031,2033-2035"}},{"id":"bengali_new_year","name":"Pohela Boishakh","en":"Bengali New Year","category":"public","regional":{"TR":"d|0414:2012,2016,2020,2024,2028,2032|0415:2010-2011,2013-2015,2017-2019,2021-2023,2025-2027,2029-2031,2033-2035","WB":"d|0414:2012,2016,2020,2024,2028,2032|0415:2010-2011,2013-2015,2017-2019,2021-2023,2025-2027,2029-2031,2033-2035"}},{"id":"dr_b_r_ambedkar_s_birthday","name":"Dr. B. R. Ambedkar's Jayanti","en":"Dr. B. R. Ambedkar's Birthday","category":"public","dates":"d|0414:2010-2060"},{"id":"tamil_new_year","name":"Puthandu","en":"Tamil New Year","category":"public","regional":{"PY":"d|0414:2010-2035","TN":"d|0414:2010-2035"}},{"id":"vishu","name":"Vishu","en":"Vishu","category":"public","regional":{"KL":"d|0414:2010-2013,2016-2021,2024-2025,2028-2029,2032-2035|0415:2014-2015,2022-2023,2026-2027,2030-2031","PY":"d|0414:2010-2013,2016-2021,2024-2025,2028-2029,2032-2035|0415:2010-2035|0416:2014-2015,2022-2023,2026-2027,2030-2031"}},{"id":"himachal_day","name":"Himachal Day","en":"Himachal Day","category":"public","regional":{"HP":"d|0415:2010-2060"}},{"id":"akshay_tritiya","name":"Akshay Tritiya","en":"Akshay Tritiya","category":"public","regional":{"HR":"d|0418:2018|0419:2026|0420:2015,2034|0422:2023|0424:2012,2031|0425:2020|0427:2028|0428:2017|0429:2025|0501:2014,2033|0503:2022|0505:2011,2030|0507:2019|0508:2016,2027|0509:2035|0510:2024|0511:2032|0512:2013|0514:2021|0516:2010,2029"}},{"id":"lord_shri_parshuram_s_birthday","name":"Bhagvan Shri Parshuram's Jayanti","en":"Lord Shri Parshuram's Birthday","category":"public","regional":{"GJ":"d|0418:2018|0419:2026|0420:2015,2034|0422:2023|0424:2012,2031|0425:2020|0427:2028|0428:2017|0429:2025|0501:2014,2033|0503:2022|0505:2011,2030|0507:2019|0508:2016,2027|0509:2035|0510:2024|0511:2032|0512:2013|0514:2021|0516:2010,2029","HP":"d|0418:2018|0419:2026|0420:2015,2034|0422:2023|0424:2012,2031|0425:2020|0427:2028|0428:2017|0429:2025|0501:2014,2033|0503:2022|0505:2011,2030|0507:2019|0508:2016,2027|0509:2035|0510:2024|0511:2032|0512:2013|0514:2021|0516:2010,2029","HR":"d|0418:2018|0419:2026|0420:2015,2034|0422:2023|0424:2012,2031|0425:2020|0427:2028|0428:2017|0429:2025|0501:2014,2033|0503:2022|0505:2011,2030|0507:2019|0508:2016,2027|0509:2035|0510:2024|0511:2032|0512:2013|0514:2021|0516:2010,2029","PB":"d|0418:2018|0419:2026|0420:2015,2034|0422:2023|0424:2012,2031|0425:2020|0427:2028|0428:2017|0429:2025|0501:2014,2033|0503:2022|0505:2011,2030|0507:2019|0508:2016,2027|0509:2035|0510:2024|0511:2032|0512:2013|0514:2021|0516:2010,2029","RJ":"d|0418:2018|0419:2026|0420:2015,2034|0422:2023|0424:2012,2031|0425:2020|0427:2028|0428:2017|0429:2025|0501:2014,2033|0503:2022|0505:2011,2030|0507:2019|0508:2016,2027|0509:2035|0510:2024|0511:2032|0512:2013|0514:2021|0516:2010,2029"}},{"id":"garia_puja","name":"Garia Puja","en":"Garia Puja","category":"public","regional":{"TR":"d|0420:2012,2016,2020,2024,2028,2032,2036,2040,2044,2048,2052,2056,2060|0421:2010-2011,2013-2015,2017-2019,2021-2023,2025-2027,2029-2031,2033-2035,2037-2039,2041-2043,2045-2047,2049-2051,2053-2055,2057-2059"}},{"id":"buddha_purnima","name":"Buddha Purnima","en":"Buddha Purnima","category":"public","dates":"d|0430:2018|0501:2026|0503:2034|0504:2015|0505:2023|0506:2012|0507:2020,2031|0508:2028|0510:2017|0512:2025|0514:2014,2033|0516:2022|0517:2011,2030|0518:2019|0520:2027|0521:2016|0522:2035|0523:2024|0525:2013,2032|0526:2021|0527:2010,2029"},{"id":"maharashtra_day","name":"Maharashtra Day","en":"Maharashtra Day","category":"public","regional":{"MH":"d|0501:2010-2060"}},{"id":"may_day","name":"May Day","en":"May Day","category":"public","regional":{"AS":"d|0501:2010-2060","KA":"d|0501:2010-2060","KL":"d|0501:2010-2060","PY":"d|0501:2010-2060","TN":"d|0501:2010-2060","WB":"d|0501:2010-2060"}},{"id":"rabindranath_tagore_s_birthday","name":"Rabindranath Tagore's Jayanti","en":"Rabindranath Tagore's Birthday","category":"public","regional":{"WB":"d|0508:2012,2016,2020,2024,2028,2032,2036,2040,2044,2048,2052,2056,2060|0509:2010-2011,2013-2015,2017-2019,2021-2023,2025-2027,2029-2031,2033-2035,2037-2039,2041-2043,2045-2047,2049-2051,2053-2055,2057-2059"}},{"id":"maharana_pratap_s_birthday","name":"Maharana Pratap's Jayanti","en":"Maharana Pratap's Birthday","category":"public","regional":{"HP":"d|0515:2029|0520:2015,2034|0522:2023|0524:2012,2031|0525:2020|0527:2028|0528:2017|0529:2025|0531:2014,2033|0602:2022|0604:2011,2030|0606:2019|0607:2016,2027|0608:2035|0609:2024|0611:2013,2032|0613:2021|0615:2010|0616:2018|0617:2026","HR":"d|0515:2029|0520:2015,2034|0522:2023|0524:2012,2031|0525:2020|0527:2028|0528:2017|0529:2025|0531:2014,2033|0602:2022|0604:2011,2030|0606:2019|0607:2016,2027|0608:2035|0609:2024|0611:2013,2032|0613:2021|0615:2010|0616:2018|0617:2026","RJ":"d|0515:2029|0520:2015,2034|0522:2023|0524:2012,2031|0525:2020|0527:2028|0528:2017|0529:2025|0531:2014,2033|0602:2022|0604:2011,2030|0606:2019|0607:2016,2027|0608:2035|0609:2024|0611:2013,2032|0613:2021|0615:2010|0616:2018|0617:2026"}},{"id":"sikkim_state_day","name":"Sikkim State Day","en":"Sikkim State Day","category":"public","regional":{"SK":"d|0516:2010-2060"}},{"id":"guru_arjun_dev_s_martyrdom_day","name":"Guru Arjun Dev's Shaheedi Diwas","en":"Guru Arjun Dev's Martyrdom Day","category":"public","regional":{"PB":"d|0521:2010|0522:2034|0523:2023|0525:2015|0526:2020,2031|0528:2012,2028|0530:2025|0602:2017,2033|0603:2022|0605:2014,2030|0608:2027|0609:2011|0610:2024|0611:2035|0613:2016,2032|0614:2021|0616:2013,2019,2029|0617:2018|0618:2026"}},{"id":"sant_kabir_s_birthday","name":"Sant Kabir's Jayanti","en":"Sant Kabir's Birthday","category":"public","regional":{"HP":"d|0602:2015,2034|0604:2012,2023|0605:2020,2031|0607:2028|0609:2017|0611:2025|0612:2033|0613:2014|0614:2022|0615:2011,2030|0617:2019|0618:2027|0620:2016,2035|0622:2024|0623:2013,2032|0624:2021|0626:2010,2029|0628:2018|0629:2026","HR":"d|0602:2015,2034|0604:2012,2023|0605:2020,2031|0607:2028|0609:2017|0611:2025|0612:2033|0613:2014|0614:2022|0615:2011,2030|0617:2019|0618:2027|0620:2016,2035|0622:2024|0623:2013,2032|0624:2021|0626:2010,2029|0628:2018|0629:2026","PB":"d|0602:2015,2034|0604:2012,2023|0605:2020,2031|0607:2028|0609:2017|0611:2025|0612:2033|0613:2014|0614:2022|0615:2011,2030|0617:2019|0618:2027|0620:2016,2035|0622:2024|0623:2013,2032|0624:2021|0626:2010,2029|0628:2018|0629:2026"}},{"id":"young_mizo_association_s_day","name":"Young Mizo Association's Day","en":"Young Mizo Association's Day","category":"public","regional":{"MZ":"d|0615:2010-2060"}},{"id":"kharchi_puja","name":"Kharchi Puja","en":"Kharchi Puja","category":"public","regional":{"TR":"d|0626:2023|0627:2012|0628:2020,2031|0630:2028|0702:2017|0703:2025|0705:2014,2033|0707:2022|0708:2011,2030|0710:2019|0711:2027|0712:2016|0713:2035|0714:2024|0716:2013,2032|0717:2021|0719:2010,2029|0721:2018|0722:2026|0724:2015,2034"}},{"id":"peace_accord_day","name":"Remna Ni","en":"Peace Accord Day","category":"public","regional":{"MZ":"d|0630:2010-2060"}},{"id":"mizo_hmeichhe_insuihkhawm_pawl_s_day","name":"Mizo Hmeichhe Insuihkhawm Pawl's Day","en":"Mizo Hmeichhe Insuihkhawm Pawl's Day","category":"public","regional":{"MZ":"d|0706:2010-2060"}},{"id":"ker_puja","name":"Ker Puja","en":"Ker Puja","category":"public","regional":{"TR":"d|0711:2012,2023|0713:2033|0714:2020|0717:2030|0718:2017|0719:2014,2025|0721:2022,2035|0723:2011|0724:2019,2032|0725:2028|0726:2016|0728:2027,2029|0730:2013|0731:2021|0801:2034|0803:2010,2024|0804:2018,2026|0805:2031|0808:2015"}},{"id":"bhanu_s_birthday","name":"Bhanu's Jayanti","en":"Bhanu's Birthday","category":"public","regional":{"SK":"d|0713:2010-2060"},"estimated":"d|0713:2057"},{"id":"harela","name":"Harela","en":"Harela","category":"public","regional":{"UK":"d|0716:2010-2060"},"estimated":"d|0716:2054"},{"id":"shaheed_udham_singh_s_martyrdom_day","name":"Shaheed Udham Singh's Shaheedi Diwas","en":"Shaheed Udham Singh's Martyrdom Day","category":"public","regional":{"HR":"d|0731:2010-2060","PB":"d|0731:2010-2060"}},{"id":"raksha_bandhan","name":"Raksha Bandhan","en":"Raksha Bandhan","category":"public","regional":{"DH":"d|0802:2012,2031|0803:2020|0805:2028|0807:2017|0809:2025|0810:2014,2033|0811:2022|0813:2011,2030|0815:2019|0817:2027|0818:2016,2035|0819:2024|0820:2013,2032|0822:2021|0823:2029|0824:2010|0826:2018|0828:2026|0829:2015,2034|0830:2023","GJ":"d|0802:2012,2031|0803:2020|0805:2028|0807:2017|0809:2025|0810:2014,2033|0811:2022|0813:2011,2030|0815:2019|0817:2027|0818:2016,2035|0819:2024|0820:2013,2032|0822:2021|0823:2029|0824:2010|0826:2018|0828:2026|0829:2015,2034|0830:2023","HR":"d|0802:2012,2031|0803:2020|0805:2028|0807:2017|0809:2025|0810:2014,2033|0811:2022|0813:2011,2030|0815:2019|0817:2027|0818:2016,2035|0819:2024|0820:2013,2032|0822:2021|0823:2029|0824:2010|0826:2018|0828:2026|0829:2015,2034|0830:2023","MP":"d|0802:2012,2031|0803:2020|0805:2028|0807:2017|0809:2025|0810:2014,2033|0811:2022|0813:2011,2030|0815:2019|0817:2027|0818:2016,2035|0819:2024|0820:2013,2032|0822:2021|0823:2029|0824:2010|0826:2018|0828:2026|0829:2015,2034|0830:2023","RJ":"d|0802:2012,2031|0803:2020|0805:2028|0807:2017|0809:2025|0810:2014,2033|0811:2022|0813:2011,2030|0815:2019|0817:2027|0818:2016,2035|0819:2024|0820:2013,2032|0822:2021|0823:2029|0824:2010|0826:2018|0828:2026|0829:2015,2034|0830:2023","UK":"d|0802:2012,2031|0803:2020|0805:2028|0807:2017|0809:2025|0810:2014,2033|0811:2022|0813:2011,2030|0815:2019|0817:2027|0818:2016,2035|0819:2024|0820:2013,2032|0822:2021|0823:2029|0824:2010|0826:2018|0828:2026|0829:2015,2034|0830:2023","UP":"d|0802:2012,2031|0803:2020|0805:2028|0807:2017|0809:2025|0810:2014,2033|0811:2022|0813:2011,2030|0815:2019|0817:2027|0818:2016,2035|0819:2024|0820:2013,2032|0822:2021|0823:2029|0824:2010|0826:2018|0828:2026|0829:2015,2034|0830:2023"}},{"id":"parsi_new_year_shahenshahi","name":"Parsi New Year (Shahenshahi)","en":"Parsi New Year (Shahenshahi)","category":"public","regional":{"DH":"d|0806:2060|0807:2056-2059|0808:2052-2055|0809:2048-2051|0810:2044-2047|0811:2040-2043|0812:2036-2039|0813:2032-2035|0814:2028-2031|0815:2024-2027|0816:2020-2023|0817:2016-2019|0818:2012-2015|0819:2010-2011","GJ":"d|0806:2060|0807:2056-2059|0808:2052-2055|0809:2048-2051|0810:2044-2047|0811:2040-2043|0812:2036-2039|0813:2032-2035|0814:2028-2031|0815:2024-2027|0816:2020-2023|0817:2016-2019|0818:2012-2015|0819:2010-2011","MH":"d|0806:2060|0807:2056-2059|0808:2052-2055|0809:2048-2051|0810:2044-2047|0811:2040-2043|0812:2036-2039|0813:2032-2035|0814:2028-2031|0815:2024-2027|0816:2020-2023|0817:2016-2019|0818:2012-2015|0819:2010-2011"},"estimated":"d|0815:2027"},{"id":"international_day_of_the_world_s_indigenous_peop","name":"International Day of Adivasi Peoples","en":"International Day of the World's Indigenous Peoples","category":"public","regional":{"RJ":"d|0809:2010-2060"}},{"id":"janmashtami_vaishnava","name":"Janmashtami (Vaishnava)","en":"Janmashtami (Vaishnava)","category":"public","dates":"d|0810:2012,2031|0812:2020|0813:2028|0816:2025|0817:2033|0818:2014|0819:2022|0821:2030|0822:2011|0824:2019|0825:2016,2027|0826:2024,2035|0828:2013,2032|0830:2021|0901:2029|0902:2010|0903:2018|0904:2026|0905:2015|0906:2034|0907:2023"},{"id":"independence_day","name":"Independence Day","en":"Independence Day","category":"public","dates":"d|0815:2010-2060","estimated":"d|0815:2054"},{"id":"puducherry_de_jure_transfer_day","name":"Puducherry De Jure Transfer Day","en":"Puducherry De Jure Transfer Day","category":"public","regional":{"PY":"d|0816:2010-2060"}},{"id":"maharaja_bir_bikram_kishore_manikya_bahadur_s_bi","name":"Maharaja Bir Bikram Kishore Manikya Bahadur's Jayanti","en":"Maharaja Bir Bikram Kishore Manikya Bahadur's Birthday","category":"public","regional":{"TR":"d|0819:2010-2060"}},{"id":"nepali_bhasa_manyata_day","name":"Nepali Bhasa Manyata Diwas","en":"Nepali Bhasa Manyata Day","category":"public","regional":{"SK":"d|0820:2010-2060"}},{"id":"onam","name":"Onam","en":"Onam","category":"public","regional":{"AN":"d|0820:2013,2032|0821:2021|0822:2029|0823:2010|0824:2018|0826:2026|0828:2015,2034|0829:2012,2023|0830:2031|0831:2020|0901:2028|0904:2017|0905:2025|0906:2014,2033|0908:2022|0909:2011,2030|0911:2019|0912:2027|0913:2016|0914:2035|0915:2024","KL":"y|2010:0823-0826|2011:0909-0912|2012:0829-0901|2013:0820-0823|2014:0906-0909|2015:0828-0831|2016:0913-0916|2017:0904-0907|2018:0824-0827|2019:0911-0914|2020:0831-0903|2021:0821-0824|2022:0908-0911|2023:0829-0901|2024:0915-0918|2025:0905-0908|2026:0825-0828|2027:0912-0915|2028:0901-0904|2029:0822-0825|2030:0909-0912|2031:0830-0902|2032:0820-0823|2033:0906-0909|2034:0828-0831|2035:0914-0917","LD":"d|0820:2013,2032|0821:2021|0822:2029|0823:2010|0824:2018|0826:2026|0828:2015,2034|0829:2012,2023|0830:2031|0831:2020|0901:2028|0904:2017|0905:2025|0906:2014,2033|0908:2022|0909:2011,2030|0911:2019|0912:2027|0913:2016|0914:2035|0915:2024","PY":"y|2010:0823-0824|2011:0909-0910|2012:0829-0830|2013:0820-0821|2014:0906-0907|2015:0828-0829|2016:0913-0914|2017:0904-0905|2018:0824-0825|2019:0911-0912|2020:0831-0901|2021:0821-0822|2022:0908-0909|2023:0829-0830|2024:0915-0916|2025:0905-0906|2026:0826-0827|2027:0912-0913|2028:0901-0902|2029:0822-0823|2030:0909-0910|2031:0830-0831|2032:0820-0821|2033:0906-0907|2034:0828-0829|2035:0914-0915"}},{"id":"hartalika_teej","name":"Hartalika Teej","en":"Hartalika Teej","category":"public","regional":{"SK":"d|0821:2020|0822:2028|0824:2017|0826:2025|0828:2014,2033|0830:2022|0831:2011,2030|0901:2019|0903:2027|0904:2016,2035|0906:2024|0908:2013,2032|0909:2021|0910:2010|0911:2029|0912:2018|0914:2026|0915:2034|0916:2015|0918:2012,2023|0919:2031"}},{"id":"ganesh_chaturthi","name":"Id-ul-Fitr","en":"Ganesh Chaturthi","category":"public","regional":{"GA":"y|2010:0911-0912|2011:0901-0902|2012:0919-0920|2013:0909-0910|2014:0829-0830|2015:0917-0918|2016:0905-0906|2017:0825-0826|2018:0913-0914|2019:0902-0903|2020:0822-0823|2021:0910-0911|2022:0831-0901|2023:0919-0920|2024:0907-0908|2025:0827-0828|2026:0914-0915|2027:0904-0905|2028:0823-0824|2029:0911-0912|2030:0901-0902|2031:0920-0921|2032:0908-0909|2033:0828-0829|2034:0916-0917|2035:0905-0906","KA":"d|0822:2020|0823:2028|0825:2017|0827:2025|0828:2033|0829:2014|0831:2022|0901:2011,2030|0902:2019|0904:2027|0905:2016,2035|0907:2024|0908:2032|0909:2013|0910:2021|0911:2010,2029|0913:2018|0914:2026|0916:2034|0917:2015|0919:2012,2023|0920:2031","LD":"d|0822:2020|0823:2028|0825:2017|0827:2025|0828:2033|0829:2014|0831:2022|0901:2011,2030|0902:2019|0904:2027|0905:2016,2035|0907:2024|0908:2032|0909:2013|0910:2021|0911:2010,2029|0913:2018|0914:2026|0916:2034|0917:2015|0919:2012,2023|0920:2031","MH":"d|0822:2020|0823:2028|0825:2017|0827:2025|0828:2033|0829:2014|0831:2022|0901:2011,2030|0902:2019|0904:2027|0905:2016,2035|0907:2024|0908:2032|0909:2013|0910:2021|0911:2010,2029|0913:2018|0914:2026|0916:2034|0917:2015|0919:2012,2023|0920:2031","PY":"d|0822:2020|0823:2028|0825:2017|0827:2025|0828:2033|0829:2014|0831:2022|0901:2011,2030|0902:2019|0904:2027|0905:2016,2035|0907:2024|0908:2032|0909:2013|0910:2021|0911:2010,2029|0913:2018|0914:2026|0916:2034|0917:2015|0919:2012,2023|0920:2031","TN":"d|0822:2020|0823:2028|0825:2017|0827:2025|0828:2033|0829:2014|0831:2022|0901:2011,2030|0902:2019|0904:2027|0905:2016,2035|0907:2024|0908:2032|0909:2013|0910:2021|0911:2010,2029|0913:2018|0914:2026|0916:2034|0917:2015|0919:2012,2023|0920:2031"}},{"id":"ganesh_chaturthi_vinayak_chaturthi","name":"Id-ul-Fitr","en":"Ganesh Chaturthi / Vinayak Chaturthi","category":"public","regional":{"DH":"d|0822:2020|0823:2028|0825:2017|0827:2025|0828:2033|0829:2014|0831:2022|0901:2011,2030|0902:2019|0904:2027|0905:2016,2035|0907:2024|0908:2032|0909:2013|0910:2021|0911:2010,2029|0913:2018|0914:2026|0916:2034|0917:2015|0919:2012,2023|0920:2031"}},{"id":"samvatsari_day","name":"Samvatsari Day","en":"Samvatsari Day","category":"public","regional":{"GJ":"d|0823:2020|0824:2028|0826:2017|0828:2025|0830:2014,2033|0901:2022|0902:2011,2030|0903:2019|0904:2027|0906:2016,2035|0908:2024|0909:2032|0910:2013|0911:2021|0912:2010,2029|0914:2018|0915:2026|0917:2034|0918:2015|0920:2012,2023|0921:2031"}},{"id":"karma_puja","name":"Karma Puja","en":"Karma Puja","category":"public","regional":{"JH":"d|0829:2020|0830:2028|0902:2017|0903:2025|0905:2014,2033|0906:2022|0908:2011,2030|0909:2019|0911:2027|0913:2016,2035|0914:2024|0915:2013,2032|0917:2021|0918:2010,2029|0920:2018|0922:2026|0924:2015,2034|0925:2023|0926:2012|0927:2031"}},{"id":"khejarli_s_martyrdom_day","name":"Khejarli's Shaheedi Diwas","en":"Khejarli's Martyrdom Day","category":"public","regional":{"RJ":"d|0911:2010-2060"}},{"id":"vishwakarma_puja","name":"Vishwakarma Puja","en":"Vishwakarma Puja","category":"public","regional":{"UK":"d|0916:2012,2016,2020,2024|0917:2010-2011,2013-2015,2017-2019,2021-2023,2025-2035"}},{"id":"mahalaya","name":"Mahalaya","en":"Mahalaya","category":"public","regional":{"WB":"d|0917:2020|0918:2028|0919:2017|0921:2025|0923:2014,2033|0925:2022|0927:2011,2030|0928:2019|0929:2027|0930:2016|1001:2035|1002:2024|1004:2013,2032|1006:2021|1007:2010,2029|1008:2018|1010:2026|1011:2034|1012:2015|1014:2023|1015:2012,2031"}},{"id":"maharaj_agrasen_s_birthday","name":"Maharaj Agrasen's Jayanti","en":"Maharaj Agrasen's Birthday","category":"public","regional":{"HR":"d|0919:2028|0921:2017|0922:2025|0924:2033|0925:2014|0926:2022|0928:2011,2030|0929:2019|0930:2027|1001:2016|1002:2035|1003:2024|1005:2013,2032|1007:2021|1008:2010,2029|1010:2018|1011:2026|1013:2015,2034|1015:2023|1016:2012|1017:2020,2031","PB":"d|0919:2028|0921:2017|0922:2025|0924:2033|0925:2014|0926:2022|0928:2011,2030|0929:2019|0930:2027|1001:2016|1002:2035|1003:2024|1005:2013,2032|1007:2021|1008:2010,2029|1010:2018|1011:2026|1013:2015,2034|1015:2023|1016:2012|1017:2020,2031","RJ":"d|0919:2028|0921:2017|0922:2025|0924:2033|0925:2014|0926:2022|0928:2011,2030|0929:2019|0930:2027|1001:2016|1002:2035|1003:2024|1005:2013,2032|1007:2021|1008:2010,2029|1010:2018|1011:2026|1013:2015,2034|1015:2023|1016:2012|1017:2020,2031"}},{"id":"sharad_navratri","name":"Sharad Navratri","en":"Sharad Navratri","category":"public","regional":{"RJ":"d|0919:2028|0921:2017|0922:2025|0924:2033|0925:2014|0926:2022|0928:2011,2030|0929:2019|0930:2027|1001:2016|1002:2035|1003:2024|1005:2013,2032|1007:2021|1008:2010,2029|1010:2018|1011:2026|1013:2015,2034|1015:2023|1016:2012|1017:2020,2031"}},{"id":"shri_narayana_guru_s_death_anniversary","name":"Shri Narayana Guru's Death Anniversary","en":"Shri Narayana Guru's Death Anniversary","category":"public","regional":{"KL":"d|0921:2010-2060"}},{"id":"haryana_war_heroes_martyrdom_day","name":"Haryana War Heroes' Shaheedi Diwas","en":"Haryana War Heroes' Martyrdom Day","category":"public","regional":{"HR":"d|0923:2010-2060"}},{"id":"maharaja_hari_singh_s_birthday","name":"Maharaja Hari Singh's Jayanti","en":"Maharaja Hari Singh's Birthday","category":"public","regional":{"JK":"d|0923:2010-2060"}},{"id":"mahasaptami","name":"Mahasaptami","en":"Mahasaptami","category":"public","regional":{"TR":"d|0925:2028|0927:2017|0929:2025|1001:2014,2033|1002:2022|1003:2011,2030|1005:2019|1006:2027|1008:2016,2035|1010:2024|1011:2013,2032|1012:2021|1013:2029|1014:2010|1016:2018|1018:2026|1019:2034|1020:2015|1021:2012,2023|1022:2020,2031","WB":"d|0925:2028|0927:2017|0929:2025|1001:2014,2033|1002:2022|1003:2011,2030|1005:2019|1006:2027|1008:2016,2035|1010:2024|1011:2013,2032|1012:2021|1013:2029|1014:2010|1016:2018|1018:2026|1019:2034|1020:2015|1021:2012,2023|1022:2020,2031"}},{"id":"ayudha_puja","name":"Ayudha Puja","en":"Ayudha Puja","category":"public","regional":{"PY":"d|0926:2028|0929:2017|1001:2025|1002:2014,2033|1004:2022|1005:2011,2030|1006:2019|1008:2027|1010:2016,2035|1011:2024|1012:2013|1013:2032|1014:2021|1015:2029|1016:2010|1017:2018|1019:2026|1021:2015,2034|1023:2012,2023|1024:2020,2031","TN":"d|0926:2028|0929:2017|1001:2025|1002:2014,2033|1004:2022|1005:2011,2030|1006:2019|1008:2027|1010:2016,2035|1011:2024|1012:2013|1013:2032|1014:2021|1015:2029|1016:2010|1017:2018|1019:2026|1021:2015,2034|1023:2012,2023|1024:2020,2031"}},{"id":"durgashtami","name":"Durgashtami","en":"Durgashtami","category":"public","regional":{"RJ":"d|0926:2028|0928:2017|0930:2025|1002:2014,2033|1003:2022|1004:2011,2030|1006:2019|1007:2027|1009:2016,2035|1011:2024|1012:2013,2032|1013:2021|1014:2029|1015:2010|1017:2018|1019:2026|1020:2034|1021:2015|1022:2012,2023|1023:2020,2031"}},{"id":"mahanavami","name":"Mahanavami","en":"Mahanavami","category":"public","regional":{"JK":"d|0926:2028|0929:2017|1001:2025|1002:2014,2033|1004:2022|1005:2011,2030|1006:2019|1008:2027|1010:2016,2035|1011:2024|1012:2013|1013:2032|1014:2021|1015:2029|1016:2010|1017:2018|1019:2026|1021:2015,2034|1023:2012,2023|1024:2020,2031","KL":"d|0926:2028|0929:2017|1001:2025|1002:2014,2033|1004:2022|1005:2011,2030|1006:2019|1008:2027|1010:2016,2035|1011:2024|1012:2013|1013:2032|1014:2021|1015:2029|1016:2010|1017:2018|1020:2026|1021:2015,2034|1023:2012,2023|1024:2020,2031","WB":"d|0926:2028|0929:2017|1001:2025|1002:2014,2033|1004:2022|1005:2011,2030|1006:2019|1008:2027|1010:2016,2035|1011:2024|1012:2013|1013:2032|1014:2021|1015:2029|1016:2010|1017:2018|1020:2026|1021:2015,2034|1023:2012,2023|1024:2020,2031"}},{"id":"mahashtami","name":"Mahashtami","en":"Mahashtami","category":"public","regional":{"AR":"d|0926:2028|0928:2017|0930:2025|1002:2014,2033|1003:2022|1004:2011,2030|1006:2019|1007:2027|1009:2016,2035|1011:2024|1012:2013,2032|1013:2021|1014:2029|1015:2010|1017:2018|1019:2026|1020:2034|1021:2015|1022:2012,2023|1023:2020,2031","TR":"d|0926:2028|0928:2017|0930:2025|1002:2014,2033|1003:2022|1004:2011,2030|1006:2019|1007:2027|1009:2016,2035|1011:2024|1012:2013,2032|1013:2021|1014:2029|1015:2010|1017:2018|1019:2026|1020:2034|1021:2015|1022:2012,2023|1023:2020,2031","WB":"d|0926:2028|0928:2017|0930:2025|1002:2014,2033|1003:2022|1004:2011,2030|1006:2019|1007:2027|1009:2016,2035|1011:2024|1012:2013,2032|1013:2021|1014:2029|1015:2010|1017:2018|1019:2026|1020:2034|1021:2015|1022:2012,2023|1023:2020,2031"}},{"id":"dussehra","name":"Dussehra","en":"Dussehra","category":"public","dates":"d|0927:2028|0930:2017|1002:2025|1003:2014,2033|1005:2022|1006:2011,2030|1008:2019|1009:2027|1011:2016,2035|1012:2024|1013:2013|1014:2032|1015:2021|1016:2029|1017:2010|1019:2018|1020:2026|1022:2015,2034|1024:2012,2023|1025:2020,2031"},{"id":"vijayadashami","name":"Vijayadashami","en":"Vijayadashami","category":"public","regional":{"KL":"d|0927:2028|0930:2017|1002:2025|1003:2014,2033|1005:2022|1006:2011,2030|1008:2019|1009:2027|1011:2016,2035|1012:2024|1013:2013|1014:2032|1015:2021|1016:2029|1017:2010|1019:2018|1021:2026|1022:2015,2034|1024:2012,2023|1025:2020,2031","WB":"d|0927:2028|0930:2017|1002:2025|1003:2014,2033|1005:2022|1006:2011,2030|1008:2019|1009:2027|1011:2016,2035|1012:2024|1013:2013|1014:2032|1015:2021|1016:2029|1017:2010|1019:2018|1021:2026|1022:2015,2034|1024:2012,2023|1025:2020,2031"}},{"id":"mahatma_gandhi_s_birthday","name":"Mahatma Gandhi's Jayanti","en":"Mahatma Gandhi's Birthday","category":"public","dates":"d|1002:2010-2060"},{"id":"maharishi_valmiki_s_birthday","name":"Maharshi Valmiki's Jayanti","en":"Maharishi Valmiki's Birthday","category":"public","regional":{"CH":"d|1003:2028|1005:2017|1007:2025|1008:2014,2033|1009:2022|1011:2011,2030|1013:2019|1015:2027|1016:2016|1017:2024,2035|1018:2013,2032|1020:2021|1022:2010,2029|1024:2018|1026:2026|1027:2015,2034|1028:2023|1029:2012|1030:2031|1031:2020","HP":"d|1003:2028|1005:2017|1007:2025|1008:2014,2033|1009:2022|1011:2011,2030|1013:2019|1015:2027|1016:2016|1017:2024,2035|1018:2013,2032|1020:2021|1022:2010,2029|1024:2018|1026:2026|1027:2015,2034|1028:2023|1029:2012|1030:2031|1031:2020","HR":"d|1003:2028|1005:2017|1007:2025|1008:2014,2033|1009:2022|1011:2011,2030|1013:2019|1015:2027|1016:2016|1017:2024,2035|1018:2013,2032|1020:2021|1022:2010,2029|1024:2018|1026:2026|1027:2015,2034|1028:2023|1029:2012|1030:2031|1031:2020","PB":"d|1003:2028|1005:2017|1007:2025|1008:2014,2033|1009:2022|1011:2011,2030|1013:2019|1015:2027|1016:2016|1017:2024,2035|1018:2013,2032|1020:2021|1022:2010,2029|1024:2018|1026:2026|1027:2015,2034|1028:2023|1029:2012|1030:2031|1031:2020","UK":"d|1003:2028|1005:2017|1007:2025|1008:2014,2033|1009:2022|1011:2011,2030|1013:2019|1015:2027|1016:2016|1017:2024,2035|1018:2013,2032|1020:2021|1022:2010,2029|1024:2018|1026:2026|1027:2015,2034|1028:2023|1029:2012|1030:2031|1031:2020"}},{"id":"late_nar_bahadur_bhandari_s_birthday","name":"Late Nar Bahadur Bhandari's Jayanti","en":"Late Nar Bahadur Bhandari's Birthday","category":"public","regional":{"SK":"d|1005:2010-2060"}},{"id":"diwali","name":"Diwali (Deepavali)","en":"Diwali","category":"public","dates":"d|1017:2028|1019:2017|1020:2025|1022:2033|1023:2014|1024:2022|1026:2011,2030|1027:2019|1029:2027|1030:2016,2035|1031:2024|1102:2032|1103:2013|1104:2021|1105:2010,2029|1107:2018|1108:2026|1110:2034|1111:2015|1112:2023|1113:2012|1114:2020,2031"},{"id":"kali_puja","name":"Kali Puja","en":"Kali Puja","category":"public","regional":{"WB":"d|1017:2028|1019:2017|1020:2025|1022:2033|1023:2014|1024:2022|1026:2011,2030|1027:2019|1028:2027|1029:2016|1030:2035|1031:2024|1102:2013,2032|1104:2021|1105:2010,2029|1106:2018|1108:2026|1110:2015,2034|1112:2023|1113:2012|1114:2020,2031"}},{"id":"diwali_bali_pratipada","name":"Diwali (Bali Pratipada)","en":"Diwali (Bali Pratipada)","category":"public","regional":{"MH":"d|1018:2028|1020:2017|1022:2025|1023:2033|1024:2014|1025:2022|1027:2011,2030|1028:2019|1030:2027|1031:2016,2035|1102:2024|1103:2032|1104:2013|1105:2021|1106:2010,2029|1108:2018|1110:2026|1111:2034|1112:2015|1113:2023|1114:2012|1115:2020,2031"}},{"id":"govardhan_puja","name":"Govardhan Puja","en":"Govardhan Puja","category":"public","regional":{"DH":"d|1018:2028|1020:2017|1022:2025|1023:2033|1024:2014|1025:2022|1027:2011,2030|1028:2019|1030:2027|1031:2016,2035|1102:2024|1103:2032|1104:2013|1105:2021|1106:2010,2029|1108:2018|1110:2026|1111:2034|1112:2015|1113:2023|1114:2012|1115:2020,2031","MP":"d|1018:2028|1020:2017|1022:2025|1023:2033|1024:2014|1025:2022|1027:2011,2030|1028:2019|1030:2027|1031:2016,2035|1102:2024|1103:2032|1104:2013|1105:2021|1106:2010,2029|1108:2018|1110:2026|1111:2034|1112:2015|1113:2023|1114:2012|1115:2020,2031","RJ":"d|1018:2028|1020:2017|1022:2025|1023:2033|1024:2014|1025:2022|1027:2011,2030|1028:2019|1030:2027|1031:2016,2035|1102:2024|1103:2032|1104:2013|1105:2021|1106:2010,2029|1108:2018|1110:2026|1111:2034|1112:2015|1113:2023|1114:2012|1115:2020,2031","UP":"d|1018:2028|1020:2017|1022:2025|1023:2033|1024:2014|1025:2022|1027:2011,2030|1028:2019|1030:2027|1031:2016,2035|1102:2024|1103:2032|1104:2013|1105:2021|1106:2010,2029|1108:2018|1110:2026|1111:2034|1112:2015|1113:2023|1114:2012|1115:2020,2031"}},{"id":"gujarati_new_year","name":"Vikram Samvat New Year","en":"Gujarati New Year","category":"public","regional":{"GJ":"d|1018:2028|1020:2017|1022:2025|1024:2014,2033|1026:2022|1027:2011,2030|1028:2019|1030:2027|1031:2016|1101:2035|1102:2024|1104:2013,2032|1105:2021|1106:2029|1107:2010|1108:2018|1110:2026|1112:2015,2034|1114:2012,2023|1115:2031|1116:2020"}},{"id":"vishwakarma_day","name":"Vishwakarma Day","en":"Vishwakarma Day","category":"public","regional":{"HR":"d|1018:2028|1020:2017|1022:2025|1023:2033|1024:2014|1025:2022|1027:2011,2030|1028:2019|1030:2027|1031:2016,2035|1102:2024|1103:2032|1104:2013|1105:2021|1106:2010,2029|1108:2018|1110:2026|1111:2034|1112:2015|1113:2023|1114:2012|1115:2020,2031","PB":"d|1018:2028|1020:2017|1022:2025|1023:2033|1024:2014|1025:2022|1027:2011,2030|1028:2019|1030:2027|1031:2016,2035|1102:2024|1103:2032|1104:2013|1105:2021|1106:2010,2029|1108:2018|1110:2026|1111:2034|1112:2015|1113:2023|1114:2012|1115:2020,2031"}},{"id":"bhai_duj","name":"Bhai Duj","en":"Bhai Duj","category":"public","regional":{"GJ":"d|1019:2028|1021:2017|1023:2025|1024:2033|1025:2014|1026:2022|1028:2011,2030|1029:2019|1031:2027|1101:2016,2035|1103:2024|1104:2032|1105:2013|1106:2021|1107:2010,2029|1109:2018|1111:2026|1112:2034|1113:2015|1114:2023|1115:2012|1116:2020,2031","RJ":"d|1019:2028|1021:2017|1023:2025|1024:2033|1025:2014|1026:2022|1028:2011,2030|1029:2019|1031:2027|1101:2016,2035|1103:2024|1104:2032|1105:2013|1106:2021|1107:2010,2029|1109:2018|1111:2026|1112:2034|1113:2015|1114:2023|1115:2012|1116:2020,2031","UP":"d|1019:2028|1021:2017|1023:2025|1024:2033|1025:2014|1026:2022|1028:2011,2030|1029:2019|1031:2027|1101:2016,2035|1103:2024|1104:2032|1105:2013|1106:2021|1107:2010,2029|1109:2018|1111:2026|1112:2034|1113:2015|1114:2023|1115:2012|1116:2020,2031","WB":"d|1019:2028|1021:2017|1023:2025|1024:2033|1025:2014|1026:2022|1028:2011,2030|1029:2019|1031:2027|1101:2016,2035|1103:2024|1104:2032|1105:2013|1106:2021|1107:2010,2029|1109:2018|1111:2026|1112:2034|1113:2015|1114:2023|1115:2012|1116:2020,2031"}},{"id":"chitragupt_s_birthday","name":"Chitragupt's Jayanti","en":"Chitragupt's Birthday","category":"public","regional":{"UP":"d|1019:2028|1021:2017|1023:2025|1024:2033|1025:2014|1026:2022|1028:2011,2030|1029:2019|1031:2027|1101:2016,2035|1103:2024|1104:2032|1105:2013|1106:2021|1107:2010,2029|1109:2018|1111:2026|1112:2034|1113:2015|1114:2023|1115:2012|1116:2020,2031"}},{"id":"chhath_puja","name":"Chhath Puja","en":"Chhath Puja","category":"public","regional":{"JH":"d|1023:2028|1026:2017|1028:2025|1029:2014,2033|1030:2022|1101:2011,2030|1102:2019|1104:2027|1106:2016,2035|1107:2024|1108:2013|1109:2032|1110:2021|1111:2010,2029|1113:2018|1115:2026|1117:2015,2034|1119:2012,2023|1120:2020,2031","WB":"d|1023:2028|1026:2017|1028:2025|1029:2014,2033|1030:2022|1101:2011,2030|1102:2019|1104:2027|1106:2016,2035|1107:2024|1108:2013|1109:2032|1110:2021|1111:2010,2029|1113:2018|1115:2026|1117:2015,2034|1119:2012,2023|1120:2020,2031"}},{"id":"pratihar_shashthi_or_surya_shashthi_chhath_puja","name":"Pratihar Shashthi or Surya Shashthi (Chhath Puja)","en":"Pratihar Shashthi or Surya Shashthi (Chhath Puja)","category":"public","regional":{"DH":"d|1023:2028|1026:2017|1028:2025|1029:2014,2033|1030:2022|1101:2011,2030|1102:2019|1104:2027|1106:2016,2035|1107:2024|1108:2013|1109:2032|1110:2021|1111:2010,2029|1113:2018|1115:2026|1117:2015,2034|1119:2012,2023|1120:2020,2031"}},{"id":"accession_day","name":"Accession Day","en":"Accession Day","category":"public","regional":{"JK":"d|1026:2010-2060"}},{"id":"sardar_vallabhbhai_patel_s_birthday","name":"Sardar Vallabhbhai Patel's Jayanti","en":"Sardar Vallabhbhai Patel's Birthday","category":"public","regional":{"GJ":"d|1031:2010-2060"}},{"id":"haryana_day","name":"Haryana Day","en":"Haryana Day","category":"public","regional":{"HR":"d|1101:2010-2060"},"estimated":"d|1101:2044"},{"id":"puducherry_liberation_day","name":"Puducherry Liberation Day","en":"Puducherry Liberation Day","category":"public","regional":{"PY":"d|1101:2010-2060"}},{"id":"guru_nanak_s_birthday","name":"Guru Nanak's Jayanti","en":"Guru Nanak's Birthday","category":"public","dates":"d|1102:2028|1104:2017|1105:2025|1106:2014,2033|1108:2022|1110:2011,2030|1112:2019|1114:2016,2027|1115:2024,2035|1117:2013,2032|1119:2021|1121:2010,2029|1123:2018|1124:2026|1125:2015,2034|1127:2023|1128:2012,2031|1130:2020"},{"id":"birsa_munda_s_birthday","name":"Birsa Munda's Jayanti","en":"Birsa Munda's Birthday","category":"public","regional":{"WB":"d|1115:2010-2060"}},{"id":"kartar_singh_sarabha_s_martyrdom_day","name":"Kartar Singh Sarabha's Shaheedi Diwas","en":"Kartar Singh Sarabha's Martyrdom Day","category":"public","regional":{"PB":"d|1116:2010-2060"}},{"id":"guru_tegh_bahadur_s_martyrdom_day","name":"Guru Tegh Bahadur's Shaheedi Diwas","en":"Guru Tegh Bahadur's Martyrdom Day","category":"public","regional":{"PB":"d|1124:2010-2060","UK":"d|1124:2010-2060"},"estimated":"d|1124:2042"},{"id":"indigenous_faith_day","name":"Indigenous Faith Day","en":"Indigenous Faith Day","category":"public","regional":{"AR":"d|1201:2010-2060"},"estimated":"d|1201:2044"},{"id":"saint_francis_xavier_s_day","name":"Saint Francis Xavier's Day","en":"Saint Francis Xavier's Day","category":"public","regional":{"GA":"d|1203:2010-2060"}},{"id":"goa_liberation_day","name":"Goa Liberation Day","en":"Goa Liberation Day","category":"public","regional":{"GA":"d|1219:2010-2060"}},{"id":"christmas_eve","name":"Christmas Eve","en":"Christmas Eve","category":"public","regional":{"MZ":"d|1224:2010-2060"},"estimated":"d|1224:2042"},{"id":"christmas","name":"Christmas","en":"Christmas","category":"public","dates":"d|1225:2010-2060"},{"id":"post_christmas","name":"Post Christmas","en":"Post Christmas","category":"public","regional":{"MZ":"d|1226:2010-2060"}},{"id":"jor_mela_fatehgarh_sahib","name":"Jor Mela Fatehgarh Sahib","en":"Jor Mela Fatehgarh Sahib","category":"public","regional":{"PB":"d|1228:2010-2060"}},{"id":"tamu_lochhar","name":"Tamu Lochhar","en":"Tamu Lochhar","category":"public","regional":{"SK":"d|1230:2010-2018,2020-2022,2024-2029,2032|1231:2019,2023,2030-2031"}},{"id":"new_year_s_eve","name":"New Year's Eve","en":"New Year's Eve","category":"public","regional":{"MZ":"d|1231:2010-2060"}},{"id":"new_year_s_day_optional","name":"New Year's Day","en":"New Year's Day","category":"optional","dates":"d|0101:2010-2060"},{"id":"cherchera_government","name":"Cherchera","en":"Cherchera","category":"government","regional":{"CG":"d|0102:2018|0103:2026|0104:2034|0105:2015|0106:2023|0108:2031|0109:2012|0110:2020|0112:2017,2028|0113:2025|0115:2033|0116:2014|0117:2022|0119:2011,2030|0121:2019|0122:2027|0123:2035|0124:2016|0125:2024|0127:2013,2032|0128:2021|1231:2028"}},{"id":"cherchera_optional","name":"Cherchera","en":"Cherchera","category":"optional","regional":{"CG":"d|0102:2018|0103:2026|0104:2034|0105:2015|0106:2023|0108:2031|0109:2012|0110:2020|0112:2017,2028|0113:2025|0115:2033|0116:2014|0117:2022|0119:2011,2030|0121:2019|0122:2027|0123:2035|0124:2016|0125:2024|0127:2013,2032|0128:2021|1231:2028"}},{"id":"guru_gobind_singh_s_birthday_optional","name":"Guru Gobind Singh's Jayanti","en":"Guru Gobind Singh's Birthday","category":"optional","dates":"d|0102:2020|0104:2028|0105:2010-2012,2015,2017|0106:2025|0107:2014,2033|0109:2022|0110:2030|0113:2019|0115:2027,2029|0116:2016,2035|0117:2024,2034|0118:2013,2032|0120:2021|1225:2017|1227:2025|1229:2022|1231:2011,2030"},{"id":"isra_and_mi_raj_optional","name":"Shab-I-Miraj","en":"Isra' and Mi'raj","category":"optional","regional":{"AP":"y|2010:0710|2011:0630|2012:0618|2013:0607|2014:0527|2015:0517|2016:0505|2017:0425|2018:0414|2019:0404|2020:0323|2021:0312|2022:0301|2023:0219|2024:0209|2025:0128|2026:0117|2027:0106,1226|2028:1215|2029:1204|2030:1124|2031:1113|2032:1102|2033:1022|2034:1011|2035:0930|2036:0919|2037:0908|2038:0829|2039:0818|2040:0806|2041:0726|2042:0716|2043:0705|2044:0624|2045:0614|2046:0603|2047:0523|2048:0511|2049:0430|2050:0420|2051:0410|2052:0329|2053:0319|2054:0308|2055:0225|2056:0214|2057:0202|2058:0123|2059:0113|2060:0102,1221","LA":"y|2010:0710|2011:0630|2012:0618|2013:0607|2014:0527|2015:0517|2016:0505|2017:0425|2018:0414|2019:0404|2020:0323|2021:0312|2022:0301|2023:0219|2024:0209|2025:0128|2026:0117|2027:0106,1226|2028:1215|2029:1204|2030:1124|2031:1113|2032:1102|2033:1022|2034:1011|2035:0930|2036:0919|2037:0908|2038:0829|2039:0818|2040:0806|2041:0726|2042:0716|2043:0705|2044:0624|2045:0614|2046:0603|2047:0523|2048:0511|2049:0430|2050:0420|2051:0410|2052:0329|2053:0319|2054:0308|2055:0225|2056:0214|2057:0202|2058:0123|2059:0113|2060:0102,1221","LD":"y|2010:0710|2011:0630|2012:0618|2013:0607|2014:0527|2015:0517|2016:0505|2017:0425|2018:0414|2019:0404|2020:0323|2021:0312|2022:0301|2023:0219|2024:0209|2025:0128|2026:0117|2027:0106,1226|2028:1215|2029:1204|2030:1124|2031:1113|2032:1102|2033:1022|2034:1011|2035:0930|2036:0919|2037:0908|2038:0829|2039:0818|2040:0806|2041:0726|2042:0716|2043:0705|2044:0624|2045:0614|2046:0603|2047:0523|2048:0511|2049:0430|2050:0420|2051:0410|2052:0329|2053:0319|2054:0308|2055:0225|2056:0214|2057:0202|2058:0123|2059:0113|2060:0102,1221","TS":"y|2010:0710|2011:0630|2012:0618|2013:0607|2014:0527|2015:0517|2016:0505|2017:0425|2018:0414|2019:0404|2020:0323|2021:0312|2022:0301|2023:0219|2024:0209|2025:0128|2026:0117|2027:0106,1226|2028:1215|2029:1204|2030:1124|2031:1113|2032:1102|2033:1022|2034:1011|2035:0930|2036:0919|2037:0908|2038:0829|2039:0818|2040:0806|2041:0726|2042:0716|2043:0705|2044:0624|2045:0614|2046:0603|2047:0523|2048:0511|2049:0430|2050:0420|2051:0410|2052:0329|2053:0319|2054:0308|2055:0225|2056:0214|2057:0202|2058:0123|2059:0113|2060:0102,1221"},"estimated":"y|2010:0710|2011:0630|2012:0618|2013:0607|2014:0527|2015:0517|2016:0505|2017:0425|2018:0414|2019:0404|2020:0323|2021:0312|2022:0301|2023:0219|2024:0209|2025:0128|2026:0117|2027:0106,1226|2028:1215|2029:1204|2030:1124|2031:1113|2032:1102|2033:1022|2034:1011|2035:0930|2036:0919|2037:0908|2038:0829|2039:0818|2040:0806|2041:0726|2042:0716|2043:0705|2044:0624|2045:0614|2046:0603|2047:0523|2048:0511|2049:0430|2050:0420|2051:0410|2052:0329|2053:0319|2054:0308|2055:0225|2056:0214|2057:0202|2058:0123|2059:0113|2060:0102,1221"},{"id":"maa_shakambhari_s_birthday_government","name":"Maa Shakambhari's Jayanti","en":"Maa Shakambhari's Birthday","category":"government","regional":{"CG":"d|0102:2018|0103:2026|0104:2034|0105:2015|0106:2023|0108:2031|0109:2012|0110:2020|0112:2017,2028|0113:2025|0115:2033|0116:2014|0117:2022|0119:2011,2030|0121:2019|0122:2027|0123:2035|0124:2016|0125:2024|0127:2013,2032|0128:2021|1231:2028"}},{"id":"maa_shakambhari_s_birthday_optional","name":"Maa Shakambhari's Jayanti","en":"Maa Shakambhari's Birthday","category":"optional","regional":{"CG":"d|0102:2018|0103:2026|0104:2034|0105:2015|0106:2023|0108:2031|0109:2012|0110:2020|0112:2017,2028|0113:2025|0115:2033|0116:2014|0117:2022|0119:2011,2030|0121:2019|0122:2027|0123:2035|0124:2016|0125:2024|0127:2013,2032|0128:2021|1231:2028"}},{"id":"ali_s_birthday_optional","name":"Hazarat Ali's Birthday","en":"Ali's Birthday","category":"optional","dates":"y|2010:0626|2011:0616|2012:0604|2013:0524|2014:0513|2015:0503|2016:0421|2017:0411|2018:0401|2019:0321|2020:0309|2021:0226|2022:0215|2023:0205|2024:0125|2025:0114|2026:0103,1223|2027:1212|2028:1201|2029:1120|2030:1110|2031:1030|2032:1019|2033:1008|2034:0927|2035:0916|2036:0905|2037:0825|2038:0815|2039:0804|2040:0723|2041:0712|2042:0702|2043:0621|2044:0610|2045:0531|2046:0520|2047:0509|2048:0427|2049:0416|2050:0406|2051:0327|2052:0315|2053:0305|2054:0222|2055:0211|2056:0131|2057:0119|2058:0109,1230|2059:1219|2060:1207","estimated":"y|2027:1212|2028:1201|2029:1120|2030:1110|2031:1030|2032:1019|2033:1008|2034:0927|2035:0916|2036:0905|2037:0825|2038:0815|2039:0804|2040:0723|2041:0712|2042:0702|2043:0621|2044:0610|2045:0531|2046:0520|2047:0509|2048:0427|2049:0416|2050:0406|2051:0327|2052:0315|2053:0305|2054:0222|2055:0211|2056:0131|2057:0119|2058:0109,1230|2059:1219|2060:1207"},{"id":"arbaeen_government","name":"Chehlum","en":"Arbaeen","category":"government","regional":{"BR":"y|2010:0205|2011:0125|2012:0115|2013:0103,1224|2014:1213|2015:1203|2016:1121|2017:1110|2018:1030|2019:1020|2020:1008|2021:0928|2022:0917|2023:0906|2024:0825|2025:0815|2026:0804|2027:0725|2028:0714|2029:0703|2030:0622|2031:0611|2032:0530|2033:0520|2034:0510|2035:0429|2036:0418|2037:0407|2038:0327|2039:0316|2040:0305|2041:0222|2042:0212|2043:0201|2044:0122|2045:0110,1230|2046:1219|2047:1209|2048:1127|2049:1117|2050:1106|2051:1026|2052:1014|2053:1003|2054:0923|2055:0913|2056:0901|2057:0821|2058:0810|2059:0731|2060:0719"},"estimated":"y|2010:0205|2011:0125|2012:0115|2013:0103,1224|2014:1213|2015:1203|2016:1121|2017:1110|2018:1030|2019:1020|2020:1008|2021:0928|2022:0917|2023:0906|2024:0825|2025:0815|2026:0804|2027:0725|2028:0714|2029:0703|2030:0622|2031:0611|2032:0530|2033:0520|2034:0510|2035:0429|2036:0418|2037:0407|2038:0327|2039:0316|2040:0305|2041:0222|2042:0212|2043:0201|2044:0122|2045:0110,1230|2046:1219|2047:1209|2048:1127|2049:1117|2050:1106|2051:1026|2052:1014|2053:1003|2054:0923|2055:0913|2056:0901|2057:0821|2058:0810|2059:0731|2060:0719"},{"id":"arbaeen_optional","name":"Chehlum","en":"Arbaeen","category":"optional","regional":{"UK":"y|2010:0205|2011:0125|2012:0115|2013:0103,1224|2014:1213|2015:1203|2016:1121|2017:1110|2018:1030|2019:1020|2020:1008|2021:0928|2022:0917|2023:0906|2024:0825|2025:0815|2026:0804|2027:0725|2028:0714|2029:0703|2030:0622|2031:0611|2032:0530|2033:0520|2034:0510|2035:0429|2036:0418|2037:0407|2038:0327|2039:0316|2040:0305|2041:0222|2042:0212|2043:0201|2044:0122|2045:0110,1230|2046:1219|2047:1209|2048:1127|2049:1117|2050:1106|2051:1026|2052:1014|2053:1003|2054:0923|2055:0913|2056:0901|2057:0821|2058:0810|2059:0731|2060:0719","UP":"y|2010:0205|2011:0125|2012:0115|2013:0103,1224|2014:1213|2015:1203|2016:1121|2017:1110|2018:1030|2019:1020|2020:1008|2021:0928|2022:0917|2023:0906|2024:0825|2025:0815|2026:0804|2027:0725|2028:0714|2029:0703|2030:0622|2031:0611|2032:0530|2033:0520|2034:0510|2035:0429|2036:0418|2037:0407|2038:0327|2039:0316|2040:0305|2041:0222|2042:0212|2043:0201|2044:0122|2045:0110,1230|2046:1219|2047:1209|2048:1127|2049:1117|2050:1106|2051:1026|2052:1014|2053:1003|2054:0923|2055:0913|2056:0901|2057:0821|2058:0810|2059:0731|2060:0719"},"estimated":"y|2010:0205|2011:0125|2012:0115|2013:0103,1224|2014:1213|2015:1203|2016:1121|2017:1110|2018:1030|2019:1020|2020:1008|2021:0928|2022:0917|2023:0906|2024:0825|2025:0815|2026:0804|2027:0725|2028:0714|2029:0703|2030:0622|2031:0611|2032:0530|2033:0520|2034:0510|2035:0429|2036:0418|2037:0407|2038:0327|2039:0316|2040:0305|2041:0222|2042:0212|2043:0201|2044:0122|2045:0110,1230|2046:1219|2047:1209|2048:1127|2049:1117|2050:1106|2051:1026|2052:1014|2053:1003|2054:0923|2055:0913|2056:0901|2057:0821|2058:0810|2059:0731|2060:0719"},{"id":"ashura_government","name":"Muharram","en":"Ashura","category":"government","regional":{"AP":"y|2010:1217|2011:1206|2012:1125|2013:1114|2014:1104|2015:1024|2016:1012|2017:1001|2018:0921|2019:0910|2020:0830|2021:0819|2022:0809|2023:0729|2024:0717|2025:0706|2026:0626|2027:0616|2028:0604|2029:0524|2030:0513|2031:0503|2032:0421|2033:0411|2034:0331|2035:0321|2036:0309|2037:0226|2038:0215|2039:0205|2040:0125|2041:0114|2042:0103,1224|2043:1213|2044:1201|2045:1120|2046:1110|2047:1030|2048:1019|2049:1008|2050:0927|2051:0916|2052:0905|2053:0825|2054:0815|2055:0804|2056:0724|2057:0713|2058:0702|2059:0621|2060:0610","BR":"y|2010:1217|2011:1206|2012:1125|2013:1114|2014:1104|2015:1024|2016:1012|2017:1001|2018:0921|2019:0910|2020:0830|2021:0819|2022:0809|2023:0729|2024:0717|2025:0706|2026:0626|2027:0616|2028:0604|2029:0524|2030:0513|2031:0503|2032:0421|2033:0411|2034:0331|2035:0321|2036:0309|2037:0226|2038:0215|2039:0205|2040:0125|2041:0114|2042:0103,1224|2043:1213|2044:1201|2045:1120|2046:1110|2047:1030|2048:1019|2049:1008|2050:0927|2051:0916|2052:0905|2053:0825|2054:0815|2055:0804|2056:0724|2057:0713|2058:0702|2059:0621|2060:0610","CG":"y|2010:1217|2011:1206|2012:1125|2013:1114|2014:1104|2015:1024|2016:1012|2017:1001|2018:0921|2019:0910|2020:0830|2021:0819|2022:0809|2023:0729|2024:0717|2025:0706|2026:0626|2027:0616|2028:0604|2029:0524|2030:0513|2031:0503|2032:0421|2033:0411|2034:0331|2035:0321|2036:0309|2037:0226|2038:0215|2039:0205|2040:0125|2041:0114|2042:0103,1224|2043:1213|2044:1201|2045:1120|2046:1110|2047:1030|2048:1019|2049:1008|2050:0927|2051:0916|2052:0905|2053:0825|2054:0815|2055:0804|2056:0724|2057:0713|2058:0702|2059:0621|2060:0610","JH":"y|2010:1217|2011:1206|2012:1125|2013:1114|2014:1104|2015:1024|2016:1012|2017:1001|2018:0921|2019:0910|2020:0830|2021:0819|2022:0809|2023:0729|2024:0717|2025:0706|2026:0626|2027:0616|2028:0604|2029:0524|2030:0513|2031:0503|2032:0421|2033:0411|2034:0331|2035:0321|2036:0309|2037:0226|2038:0215|2039:0205|2040:0125|2041:0114|2042:0103,1224|2043:1213|2044:1201|2045:1120|2046:1110|2047:1030|2048:1019|2049:1008|2050:0927|2051:0916|2052:0905|2053:0825|2054:0815|2055:0804|2056:0724|2057:0713|2058:0702|2059:0621|2060:0610","ML":"y|2010:1217|2011:1206|2012:1125|2013:1114|2014:1104|2015:1024|2016:1012|2017:1001|2018:0921|2019:0910|2020:0830|2021:0819|2022:0809|2023:0729|2024:0717|2025:0706|2026:0626|2027:0616|2028:0604|2029:0524|2030:0513|2031:0503|2032:0421|2033:0411|2034:0331|2035:0321|2036:0309|2037:0226|2038:0215|2039:0205|2040:0125|2041:0114|2042:0103,1224|2043:1213|2044:1201|2045:1120|2046:1110|2047:1030|2048:1019|2049:1008|2050:0927|2051:0916|2052:0905|2053:0825|2054:0815|2055:0804|2056:0724|2057:0713|2058:0702|2059:0621|2060:0610","MN":"y|2010:1217|2011:1206|2012:1125|2013:1114|2014:1104|2015:1024|2016:1012|2017:1001|2018:0921|2019:0910|2020:0830|2021:0819|2022:0809|2023:0729|2024:0717|2025:0706|2026:0626|2027:0616|2028:0604|2029:0524|2030:0513|2031:0503|2032:0421|2033:0411|2034:0331|2035:0321|2036:0309|2037:0226|2038:0215|2039:0205|2040:0125|2041:0114|2042:0103,1224|2043:1213|2044:1201|2045:1120|2046:1110|2047:1030|2048:1019|2049:1008|2050:0927|2051:0916|2052:0905|2053:0825|2054:0815|2055:0804|2056:0724|2057:0713|2058:0702|2059:0621|2060:0610","MP":"y|2010:1217|2011:1206|2012:1125|2013:1114|2014:1104|2015:1024|2016:1012|2017:1001|2018:0921|2019:0910|2020:0830|2021:0819|2022:0809|2023:0729|2024:0717|2025:0706|2026:0626|2027:0616|2028:0604|2029:0524|2030:0513|2031:0503|2032:0421|2033:0411|2034:0331|2035:0321|2036:0309|2037:0226|2038:0215|2039:0205|2040:0125|2041:0114|2042:0103,1224|2043:1213|2044:1201|2045:1120|2046:1110|2047:1030|2048:1019|2049:1008|2050:0927|2051:0916|2052:0905|2053:0825|2054:0815|2055:0804|2056:0724|2057:0713|2058:0702|2059:0621|2060:0610","OD":"y|2010:1217|2011:1206|2012:1125|2013:1114|2014:1104|2015:1024|2016:1012|2017:1001|2018:0921|2019:0910|2020:0830|2021:0819|2022:0809|2023:0729|2024:0717|2025:0706|2026:0626|2027:0616|2028:0604|2029:0524|2030:0513|2031:0503|2032:0421|2033:0411|2034:0331|2035:0321|2036:0309|2037:0226|2038:0215|2039:0205|2040:0125|2041:0114|2042:0103,1224|2043:1213|2044:1201|2045:1120|2046:1110|2047:1030|2048:1019|2049:1008|2050:0927|2051:0916|2052:0905|2053:0825|2054:0815|2055:0804|2056:0724|2057:0713|2058:0702|2059:0621|2060:0610","TS":"y|2010:1217|2011:1206|2012:1125|2013:1114|2014:1104|2015:1024|2016:1012|2017:1001|2018:0921|2019:0910|2020:0830|2021:0819|2022:0809|2023:0729|2024:0717|2025:0706|2026:0626|2027:0616|2028:0604|2029:0524|2030:0513|2031:0503|2032:0421|2033:0411|2034:0331|2035:0321|2036:0309|2037:0226|2038:0215|2039:0205|2040:0125|2041:0114|2042:0103,1224|2043:1213|2044:1201|2045:1120|2046:1110|2047:1030|2048:1019|2049:1008|2050:0927|2051:0916|2052:0905|2053:0825|2054:0815|2055:0804|2056:0724|2057:0713|2058:0702|2059:0621|2060:0610"},"estimated":"y|2027:0616|2028:0604|2029:0524|2030:0513|2031:0503|2032:0421|2033:0411|2034:0331|2035:0321|2036:0309|2037:0226|2038:0215|2039:0205|2040:0125|2041:0114|2042:0103,1224|2043:1213|2044:1201|2045:1120|2046:1110|2047:1030|2048:1019|2049:1008|2050:0927|2051:0916|2052:0905|2053:0825|2054:0815|2055:0804|2056:0724|2057:0713|2058:0702|2059:0621|2060:0610"},{"id":"eid_al_fitr_government","name":"Ganesh Chaturthi","en":"Eid al-Fitr","category":"government","regional":{"AP":"y|2010:0911|2011:0831|2012:0820|2013:0809|2014:0729|2015:0718|2016:0706|2017:0626|2018:0616|2019:0605|2020:0525|2021:0514|2022:0503|2023:0422|2024:0411|2025:0331|2026:0321|2027:0310|2028:0227|2029:0215|2030:0205|2031:0125|2032:0115|2033:0103,1224|2034:1213|2035:1202|2036:1120|2037:1109|2038:1030|2039:1020|2040:1008|2041:0927|2042:0916|2043:0905|2044:0825|2045:0815|2046:0804|2047:0725|2048:0713|2049:0702|2050:0621|2051:0611|2052:0530|2053:0520|2054:0510|2055:0429|2056:0417|2057:0406|2058:0326|2059:0316|2060:0305","BR":"y|2010:0911|2011:0831|2012:0820|2013:0809|2014:0729|2015:0718|2016:0706|2017:0626|2018:0616|2019:0605|2020:0525|2021:0514|2022:0503|2023:0422|2024:0411|2025:0331|2026:0321|2027:0310|2028:0227|2029:0215|2030:0205|2031:0125|2032:0115|2033:0103,1224|2034:1213|2035:1202|2036:1120|2037:1109|2038:1030|2039:1020|2040:1008|2041:0927|2042:0916|2043:0905|2044:0825|2045:0815|2046:0804|2047:0725|2048:0713|2049:0702|2050:0621|2051:0611|2052:0530|2053:0520|2054:0510|2055:0429|2056:0417|2057:0406|2058:0326|2059:0316|2060:0305","CG":"y|2010:0911|2011:0831|2012:0820|2013:0809|2014:0729|2015:0718|2016:0706|2017:0626|2018:0616|2019:0605|2020:0525|2021:0514|2022:0503|2023:0422|2024:0411|2025:0331|2026:0321|2027:0310|2028:0227|2029:0215|2030:0205|2031:0125|2032:0115|2033:0103,1224|2034:1213|2035:1202|2036:1120|2037:1109|2038:1030|2039:1020|2040:1008|2041:0927|2042:0916|2043:0905|2044:0825|2045:0815|2046:0804|2047:0725|2048:0713|2049:0702|2050:0621|2051:0611|2052:0530|2053:0520|2054:0510|2055:0429|2056:0417|2057:0406|2058:0326|2059:0316|2060:0305","JH":"y|2010:0911|2011:0831|2012:0820|2013:0809|2014:0729|2015:0718|2016:0706|2017:0626|2018:0616|2019:0605|2020:0525|2021:0514|2022:0503|2023:0422|2024:0411|2025:0331|2026:0321|2027:0310|2028:0227|2029:0215|2030:0205|2031:0125|2032:0115|2033:0103,1224|2034:1213|2035:1202|2036:1120|2037:1109|2038:1030|2039:1020|2040:1008|2041:0927|2042:0916|2043:0905|2044:0825|2045:0815|2046:0804|2047:0725|2048:0713|2049:0702|2050:0621|2051:0611|2052:0530|2053:0520|2054:0510|2055:0429|2056:0417|2057:0406|2058:0326|2059:0316|2060:0305","ML":"y|2010:0911|2011:0831|2012:0820|2013:0809|2014:0729|2015:0718|2016:0706|2017:0626|2018:0616|2019:0605|2020:0525|2021:0514|2022:0503|2023:0422|2024:0411|2025:0331|2026:0321|2027:0310|2028:0227|2029:0215|2030:0205|2031:0125|2032:0115|2033:0103,1224|2034:1213|2035:1202|2036:1120|2037:1109|2038:1030|2039:1020|2040:1008|2041:0927|2042:0916|2043:0905|2044:0825|2045:0815|2046:0804|2047:0725|2048:0713|2049:0702|2050:0621|2051:0611|2052:0530|2053:0520|2054:0510|2055:0429|2056:0417|2057:0406|2058:0326|2059:0316|2060:0305","MN":"y|2010:0911|2011:0831|2012:0820|2013:0809|2014:0729|2015:0718|2016:0706|2017:0626|2018:0616|2019:0605|2020:0525|2021:0514|2022:0503|2023:0422|2024:0411|2025:0331|2026:0321|2027:0310|2028:0227|2029:0215|2030:0205|2031:0125|2032:0115|2033:0103,1224|2034:1213|2035:1202|2036:1120|2037:1109|2038:1030|2039:1020|2040:1008|2041:0927|2042:0916|2043:0905|2044:0825|2045:0815|2046:0804|2047:0725|2048:0713|2049:0702|2050:0621|2051:0611|2052:0530|2053:0520|2054:0510|2055:0429|2056:0417|2057:0406|2058:0326|2059:0316|2060:0305","MP":"y|2010:0911|2011:0831|2012:0820|2013:0809|2014:0729|2015:0718|2016:0706|2017:0626|2018:0616|2019:0605|2020:0525|2021:0514|2022:0503|2023:0422|2024:0411|2025:0331|2026:0321|2027:0310|2028:0227|2029:0215|2030:0205|2031:0125|2032:0115|2033:0103,1224|2034:1213|2035:1202|2036:1120|2037:1109|2038:1030|2039:1020|2040:1008|2041:0927|2042:0916|2043:0905|2044:0825|2045:0815|2046:0804|2047:0725|2048:0713|2049:0702|2050:0621|2051:0611|2052:0530|2053:0520|2054:0510|2055:0429|2056:0417|2057:0406|2058:0326|2059:0316|2060:0305","OD":"y|2010:0911|2011:0831|2012:0820|2013:0809|2014:0729|2015:0718|2016:0706|2017:0626|2018:0616|2019:0605|2020:0525|2021:0514|2022:0503|2023:0422|2024:0411|2025:0331|2026:0321|2027:0310|2028:0227|2029:0215|2030:0205|2031:0125|2032:0115|2033:0103,1224|2034:1213|2035:1202|2036:1120|2037:1109|2038:1030|2039:1020|2040:1008|2041:0927|2042:0916|2043:0905|2044:0825|2045:0815|2046:0804|2047:0725|2048:0713|2049:0702|2050:0621|2051:0611|2052:0530|2053:0520|2054:0510|2055:0429|2056:0417|2057:0406|2058:0326|2059:0316|2060:0305","TS":"y|2010:0911|2011:0831|2012:0820|2013:0809|2014:0729|2015:0718|2016:0706|2017:0626|2018:0616|2019:0605|2020:0525|2021:0514|2022:0503|2023:0422|2024:0411|2025:0331|2026:0321|2027:0310|2028:0227|2029:0215|2030:0205|2031:0125|2032:0115|2033:0103,1224|2034:1213|2035:1202|2036:1120|2037:1109|2038:1030|2039:1020|2040:1008|2041:0927|2042:0916|2043:0905|2044:0825|2045:0815|2046:0804|2047:0725|2048:0713|2049:0702|2050:0621|2051:0611|2052:0530|2053:0520|2054:0510|2055:0429|2056:0417|2057:0406|2058:0326|2059:0316|2060:0305"},"estimated":"y|2027:0310|2028:0227|2029:0215|2030:0205|2031:0125|2032:0115|2033:0103,1224|2034:1213|2035:1202|2036:1120|2037:1109|2038:1030|2039:1020|2040:1008|2041:0927|2042:0916|2043:0905|2044:0825|2045:0815|2046:0804|2047:0725|2048:0713|2049:0702|2050:0621|2051:0611|2052:0530|2053:0520|2054:0510|2055:0429|2056:0417|2057:0406|2058:0326|2059:0316|2060:0305"},{"id":"savitribai_phule_s_birthday_optional","name":"Savitribai Phule's Jayanti","en":"Savitribai Phule's Birthday","category":"optional","regional":{"CG":"d|0103:2010-2060"}},{"id":"eid_al_ghadir_optional","name":"Eid-e-Ghadeer","en":"Eid al-Ghadir","category":"optional","regional":{"AP":"y|2010:1125|2011:1115|2012:1104|2013:1024|2014:1013|2015:1002|2016:0920|2017:0910|2018:0830|2019:0820|2020:0809|2021:0729|2022:0718|2023:0707|2024:0625|2025:0615|2026:0605|2027:0525|2028:0514|2029:0503|2030:0422|2031:0411|2032:0331|2033:0320|2034:0310|2035:0227|2036:0216|2037:0204|2038:0125|2039:0114|2040:0104,1223|2041:1213|2042:1202|2043:1121|2044:1109|2045:1030|2046:1019|2047:1009|2048:0928|2049:0917|2050:0906|2051:0826|2052:0814|2053:0804|2054:0724|2055:0714|2056:0702|2057:0621|2058:0610|2059:0531|2060:0519","LA":"y|2010:1125|2011:1115|2012:1104|2013:1024|2014:1013|2015:1002|2016:0920|2017:0910|2018:0830|2019:0820|2020:0809|2021:0729|2022:0718|2023:0707|2024:0625|2025:0615|2026:0604-0605|2027:0525|2028:0514|2029:0503|2030:0422|2031:0411|2032:0331|2033:0320|2034:0310|2035:0227|2036:0216|2037:0204|2038:0125|2039:0114|2040:0104,1223|2041:1213|2042:1202|2043:1121|2044:1109|2045:1030|2046:1019|2047:1009|2048:0928|2049:0917|2050:0906|2051:0826|2052:0814|2053:0804|2054:0724|2055:0714|2056:0702|2057:0621|2058:0610|2059:0531|2060:0519","TS":"y|2010:1125|2011:1115|2012:1104|2013:1024|2014:1013|2015:1002|2016:0920|2017:0910|2018:0830|2019:0820|2020:0809|2021:0729|2022:0718|2023:0707|2024:0625|2025:0615|2026:0605|2027:0525|2028:0514|2029:0503|2030:0422|2031:0411|2032:0331|2033:0320|2034:0310|2035:0227|2036:0216|2037:0204|2038:0125|2039:0114|2040:0104,1223|2041:1213|2042:1202|2043:1121|2044:1109|2045:1030|2046:1019|2047:1009|2048:0928|2049:0917|2050:0906|2051:0826|2052:0814|2053:0804|2054:0724|2055:0714|2056:0702|2057:0621|2058:0610|2059:0531|2060:0519"},"estimated":"y|2010:1125|2011:1115|2012:1104|2013:1024|2014:1013|2015:1002|2016:0920|2017:0910|2018:0830|2019:0820|2020:0809|2021:0729|2022:0718|2023:0707|2024:0625|2025:0615|2026:0605|2027:0525|2028:0514|2029:0503|2030:0422|2031:0411|2032:0331|2033:0320|2034:0310|2035:0227|2036:0216|2037:0204|2038:0125|2039:0114|2040:0104,1223|2041:1213|2042:1202|2043:1121|2044:1109|2045:1030|2046:1019|2047:1009|2048:0928|2049:0917|2050:0906|2051:0826|2052:0814|2053:0804|2054:0724|2055:0714|2056:0702|2057:0621|2058:0610|2059:0531|2060:0519"},{"id":"prophet_s_birthday_government","name":"Milad-un-Nabi","en":"Prophet's Birthday","category":"government","regional":{"AP":"y|2010:0227|2011:0216|2012:0205|2013:0125|2014:0114|2015:0104,1224|2016:1213|2017:1202|2018:1121|2019:1110|2020:1030|2021:1019|2022:1009|2023:0928|2024:0916|2025:0905|2026:0826|2027:0815|2028:0804|2029:0725|2030:0714|2031:0703|2032:0621|2033:0610|2034:0531|2035:0521|2036:0509|2037:0429|2038:0418|2039:0407|2040:0326|2041:0316|2042:0305|2043:0223|2044:0212|2045:0131|2046:0120|2047:0109,1230|2048:1219|2049:1208|2050:1127|2051:1117|2052:1105|2053:1025|2054:1014|2055:1004|2056:0923|2057:0912|2058:0901|2059:0821|2060:0809","BR":"y|2010:0227|2011:0216|2012:0205|2013:0125|2014:0114|2015:0104,1224|2016:1213|2017:1202|2018:1121|2019:1110|2020:1030|2021:1019|2022:1009|2023:0928|2024:0916|2025:0905|2026:0826|2027:0815|2028:0804|2029:0725|2030:0714|2031:0703|2032:0621|2033:0610|2034:0531|2035:0521|2036:0509|2037:0429|2038:0418|2039:0407|2040:0326|2041:0316|2042:0305|2043:0223|2044:0212|2045:0131|2046:0120|2047:0109,1230|2048:1219|2049:1208|2050:1127|2051:1117|2052:1105|2053:1025|2054:1014|2055:1004|2056:0923|2057:0912|2058:0901|2059:0821|2060:0809","CG":"y|2010:0227|2011:0216|2012:0205|2013:0125|2014:0114|2015:0104,1224|2016:1213|2017:1202|2018:1121|2019:1110|2020:1030|2021:1019|2022:1009|2023:0928|2024:0916|2025:0905|2026:0826|2027:0815|2028:0804|2029:0725|2030:0714|2031:0703|2032:0621|2033:0610|2034:0531|2035:0521|2036:0509|2037:0429|2038:0418|2039:0407|2040:0326|2041:0316|2042:0305|2043:0223|2044:0212|2045:0131|2046:0120|2047:0109,1230|2048:1219|2049:1208|2050:1127|2051:1117|2052:1105|2053:1025|2054:1014|2055:1004|2056:0923|2057:0912|2058:0901|2059:0821|2060:0809","JH":"y|2010:0227|2011:0216|2012:0205|2013:0125|2014:0114|2015:0104,1224|2016:1213|2017:1202|2018:1121|2019:1110|2020:1030|2021:1019|2022:1009|2023:0928|2024:0916|2025:0905|2026:0826|2027:0815|2028:0804|2029:0725|2030:0714|2031:0703|2032:0621|2033:0610|2034:0531|2035:0521|2036:0509|2037:0429|2038:0418|2039:0407|2040:0326|2041:0316|2042:0305|2043:0223|2044:0212|2045:0131|2046:0120|2047:0109,1230|2048:1219|2049:1208|2050:1127|2051:1117|2052:1105|2053:1025|2054:1014|2055:1004|2056:0923|2057:0912|2058:0901|2059:0821|2060:0809","ML":"y|2010:0227|2011:0216|2012:0205|2013:0125|2014:0114|2015:0104,1224|2016:1213|2017:1202|2018:1121|2019:1110|2020:1030|2021:1019|2022:1009|2023:0928|2024:0916|2025:0905|2026:0826|2027:0815|2028:0804|2029:0725|2030:0714|2031:0703|2032:0621|2033:0610|2034:0531|2035:0521|2036:0509|2037:0429|2038:0418|2039:0407|2040:0326|2041:0316|2042:0305|2043:0223|2044:0212|2045:0131|2046:0120|2047:0109,1230|2048:1219|2049:1208|2050:1127|2051:1117|2052:1105|2053:1025|2054:1014|2055:1004|2056:0923|2057:0912|2058:0901|2059:0821|2060:0809","MN":"y|2010:0227|2011:0216|2012:0205|2013:0125|2014:0114|2015:0104,1224|2016:1213|2017:1202|2018:1121|2019:1110|2020:1030|2021:1019|2022:1009|2023:0928|2024:0916|2025:0905|2026:0826|2027:0815|2028:0804|2029:0725|2030:0714|2031:0703|2032:0621|2033:0610|2034:0531|2035:0521|2036:0509|2037:0429|2038:0418|2039:0407|2040:0326|2041:0316|2042:0305|2043:0223|2044:0212|2045:0131|2046:0120|2047:0109,1230|2048:1219|2049:1208|2050:1127|2051:1117|2052:1105|2053:1025|2054:1014|2055:1004|2056:0923|2057:0912|2058:0901|2059:0821|2060:0809","MP":"y|2010:0227|2011:0216|2012:0205|2013:0125|2014:0114|2015:0104,1224|2016:1213|2017:1202|2018:1121|2019:1110|2020:1030|2021:1019|2022:1009|2023:0928|2024:0916|2025:0905|2026:0826|2027:0815|2028:0804|2029:0725|2030:0714|2031:0703|2032:0621|2033:0610|2034:0531|2035:0521|2036:0509|2037:0429|2038:0418|2039:0407|2040:0326|2041:0316|2042:0305|2043:0223|2044:0212|2045:0131|2046:0120|2047:0109,1230|2048:1219|2049:1208|2050:1127|2051:1117|2052:1105|2053:1025|2054:1014|2055:1004|2056:0923|2057:0912|2058:0901|2059:0821|2060:0809","OD":"y|2010:0227|2011:0216|2012:0205|2013:0125|2014:0114|2015:0104,1224|2016:1213|2017:1202|2018:1121|2019:1110|2020:1030|2021:1019|2022:1009|2023:0928|2024:0916|2025:0905|2026:0826|2027:0815|2028:0804|2029:0725|2030:0714|2031:0703|2032:0621|2033:0610|2034:0531|2035:0521|2036:0509|2037:0429|2038:0418|2039:0407|2040:0326|2041:0316|2042:0305|2043:0223|2044:0212|2045:0131|2046:0120|2047:0109,1230|2048:1219|2049:1208|2050:1127|2051:1117|2052:1105|2053:1025|2054:1014|2055:1004|2056:0923|2057:0912|2058:0901|2059:0821|2060:0809","TS":"y|2010:0227|2011:0216|2012:0205|2013:0125|2014:0114|2015:0104,1224|2016:1213|2017:1202|2018:1121|2019:1110|2020:1030|2021:1019|2022:1009|2023:0928|2024:0916|2025:0905|2026:0826|2027:0815|2028:0804|2029:0725|2030:0714|2031:0703|2032:0621|2033:0610|2034:0531|2035:0521|2036:0509|2037:0429|2038:0418|2039:0407|2040:0326|2041:0316|2042:0305|2043:0223|2044:0212|2045:0131|2046:0120|2047:0109,1230|2048:1219|2049:1208|2050:1127|2051:1117|2052:1105|2053:1025|2054:1014|2055:1004|2056:0923|2057:0912|2058:0901|2059:0821|2060:0809"},"estimated":"y|2027:0815|2028:0804|2029:0725|2030:0714|2031:0703|2032:0621|2033:0610|2034:0531|2035:0521|2036:0509|2037:0429|2038:0418|2039:0407|2040:0326|2041:0316|2042:0305|2043:0223|2044:0212|2045:0131|2046:0120|2047:0109,1230|2048:1219|2049:1208|2050:1127|2051:1117|2052:1105|2053:1025|2054:1014|2055:1004|2056:0923|2057:0912|2058:0901|2059:0821|2060:0809"},{"id":"eid_al_adha_government","name":"Id-ul-Zuha (Bakrid)","en":"Eid al-Adha","category":"government","regional":{"AP":"y|2010:1117|2011:1107|2012:1027|2013:1016|2014:1006|2015:0925|2016:0912|2017:0902|2018:0822|2019:0812|2020:0801|2021:0721|2022:0710|2023:0629|2024:0617|2025:0607|2026:0527|2027:0517|2028:0506|2029:0425|2030:0414|2031:0403|2032:0323|2033:0312|2034:0302|2035:0219|2036:0208|2037:0127|2038:0117|2039:0106,1227|2040:1215|2041:1205|2042:1124|2043:1113|2044:1101|2045:1022|2046:1011|2047:1001|2048:0920|2049:0909|2050:0829|2051:0818|2052:0806|2053:0727|2054:0716|2055:0706|2056:0624|2057:0613|2058:0602|2059:0523|2060:0511","BR":"y|2010:1117|2011:1107|2012:1027|2013:1016|2014:1006|2015:0925|2016:0912|2017:0902|2018:0822|2019:0812|2020:0801|2021:0721|2022:0710|2023:0629|2024:0617|2025:0607|2026:0527|2027:0517|2028:0506|2029:0425|2030:0414|2031:0403|2032:0323|2033:0312|2034:0302|2035:0219|2036:0208|2037:0127|2038:0117|2039:0106,1227|2040:1215|2041:1205|2042:1124|2043:1113|2044:1101|2045:1022|2046:1011|2047:1001|2048:0920|2049:0909|2050:0829|2051:0818|2052:0806|2053:0727|2054:0716|2055:0706|2056:0624|2057:0613|2058:0602|2059:0523|2060:0511","CG":"y|2010:1117|2011:1107|2012:1027|2013:1016|2014:1006|2015:0925|2016:0912|2017:0902|2018:0822|2019:0812|2020:0801|2021:0721|2022:0710|2023:0629|2024:0617|2025:0607|2026:0527|2027:0517|2028:0506|2029:0425|2030:0414|2031:0403|2032:0323|2033:0312|2034:0302|2035:0219|2036:0208|2037:0127|2038:0117|2039:0106,1227|2040:1215|2041:1205|2042:1124|2043:1113|2044:1101|2045:1022|2046:1011|2047:1001|2048:0920|2049:0909|2050:0829|2051:0818|2052:0806|2053:0727|2054:0716|2055:0706|2056:0624|2057:0613|2058:0602|2059:0523|2060:0511","JH":"y|2010:1117|2011:1107|2012:1027|2013:1016|2014:1006|2015:0925|2016:0912|2017:0902|2018:0822|2019:0812|2020:0801|2021:0721|2022:0710|2023:0629|2024:0617|2025:0607|2026:0527|2027:0517|2028:0506|2029:0425|2030:0414|2031:0403|2032:0323|2033:0312|2034:0302|2035:0219|2036:0208|2037:0127|2038:0117|2039:0106,1227|2040:1215|2041:1205|2042:1124|2043:1113|2044:1101|2045:1022|2046:1011|2047:1001|2048:0920|2049:0909|2050:0829|2051:0818|2052:0806|2053:0727|2054:0716|2055:0706|2056:0624|2057:0613|2058:0602|2059:0523|2060:0511","ML":"y|2010:1117|2011:1107|2012:1027|2013:1016|2014:1006|2015:0925|2016:0912|2017:0902|2018:0822|2019:0812|2020:0801|2021:0721|2022:0710|2023:0629|2024:0617|2025:0607|2026:0527|2027:0517|2028:0506|2029:0425|2030:0414|2031:0403|2032:0323|2033:0312|2034:0302|2035:0219|2036:0208|2037:0127|2038:0117|2039:0106,1227|2040:1215|2041:1205|2042:1124|2043:1113|2044:1101|2045:1022|2046:1011|2047:1001|2048:0920|2049:0909|2050:0829|2051:0818|2052:0806|2053:0727|2054:0716|2055:0706|2056:0624|2057:0613|2058:0602|2059:0523|2060:0511","MN":"y|2010:1117|2011:1107|2012:1027|2013:1016|2014:1006|2015:0925|2016:0912|2017:0902|2018:0822|2019:0812|2020:0801|2021:0721|2022:0710|2023:0629|2024:0617|2025:0607|2026:0527|2027:0517|2028:0506|2029:0425|2030:0414|2031:0403|2032:0323|2033:0312|2034:0302|2035:0219|2036:0208|2037:0127|2038:0117|2039:0106,1227|2040:1215|2041:1205|2042:1124|2043:1113|2044:1101|2045:1022|2046:1011|2047:1001|2048:0920|2049:0909|2050:0829|2051:0818|2052:0806|2053:0727|2054:0716|2055:0706|2056:0624|2057:0613|2058:0602|2059:0523|2060:0511","MP":"y|2010:1117|2011:1107|2012:1027|2013:1016|2014:1006|2015:0925|2016:0912|2017:0902|2018:0822|2019:0812|2020:0801|2021:0721|2022:0710|2023:0629|2024:0617|2025:0607|2026:0527|2027:0517|2028:0506|2029:0425|2030:0414|2031:0403|2032:0323|2033:0312|2034:0302|2035:0219|2036:0208|2037:0127|2038:0117|2039:0106,1227|2040:1215|2041:1205|2042:1124|2043:1113|2044:1101|2045:1022|2046:1011|2047:1001|2048:0920|2049:0909|2050:0829|2051:0818|2052:0806|2053:0727|2054:0716|2055:0706|2056:0624|2057:0613|2058:0602|2059:0523|2060:0511","OD":"y|2010:1117|2011:1107|2012:1027|2013:1016|2014:1006|2015:0925|2016:0912|2017:0902|2018:0822|2019:0812|2020:0801|2021:0721|2022:0710|2023:0629|2024:0617|2025:0607|2026:0527|2027:0517|2028:0506|2029:0425|2030:0414|2031:0403|2032:0323|2033:0312|2034:0302|2035:0219|2036:0208|2037:0127|2038:0117|2039:0106,1227|2040:1215|2041:1205|2042:1124|2043:1113|2044:1101|2045:1022|2046:1011|2047:1001|2048:0920|2049:0909|2050:0829|2051:0818|2052:0806|2053:0727|2054:0716|2055:0706|2056:0624|2057:0613|2058:0602|2059:0523|2060:0511","TS":"y|2010:1117|2011:1107|2012:1027|2013:1016|2014:1006|2015:0925|2016:0912|2017:0902|2018:0822|2019:0812|2020:0801|2021:0721|2022:0710|2023:0629|2024:0617|2025:0607|2026:0527|2027:0517|2028:0506|2029:0425|2030:0414|2031:0403|2032:0323|2033:0312|2034:0302|2035:0219|2036:0208|2037:0127|2038:0117|2039:0106,1227|2040:1215|2041:1205|2042:1124|2043:1113|2044:1101|2045:1022|2046:1011|2047:1001|2048:0920|2049:0909|2050:0829|2051:0818|2052:0806|2053:0727|2054:0716|2055:0706|2056:0624|2057:0613|2058:0602|2059:0523|2060:0511"},"estimated":"y|2027:0517|2028:0506|2029:0425|2030:0414|2031:0403|2032:0323|2033:0312|2034:0302|2035:0219|2036:0208|2037:0127|2038:0117|2039:0106,1227|2040:1215|2041:1205|2042:1124|2043:1113|2044:1101|2045:1022|2046:1011|2047:1001|2048:0920|2049:0909|2050:0829|2051:0818|2052:0806|2053:0727|2054:0716|2055:0706|2056:0624|2057:0613|2058:0602|2059:0523|2060:0511"},{"id":"maharishi_guru_gokuldas_s_birthday_optional","name":"Maharshi Guru Gokuldas's Jayanti","en":"Maharishi Guru Gokuldas's Birthday","category":"optional","regional":{"MP":"d|0106:2010-2060"}},{"id":"jumu_atul_wida_optional","name":"Jamat-Ul-Vida","en":"Jumu'atul-Wida","category":"optional","dates":"y|2010:0910|2011:0826|2012:0817|2013:0802|2014:0725|2015:0717|2016:0701|2017:0623|2018:0615|2019:0531|2020:0522|2021:0507|2022:0429|2023:0421|2024:0405|2025:0328|2026:0320|2027:0305|2028:0225|2029:0209|2030:0201|2031:0117|2032:0109,1231|2033:1216|2034:1208|2035:1130|2036:1114|2037:1106|2038:1022|2039:1014|2040:1005|2041:0920|2042:0912|2043:0828|2044:0819|2045:0811|2046:0727|2047:0719|2048:0710|2049:0625|2050:0617|2051:0609|2052:0524|2053:0516|2054:0508|2055:0423|2056:0414|2057:0330|2058:0322|2059:0314|2060:0227","estimated":"y|2027:0305|2028:0225|2029:0209|2030:0201|2031:0117|2032:0109,1231|2033:1216|2034:1208|2035:1130|2036:1114|2037:1106|2038:1022|2039:1014|2040:1005|2041:0920|2042:0912|2043:0828|2044:0819|2045:0811|2046:0727|2047:0719|2048:0710|2049:0625|2050:0617|2051:0609|2052:0524|2053:0516|2054:0508|2055:0423|2056:0414|2057:0330|2058:0322|2059:0314|2060:0227"},{"id":"maharaja_gambhir_singh_s_death_anniversary_government","name":"Maharaja Gambhir Singh's Death Anniversary","en":"Maharaja Gambhir Singh's Death Anniversary","category":"government","regional":{"MN":"d|0109:2010-2060"}},{"id":"bhogi_government","name":"Bhogi","en":"Bhogi","category":"government","regional":{"AP":"d|0113:2010,2013-2014,2017-2018,2021-2026,2029-2030,2033-2034|0114:2011-2012,2015-2016,2019-2020,2027-2028,2031-2032,2035"}},{"id":"lohri_optional","name":"Lohri","en":"Lohri","category":"optional","dates":"d|0113:2021-2022,2024|0114:2020","regional":{"PB":"d|0113:2010,2013-2014,2017-2018,2023,2025-2026,2029-2030,2033-2034|0114:2011-2012,2015-2016,2019,2027-2028,2031-2032,2035"}},{"id":"magh_bihu_optional","name":"Magh Bihu","en":"Magh Bihu","category":"optional","dates":"d|0114:2021-2023,2025-2026,2029-2030,2033-2034|0115:2024,2027-2028,2031-2032,2035"},{"id":"makar_sankranti_government","name":"Makar Sankranti","en":"Makar Sankranti","category":"government","regional":{"AP":"d|0114:2010,2013-2014,2017-2018,2021-2026,2029-2030,2033-2034|0115:2011-2012,2015-2016,2019-2020,2027-2028,2031-2032,2035","JH":"d|0114:2010,2013-2014,2017-2018,2021-2026,2029-2030,2033-2034|0115:2011-2012,2015-2016,2019-2020,2027-2028,2031-2032,2035"}},{"id":"makar_sankranti_optional","name":"Makar Sankranti","en":"Makar Sankranti","category":"optional","dates":"d|0114:2010,2013-2014,2017-2018,2021-2026,2029-2030,2033-2034|0115:2011-2012,2015-2016,2019-2020,2027-2028,2031-2032,2035"},{"id":"pongal_optional","name":"Pongal","en":"Pongal","category":"optional","dates":"d|0114:2010,2013-2014,2017-2018,2021-2022,2025-2026,2029-2030,2033-2034|0115:2011-2012,2015-2016,2019-2020,2023-2024,2027-2028,2031-2032,2035"},{"id":"kanuma_government","name":"Kanuma","en":"Kanuma","category":"government","regional":{"AP":"d|0115:2010,2013-2014,2017-2018,2021-2026,2029-2030,2033-2034|0116:2011-2012,2015-2016,2019-2020,2027-2028,2031-2032,2035"}},{"id":"kanuma_optional","name":"Kanuma","en":"Kanuma","category":"optional","regional":{"PY":"d|0115:2010,2013-2014,2017-2018,2021-2025,2029-2030,2033-2034|0116:2011-2012,2015-2016,2019-2020,2026-2028,2031-2032,2035","TS":"d|0115:2010,2013-2014,2017-2018,2021-2026,2029-2030,2033-2034|0116:2011-2012,2015-2016,2019-2020,2027-2028,2031-2032,2035"}},{"id":"vassi_uttarayan_optional","name":"Vassi Uttarayan","en":"Vassi Uttarayan","category":"optional","regional":{"GJ":"d|0115:2010,2013-2014,2017-2018,2021-2026,2029-2030,2033-2034|0116:2011-2012,2015-2016,2019-2020,2027-2028,2031-2032,2035"}},{"id":"saint_joseph_vaz_s_day_optional","name":"Saint Joseph Vaz's Day","en":"Saint Joseph Vaz's Day","category":"optional","regional":{"GA":"d|0116:2010-2060","PY":"d|0116:2010-2060"}},{"id":"silpi_day_optional","name":"Silpi Divas","en":"Silpi Day","category":"optional","regional":{"AS":"d|0117:2010-2060"}},{"id":"basant_panchami_shri_panchami_optional","name":"Basant Panchami / Shri Panchami","en":"Basant Panchami / Shri Panchami","category":"optional","dates":"d|0119:2029|0120:2010|0122:2018|0123:2026|0124:2015,2034|0126:2023|0127:2031|0128:2012|0129:2020|0131:2028|0201:2017|0202:2025|0204:2014,2033|0205:2022|0207:2030|0208:2011|0209:2019|0211:2027|0212:2016,2035|0214:2024|0215:2032|0216:2021"},{"id":"satguru_ram_singh_s_birthday_optional","name":"Satguru Ram Singh's Jayanti","en":"Satguru Ram Singh's Birthday","category":"optional","regional":{"PB":"d|0119:2029|0120:2010|0122:2018|0123:2026|0124:2015,2034|0126:2023|0127:2031|0128:2012|0129:2020|0131:2028|0201:2017|0202:2025|0204:2014,2033|0205:2022|0207:2030|0208:2011|0209:2019|0211:2027|0212:2016,2035|0214:2013,2024|0215:2032|0216:2021"}},{"id":"gend_singh_s_martyrdom_day_optional","name":"Gend Singh's Shaheedi Diwas","en":"Gend Singh's Martyrdom Day","category":"optional","regional":{"CG":"d|0120:2010-2060"}},{"id":"hemu_kalani_s_martyrdom_day_optional","name":"Hemu Kalani's Shaheedi Diwas","en":"Hemu Kalani's Martyrdom Day","category":"optional","regional":{"MP":"d|0121:2010-2060"}},{"id":"netaji_subhas_chandra_bose_s_birthday_government","name":"Netaji Subhas Chandra Bose's Jayanti","en":"Netaji Subhas Chandra Bose's Birthday","category":"government","regional":{"JH":"d|0123:2010-2060","OD":"d|0123:2010-2060"}},{"id":"netaji_subhas_chandra_bose_s_birthday_optional","name":"Netaji Subhas Chandra Bose's Jayanti","en":"Netaji Subhas Chandra Bose's Birthday","category":"optional","regional":{"CG":"d|0123:2010-2060"}},{"id":"karpuri_thakur_s_birthday_optional","name":"Karpuri Thakur's Jayanti","en":"Karpuri Thakur's Birthday","category":"optional","regional":{"BR":"d|0124:2010-2060","UP":"d|0124:2010-2060"}},{"id":"republic_day_government","name":"Republic Day","en":"Republic Day","category":"government","regional":{"AP":"d|0126:2010-2060","BR":"d|0126:2010-2060","CG":"d|0126:2010-2060","JH":"d|0126:2010-2060","ML":"d|0126:2010-2060","MN":"d|0126:2010-2060","MP":"d|0126:2010-2060","OD":"d|0126:2010-2060","TS":"d|0126:2010-2060"}},{"id":"guru_ravi_das_s_birthday_optional","name":"Guru Ravi Das's Jayanti","en":"Guru Ravi Das's Birthday","category":"optional","dates":"d|0130:2010,2029|0131:2018|0201:2026|0203:2015,2034|0205:2023|0207:2012,2031|0209:2020|0210:2017,2028|0212:2025|0214:2014,2033|0216:2022|0218:2011,2030|0219:2019|0220:2027|0222:2016,2035|0224:2024|0225:2013|0226:2032|0227:2021"},{"id":"swami_dayanand_saraswati_s_birthday_optional","name":"Swami Dayanand Saraswati's Jayanti","en":"Swami Dayanand Saraswati's Birthday","category":"optional","dates":"d|0208:2010,2029|0210:2018|0212:2026|0213:2034|0214:2015|0215:2023|0216:2012|0217:2031|0218:2020|0219:2028|0221:2017|0223:2013,2025|0224:2014,2033|0226:2022|0227:2011,2030|0301:2019|0302:2027|0304:2016,2035|0306:2024|0307:2032|0308:2021"},{"id":"maha_shivaratri_government","name":"Maha Shivaratri","en":"Maha Shivaratri","category":"government","regional":{"AP":"d|0211:2029|0212:2010|0213:2018|0215:2026|0217:2015,2034|0218:2023|0220:2012,2031|0221:2020|0223:2028|0224:2017|0226:2025|0227:2014,2033|0301:2022|0302:2011,2030|0304:2019|0306:2027|0307:2016|0308:2024,2035|0310:2013,2032|0311:2021","BR":"d|0211:2029|0213:2018|0217:2034|0220:2012,2031|0223:2028|0224:2017|0226:2025|0227:2033|0301:2022|0302:2011,2030|0304:2019|0306:2027|0308:2035|0310:2032","CG":"d|0211:2029|0213:2018|0217:2034|0220:2012,2031|0223:2028|0224:2017|0226:2025|0227:2033|0301:2022|0302:2011,2030|0304:2019|0306:2027|0308:2035|0310:2032","JH":"d|0211:2029|0213:2018|0217:2034|0220:2012,2031|0223:2028|0224:2017|0226:2025|0227:2033|0301:2022|0302:2011,2030|0304:2019|0306:2027|0308:2035|0310:2032","ML":"d|0211:2029|0213:2018|0217:2034|0220:2012,2031|0223:2028|0224:2017|0226:2025|0227:2033|0301:2022|0302:2011,2030|0304:2019|0306:2027|0308:2035|0310:2032","MN":"d|0211:2029|0213:2018|0217:2034|0220:2012,2031|0223:2028|0224:2017|0226:2025|0227:2033|0301:2022|0302:2011,2030|0304:2019|0306:2027|0308:2035|0310:2032","MP":"d|0211:2029|0213:2018|0217:2034|0220:2012,2031|0223:2028|0224:2017|0226:2025|0227:2033|0301:2022|0302:2011,2030|0304:2019|0306:2027|0308:2035|0310:2032","OD":"d|0211:2029|0213:2018|0217:2034|0220:2012,2031|0223:2028|0224:2017|0226:2025|0227:2033|0301:2022|0302:2011,2030|0304:2019|0306:2027|0308:2035|0310:2032","TS":"d|0211:2029|0213:2018|0217:2034|0220:2012,2031|0223:2028|0224:2017|0226:2025|0227:2033|0301:2022|0302:2011,2030|0304:2019|0306:2027|0308:2035|0310:2032"}},{"id":"maha_shivaratri_optional","name":"Maha Shivaratri","en":"Maha Shivaratri","category":"optional","dates":"d|0212:2010|0215:2026|0217:2015|0218:2023|0221:2020|0227:2014|0307:2016|0308:2024|0310:2013|0311:2021"},{"id":"lui_ngai_ni_government","name":"Lui Ngai Ni","en":"Lui Ngai Ni","category":"government","regional":{"MN":"d|0215:2010-2060"},"estimated":"d|0215:2038"},{"id":"shivaji_s_birthday_optional","name":"Shivaji's Jayanti","en":"Shivaji's Birthday","category":"optional","dates":"d|0219:2010-2060"},{"id":"zomi_namni_optional","name":"Zomi Namni","en":"Zomi Namni","category":"optional","regional":{"MZ":"d|0220:2010-2060"}},{"id":"gadge_maharaj_s_birthday_optional","name":"Gadge Maharaj's Jayanti","en":"Gadge Maharaj's Birthday","category":"optional","regional":{"RJ":"d|0223:2010-2060"}},{"id":"players_day_optional","name":"Players' Day","en":"Players' Day","category":"optional","regional":{"MN":"d|0225:2010-2060"}},{"id":"dola_purnima_government","name":"Dola Purnima","en":"Dola Purnima","category":"government","regional":{"OD":"d|0228:2010,2029|0301:2018|0303:2026|0304:2034|0305:2015|0307:2012,2023|0308:2031|0309:2020|0310:2028|0312:2017|0313:2025|0315:2033|0316:2014|0317:2022|0319:2011,2030|0320:2019|0321:2027|0323:2016,2035|0324:2024|0326:2013,2032|0328:2021"}},{"id":"dolyatra_optional","name":"Dolyatra","en":"Dolyatra","category":"optional","dates":"d|0228:2010,2029|0301:2018|0303:2026|0304:2034|0307:2023|0308:2031|0309:2020|0310:2028|0312:2017|0313:2025|0315:2033|0317:2022|0319:2011,2030|0320:2019|0321:2027|0323:2016,2035|0324:2024|0326:2032|0328:2021"},{"id":"holika_dahan_government","name":"Holika Dahan","en":"Holika Dahan","category":"government","regional":{"MN":"d|0228:2010,2029|0301:2018|0303:2026|0304:2034|0305:2015|0307:2012,2023|0308:2031|0309:2020|0310:2028|0312:2017|0313:2025|0315:2033|0316:2014|0317:2022|0319:2011,2030|0320:2019|0321:2027|0323:2016,2035|0324:2024|0326:2013,2032|0328:2021"}},{"id":"holika_dahan_optional","name":"Holika Dahan","en":"Holika Dahan","category":"optional","dates":"d|0228:2010,2029|0301:2018|0303:2026|0304:2034|0305:2015|0307:2012,2023|0308:2031|0309:2020|0310:2028|0312:2017|0313:2025|0315:2033|0316:2014|0317:2022|0319:2011,2030|0320:2019|0321:2027|0323:2016,2035|0324:2024|0326:2013,2032|0328:2021"},{"id":"hola_mohalla_optional","name":"Hola Mohalla","en":"Hola Mohalla","category":"optional","regional":{"PB":"d|0301:2010,2029|0302:2018|0303:2026|0306:2015,2034|0309:2012|0310:2020,2031|0311:2023,2028|0313:2017|0314:2025|0317:2014,2033|0318:2022|0320:2011,2030|0321:2019|0323:2027|0324:2016,2035|0325:2024|0327:2032|0328:2013|0329:2021"}},{"id":"holi_government","name":"Holi","en":"Holi","category":"government","regional":{"AP":"d|0301:2010,2029|0302:2018|0304:2026|0305:2034|0306:2015|0308:2012,2023|0309:2031|0310:2020|0311:2028|0313:2017|0314:2025|0316:2033|0317:2014|0318:2022|0320:2030|0321:2019|0322:2027|0324:2016,2035|0325:2024|0327:2013,2032|0329:2021","BR":"d|0301:2010,2029|0302:2018|0304:2026|0305:2034|0306:2015|0308:2012,2023|0309:2031|0310:2020|0311:2028|0313:2017|0314:2025|0316:2033|0317:2014|0318:2022|0320:2030|0321:2019|0322:2027|0324:2016,2035|0325:2024|0327:2013,2032|0329:2021","CG":"d|0301:2010,2029|0302:2018|0304:2026|0305:2034|0306:2015|0308:2012,2023|0309:2031|0310:2020|0311:2028|0313:2017|0314:2025|0316:2033|0317:2014|0318:2022|0320:2030|0321:2019|0322:2027|0324:2016,2035|0325:2024|0327:2013,2032|0329:2021","JH":"d|0301:2010,2029|0302:2018|0304:2026|0305:2034|0306:2015|0308:2012,2023|0309:2031|0310:2020|0311:2028|0313:2017|0314:2025|0316:2033|0317:2014|0318:2022|0320:2030|0321:2019|0322:2027|0324:2016,2035|0325:2024|0327:2013,2032|0329:2021","ML":"d|0301:2010,2029|0302:2018|0304:2026|0305:2034|0306:2015|0308:2012,2023|0309:2031|0310:2020|0311:2028|0313:2017|0314:2025|0316:2033|0317:2014|0318:2022|0320:2030|0321:2019|0322:2027|0324:2016,2035|0325:2024|0327:2013,2032|0329:2021","MN":"d|0301:2010,2029|0302:2018|0304:2026|0305:2034|0306:2015|0308:2012,2023|0309:2031|0310:2020|0311:2028|0313:2017|0314:2025|0316:2033|0317:2014|0318:2022|0320:2030|0321:2019|0322:2027|0324:2016,2035|0325:2024|0327:2013,2032|0329:2021","MP":"d|0301:2010,2029|0302:2018|0304:2026|0305:2034|0306:2015|0308:2012,2023|0309:2031|0310:2020|0311:2028|0313:2017|0314:2025|0316:2033|0317:2014|0318:2022|0320:2030|0321:2019|0322:2027|0324:2016,2035|0325:2024|0327:2013,2032|0329:2021","OD":"d|0301:2010,2029|0302:2018|0304:2026|0305:2034|0306:2015|0308:2012,2023|0309:2031|0310:2020|0311:2028|0313:2017|0314:2025|0316:2033|0317:2014|0318:2022|0320:2030|0321:2019|0322:2027|0324:2016,2035|0325:2024|0327:2013,2032|0329:2021","TS":"d|0301:2010,2029|0302:2018|0304:2026|0305:2034|0306:2015|0308:2012,2023|0309:2031|0310:2020|0311:2028|0313:2017|0314:2025|0316:2033|0317:2014|0318:2022|0320:2030|0321:2019|0322:2027|0324:2016,2035|0325:2024|0327:2013,2032|0329:2021"}},{"id":"international_women_s_day_optional","name":"International Women's Day","en":"International Women's Day","category":"optional","regional":{"PB":"d|0308:2010-2060"}},{"id":"chaitra_sukladi_optional","name":"Chaitra Sukladi","en":"Chaitra Sukladi","category":"optional","dates":"d|0316:2010|0318:2018|0319:2026|0321:2015,2034|0322:2023|0323:2012|0324:2031|0325:2020|0327:2028|0328:2017|0330:2025|0331:2014,2033|0402:2022|0403:2030|0404:2011|0406:2019|0407:2027|0408:2016|0409:2024,2035|0411:2013,2032|0413:2021|0414:2029"},{"id":"cheti_chand_government","name":"Cheti Chand","en":"Cheti Chand","category":"government","regional":{"MP":"d|0316:2010|0318:2018|0319:2026|0321:2015,2034|0322:2023|0323:2012|0324:2031|0325:2020|0327:2028|0328:2017|0330:2025|0331:2014,2033|0402:2022|0403:2030|0404:2011|0406:2019|0407:2027|0408:2016|0409:2024,2035|0411:2013,2032|0413:2021|0414:2029"}},{"id":"cheti_chand_optional","name":"Cheti Chand","en":"Cheti Chand","category":"optional","dates":"d|0316:2010|0318:2018|0319:2026|0321:2015,2034|0322:2023|0323:2012|0324:2031|0325:2020|0327:2028|0328:2017|0330:2025|0331:2014,2033|0402:2022|0403:2030|0404:2011|0406:2019|0407:2027|0408:2016|0409:2024,2035|0411:2013,2032|0413:2021|0414:2029"},{"id":"gudi_padwa_government","name":"Gudi Padwa","en":"Gudi Padwa","category":"government","regional":{"MP":"d|0316:2010|0318:2018|0319:2026|0321:2015,2034|0322:2023|0323:2012|0324:2031|0325:2020|0327:2028|0328:2017|0330:2025|0331:2014,2033|0402:2022|0403:2030|0404:2011|0406:2019|0407:2027|0408:2016|0409:2024,2035|0411:2013,2032|0413:2021|0414:2029"}},{"id":"gudi_padwa_optional","name":"Gudi Padwa","en":"Gudi Padwa","category":"optional","dates":"d|0316:2010|0318:2018|0319:2026|0321:2015,2034|0322:2023|0323:2012|0324:2031|0325:2020|0327:2028|0328:2017|0330:2025|0331:2014,2033|0402:2022|0403:2030|0404:2011|0406:2019|0407:2027|0408:2016|0409:2024,2035|0411:2013,2032|0413:2021|0414:2029"},{"id":"ugadi_government","name":"Ugadi","en":"Ugadi","category":"government","regional":{"AP":"d|0316:2010|0318:2018|0319:2026|0321:2015,2034|0322:2023|0323:2012|0324:2031|0325:2020|0327:2028|0328:2017|0330:2025|0331:2014,2033|0402:2022|0403:2030|0404:2011|0406:2019|0407:2027|0408:2016|0409:2024,2035|0411:2013,2032|0413:2021|0414:2029"}},{"id":"ugadi_optional","name":"Ugadi","en":"Ugadi","category":"optional","dates":"d|0316:2010|0318:2018|0319:2026|0321:2015,2034|0322:2023|0323:2012|0324:2031|0325:2020|0327:2028|0328:2017|0330:2025|0331:2014,2033|0402:2022|0403:2030|0404:2011|0406:2019|0407:2027|0408:2016|0409:2024,2035|0411:2013,2032|0413:2021|0414:2029"},{"id":"1st_navratra_optional","name":"1st Navratra","en":"1st Navratra","category":"optional","regional":{"LA":"d|0317:2010|0318:2018|0320:2026|0321:2015,2034|0322:2023|0323:2012|0324:2031|0325:2020|0327:2028|0329:2017|0330:2025|0331:2014,2033|0402:2022|0404:2011,2030|0406:2019|0408:2016,2027|0409:2024,2035|0411:2013,2032|0413:2021|0415:2029"}},{"id":"lord_meenesh_s_birthday_optional","name":"Bhagvan Meenesh's Jayanti","en":"Lord Meenesh's Birthday","category":"optional","regional":{"MP":"d|0318:2010|0320:2018|0321:2026|0322:2015,2034|0324:2023|0325:2012,2031|0327:2020|0328:2028|0330:2017|0331:2025|0402:2014,2033|0403:2022|0405:2030|0406:2011|0408:2019|0409:2016,2027|0410:2035|0411:2024|0412:2032|0413:2013|0415:2021|0416:2029"}},{"id":"veerangana_avantibai_s_martyrdom_day_optional","name":"Veerangana Avantibai's Shaheedi Diwas","en":"Veerangana Avantibai's Martyrdom Day","category":"optional","regional":{"CG":"d|0320:2010-2060"}},{"id":"maundy_thursday_optional","name":"Maundy Thursday","en":"Maundy Thursday","category":"optional","regional":{"GA":"d|0322:2035,2046|0324:2016|0325:2027,2032|0326:2043,2054|0327:2059|0328:2013,2024|0329:2018,2029,2040|0330:2051,2056|0401:2010,2021|0402:2015,2026,2037,2048|0403:2042,2053|0405:2012|0406:2023,2034,2045|0407:2039,2050|0409:2020|0410:2031,2036|0411:2047,2058|0413:2017,2028|0414:2022,2033,2044|0415:2049,2055,2060|0417:2014,2025|0418:2019,2030,2041,2052|0419:2057|0421:2011|0422:2038"}},{"id":"good_friday_government","name":"Good Friday","en":"Good Friday","category":"government","regional":{"AP":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038","BR":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038","CG":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038","JH":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038","ML":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038","MN":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038","MP":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038","OD":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038","TS":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"}},{"id":"holy_saturday_optional","name":"Holy Saturday","en":"Holy Saturday","category":"optional","regional":{"KA":"d|0324:2035,2046|0326:2016|0327:2027,2032|0328:2043,2054|0329:2059|0330:2013,2024|0331:2018,2029,2040|0401:2051,2056|0403:2010,2021|0404:2015,2026,2037,2048|0405:2042,2053|0407:2012|0408:2023,2034,2045|0409:2039,2050|0411:2020|0412:2031,2036|0413:2047,2058|0415:2017,2028|0416:2022,2033,2044|0417:2049,2055,2060|0419:2014,2025|0420:2019,2030,2041,2052|0421:2057|0423:2011|0424:2038"}},{"id":"ram_navami_government","name":"Ram Navami","en":"Ram Navami","category":"government","regional":{"AP":"d|0324:2010|0326:2026|0328:2015,2034|0330:2023|0401:2031|0402:2020|0403:2028|0404:2017|0407:2033|0408:2014|0412:2011,2030|0413:2019|0415:2016,2027|0416:2035|0417:2024|0419:2013,2032|0421:2021","BR":"d|0324:2010|0326:2026|0328:2015,2034|0330:2023|0401:2031|0402:2020|0403:2028|0404:2017|0407:2033|0408:2014|0412:2011,2030|0413:2019|0415:2016,2027|0416:2035|0417:2024|0419:2013,2032|0421:2021","CG":"d|0324:2010|0326:2026|0328:2015,2034|0330:2023|0401:2031|0402:2020|0403:2028|0404:2017|0407:2033|0408:2014|0412:2011,2030|0413:2019|0415:2016,2027|0416:2035|0417:2024|0419:2013,2032|0421:2021","JH":"d|0324:2010|0326:2026|0328:2015,2034|0330:2023|0401:2031|0402:2020|0403:2028|0404:2017|0407:2033|0408:2014|0412:2011,2030|0413:2019|0415:2016,2027|0416:2035|0417:2024|0419:2013,2032|0421:2021","ML":"d|0324:2010|0326:2026|0328:2015,2034|0330:2023|0401:2031|0402:2020|0403:2028|0404:2017|0407:2033|0408:2014|0412:2011,2030|0413:2019|0415:2016,2027|0416:2035|0417:2024|0419:2013,2032|0421:2021","MN":"d|0324:2010|0326:2026|0328:2015,2034|0330:2023|0401:2031|0402:2020|0403:2028|0404:2017|0407:2033|0408:2014|0412:2011,2030|0413:2019|0415:2016,2027|0416:2035|0417:2024|0419:2013,2032|0421:2021","MP":"d|0324:2010|0326:2026|0328:2015,2034|0330:2023|0401:2031|0402:2020|0403:2028|0404:2017|0407:2033|0408:2014|0412:2011,2030|0413:2019|0415:2016,2027|0416:2035|0417:2024|0419:2013,2032|0421:2021","OD":"d|0324:2010|0327:2026|0328:2015,2034|0330:2023|0401:2031|0402:2020|0403:2028|0404:2017|0407:2033|0408:2014|0412:2011,2030|0413:2019|0415:2016,2027|0416:2035|0417:2024|0419:2013,2032|0421:2021","TS":"d|0324:2010|0326:2026|0328:2015,2034|0330:2023|0401:2031|0402:2020|0403:2028|0404:2017|0407:2033|0408:2014|0412:2011,2030|0413:2019|0415:2016,2027|0416:2035|0417:2024|0419:2013,2032|0421:2021"}},{"id":"easter_sunday_optional","name":"Easter Sunday","en":"Easter Sunday","category":"optional","dates":"d|0325:2035,2046|0327:2016|0328:2027,2032|0329:2043,2054|0330:2059|0331:2013,2024|0401:2018,2029,2040|0402:2051,2056|0404:2010,2021|0405:2015,2026,2037,2048|0406:2042,2053|0408:2012|0409:2023,2034,2045|0410:2039,2050|0412:2020|0413:2031,2036|0414:2047,2058|0416:2017,2028|0417:2022,2033,2044|0418:2049,2055,2060|0420:2014,2025|0421:2019,2030,2041,2052|0422:2057|0424:2011|0425:2038"},{"id":"ram_navami_optional","name":"Ram Navami","en":"Ram Navami","category":"optional","dates":"d|0325:2018|0401:2012|0406:2025|0410:2022|0422:2029"},{"id":"easter_monday_optional","name":"Easter Monday","en":"Easter Monday","category":"optional","regional":{"UK":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038","UP":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"}},{"id":"hatkeshwar_s_birthday_optional","name":"Hatkeshwar's Jayanti","en":"Hatkeshwar's Birthday","category":"optional","regional":{"GJ":"d|0329:2010|0330:2018|0401:2026|0402:2034|0403:2015|0405:2012,2023|0406:2031|0407:2020|0408:2028|0410:2017|0411:2025|0413:2033|0414:2014|0415:2022|0417:2011,2030|0418:2019|0419:2027|0421:2016,2035|0422:2024|0424:2013,2032|0426:2021|0427:2029"}},{"id":"mahavira_s_birthday_government","name":"Mahavir Jayanti","en":"Mahavira's Birthday","category":"government","regional":{"AP":"d|0329:2018|0331:2026|0401:2034|0402:2015|0404:2023|0405:2012,2031|0406:2020|0407:2028|0409:2017|0410:2025|0412:2033|0413:2014|0414:2022|0416:2011,2030|0417:2019|0418:2027|0420:2016,2035|0421:2024|0423:2032|0424:2013|0425:2021|0426:2029|0428:2010","BR":"d|0329:2018|0331:2026|0401:2034|0402:2015|0404:2023|0405:2012,2031|0406:2020|0407:2028|0409:2017|0410:2025|0412:2033|0413:2014|0414:2022|0416:2011,2030|0417:2019|0418:2027|0420:2016,2035|0421:2024|0423:2032|0424:2013|0425:2021|0426:2029|0428:2010","CG":"d|0329:2018|0331:2026|0401:2034|0402:2015|0404:2023|0405:2012,2031|0406:2020|0407:2028|0409:2017|0410:2025|0412:2033|0413:2014|0414:2022|0416:2011,2030|0417:2019|0418:2027|0420:2016,2035|0421:2024|0423:2032|0424:2013|0425:2021|0426:2029|0428:2010","JH":"d|0329:2018|0331:2026|0401:2034|0402:2015|0404:2023|0405:2012,2031|0406:2020|0407:2028|0409:2017|0410:2025|0412:2033|0413:2014|0414:2022|0416:2011,2030|0417:2019|0418:2027|0420:2016,2035|0421:2024|0423:2032|0424:2013|0425:2021|0426:2029|0428:2010","ML":"d|0329:2018|0331:2026|0401:2034|0402:2015|0404:2023|0405:2012,2031|0406:2020|0407:2028|0409:2017|0410:2025|0412:2033|0413:2014|0414:2022|0416:2011,2030|0417:2019|0418:2027|0420:2016,2035|0421:2024|0423:2032|0424:2013|0425:2021|0426:2029|0428:2010","MN":"d|0329:2018|0331:2026|0401:2034|0402:2015|0404:2023|0405:2012,2031|0406:2020|0407:2028|0409:2017|0410:2025|0412:2033|0413:2014|0414:2022|0416:2011,2030|0417:2019|0418:2027|0420:2016,2035|0421:2024|0423:2032|0424:2013|0425:2021|0426:2029|0428:2010","MP":"d|0329:2018|0331:2026|0401:2034|0402:2015|0404:2023|0405:2012,2031|0406:2020|0407:2028|0409:2017|0410:2025|0412:2033|0413:2014|0414:2022|0416:2011,2030|0417:2019|0418:2027|0420:2016,2035|0421:2024|0423:2032|0424:2013|0425:2021|0426:2029|0428:2010","OD":"d|0329:2018|0331:2026|0401:2034|0402:2015|0404:2023|0405:2012,2031|0406:2020|0407:2028|0409:2017|0410:2025|0412:2033|0413:2014|0414:2022|0416:2011,2030|0417:2019|0418:2027|0420:2016,2035|0421:2024|0423:2032|0424:2013|0425:2021|0426:2029|0428:2010","TS":"d|0329:2018|0331:2026|0401:2034|0402:2015|0404:2023|0405:2012,2031|0406:2020|0407:2028|0409:2017|0410:2025|0412:2033|0413:2014|0414:2022|0416:2011,2030|0417:2019|0418:2027|0420:2016,2035|0421:2024|0423:2032|0424:2013|0425:2021|0426:2029|0428:2010"}},{"id":"hanuman_s_birthday_optional","name":"Hanuman's Jayanti","en":"Hanuman's Birthday","category":"optional","regional":{"GJ":"d|0330:2010|0331:2018|0402:2026|0403:2034|0404:2015|0406:2012,2023|0407:2031|0408:2020|0409:2028|0411:2017|0412:2025|0414:2033|0415:2014|0416:2022|0418:2011,2030|0419:2019|0420:2027|0422:2016,2035|0423:2024|0425:2013,2032|0427:2021|0428:2029"}},{"id":"utkal_day_government","name":"Utkal Divas","en":"Utkal Day","category":"government","regional":{"OD":"d|0401:2010-2060"}},{"id":"babu_jagjivan_ram_s_birthday_government","name":"Babu Jagjivan Ram's Jayanti","en":"Babu Jagjivan Ram's Birthday","category":"government","regional":{"AP":"d|0405:2010-2060","TS":"d|0405:2010-2060"}},{"id":"maharishi_kashyap_and_maharaj_nishad_raj_s_graha_optional","name":"Maharshi Kashyap and Maharaj Nishad Raj's Graha Jayanti","en":"Maharishi Kashyap and Maharaj Nishad Raj's Graha Birthday","category":"optional","regional":{"UP":"d|0405:2010-2060"}},{"id":"shri_vallabhacharya_s_birthday_optional","name":"Shri Vallabhacharya's Jayanti","en":"Shri Vallabhacharya's Birthday","category":"optional","regional":{"CG":"d|0410:2010|0412:2018|0413:2026|0415:2015,2034|0416:2012,2023|0417:2031|0418:2020|0420:2028|0422:2017|0424:2025|0425:2014,2033|0426:2022|0428:2011,2030|0430:2019|0501:2027|0502:2016|0503:2024,2035|0505:2013,2032|0506:2021|0508:2029","GJ":"d|0410:2010|0412:2018|0413:2026|0415:2015,2034|0416:2012,2023|0417:2031|0418:2020|0420:2028|0422:2017|0424:2025|0425:2014,2033|0426:2022|0428:2011,2030|0430:2019|0501:2027|0502:2016|0503:2024,2035|0505:2013,2032|0506:2021|0508:2029","MP":"d|0410:2010|0412:2018|0413:2026|0415:2015,2034|0416:2012,2023|0417:2031|0418:2020|0420:2028|0422:2017|0424:2025|0425:2014,2033|0426:2022|0428:2011,2030|0430:2019|0501:2027|0502:2016|0503:2024,2035|0505:2013,2032|0506:2021|0508:2029"}},{"id":"mahatma_jyotiba_phule_s_birthday_optional","name":"Mahatma Jyotiba Phule's Jayanti","en":"Mahatma Jyotiba Phule's Birthday","category":"optional","regional":{"MP":"d|0411:2010-2060"}},{"id":"maha_visuva_sankranti_government","name":"Maha Visuva Sankranti","en":"Maha Visuva Sankranti","category":"government","regional":{"OD":"d|0413:2012,2016,2020,2024,2028,2032,2036,2040,2044,2048,2052,2056,2060|0414:2010-2011,2013-2015,2017-2019,2021-2023,2025-2027,2029-2031,2033-2035,2037-2039,2041-2043,2045-2047,2049-2051,2053-2055,2057-2059"}},{"id":"vaisakhi_optional","name":"Vaisakhi","en":"Vaisakhi","category":"optional","dates":"d|0413:2012-2013,2016,2020,2024-2025,2028,2032|0414:2010-2011,2014-2015,2017-2019,2021-2023,2026-2027,2029-2031,2033-2035"},{"id":"bahag_bihu_optional","name":"Bahag Bihu","en":"Bahag Bihu","category":"optional","dates":"d|0414:2012,2016,2020,2024,2028,2032,2036,2040,2044,2048,2052,2056,2060|0415:2010-2011,2013-2015,2017-2019,2021-2023,2025-2027,2029-2031,2033-2035,2037-2039,2041-2043,2045-2047,2049-2051,2053-2055,2057-2059"},{"id":"dr_b_r_ambedkar_s_birthday_government","name":"Dr. B. R. Ambedkar's Jayanti","en":"Dr. B. R. Ambedkar's Birthday","category":"government","regional":{"AP":"d|0414:2010-2060","BR":"d|0414:2010-2060","CG":"d|0414:2010-2060","JH":"d|0414:2010-2060","ML":"d|0414:2010-2060","MN":"d|0414:2010-2060","MP":"d|0414:2010-2060","OD":"d|0414:2010-2060","TS":"d|0414:2010-2060"}},{"id":"meshadi_tamil_new_year_s_day_optional","name":"Meshadi (Tamil New Year's Day)","en":"Meshadi (Tamil New Year's Day)","category":"optional","dates":"d|0414:2010-2035"},{"id":"vaisakhadi_optional","name":"Vaisakhadi","en":"Vaisakhadi","category":"optional","dates":"d|0414:2012,2016,2020,2024,2028,2032,2036,2040,2044,2048,2052,2056,2060|0415:2010-2011,2013-2015,2017-2019,2021-2023,2025-2027,2029-2031,2033-2035,2037-2039,2041-2043,2045-2047,2049-2051,2053-2055,2057-2059"},{"id":"vishu_optional","name":"Vishu","en":"Vishu","category":"optional","dates":"d|0414:2010-2013,2016-2021,2024-2025,2028-2029,2032-2035|0415:2014-2015,2022-2023,2026-2027,2030-2031"},{"id":"chandrashekhar_s_birthday_optional","name":"Chandrashekhar's Jayanti","en":"Chandrashekhar's Birthday","category":"optional","regional":{"UP":"d|0417:2010-2060"}},{"id":"akshay_tritiya_optional","name":"Akshay Tritiya","en":"Akshay Tritiya","category":"optional","regional":{"MP":"d|0418:2018|0419:2026|0420:2015,2034|0422:2023|0424:2012,2031|0425:2020|0427:2028|0428:2017|0429:2025|0501:2014,2033|0503:2022|0505:2011,2030|0507:2019|0508:2016,2027|0509:2035|0510:2024|0511:2032|0512:2013|0514:2021|0516:2010,2029"}},{"id":"gurudev_kalicharan_brahma_s_birthday_optional","name":"Gurudev Kalicharan Brahma's Jayanti","en":"Gurudev Kalicharan Brahma's Birthday","category":"optional","regional":{"AS":"d|0418:2010-2060"}},{"id":"lord_shri_parshuram_s_birthday_government","name":"Bhagvan Shri Parshuram's Jayanti","en":"Lord Shri Parshuram's Birthday","category":"government","regional":{"MP":"d|0418:2018|0419:2026|0420:2015,2034|0422:2023|0424:2012,2031|0425:2020|0427:2028|0428:2017|0429:2025|0501:2014,2033|0503:2022|0505:2011,2030|0507:2019|0508:2016,2027|0509:2035|0510:2024|0511:2032|0512:2013|0514:2021|0516:2010,2029"}},{"id":"lord_shri_parshuram_s_birthday_optional","name":"Bhagvan Shri Parshuram's Jayanti","en":"Lord Shri Parshuram's Birthday","category":"optional","regional":{"CG":"d|0418:2018|0419:2026|0420:2015,2034|0422:2023|0424:2012,2031|0425:2020|0427:2028|0428:2017|0429:2025|0501:2014,2033|0503:2022|0505:2011,2030|0507:2019|0508:2016,2027|0509:2035|0510:2024|0511:2032|0512:2013|0514:2021|0516:2010,2029","UP":"d|0418:2018|0419:2026|0420:2015,2034|0422:2023|0424:2012,2031|0425:2020|0427:2028|0428:2017|0429:2025|0501:2014,2033|0503:2022|0505:2011,2030|0507:2019|0508:2016,2027|0509:2035|0510:2024|0511:2032|0512:2013|0514:2021|0516:2010,2029"}},{"id":"adi_shankaracharya_s_birthday_optional","name":"Adi Shankaracharya's Jayanti","en":"Adi Shankaracharya's Birthday","category":"optional","regional":{"CG":"d|0420:2018|0421:2026|0423:2015,2034|0425:2023|0426:2012,2031|0428:2020|0429:2028|0430:2017|0501:2025|0502:2033|0503:2014|0505:2022|0507:2011,2030|0509:2019|0510:2016,2027|0511:2035|0512:2024|0513:2032|0514:2013|0516:2021|0517:2029|0518:2010","GJ":"d|0420:2018|0421:2026|0423:2015,2034|0425:2023|0426:2012,2031|0428:2020|0429:2028|0430:2017|0501:2025|0502:2033|0503:2014|0505:2022|0507:2011,2030|0509:2019|0510:2016,2027|0511:2035|0512:2024|0513:2032|0514:2013|0516:2021|0517:2029|0518:2010","KA":"d|0420:2018|0421:2026|0423:2015,2034|0425:2023|0426:2012,2031|0428:2020|0429:2028|0430:2017|0501:2025|0502:2033|0503:2014|0505:2022|0507:2011,2030|0509:2019|0510:2016,2027|0511:2035|0512:2024|0513:2032|0514:2013|0516:2021|0517:2029|0518:2010","MP":"d|0420:2018|0421:2026|0423:2015,2034|0425:2023|0426:2012,2031|0428:2020|0429:2028|0430:2017|0501:2025|0502:2033|0503:2014|0505:2022|0507:2011,2030|0509:2019|0510:2016,2027|0511:2035|0512:2024|0513:2032|0514:2013|0516:2021|0517:2029|0518:2010"}},{"id":"khongjom_day_government","name":"Khongjom Day","en":"Khongjom Day","category":"government","regional":{"MN":"d|0423:2010-2060"}},{"id":"buddha_purnima_government","name":"Buddha Purnima","en":"Buddha Purnima","category":"government","regional":{"AP":"d|0430:2018|0501:2026|0503:2034|0504:2015|0505:2023|0506:2012|0507:2020,2031|0508:2028|0510:2017|0512:2025|0514:2014,2033|0516:2022|0517:2011,2030|0518:2019|0520:2027|0521:2016|0522:2035|0523:2024|0525:2013,2032|0526:2021|0527:2010,2029","BR":"d|0430:2018|0501:2026|0503:2034|0504:2015|0505:2023|0506:2012|0507:2020,2031|0508:2028|0510:2017|0512:2025|0514:2014,2033|0516:2022|0517:2011,2030|0518:2019|0520:2027|0521:2016|0522:2035|0523:2024|0525:2013,2032|0526:2021|0527:2010,2029","CG":"d|0430:2018|0501:2026|0503:2034|0504:2015|0505:2023|0506:2012|0507:2020,2031|0508:2028|0510:2017|0512:2025|0514:2014,2033|0516:2022|0517:2011,2030|0518:2019|0520:2027|0521:2016|0522:2035|0523:2024|0525:2013,2032|0526:2021|0527:2010,2029","JH":"d|0430:2018|0501:2026|0503:2034|0504:2015|0505:2023|0506:2012|0507:2020,2031|0508:2028|0510:2017|0512:2025|0514:2014,2033|0516:2022|0517:2011,2030|0518:2019|0520:2027|0521:2016|0522:2035|0523:2024|0525:2013,2032|0526:2021|0527:2010,2029","ML":"d|0430:2018|0501:2026|0503:2034|0504:2015|0505:2023|0506:2012|0507:2020,2031|0508:2028|0510:2017|0512:2025|0514:2014,2033|0516:2022|0517:2011,2030|0518:2019|0520:2027|0521:2016|0522:2035|0523:2024|0525:2013,2032|0526:2021|0527:2010,2029","MN":"d|0430:2018|0501:2026|0503:2034|0504:2015|0505:2023|0506:2012|0507:2020,2031|0508:2028|0510:2017|0512:2025|0514:2014,2033|0516:2022|0517:2011,2030|0518:2019|0520:2027|0521:2016|0522:2035|0523:2024|0525:2013,2032|0526:2021|0527:2010,2029","MP":"d|0430:2018|0501:2026|0503:2034|0504:2015|0505:2023|0506:2012|0507:2020,2031|0508:2028|0510:2017|0512:2025|0514:2014,2033|0516:2022|0517:2011,2030|0518:2019|0520:2027|0521:2016|0522:2035|0523:2024|0525:2013,2032|0526:2021|0527:2010,2029","OD":"d|0430:2018|0501:2026|0503:2034|0504:2015|0505:2023|0506:2012|0507:2020,2031|0508:2028|0510:2017|0512:2025|0514:2014,2033|0516:2022|0517:2011,2030|0518:2019|0520:2027|0521:2016|0522:2035|0523:2024|0525:2013,2032|0526:2021|0527:2010,2029","TS":"d|0430:2018|0501:2026|0503:2034|0504:2015|0505:2023|0506:2012|0507:2020,2031|0508:2028|0510:2017|0512:2025|0514:2014,2033|0516:2022|0517:2011,2030|0518:2019|0520:2027|0521:2016|0522:2035|0523:2024|0525:2013,2032|0526:2021|0527:2010,2029"}},{"id":"labor_day_government","name":"Majdoor Diwas","en":"Labor Day","category":"government","regional":{"JH":"d|0501:2010-2060"}},{"id":"may_day_government","name":"May Day","en":"May Day","category":"government","regional":{"MN":"d|0501:2010-2060"}},{"id":"may_day_optional","name":"May Day","en":"May Day","category":"optional","regional":{"SK":"d|0501:2010-2060"}},{"id":"veer_kesari_chand_s_martyrdom_day_optional","name":"Veer Kesari Chand's Shaheedi Diwas","en":"Veer Kesari Chand's Martyrdom Day","category":"optional","regional":{"UK":"d|0503:2010-2060"}},{"id":"guru_rabindranath_s_birthday_optional","name":"Guru Rabindranath's Jayanti","en":"Guru Rabindranath's Birthday","category":"optional","dates":"d|0508:2012,2016,2020,2024,2028,2032,2036,2040,2044,2048,2052,2056,2060|0509:2010-2011,2013-2015,2017-2019,2021-2023,2025-2027,2029-2031,2033-2035,2037-2039,2041-2043,2045-2047,2049-2051,2053-2055,2057-2059","estimated":"d|0509:2047"},{"id":"maharana_pratap_s_birthday_optional","name":"Maharana Pratap's Jayanti","en":"Maharana Pratap's Birthday","category":"optional","regional":{"CG":"d|0515:2029|0520:2015,2034|0522:2023|0524:2012,2031|0525:2020|0527:2028|0528:2017|0529:2025|0531:2014,2033|0602:2022|0604:2011,2030|0606:2019|0607:2016,2027|0608:2035|0609:2024|0611:2013,2032|0613:2021|0615:2010|0616:2018|0617:2026","UP":"d|0515:2029|0520:2015,2034|0522:2023|0524:2012,2031|0525:2020|0527:2028|0528:2017|0529:2025|0531:2014,2033|0602:2022|0604:2011,2030|0606:2019|0607:2016,2027|0608:2035|0609:2024|0611:2013,2032|0613:2021|0615:2010|0616:2018|0617:2026"}},{"id":"guru_arjun_dev_s_martyrdom_day_optional","name":"Guru Arjun Dev's Shaheedi Diwas","en":"Guru Arjun Dev's Martyrdom Day","category":"optional","regional":{"CH":"d|0521:2010|0522:2034|0523:2023|0525:2015|0526:2020,2031|0528:2012,2028|0530:2025|0602:2017,2033|0603:2022|0605:2014,2030|0608:2027|0609:2011|0610:2024|0611:2035|0613:2016,2032|0614:2021|0616:2013,2019,2029|0617:2018|0618:2026","GJ":"d|0521:2010|0522:2034|0523:2023|0525:2015|0526:2020,2031|0528:2012,2028|0530:2025|0602:2017,2033|0603:2022|0605:2014,2030|0608:2027|0609:2011|0610:2024|0611:2035|0613:2016,2032|0614:2021|0616:2013,2019,2029|0617:2018|0618:2026","HR":"d|0521:2010|0522:2034|0523:2023|0525:2015|0526:2020,2031|0528:2012,2028|0530:2025|0602:2017,2033|0603:2022|0605:2014,2030|0608:2027|0609:2011|0610:2024|0611:2035|0613:2016,2032|0614:2021|0616:2013,2019,2029|0617:2018|0618:2026","JK":"d|0521:2010|0522:2034|0523:2023|0525:2015|0526:2020,2031|0528:2012,2028|0530:2025|0602:2017,2033|0603:2022|0605:2014,2030|0608:2027|0609:2011|0610:2024|0611:2035|0613:2016,2032|0614:2021|0616:2013,2019,2029|0617:2018|0618:2026","LA":"d|0521:2010|0522:2034|0523:2023|0525:2015|0526:2020,2031|0528:2012,2028|0530:2025|0602:2017,2033|0603:2022|0605:2014,2030|0608:2027|0609:2011|0610:2024|0611:2035|0613:2016,2032|0614:2021|0616:2013,2019,2029|0617:2018|0618:2026"}},{"id":"mahesh_navami_optional","name":"Mahesh Navami","en":"Mahesh Navami","category":"optional","regional":{"CG":"d|0525:2034|0526:2015|0528:2023|0529:2012|0530:2020,2031|0601:2028|0603:2017|0604:2025|0606:2033|0607:2014|0609:2022|0610:2011,2030|0611:2019|0612:2027|0613:2016|0614:2035|0615:2024|0617:2032|0618:2013|0619:2021|0620:2010,2029|0621:2018|0623:2026","MP":"d|0525:2034|0526:2015|0528:2023|0529:2012|0530:2020,2031|0601:2028|0603:2017|0604:2025|0606:2033|0607:2014|0609:2022|0610:2011,2030|0611:2019|0612:2027|0613:2016|0614:2035|0615:2024|0617:2032|0618:2013|0619:2021|0620:2010,2029|0621:2018|0623:2026"}},{"id":"nirjala_ekadashi_optional","name":"Nirjala Ekadashi","en":"Nirjala Ekadashi","category":"optional","regional":{"PB":"d|0528:2015,2034|0530:2023|0601:2012|0602:2020,2031|0603:2028|0605:2017|0607:2025|0609:2014,2033|0611:2022|0612:2011,2030|0613:2019|0615:2027|0616:2016,2035|0618:2024|0619:2032|0620:2013|0621:2021|0622:2010,2029|0624:2018|0625:2026"}},{"id":"late_tenzing_norgay_sherpa_s_birthday_optional","name":"Late Tenzing Norgay Sherpa's Jayanti","en":"Late Tenzing Norgay Sherpa's Birthday","category":"optional","regional":{"SK":"d|0529:2010-2060"}},{"id":"sacred_heart_optional","name":"Feast of Sacred Heart of Jesus","en":"Sacred Heart","category":"optional","regional":{"GA":"d|0601:2035,2046|0603:2016|0604:2027,2032|0605:2043,2054|0606:2059|0607:2013,2024|0608:2018,2029,2040|0609:2051,2056|0611:2010,2021|0612:2015,2026,2037,2048|0613:2042,2053|0615:2012|0616:2023,2034,2045|0617:2039,2050|0619:2020|0620:2031,2036|0621:2047,2058|0623:2017,2028|0624:2022,2033,2044|0625:2049,2055,2060|0627:2014,2025|0628:2019,2030,2041,2052|0629:2057|0701:2011|0702:2038"},"estimated":"d|0609:2051|0617:2050|0625:2049"},{"id":"sant_kabir_s_birthday_government","name":"Sant Kabir's Jayanti","en":"Sant Kabir's Birthday","category":"government","regional":{"CG":"d|0602:2015,2034|0604:2012,2023|0605:2020,2031|0607:2028|0609:2017|0611:2025|0612:2033|0613:2014|0614:2022|0615:2011,2030|0617:2019|0618:2027|0620:2016,2035|0622:2024|0623:2013,2032|0624:2021|0626:2010,2029|0628:2018|0629:2026"}},{"id":"sant_kabir_s_birthday_optional","name":"Sant Kabir's Jayanti","en":"Sant Kabir's Birthday","category":"optional","regional":{"CH":"d|0602:2015,2034|0604:2012,2023|0605:2020,2031|0607:2028|0609:2017|0611:2025|0612:2033|0613:2014|0614:2022|0615:2011,2030|0617:2019|0618:2027|0620:2016,2035|0622:2024|0623:2013,2032|0624:2021|0626:2010,2029|0628:2018|0629:2026","MP":"d|0602:2015,2034|0604:2012,2023|0605:2020,2031|0607:2028|0609:2017|0611:2025|0612:2033|0613:2014|0614:2022|0615:2011,2030|0617:2019|0618:2027|0620:2016,2035|0622:2024|0623:2013,2032|0624:2021|0626:2010,2029|0628:2018|0629:2026"}},{"id":"bishnu_prasad_rabha_s_death_anniversary_optional","name":"Bishnu Prasad Rabha's Death Anniversary","en":"Bishnu Prasad Rabha's Death Anniversary","category":"optional","regional":{"AS":"d|0620:2010-2060"}},{"id":"rath_yatra_government","name":"Rath Yatra","en":"Rath Yatra","category":"government","regional":{"JH":"d|0620:2023|0621:2012|0622:2031|0623:2020|0624:2028|0625:2017|0627:2025|0628:2033|0629:2014|0701:2022|0702:2030|0703:2011|0704:2019|0705:2027|0706:2016|0707:2024,2035|0709:2032|0710:2013|0712:2021|0713:2010,2029|0714:2018|0716:2026|0717:2034|0718:2015","MN":"d|0620:2023|0621:2012|0622:2031|0623:2020|0624:2028|0625:2017|0627:2025|0628:2033|0629:2014|0701:2022|0702:2030|0703:2011|0704:2019|0705:2027|0706:2016|0707:2024,2035|0709:2032|0710:2013|0712:2021|0713:2010,2029|0714:2018|0716:2026|0717:2034|0718:2015","OD":"d|0620:2023|0621:2012|0622:2031|0623:2020|0624:2028|0625:2017|0627:2025|0628:2033|0629:2014|0701:2022|0702:2030|0703:2011|0704:2019|0705:2027|0706:2016|0707:2024,2035|0709:2032|0710:2013|0712:2021|0713:2010,2029|0714:2018|0716:2026|0717:2034|0718:2015"}},{"id":"rath_yatra_optional","name":"Rath Yatra","en":"Rath Yatra","category":"optional","dates":"d|0620:2023|0621:2012|0622:2031|0623:2020|0624:2028|0625:2017|0627:2025|0628:2033|0629:2014|0701:2022|0702:2030|0703:2011|0704:2019|0705:2027|0706:2016|0707:2024,2035|0709:2032|0710:2013|0712:2021|0713:2010,2029|0714:2018|0716:2026|0717:2034|0718:2015"},{"id":"veerangana_durgavati_s_martyrdom_day_optional","name":"Veerangana Durgavati's Shaheedi Diwas","en":"Veerangana Durgavati's Martyrdom Day","category":"optional","regional":{"CG":"d|0624:2010-2060","MP":"d|0624:2010-2060"}},{"id":"maharaja_ranjit_singh_s_death_anniversary_optional","name":"Maharaja Ranjit Singh's Death Anniversary","en":"Maharaja Ranjit Singh's Death Anniversary","category":"optional","regional":{"PB":"d|0627:2010-2060"}},{"id":"guru_purnima_optional","name":"Guru Purnima","en":"Guru Purnima","category":"optional","regional":{"MP":"d|0703:2012,2023|0704:2031|0705:2020|0706:2028|0709:2017|0710:2025|0712:2014,2033|0713:2022|0715:2011,2030|0716:2019|0718:2027|0719:2016|0720:2035|0721:2024|0722:2013,2032|0724:2021|0725:2010,2029|0727:2018|0729:2026|0731:2015,2034"}},{"id":"the_dalai_lama_s_birthday_optional","name":"The Dalai Lama's Jayanti","en":"The Dalai Lama's Birthday","category":"optional","regional":{"SK":"d|0706:2010-2060"}},{"id":"bonalu_government","name":"Bonalu","en":"Bonalu","category":"government","regional":{"TS":"d|0717:2023|0720:2020|0721:2025|0725:2022|0729:2019,2024|0802:2021,2027|0806:2018|0810:2026"}},{"id":"hareli_government","name":"Hareli","en":"Hareli","category":"government","regional":{"CG":"d|0717:2023|0719:2012,2031|0720:2020|0722:2028|0723:2017|0724:2025|0726:2014,2033|0728:2022|0730:2011,2030|0801:2019|0802:2016,2027|0803:2035|0804:2024|0806:2013,2032|0808:2021|0810:2010,2029|0811:2018|0812:2026|0814:2015,2034"}},{"id":"u_tirot_sing_s_death_anniversary_government","name":"U Tirot Sing's Death Anniversary","en":"U Tirot Sing's Death Anniversary","category":"government","regional":{"ML":"d|0717:2010-2060"}},{"id":"tulsidas_s_birthday_optional","name":"Tulsidas's Jayanti","en":"Tulsidas's Birthday","category":"optional","regional":{"MP":"d|0725:2012|0726:2031|0727:2020|0728:2028|0730:2017|0731:2025|0802:2033|0803:2014|0804:2022|0805:2011|0806:2030|0807:2019|0808:2027|0810:2016,2035|0811:2024|0813:2013,2032|0815:2021|0816:2010,2029|0817:2018|0819:2026|0821:2034|0822:2015|0823:2023"}},{"id":"drivers_day_optional","name":"Sarathi Diwas","en":"Drivers' Day","category":"optional","regional":{"SK":"d|0727:2010-2060"},"estimated":"d|0727:2046"},{"id":"varalakshmi_vratam_government","name":"Varalakshmi Vratam","en":"Varalakshmi Vratam","category":"government","regional":{"AP":"d|0727:2012|0731:2020|0801:2031|0804:2017,2028|0805:2033|0808:2014,2025|0809:2019,2030|0812:2011,2016,2022|0813:2027|0816:2013,2024|0817:2035|0820:2010,2021,2032|0824:2018,2029|0825:2023,2034|0828:2015,2026"}},{"id":"varalakshmi_vratam_optional","name":"Varalakshmi Vratam","en":"Varalakshmi Vratam","category":"optional","regional":{"KA":"d|0727:2012|0731:2020|0801:2031|0804:2017,2028|0805:2033|0808:2014,2025|0809:2019,2030|0812:2011,2016,2022|0813:2027|0816:2013,2024|0817:2035|0820:2010,2021,2032|0824:2018,2029|0825:2023,2034|0828:2015,2026","TS":"d|0727:2012|0731:2020|0801:2031|0804:2017,2028|0805:2033|0808:2014,2025|0809:2019,2030|0812:2011,2016,2022|0813:2027|0816:2013,2024|0817:2035|0820:2010,2021,2032|0824:2018,2029|0825:2023,2034|0828:2015,2026"}},{"id":"naag_panchami_optional","name":"Naag Panchami","en":"Naag Panchami","category":"optional","regional":{"CG":"d|0801:2014|0802:2022|0804:2011,2030|0805:2019|0806:2012,2027|0807:2016,2031|0808:2020,2035|0809:2024|0810:2028|0811:2013,2032|0812:2017|0813:2021,2025|0814:2010,2029|0815:2018,2033|0817:2026|0819:2015,2034|0821:2023","MP":"d|0801:2014|0802:2022|0804:2011,2030|0805:2019|0806:2012,2027|0807:2016,2031|0808:2020,2035|0809:2024|0810:2028|0811:2013,2032|0812:2017|0813:2021,2025|0814:2010,2029|0815:2018,2033|0817:2026|0819:2015,2034|0821:2023"}},{"id":"raksha_bandhan_government","name":"Raksha Bandhan","en":"Raksha Bandhan","category":"government","regional":{"CG":"d|0802:2012,2031|0803:2020|0805:2028|0807:2017|0809:2025|0810:2014,2033|0811:2022|0813:2011,2030|0815:2019|0817:2027|0818:2016,2035|0819:2024|0820:2013,2032|0822:2021|0823:2029|0824:2010|0826:2018|0828:2026|0829:2015,2034|0830:2023","MP":"d|0802:2012,2031|0803:2020|0805:2028|0807:2017|0809:2025|0810:2014,2033|0811:2022|0813:2011,2030|0815:2019|0817:2027|0818:2016,2035|0819:2024|0820:2013,2032|0822:2021|0823:2029|0824:2010|0826:2018|0828:2026|0829:2015,2034|0830:2023"}},{"id":"raksha_bandhan_optional","name":"Raksha Bandhan","en":"Raksha Bandhan","category":"optional","dates":"d|0802:2012,2031|0803:2020|0805:2028|0807:2017|0809:2025|0810:2014,2033|0811:2022|0813:2011,2030|0815:2019|0817:2027|0818:2016,2035|0819:2024|0820:2013,2032|0822:2021|0823:2029|0824:2010|0826:2018|0828:2026|0829:2015,2034|0830:2023"},{"id":"raksha_bandhan_optional_women","name":"Raksha Bandhan","en":"Raksha Bandhan","category":"optional_women","regional":{"HP":"d|0802:2012,2031|0803:2020|0805:2028|0807:2017|0809:2025|0810:2014,2033|0811:2022|0813:2011,2030|0815:2019|0817:2027|0818:2016,2035|0819:2024|0820:2013,2032|0822:2021|0823:2029|0824:2010|0826:2018|0828:2026|0829:2015,2034|0830:2023"}},{"id":"parsi_new_year_optional","name":"Parsi New Year","en":"Parsi New Year","category":"optional","dates":"d|0806:2060|0807:2056-2059|0808:2052-2055|0809:2048-2051|0810:2044-2047|0811:2040-2043|0812:2036-2039|0813:2032-2035|0814:2028-2031|0815:2024-2027|0816:2020-2023|0817:2016-2019|0818:2012-2015|0819:2010-2011"},{"id":"harchath_optional","name":"Harchath","en":"Harchath","category":"optional","regional":{"CG":"d|0808:2012,2031|0809:2020|0811:2028|0813:2017|0814:2025|0816:2014,2033|0817:2022|0819:2030|0820:2011|0821:2019|0823:2016,2027|0824:2024,2035|0826:2013,2032|0828:2021|0830:2029|0831:2010|0901:2018|0902:2026|0903:2015|0904:2034|0905:2023"}},{"id":"international_day_of_the_world_s_indigenous_peop_government","name":"International Day of Adivasi Peoples","en":"International Day of the World's Indigenous Peoples","category":"government","regional":{"CG":"d|0809:2010-2060"}},{"id":"janmashtami_vaishnava_government","name":"Janmashtami (Vaishnava)","en":"Janmashtami (Vaishnava)","category":"government","regional":{"AP":"d|0810:2012,2031|0812:2020|0813:2028|0816:2025|0817:2033|0818:2014|0819:2022|0821:2030|0822:2011|0824:2019|0825:2016,2027|0826:2024,2035|0828:2013,2032|0830:2021|0901:2029|0902:2010|0903:2018|0904:2026|0905:2015|0906:2034|0907:2023","BR":"d|0810:2012,2031|0812:2020|0813:2028|0816:2025|0817:2033|0818:2014|0819:2022|0821:2030|0822:2011|0824:2019|0825:2016,2027|0826:2024,2035|0828:2013,2032|0830:2021|0901:2029|0902:2010|0903:2018|0904:2026|0905:2015|0906:2034|0907:2023","CG":"d|0810:2012,2031|0812:2020|0813:2028|0816:2025|0817:2033|0818:2014|0819:2022|0821:2030|0822:2011|0824:2019|0825:2016,2027|0826:2024,2035|0828:2013,2032|0830:2021|0901:2029|0902:2010|0903:2018|0904:2026|0905:2015|0906:2034|0907:2023","JH":"d|0810:2012,2031|0812:2020|0813:2028|0816:2025|0817:2033|0818:2014|0819:2022|0821:2030|0822:2011|0824:2019|0825:2016,2027|0826:2024,2035|0828:2013,2032|0830:2021|0901:2029|0902:2010|0903:2018|0904:2026|0905:2015|0906:2034|0907:2023","ML":"d|0810:2012,2031|0812:2020|0813:2028|0816:2025|0817:2033|0818:2014|0819:2022|0821:2030|0822:2011|0824:2019|0825:2016,2027|0826:2024,2035|0828:2013,2032|0830:2021|0901:2029|0902:2010|0903:2018|0904:2026|0905:2015|0906:2034|0907:2023","MN":"d|0810:2012,2031|0812:2020|0813:2028|0816:2025|0817:2033|0818:2014|0819:2022|0821:2030|0822:2011|0824:2019|0825:2016,2027|0826:2024,2035|0828:2013,2032|0830:2021|0901:2029|0902:2010|0903:2018|0904:2026|0905:2015|0906:2034|0907:2023","MP":"d|0810:2012,2031|0812:2020|0813:2028|0816:2025|0817:2033|0818:2014|0819:2022|0821:2030|0822:2011|0824:2019|0825:2016,2027|0826:2024,2035|0828:2013,2032|0830:2021|0901:2029|0902:2010|0903:2018|0904:2026|0905:2015|0906:2034|0907:2023","OD":"d|0810:2012,2031|0812:2020|0813:2028|0816:2025|0817:2033|0818:2014|0819:2022|0821:2030|0822:2011|0824:2019|0825:2016,2027|0826:2024,2035|0828:2013,2032|0830:2021|0901:2029|0902:2010|0903:2018|0904:2026|0905:2015|0906:2034|0907:2023","TS":"d|0810:2012,2031|0812:2020|0813:2028|0816:2025|0817:2033|0818:2014|0819:2022|0821:2030|0822:2011|0824:2019|0825:2016,2027|0826:2024,2035|0828:2013,2032|0830:2021|0901:2029|0902:2010|0903:2018|0904:2026|0905:2015|0906:2034|0907:2023"}},{"id":"durgadas_rathore_s_birthday_optional","name":"Durgadas Rathore's Jayanti","en":"Durgadas Rathore's Birthday","category":"optional","regional":{"MP":"d|0813:2010-2060"}},{"id":"patriot_s_day_government","name":"Patriot's Day","en":"Patriot's Day","category":"government","regional":{"MN":"d|0813:2010-2060"}},{"id":"independence_day_government","name":"Independence Day","en":"Independence Day","category":"government","regional":{"AP":"d|0815:2010-2060","BR":"d|0815:2010-2060","CG":"d|0815:2010-2060","JH":"d|0815:2010-2060","ML":"d|0815:2010-2060","MN":"d|0815:2010-2060","MP":"d|0815:2010-2060","OD":"d|0815:2010-2060","TS":"d|0815:2010-2060"},"estimated":"d|0815:2054"},{"id":"onam_optional","name":"Onam","en":"Onam","category":"optional","dates":"d|0820:2013,2032|0821:2021|0822:2029|0823:2010|0824:2018|0826:2026|0828:2015,2034|0829:2012,2023|0830:2031|0831:2020|0901:2028|0904:2017|0905:2025|0906:2014,2033|0908:2022|0909:2011,2030|0911:2019|0912:2027|0913:2016|0914:2035|0915:2024"},{"id":"hartalika_teej_government","name":"Hartalika Teej","en":"Hartalika Teej","category":"government","regional":{"CG":"d|0821:2020|0822:2028|0824:2017|0826:2025|0828:2014,2033|0830:2022|0831:2011,2030|0901:2019|0903:2027|0904:2016,2035|0906:2024|0908:2013,2032|0909:2021|0910:2010|0911:2029|0912:2018|0914:2026|0915:2034|0916:2015|0918:2012,2023|0919:2031"}},{"id":"hartalika_teej_optional","name":"Hartalika Teej","en":"Hartalika Teej","category":"optional","regional":{"BR":"d|0821:2020|0822:2028|0824:2017|0826:2025|0828:2014,2033|0830:2022|0831:2011,2030|0901:2019|0903:2027|0904:2016,2035|0906:2024|0908:2013,2032|0909:2021|0910:2010|0911:2029|0912:2018|0914:2026|0915:2034|0916:2015|0918:2012,2023|0919:2031"}},{"id":"ganesh_chaturthi_government","name":"Id-ul-Fitr","en":"Ganesh Chaturthi","category":"government","regional":{"AP":"d|0822:2020|0823:2028|0825:2017|0827:2025|0828:2033|0829:2014|0831:2022|0901:2011,2030|0902:2019|0904:2027|0905:2016,2035|0907:2024|0908:2032|0909:2013|0910:2021|0911:2010,2029|0913:2018|0914:2026|0916:2034|0917:2015|0919:2012,2023|0920:2031","MP":"d|0822:2020|0823:2028|0825:2017|0827:2025|0828:2033|0829:2014|0831:2022|0901:2011,2030|0902:2019|0904:2027|0905:2016,2035|0907:2024|0908:2032|0909:2013|0910:2021|0911:2010,2029|0913:2018|0914:2026|0916:2034|0917:2015|0919:2012,2023|0920:2031","OD":"d|0822:2020|0823:2028|0825:2017|0827:2025|0828:2033|0829:2014|0831:2022|0901:2011,2030|0902:2019|0904:2027|0905:2016,2035|0907:2024|0908:2032|0909:2013|0910:2021|0911:2010,2029|0913:2018|0914:2026|0916:2034|0917:2015|0919:2012,2023|0920:2031","TS":"d|0822:2020|0823:2028|0825:2017|0827:2025|0828:2033|0829:2014|0831:2022|0901:2011,2030|0902:2019|0904:2027|0905:2016,2035|0907:2024|0908:2032|0909:2013|0910:2021|0911:2010,2029|0913:2018|0914:2026|0916:2034|0917:2015|0919:2012,2023|0920:2031"}},{"id":"ganesh_chaturthi_vinayak_chaturthi_optional","name":"Ganesh Chaturthi / Vinayak Chaturthi","en":"Ganesh Chaturthi / Vinayak Chaturthi","category":"optional","dates":"d|0822:2020|0823:2028|0825:2017|0827:2025|0828:2033|0829:2014|0831:2022|0901:2011,2030|0902:2019|0904:2027|0905:2016,2035|0907:2024|0908:2032|0909:2013|0910:2021|0911:2010,2029|0913:2018|0914:2026|0916:2034|0917:2015|0920:2031"},{"id":"samvatsari_day_optional","name":"Samvatsari Day","en":"Samvatsari Day","category":"optional","regional":{"PB":"d|0823:2020|0824:2028|0826:2017|0828:2025|0830:2014,2033|0901:2022|0902:2011,2030|0903:2019|0904:2027|0906:2016,2035|0908:2024|0909:2032|0910:2013|0911:2021|0912:2010,2029|0914:2018|0915:2026|0917:2034|0918:2015|0920:2012,2023|0921:2031"}},{"id":"dol_gyaras_optional","name":"Dol Gyaras","en":"Dol Gyaras","category":"optional","regional":{"MP":"d|0829:2020|0830:2028|0902:2017|0903:2025|0905:2014,2033|0906:2022|0908:2011,2030|0909:2019|0911:2027|0913:2016,2035|0914:2024|0915:2013,2032|0917:2021|0918:2010,2029|0920:2018|0922:2026|0924:2015,2034|0925:2023|0926:2012|0927:2031"}},{"id":"anant_chaturdashi_optional","name":"Anant Chaturdashi","en":"Anant Chaturdashi","category":"optional","regional":{"BR":"d|0901:2020|0902:2028|0905:2017|0906:2025|0908:2014,2033|0909:2022|0911:2011,2030|0913:2019|0914:2027|0915:2016|0916:2035|0917:2024|0918:2013,2032|0920:2021|0921:2029|0922:2010|0924:2018|0925:2026|0927:2015,2034|0928:2023|0929:2012,2031","CG":"d|0901:2020|0902:2028|0905:2017|0906:2025|0908:2014,2033|0909:2022|0911:2011,2030|0913:2019|0914:2027|0915:2016|0916:2035|0917:2024|0918:2013,2032|0920:2021|0921:2029|0922:2010|0924:2018|0925:2026|0927:2015,2034|0928:2023|0929:2012,2031","KA":"d|0901:2020|0902:2028|0905:2017|0906:2025|0908:2014,2033|0909:2022|0911:2011,2030|0913:2019|0914:2027|0915:2016|0916:2035|0917:2024|0918:2013,2032|0920:2021|0921:2029|0922:2010|0924:2018|0925:2026|0927:2015,2034|0928:2023|0929:2012,2031","MP":"d|0901:2020|0902:2028|0905:2017|0906:2025|0908:2014,2033|0909:2022|0911:2011,2030|0913:2019|0914:2027|0915:2016|0916:2035|0917:2024|0918:2013,2032|0920:2021|0921:2029|0922:2010|0924:2018|0925:2026|0927:2015,2034|0928:2023|0929:2012,2031","PB":"d|0901:2020|0902:2028|0905:2017|0906:2025|0908:2014,2033|0909:2022|0911:2011,2030|0913:2019|0914:2027|0915:2016|0916:2035|0917:2024|0918:2013,2032|0920:2021|0921:2029|0922:2010|0924:2018|0925:2026|0927:2015,2034|0928:2023|0929:2012,2031","RJ":"d|0901:2020|0902:2028|0905:2017|0906:2025|0908:2014,2033|0909:2022|0911:2011,2030|0913:2019|0914:2027|0915:2016|0916:2035|0917:2024|0918:2013,2032|0920:2021|0921:2029|0922:2010|0924:2018|0925:2026|0927:2015,2034|0928:2023|0929:2012,2031","UK":"d|0901:2020|0902:2028|0905:2017|0906:2025|0908:2014,2033|0909:2022|0911:2011,2030|0913:2019|0914:2027|0915:2016|0916:2035|0917:2024|0918:2013,2032|0920:2021|0921:2029|0922:2010|0924:2018|0925:2026|0927:2015,2034|0928:2023|0929:2012,2031","UP":"d|0901:2020|0902:2028|0905:2017|0906:2025|0908:2014,2033|0909:2022|0911:2011,2030|0913:2019|0914:2027|0915:2016|0916:2035|0917:2024|0918:2013,2032|0920:2021|0921:2029|0922:2010|0924:2018|0925:2026|0927:2015,2034|0928:2023|0929:2012,2031"},"estimated":"d|0916:2035|0927:2034"},{"id":"saragarhi_day_optional","name":"Saragarhi Day","en":"Saragarhi Day","category":"optional","regional":{"PB":"d|0912:2010-2060"}},{"id":"vishwakarma_puja_government","name":"Vishwakarma Puja","en":"Vishwakarma Puja","category":"government","regional":{"JH":"d|0916:2012,2016,2020,2024|0917:2010-2011,2013-2015,2017-2019,2021-2023,2025-2035"}},{"id":"vishwakarma_puja_optional","name":"Vishwakarma Puja","en":"Vishwakarma Puja","category":"optional","regional":{"BR":"d|0916:2012,2016,2020,2024|0917:2010-2011,2013-2015,2017-2019,2021-2023,2025-2035","KA":"d|0916:2012,2016,2020,2024|0917:2010-2011,2013-2015,2017-2019,2021-2023,2025-2035","KL":"d|0916:2012,2016,2020,2024|0917:2010-2011,2013-2015,2017-2019,2021-2023,2025-2035","MP":"d|0916:2012,2016,2020,2024|0917:2010-2011,2013-2015,2017-2019,2021-2023,2025-2035","UP":"d|0916:2012,2016,2020,2024|0917:2010-2011,2013-2015,2017-2019,2021-2023,2025-2035"}},{"id":"mahalaya_government","name":"Mahalaya","en":"Mahalaya","category":"government","regional":{"OD":"d|0917:2020|0918:2028|0919:2017|0921:2025|0923:2014,2033|0925:2022|0927:2011,2030|0928:2019|0929:2027|0930:2016|1001:2035|1002:2024|1004:2013,2032|1006:2021|1007:2010,2029|1008:2018|1010:2026|1011:2034|1012:2015|1014:2023|1015:2012,2031"}},{"id":"sarva_pitra_moksha_amavasya_optional","name":"Sarva Pitra Moksha Amavasya","en":"Sarva Pitra Moksha Amavasya","category":"optional","regional":{"CG":"d|0917:2020|0918:2028|0919:2017|0921:2025|0923:2014,2033|0925:2022|0927:2011,2030|0928:2019|0929:2027|0930:2016|1001:2035|1002:2024|1004:2013,2032|1006:2021|1007:2010,2029|1008:2018|1010:2026|1011:2034|1012:2015|1014:2023|1015:2012,2031","MP":"d|0917:2020|0918:2028|0919:2017|0921:2025|0923:2014,2033|0925:2022|0927:2011,2030|0928:2019|0929:2027|0930:2016|1001:2035|1002:2024|1004:2013,2032|1006:2021|1007:2010,2029|1008:2018|1010:2026|1011:2034|1012:2015|1014:2023|1015:2012,2031"}},{"id":"bathukamma_government","name":"Bathukamma","en":"Bathukamma","category":"government","regional":{"TS":"d|0918:2028|0919:2017|0921:2025|0923:2014,2033|0925:2022|0927:2011,2030|0928:2019|0929:2027|0930:2016|1001:2035|1002:2024|1004:2013,2032|1006:2021|1007:2010,2029|1008:2018|1010:2026|1011:2034|1012:2015|1014:2023|1015:2012,2031|1016:2020"}},{"id":"maharaj_agrasen_s_birthday_optional","name":"Maharaj Agrasen's Jayanti","en":"Maharaj Agrasen's Birthday","category":"optional","regional":{"CH":"d|0919:2028|0921:2017|0922:2025|0924:2033|0925:2014|0926:2022|0928:2011,2030|0929:2019|0930:2027|1001:2016|1002:2035|1003:2024|1005:2013,2032|1007:2021|1008:2010,2029|1010:2018|1011:2026|1013:2015,2034|1015:2023|1016:2012|1017:2020,2031","MP":"d|0919:2028|0921:2017|0922:2025|0924:2033|0925:2014|0926:2022|0928:2011,2030|0929:2019|0930:2027|1001:2016|1002:2035|1003:2024|1005:2013,2032|1007:2021|1008:2010,2029|1010:2018|1011:2026|1013:2015,2034|1015:2023|1016:2012|1017:2020,2031","UK":"d|0919:2028|0921:2017|0922:2025|0924:2033|0925:2014|0926:2022|0928:2011,2030|0929:2019|0930:2027|1001:2016|1002:2035|1003:2024|1005:2013,2032|1007:2021|1008:2010,2029|1010:2018|1011:2026|1013:2015,2034|1015:2023|1016:2012|1017:2020,2031","UP":"d|0919:2028|0921:2017|0922:2025|0924:2033|0925:2014|0926:2022|0928:2011,2030|0929:2019|0930:2027|1001:2016|1002:2035|1003:2024|1005:2013,2032|1007:2021|1008:2010,2029|1010:2018|1011:2026|1013:2015,2034|1015:2023|1016:2012|1017:2020,2031"}},{"id":"mera_chaoren_houba_government","name":"Mera Chaoren Houba","en":"Mera Chaoren Houba","category":"government","regional":{"MN":"d|0919:2028|0921:2017|0922:2025|0924:2033|0925:2014|0926:2022|0928:2011,2030|0929:2019|0930:2027|1001:2016|1002:2035|1003:2024|1005:2013,2032|1007:2021|1008:2010,2029|1010:2018|1011:2026|1013:2015,2034|1015:2023|1016:2012|1017:2020,2031"}},{"id":"dussehra_saptami_optional","name":"Dussehra (Saptami)","en":"Dussehra (Saptami)","category":"optional","dates":"d|0925:2028|0927:2017|0929:2025|1001:2014,2033|1002:2022|1003:2011,2030|1005:2019|1006:2027|1008:2016,2035|1010:2024|1011:2013,2032|1012:2021|1013:2029|1014:2010|1016:2018|1018:2026|1019:2034|1020:2015|1021:2012,2023|1022:2020,2031"},{"id":"mahasaptami_government","name":"Mahasaptami","en":"Mahasaptami","category":"government","regional":{"BR":"d|0925:2028|0927:2017|0929:2025|1001:2014,2033|1002:2022|1003:2011,2030|1005:2019|1006:2027|1008:2016,2035|1010:2024|1011:2013,2032|1012:2021|1013:2029|1014:2010|1016:2018|1018:2026|1019:2034|1020:2015|1021:2012,2023|1022:2020,2031","OD":"d|0925:2028|0927:2017|0929:2025|1001:2014,2033|1002:2022|1003:2011,2030|1005:2019|1006:2027|1008:2016,2035|1010:2024|1011:2013,2032|1012:2021|1013:2029|1014:2010|1016:2018|1017:2026|1019:2034|1020:2015|1021:2012,2023|1022:2020,2031"}},{"id":"durgashtami_government","name":"Durgashtami","en":"Durgashtami","category":"government","regional":{"AP":"d|0926:2028|0928:2017|0930:2025|1002:2014,2033|1003:2022|1004:2011,2030|1006:2019|1007:2027|1009:2016,2035|1011:2024|1012:2013,2032|1013:2021|1014:2029|1015:2010|1017:2018|1019:2026|1020:2034|1021:2015|1022:2012,2023|1023:2020,2031"}},{"id":"dussehra_mahanavami_optional","name":"Dussehra (Mahanavami)","en":"Dussehra (Mahanavami)","category":"optional","dates":"d|0926:2028|0929:2017|1001:2025|1002:2014,2033|1004:2022|1005:2011,2030|1006:2019|1008:2027|1010:2016,2035|1011:2024|1012:2013|1013:2032|1014:2021|1015:2029|1016:2010|1017:2018|1019:2026|1021:2015,2034|1023:2012,2023|1024:2020,2031"},{"id":"dussehra_mahashtami_optional","name":"Dussehra (Mahashtami)","en":"Dussehra (Mahashtami)","category":"optional","dates":"d|0926:2028|0928:2017|0930:2025|1002:2014,2033|1003:2022|1004:2011,2030|1006:2019|1007:2027|1009:2016,2035|1011:2024|1012:2013,2032|1013:2021|1014:2029|1015:2010|1017:2018|1019:2026|1020:2034|1021:2015|1022:2012,2023|1023:2020,2031"},{"id":"mahanavami_government","name":"Mahanavami","en":"Mahanavami","category":"government","regional":{"OD":"d|0926:2028|0929:2017|1001:2025|1002:2014,2033|1004:2022|1005:2011,2030|1006:2019|1008:2027|1010:2016,2035|1011:2024|1012:2013|1013:2032|1014:2021|1015:2029|1016:2010|1017:2018|1019:2026|1021:2015,2034|1023:2012,2023|1024:2020,2031"}},{"id":"mahashtami_government","name":"Mahashtami","en":"Mahashtami","category":"government","regional":{"OD":"d|0926:2028|0928:2017|0930:2025|1002:2014,2033|1003:2022|1004:2011,2030|1006:2019|1007:2027|1009:2016,2035|1011:2024|1012:2013,2032|1013:2021|1014:2029|1015:2010|1017:2018|1018:2026|1020:2034|1021:2015|1022:2012,2023|1023:2020,2031"}},{"id":"dussehra_government","name":"Dussehra","en":"Dussehra","category":"government","regional":{"AP":"d|0927:2028|0930:2017|1002:2025|1003:2014,2033|1005:2022|1006:2011,2030|1008:2019|1009:2027|1011:2016,2035|1012:2024|1013:2013|1014:2032|1015:2021|1016:2029|1017:2010|1019:2018|1020:2026|1022:2015,2034|1024:2012,2023|1025:2020,2031","BR":"d|0927:2028|0930:2017|1002:2025|1003:2014,2033|1005:2022|1006:2011,2030|1008:2019|1009:2027|1011:2016,2035|1012:2024|1013:2013|1014:2032|1015:2021|1016:2029|1017:2010|1019:2018|1020:2026|1022:2015,2034|1024:2012,2023|1025:2020,2031","CG":"d|0927:2028|0930:2017|1002:2025|1003:2014,2033|1005:2022|1006:2011,2030|1008:2019|1009:2027|1011:2016,2035|1012:2024|1013:2013|1014:2032|1015:2021|1016:2029|1017:2010|1019:2018|1020:2026|1022:2015,2034|1024:2012,2023|1025:2020,2031","JH":"d|0927:2028|0930:2017|1002:2025|1003:2014,2033|1005:2022|1006:2011,2030|1008:2019|1009:2027|1011:2016,2035|1012:2024|1013:2013|1014:2032|1015:2021|1016:2029|1017:2010|1019:2018|1020:2026|1022:2015,2034|1024:2012,2023|1025:2020,2031","ML":"d|0927:2028|0930:2017|1002:2025|1003:2014,2033|1005:2022|1006:2011,2030|1008:2019|1009:2027|1011:2016,2035|1012:2024|1013:2013|1014:2032|1015:2021|1016:2029|1017:2010|1019:2018|1020:2026|1022:2015,2034|1024:2012,2023|1025:2020,2031","MN":"d|0927:2028|0930:2017|1002:2025|1003:2014,2033|1005:2022|1006:2011,2030|1008:2019|1009:2027|1011:2016,2035|1012:2024|1013:2013|1014:2032|1015:2021|1016:2029|1017:2010|1019:2018|1020:2026|1022:2015,2034|1024:2012,2023|1025:2020,2031","MP":"d|0927:2028|0930:2017|1002:2025|1003:2014,2033|1005:2022|1006:2011,2030|1008:2019|1009:2027|1011:2016,2035|1012:2024|1013:2013|1014:2032|1015:2021|1016:2029|1017:2010|1019:2018|1020:2026|1022:2015,2034|1024:2012,2023|1025:2020,2031","OD":"d|0927:2028|0930:2017|1002:2025|1003:2014,2033|1005:2022|1006:2011,2030|1008:2019|1009:2027|1011:2016,2035|1012:2024|1013:2013|1014:2032|1015:2021|1016:2029|1017:2010|1019:2018|1020:2026|1022:2015,2034|1024:2012,2023|1025:2020,2031","TS":"d|0927:2028|0930:2017|1002:2025|1003:2014,2033|1005:2022|1006:2011,2030|1008:2019|1009:2027|1011:2016,2035|1012:2024|1013:2013|1014:2032|1015:2021|1016:2029|1017:2010|1019:2018|1020:2026|1022:2015,2034|1024:2012,2023|1025:2020,2031"}},{"id":"bhagat_singh_s_birthday_optional","name":"Bhagat Singh's Jayanti","en":"Bhagat Singh's Birthday","category":"optional","regional":{"PB":"d|0928:2010-2060"}},{"id":"mahatma_gandhi_s_birthday_government","name":"Mahatma Gandhi's Jayanti","en":"Mahatma Gandhi's Birthday","category":"government","regional":{"AP":"d|1002:2010-2060","BR":"d|1002:2010-2060","CG":"d|1002:2010-2060","JH":"d|1002:2010-2060","ML":"d|1002:2010-2060","MN":"d|1002:2010-2060","MP":"d|1002:2010-2060","OD":"d|1002:2010-2060","TS":"d|1002:2010-2060"}},{"id":"maharaj_ajmodh_dev_s_birthday_optional","name":"Maharaj Ajmodh Dev's Jayanti","en":"Maharaj Ajmodh Dev's Birthday","category":"optional","regional":{"CG":"d|1003:2028|1005:2017|1007:2025|1008:2014,2033|1009:2022|1011:2011,2030|1013:2019|1015:2027|1016:2016|1017:2024,2035|1018:2013,2032|1020:2021|1022:2010,2029|1024:2018|1026:2026|1027:2015,2034|1028:2023|1029:2012|1030:2031|1031:2020","MP":"d|1003:2028|1005:2017|1007:2025|1008:2014,2033|1009:2022|1011:2011,2030|1013:2019|1015:2027|1016:2016|1017:2024,2035|1018:2013,2032|1020:2021|1022:2010,2029|1024:2018|1026:2026|1027:2015,2034|1028:2023|1029:2012|1030:2031|1031:2020"}},{"id":"maharishi_valmiki_s_birthday_government","name":"Maharshi Valmiki's Jayanti","en":"Maharishi Valmiki's Birthday","category":"government","regional":{"MP":"d|1003:2028|1005:2017|1007:2025|1008:2014,2033|1009:2022|1011:2011,2030|1013:2019|1015:2027|1016:2016|1017:2024,2035|1018:2013,2032|1020:2021|1022:2010,2029|1024:2018|1026:2026|1027:2015,2034|1028:2023|1029:2012|1030:2031|1031:2020"}},{"id":"maharishi_valmiki_s_birthday_optional","name":"Maharshi Valmiki's Jayanti","en":"Maharishi Valmiki's Birthday","category":"optional","dates":"d|1003:2028|1005:2017|1007:2025|1008:2014,2033|1009:2022|1011:2011,2030|1013:2019|1015:2027|1016:2016|1017:2024,2035|1018:2013,2032|1020:2021|1022:2010,2029|1024:2018|1026:2026|1027:2015,2034|1028:2023|1029:2012|1030:2031|1031:2020"},{"id":"sant_guru_tekchand_maharaj_samadhi_utsav_optional","name":"Sant Guru Tekchand Maharaj Samadhi Utsav","en":"Sant Guru Tekchand Maharaj Samadhi Utsav","category":"optional","regional":{"CG":"d|1003:2028|1005:2017|1007:2025|1008:2014,2033|1009:2022|1011:2011,2030|1013:2019|1015:2027|1016:2016|1017:2024,2035|1018:2013,2032|1020:2021|1022:2010,2029|1024:2018|1026:2026|1027:2015,2034|1028:2023|1029:2012|1030:2031|1031:2020","MP":"d|1003:2028|1005:2017|1007:2025|1008:2014,2033|1009:2022|1011:2011,2030|1013:2019|1015:2027|1016:2016|1017:2024,2035|1018:2013,2032|1020:2021|1022:2010,2029|1024:2018|1026:2026|1027:2015,2034|1028:2023|1029:2012|1030:2031|1031:2020"}},{"id":"karaka_chaturthi_karwa_chouth_optional","name":"Karaka Chaturthi (Karwa Chouth)","en":"Karaka Chaturthi (Karwa Chouth)","category":"optional","dates":"d|1007:2028|1008:2017|1010:2025|1011:2014,2033|1013:2022|1015:2030|1017:2019|1018:2027|1019:2016|1020:2024,2035|1021:2032|1022:2013|1024:2021|1026:2029|1027:2018|1029:2026|1030:2015,2034|1101:2023|1102:2012,2031|1104:2020"},{"id":"karwa_chouth_optional_women","name":"Karwa Chouth","en":"Karwa Chouth","category":"optional_women","regional":{"HP":"d|1007:2028|1008:2017|1010:2025|1011:2014,2033|1013:2022|1015:2011,2030|1017:2019|1018:2027|1019:2016|1020:2024,2035|1021:2032|1022:2013|1024:2021|1026:2010,2029|1027:2018|1029:2026|1030:2015,2034|1101:2023|1102:2012,2031|1104:2020"}},{"id":"baba_banda_singh_bahadur_s_birthday_optional","name":"Baba Banda Singh Bahadur's Jayanti","en":"Baba Banda Singh Bahadur's Birthday","category":"optional","regional":{"PB":"d|1016:2010-2060"}},{"id":"diwali_south_india_optional","name":"Deepavali (South India)","en":"Diwali (South India)","category":"optional","dates":"d|1018:2017|1022:2014|1025:2011|1029:2016|1102:2013|1104:2010|1106:2018|1110:2015|1112:2012","regional":{"CG":"d|1016:2028|1019:2025|1021:2033|1023:2022|1025:2030|1026:2019|1028:2027|1029:2035|1030:2024|1101:2032|1103:2021|1104:2029|1107:2026|1109:2034|1111:2023|1113:2020,2031"}},{"id":"diwali_government","name":"Diwali (Deepavali)","en":"Diwali","category":"government","regional":{"AP":"d|1017:2028|1019:2017|1020:2025|1022:2033|1023:2014|1024:2022|1026:2011,2030|1027:2019|1029:2027|1030:2016,2035|1031:2024|1102:2032|1103:2013|1104:2021|1105:2010,2029|1107:2018|1108:2026|1110:2034|1111:2015|1112:2023|1113:2012|1114:2020,2031","BR":"d|1017:2028|1019:2017|1020:2025|1022:2033|1023:2014|1024:2022|1026:2011,2030|1027:2019|1029:2027|1030:2016,2035|1031:2024|1102:2032|1103:2013|1104:2021|1105:2010,2029|1107:2018|1108:2026|1110:2034|1111:2015|1112:2023|1113:2012|1114:2020,2031","CG":"d|1017:2028|1019:2017|1020:2025|1022:2033|1023:2014|1024:2022|1026:2011,2030|1027:2019|1029:2027|1030:2016,2035|1031:2024|1102:2032|1103:2013|1104:2021|1105:2010,2029|1107:2018|1108:2026|1110:2034|1111:2015|1112:2023|1113:2012|1114:2020,2031","JH":"d|1017:2028|1019:2017|1020:2025|1022:2033|1023:2014|1024:2022|1026:2011,2030|1027:2019|1029:2027|1030:2016,2035|1031:2024|1102:2032|1103:2013|1104:2021|1105:2010,2029|1107:2018|1108:2026|1110:2034|1111:2015|1112:2023|1113:2012|1114:2020,2031","ML":"d|1017:2028|1019:2017|1020:2025|1022:2033|1023:2014|1024:2022|1026:2011,2030|1027:2019|1029:2027|1030:2016,2035|1031:2024|1102:2032|1103:2013|1104:2021|1105:2010,2029|1107:2018|1108:2026|1110:2034|1111:2015|1112:2023|1113:2012|1114:2020,2031","MN":"d|1017:2028|1019:2017|1020:2025|1022:2033|1023:2014|1024:2022|1026:2011,2030|1027:2019|1029:2027|1030:2016,2035|1031:2024|1102:2032|1103:2013|1104:2021|1105:2010,2029|1107:2018|1108:2026|1110:2034|1111:2015|1112:2023|1113:2012|1114:2020,2031","MP":"d|1017:2028|1019:2017|1020:2025|1022:2033|1023:2014|1024:2022|1026:2011,2030|1027:2019|1029:2027|1030:2016,2035|1031:2024|1102:2032|1103:2013|1104:2021|1105:2010,2029|1107:2018|1108:2026|1110:2034|1111:2015|1112:2023|1113:2012|1114:2020,2031","OD":"d|1017:2028|1019:2017|1020:2025|1022:2033|1023:2014|1024:2022|1026:2011,2030|1027:2019|1029:2027|1030:2016,2035|1031:2024|1102:2032|1103:2013|1104:2021|1105:2010,2029|1107:2018|1108:2026|1110:2034|1111:2015|1112:2023|1113:2012|1114:2020,2031","TS":"d|1017:2028|1019:2017|1020:2025|1022:2033|1023:2014|1024:2022|1026:2011,2030|1027:2019|1029:2027|1030:2016,2035|1031:2024|1102:2032|1103:2013|1104:2021|1105:2010,2029|1107:2018|1108:2026|1110:2034|1111:2015|1112:2023|1113:2012|1114:2020,2031"}},{"id":"naraka_chaturdashi_optional","name":"Naraka Chaturdashi","en":"Naraka Chaturdashi","category":"optional","dates":"d|1017:2028|1018:2017|1020:2025|1022:2014,2033|1024:2022|1026:2011,2030|1027:2019|1028:2027|1029:2016|1030:2035|1031:2024|1102:2013,2032|1104:2021|1105:2010,2029|1106:2018|1108:2026|1109:2034|1110:2015|1112:2012,2023|1113:2031|1114:2020"},{"id":"govardhan_puja_government","name":"Govardhan Puja","en":"Govardhan Puja","category":"government","regional":{"JH":"d|1018:2028|1020:2017|1022:2025|1023:2033|1024:2014|1025:2022|1027:2011,2030|1028:2019|1030:2027|1031:2016,2035|1102:2024|1103:2032|1104:2013|1105:2021|1106:2010,2029|1108:2018|1110:2026|1111:2034|1112:2015|1113:2023|1114:2012|1115:2020,2031","MP":"d|1018:2028|1020:2017|1022:2025|1023:2033|1024:2014|1025:2022|1027:2011,2030|1028:2019|1030:2027|1031:2016,2035|1102:2024|1103:2032|1104:2013|1105:2021|1106:2010,2029|1108:2018|1110:2026|1111:2034|1112:2015|1113:2023|1114:2012|1115:2020,2031"}},{"id":"govardhan_puja_optional","name":"Govardhan Puja","en":"Govardhan Puja","category":"optional","dates":"d|1018:2028|1020:2017|1022:2025|1023:2033|1024:2014|1025:2022|1027:2011,2030|1028:2019|1030:2027|1031:2016,2035|1102:2024|1103:2032|1104:2013|1105:2021|1106:2010,2029|1108:2018|1110:2026|1111:2034|1112:2015|1113:2023|1114:2012|1115:2020,2031"},{"id":"bhai_duj_government","name":"Bhai Duj","en":"Bhai Duj","category":"government","regional":{"JH":"d|1019:2028|1021:2017|1023:2025|1024:2033|1025:2014|1026:2022|1028:2011,2030|1029:2019|1031:2027|1101:2016,2035|1103:2024|1104:2032|1105:2013|1106:2021|1107:2010,2029|1109:2018|1111:2026|1112:2034|1113:2015|1114:2023|1115:2012|1116:2020,2031"}},{"id":"bhai_duj_optional","name":"Bhai Duj","en":"Bhai Duj","category":"optional","dates":"d|1019:2028|1021:2017|1023:2025|1024:2033|1025:2014|1026:2022|1028:2011,2030|1029:2019|1031:2027|1101:2016,2035|1103:2024|1104:2032|1105:2013|1106:2021|1107:2010,2029|1109:2018|1111:2026|1112:2034|1113:2015|1114:2023|1115:2012|1116:2020,2031"},{"id":"bhai_duj_optional_women","name":"Bhai Duj","en":"Bhai Duj","category":"optional_women","regional":{"HP":"d|1019:2028|1021:2017|1023:2025|1024:2033|1025:2014|1026:2022|1028:2011,2030|1029:2019|1031:2027|1101:2016,2035|1103:2024|1104:2032|1105:2013|1106:2021|1107:2010,2029|1109:2018|1111:2026|1112:2034|1113:2015|1114:2023|1115:2012|1116:2020,2031"}},{"id":"chhath_puja_government","name":"Chhath Puja","en":"Chhath Puja","category":"government","regional":{"BR":"d|1023:2028|1026:2017|1028:2025|1029:2014,2033|1030:2022|1101:2011,2030|1102:2019|1104:2027|1106:2016,2035|1107:2024|1108:2013|1109:2032|1110:2021|1111:2010,2029|1113:2018|1115:2026|1117:2015,2034|1119:2012,2023|1120:2020,2031","CG":"d|1023:2028|1026:2017|1028:2025|1029:2014,2033|1030:2022|1101:2011,2030|1102:2019|1104:2027|1106:2016,2035|1107:2024|1108:2013|1109:2032|1110:2021|1111:2010,2029|1113:2018|1115:2026|1117:2015,2034|1119:2012,2023|1120:2020,2031"}},{"id":"chhath_puja_optional","name":"Chhath Puja","en":"Chhath Puja","category":"optional","regional":{"SK":"d|1023:2028|1026:2017|1028:2025|1029:2014,2033|1030:2022|1101:2011,2030|1102:2019|1104:2027|1106:2016,2035|1107:2024|1108:2013|1109:2032|1110:2021|1111:2010,2029|1113:2018|1115:2026|1117:2015,2034|1119:2012,2023|1120:2020,2031"}},{"id":"pratihar_shashthi_or_surya_shashthi_chhath_puja_optional","name":"Pratihar Shashthi or Surya Shashthi (Chhath Puja)","en":"Pratihar Shashthi or Surya Shashthi (Chhath Puja)","category":"optional","dates":"d|1023:2028|1026:2017|1028:2025|1029:2014,2033|1030:2022|1101:2011,2030|1102:2019|1104:2027|1106:2016,2035|1107:2024|1108:2013|1109:2032|1110:2021|1111:2029|1113:2018|1115:2026|1117:2015,2034|1119:2012,2023|1120:2020,2031"},{"id":"acharya_narendra_dev_s_birthday_optional","name":"Acharya Narendra Dev's Jayanti","en":"Acharya Narendra Dev's Birthday","category":"optional","regional":{"UP":"d|1031:2010-2060"}},{"id":"sardar_vallabhbhai_patel_s_birthday_optional","name":"Sardar Vallabhbhai Patel's Jayanti","en":"Sardar Vallabhbhai Patel's Birthday","category":"optional","regional":{"UP":"d|1031:2010-2060"}},{"id":"dev_diwali_optional","name":"Dev Diwali","en":"Dev Diwali","category":"optional","regional":{"GJ":"d|1101:2028|1103:2017|1105:2025|1106:2014,2033|1107:2022|1109:2030|1110:2011|1112:2019|1113:2027|1114:2016|1115:2024,2035|1116:2032|1117:2013|1118:2021|1120:2029|1121:2010|1122:2018|1124:2026|1125:2015,2034|1126:2023|1128:2012,2031|1129:2020"},"estimated":"d|1120:2029"},{"id":"kut_government","name":"Kut","en":"Kut","category":"government","regional":{"MN":"d|1101:2010-2060"}},{"id":"kut_optional","name":"Kut","en":"Kut","category":"optional","regional":{"AS":"d|1101:2010-2060"}},{"id":"new_punjab_day_optional","name":"New Punjab Day","en":"New Punjab Day","category":"optional","regional":{"PB":"d|1101:2010-2060"}},{"id":"all_souls_day_optional","name":"All Souls' Day","en":"All Souls' Day","category":"optional","regional":{"GA":"d|1102:2010-2060","ML":"d|1102:2010-2060","PY":"d|1102:2010-2060"}},{"id":"guru_nanak_s_birthday_government","name":"Guru Nanak's Jayanti","en":"Guru Nanak's Birthday","category":"government","regional":{"AP":"d|1102:2028|1104:2017|1105:2025|1106:2014,2033|1108:2022|1110:2011,2030|1112:2019|1114:2016,2027|1115:2024,2035|1117:2013,2032|1119:2021|1121:2010,2029|1123:2018|1124:2026|1125:2015,2034|1127:2023|1128:2012,2031|1130:2020","BR":"d|1102:2028|1104:2017|1105:2025|1106:2014,2033|1108:2022|1110:2011,2030|1112:2019|1114:2016,2027|1115:2024,2035|1117:2013,2032|1119:2021|1121:2010,2029|1123:2018|1124:2026|1125:2015,2034|1127:2023|1128:2012,2031|1130:2020","CG":"d|1102:2028|1104:2017|1105:2025|1106:2014,2033|1108:2022|1110:2011,2030|1112:2019|1114:2016,2027|1115:2024,2035|1117:2013,2032|1119:2021|1121:2010,2029|1123:2018|1124:2026|1125:2015,2034|1127:2023|1128:2012,2031|1130:2020","JH":"d|1102:2028|1104:2017|1105:2025|1106:2014,2033|1108:2022|1110:2011,2030|1112:2019|1114:2016,2027|1115:2024,2035|1117:2013,2032|1119:2021|1121:2010,2029|1123:2018|1124:2026|1125:2015,2034|1127:2023|1128:2012,2031|1130:2020","ML":"d|1102:2028|1104:2017|1105:2025|1106:2014,2033|1108:2022|1110:2011,2030|1112:2019|1114:2016,2027|1115:2024,2035|1117:2013,2032|1119:2021|1121:2010,2029|1123:2018|1124:2026|1125:2015,2034|1127:2023|1128:2012,2031|1130:2020","MN":"d|1102:2028|1104:2017|1105:2025|1106:2014,2033|1108:2022|1110:2011,2030|1112:2019|1114:2016,2027|1115:2024,2035|1117:2013,2032|1119:2021|1121:2010,2029|1123:2018|1124:2026|1125:2015,2034|1127:2023|1128:2012,2031|1130:2020","MP":"d|1102:2028|1104:2017|1105:2025|1106:2014,2033|1108:2022|1110:2011,2030|1112:2019|1114:2016,2027|1115:2024,2035|1117:2013,2032|1119:2021|1121:2010,2029|1123:2018|1124:2026|1125:2015,2034|1127:2023|1128:2012,2031|1130:2020","OD":"d|1102:2028|1104:2017|1105:2025|1106:2014,2033|1108:2022|1110:2011,2030|1112:2019|1114:2016,2027|1115:2024,2035|1117:2013,2032|1119:2021|1121:2010,2029|1123:2018|1124:2026|1125:2015,2034|1127:2023|1128:2012,2031|1130:2020","TS":"d|1102:2028|1104:2017|1105:2025|1106:2014,2033|1108:2022|1110:2011,2030|1112:2019|1114:2016,2027|1115:2024,2035|1117:2013,2032|1119:2021|1121:2010,2029|1123:2018|1124:2026|1125:2015,2034|1127:2023|1128:2012,2031|1130:2020"}},{"id":"wangala_festival_government","name":"Wangala Festival","en":"Wangala Festival","category":"government","regional":{"ML":"d|1107:2025|1108:2013,2019,2024,2030,2041,2047,2052,2058|1109:2012,2018,2029,2035,2040,2046,2057|1110:2017,2023,2028,2034,2045,2051,2056|1111:2011,2016,2022,2033,2039,2044,2050|1112:2010,2021,2027,2032,2038,2049,2055,2060|1113:2015,2020,2026,2037,2043,2048,2054|1114:2014,2031,2036,2042,2053,2059"}},{"id":"wangala_festival_optional","name":"Wangala Festival","en":"Wangala Festival","category":"optional","regional":{"AS":"d|1107:2025|1108:2013,2019,2024,2030,2041,2047,2052,2058|1109:2012,2018,2029,2035,2040,2046,2057|1110:2017,2023,2028,2034,2045,2051,2056|1111:2011,2016,2022,2033,2039,2044,2050|1112:2010,2021,2027,2032,2038,2049,2055,2060|1113:2015,2020,2026,2037,2043,2048,2054|1114:2014,2031,2036,2042,2053,2059"}},{"id":"tribal_pride_day_government","name":"Janjatiya Gaurav Divas","en":"Tribal Pride Day","category":"government","regional":{"MP":"d|1115:2010-2060"}},{"id":"veerangana_uda_devi_s_martyrdom_day_optional","name":"Veerangana Uda Devi's Shaheedi Diwas","en":"Veerangana Uda Devi's Martyrdom Day","category":"optional","regional":{"UP":"d|1116:2010-2060"}},{"id":"satya_sai_baba_of_puttaparthi_s_birthday_optional","name":"Satya Sai Baba of Puttaparthi's Jayanti","en":"Satya Sai Baba of Puttaparthi's Birthday","category":"optional","regional":{"SK":"d|1123:2010-2060"}},{"id":"seng_kut_snem_government","name":"Seng Kut Snem","en":"Seng Kut Snem","category":"government","regional":{"ML":"d|1123:2010-2060"}},{"id":"guru_tegh_bahadur_s_martyrdom_day_optional","name":"Guru Tegh Bahadur's Shaheedi Diwas","en":"Guru Tegh Bahadur's Martyrdom Day","category":"optional","dates":"d|1124:2010-2060"},{"id":"dattatreya_s_birthday_optional","name":"Dattatreya's Jayanti","en":"Dattatreya's Birthday","category":"optional","regional":{"CG":"d|1201:2028|1203:2017|1204:2025|1205:2033|1206:2014|1207:2022|1209:2030|1210:2011|1211:2019|1213:2016,2027|1214:2024,2035|1216:2013,2032|1218:2021|1220:2010,2029|1222:2018|1223:2026|1224:2015,2034|1226:2023|1227:2012|1228:2031|1229:2020","MP":"d|1201:2028|1203:2017|1204:2025|1205:2033|1206:2014|1207:2022|1209:2030|1210:2011|1211:2019|1213:2016,2027|1214:2024,2035|1216:2013,2032|1218:2021|1220:2010,2029|1222:2018|1223:2026|1224:2015,2034|1226:2023|1227:2012|1228:2031|1229:2020"},"estimated":"d|1201:2028"},{"id":"shaheed_veer_narayan_singh_s_martyrdom_day_optional","name":"Shaheed Veer Narayan Singh's Shaheedi Diwas","en":"Shaheed Veer Narayan Singh's Martyrdom Day","category":"optional","regional":{"CG":"d|1201:2010-2060"}},{"id":"international_day_of_persons_with_disabilities_optional","name":"Vishva Divyang Divas","en":"International Day of Persons with Disabilities","category":"optional","regional":{"MP":"d|1203:2010-2060"}},{"id":"rajendra_prasad_s_birthday_optional","name":"Rajendra Prasad's Jayanti","en":"Rajendra Prasad's Birthday","category":"optional","regional":{"BR":"d|1203:2010-2060"}},{"id":"immaculate_conception_optional","name":"Feast of Immaculate Conception of Mary","en":"Immaculate Conception","category":"optional","regional":{"GA":"d|1208:2010-2060","PY":"d|1208:2010-2060"}},{"id":"martyrs_day_optional","name":"Shaheedi Divas","en":"Martyrs' Day","category":"optional","regional":{"AS":"d|1210:2010-2060"}},{"id":"pa_togan_nengminja_sangma_s_death_anniversary_government","name":"Pa Togan Nengminja Sangma's Death Anniversary","en":"Pa Togan Nengminja Sangma's Death Anniversary","category":"government","regional":{"ML":"d|1212:2010-2060"}},{"id":"guru_ghasidas_s_birthday_government","name":"Guru Ghasidas's Jayanti","en":"Guru Ghasidas's Birthday","category":"government","regional":{"CG":"d|1218:2010-2060"}},{"id":"guru_ghasidas_s_birthday_optional","name":"Guru Ghasidas's Jayanti","en":"Guru Ghasidas's Birthday","category":"optional","regional":{"MP":"d|1218:2010-2060"}},{"id":"u_soso_thama_s_death_anniversary_government","name":"U Soso Thama's Death Anniversary","en":"U Soso Thama's Death Anniversary","category":"government","regional":{"ML":"d|1218:2010-2060"}},{"id":"chaudhary_charan_singh_s_birthday_optional","name":"Chaudhary Charan Singh's Jayanti","en":"Chaudhary Charan Singh's Birthday","category":"optional","regional":{"UP":"d|1223:2010-2060"}},{"id":"christmas_eve_optional","name":"Christmas Eve","en":"Christmas Eve","category":"optional","dates":"d|1224:2010-2060"},{"id":"christmas_festival_government","name":"Christmas Festival","en":"Christmas Festival","category":"government","regional":{"ML":"d|1224:2010-2060|1226:2010-2060|1227:2010-2060"},"estimated":"d|1224:2042"},{"id":"christmas_government","name":"Christmas","en":"Christmas","category":"government","regional":{"AP":"d|1225:2010-2060","BR":"d|1225:2010-2060","CG":"d|1225:2010-2060","JH":"d|1225:2010-2060","ML":"d|1225:2010-2060","MN":"d|1225:2010-2060","MP":"d|1225:2010-2060","OD":"d|1225:2010-2060","TS":"d|1225:2010-2060"}},{"id":"boxing_day_government","name":"Boxing Day","en":"Boxing Day","category":"government","regional":{"TS":"d|1226:2010-2060"}},{"id":"boxing_day_optional","name":"Boxing Day","en":"Boxing Day","category":"optional","regional":{"AP":"d|1226:2010-2060"}},{"id":"jor_mela_fatehgarh_sahib_optional","name":"Jor Mela Fatehgarh Sahib","en":"Jor Mela Fatehgarh Sahib","category":"optional","regional":{"CH":"d|1226:2010-2060|1227:2010-2060|1228:2010-2060"}},{"id":"lingri_niki_sii_donyi_polo_yullo_optional","name":"Lingri Niki Sii Donyi Polo Yullo","en":"Lingri Niki Sii Donyi Polo Yullo","category":"optional","regional":{"AR":"d|1226:2010-2060"}},{"id":"post_christmas_optional","name":"Post Christmas","en":"Post Christmas","category":"optional","regional":{"MN":"d|1226:2010-2060","MZ":"d|1227:2010-2060|1228:2010-2060"}},{"id":"shaheed_udham_singh_s_birthday_optional","name":"Shaheed Udham Singh's Jayanti","en":"Shaheed Udham Singh's Birthday","category":"optional","regional":{"HR":"d|1226:2010-2060"}},{"id":"first_flag_hoisting_of_independent_india_by_neta_optional","name":"First Flag Hoisting of Independent India by Netaji Subhas Chandra Bose","en":"First Flag Hoisting of Independent India by Netaji Subhas Chandra Bose","category":"optional","regional":{"AN":"d|1230:2010-2060"},"estimated":"d|1230:2058"},{"id":"u_kiang_nongbah_s_death_anniversary_government","name":"U Kiang Nongbah's Death Anniversary","en":"U Kiang Nongbah's Death Anniversary","category":"government","regional":{"ML":"d|1230:2010-2060"}},{"id":"new_year_s_eve_optional","name":"New Year's Eve","en":"New Year's Eve","category":"optional","regional":{"GA":"d|1231:2010-2060","MN":"d|1231:2010-2060"}}]} diff --git a/app/Support/Holidays/data/IO.json b/app/Support/Holidays/data/IO.json new file mode 100644 index 000000000..61875afa7 --- /dev/null +++ b/app/Support/Holidays/data/IO.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"IO","language":null,"years":[2010,2060],"subdivisions":{},"holidays":[]} diff --git a/app/Support/Holidays/data/IQ.json b/app/Support/Holidays/data/IQ.json new file mode 100644 index 000000000..1537d6ca8 --- /dev/null +++ b/app/Support/Holidays/data/IQ.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"IQ","language":"ar","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"رأس السنة الميلادية","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"ashura","name":"عاشوراء","en":"Ashura","category":"public","dates":"y|2010:1216|2011:1205|2012:1124|2013:1114|2014:1103|2015:1024|2016:1012|2017:1001|2018:0920|2019:0910|2020:0830|2021:0819|2022:0808|2023:0729|2024:0716|2025:0705|2026:0625|2027:0615|2028:0603|2029:0523|2030:0512|2031:0502|2032:0420|2033:0410|2034:0330|2035:0320|2036:0308|2037:0225|2038:0214|2039:0204|2040:0124|2041:0113|2042:0102,1223|2043:1212|2044:1130|2045:1119|2046:1109|2047:1029|2048:1018|2049:1007|2050:0926|2051:0915|2052:0904|2053:0824|2054:0814|2055:0803|2056:0723|2057:0712|2058:0701|2059:0620|2060:0609","estimated":"y|2010:1216|2011:1205|2012:1124|2026:0625|2027:0615|2028:0603|2029:0523|2030:0512|2031:0502|2032:0420|2033:0410|2034:0330|2035:0320|2036:0308|2037:0225|2038:0214|2039:0204|2040:0124|2041:0113|2042:0102,1223|2043:1212|2044:1130|2045:1119|2046:1109|2047:1029|2048:1018|2049:1007|2050:0926|2051:0915|2052:0904|2053:0824|2054:0814|2055:0803|2056:0723|2057:0712|2058:0701|2059:0620|2060:0609"},{"id":"eid_al_fitr","name":"عيد الفطر","en":"Eid al-Fitr","category":"public","dates":"y|2010:0910-0912|2011:0830-0901|2012:0819-0821|2013:0808-0810|2014:0728-0730|2015:0718-0720|2016:0707-0709|2017:0626-0628|2018:0615-0617|2019:0604-0606|2020:0524-0526|2021:0513-0515|2022:0502-0504|2023:0421-0423|2024:0410-0412|2025:0331-0402|2026:0320-0322|2027:0309-0311|2028:0226-0228|2029:0214-0216|2030:0204-0206|2031:0124-0126|2032:0114-0116|2033:0102-0104,1223-1225|2034:1212-1214|2035:1201-1203|2036:1119-1121|2037:1108-1110|2038:1029-1031|2039:1019-1021|2040:1007-1009|2041:0926-0928|2042:0915-0917|2043:0904-0906|2044:0824-0826|2045:0814-0816|2046:0803-0805|2047:0724-0726|2048:0712-0714|2049:0701-0703|2050:0620-0622|2051:0610-0612|2052:0529-0531|2053:0519-0521|2054:0509-0511|2055:0428-0430|2056:0416-0418|2057:0405-0407|2058:0325-0327|2059:0315-0317|2060:0304-0306","estimated":"y|2010:0910-0912|2011:0830-0901|2012:0819-0821|2026:0320-0322|2027:0309-0311|2028:0226-0228|2029:0214-0216|2030:0204-0206|2031:0124-0126|2032:0114-0116|2033:0102-0104,1223-1225|2034:1212-1214|2035:1201-1203|2036:1119-1121|2037:1108-1110|2038:1029-1031|2039:1019-1021|2040:1007-1009|2041:0926-0928|2042:0915-0917|2043:0904-0906|2044:0824-0826|2045:0814-0816|2046:0803-0805|2047:0724-0726|2048:0712-0714|2049:0701-0703|2050:0620-0622|2051:0610-0612|2052:0529-0531|2053:0519-0521|2054:0509-0511|2055:0428-0430|2056:0416-0418|2057:0405-0407|2058:0325-0327|2059:0315-0317|2060:0304-0306"},{"id":"eid_al_ghadir","name":"عيد الغدير","en":"Eid al-Ghadir","category":"public","dates":"y|2024:0624|2025:0615|2026:0604|2027:0524|2028:0513|2029:0502|2030:0421|2031:0410|2032:0330|2033:0319|2034:0309|2035:0226|2036:0215|2037:0203|2038:0124|2039:0113|2040:0103,1222|2041:1212|2042:1201|2043:1120|2044:1108|2045:1029|2046:1018|2047:1008|2048:0927|2049:0916|2050:0905|2051:0825|2052:0813|2053:0803|2054:0723|2055:0713|2056:0701|2057:0620|2058:0609|2059:0530|2060:0518","estimated":"y|2026:0604|2027:0524|2028:0513|2029:0502|2030:0421|2031:0410|2032:0330|2033:0319|2034:0309|2035:0226|2036:0215|2037:0203|2038:0124|2039:0113|2040:0103,1222|2041:1212|2042:1201|2043:1120|2044:1108|2045:1029|2046:1018|2047:1008|2048:0927|2049:0916|2050:0905|2051:0825|2052:0813|2053:0803|2054:0723|2055:0713|2056:0701|2057:0620|2058:0609|2059:0530|2060:0518"},{"id":"prophet_s_birthday","name":"المولد النبوي الشريف","en":"Prophet's Birthday","category":"public","dates":"y|2010:0226|2011:0215|2012:0204|2013:0124|2014:0114|2015:0103,1223|2016:1212|2017:1201|2018:1121|2019:1109|2020:1029|2021:1018|2022:1008|2023:0927|2024:0915|2025:0904|2026:0825|2027:0814|2028:0803|2029:0724|2030:0713|2031:0702|2032:0620|2033:0609|2034:0530|2035:0520|2036:0508|2037:0428|2038:0417|2039:0406|2040:0325|2041:0315|2042:0304|2043:0222|2044:0211|2045:0130|2046:0119|2047:0108,1229|2048:1218|2049:1207|2050:1126|2051:1116|2052:1104|2053:1024|2054:1013|2055:1003|2056:0922|2057:0911|2058:0831|2059:0820|2060:0808","estimated":"y|2010:0226|2011:0215|2012:0204|2026:0825|2027:0814|2028:0803|2029:0724|2030:0713|2031:0702|2032:0620|2033:0609|2034:0530|2035:0520|2036:0508|2037:0428|2038:0417|2039:0406|2040:0325|2041:0315|2042:0304|2043:0222|2044:0211|2045:0130|2046:0119|2047:0108,1229|2048:1218|2049:1207|2050:1126|2051:1116|2052:1104|2053:1024|2054:1013|2055:1003|2056:0922|2057:0911|2058:0831|2059:0820|2060:0808"},{"id":"day_of_arafah","name":"يوم عرفة","en":"Day of Arafah","category":"public","dates":"y|2010:1115|2011:1105|2012:1025|2013:1014|2014:1003|2015:0923|2016:0912|2017:0901|2018:0821|2019:0811|2020:0730|2021:0719|2022:0708|2023:0627|2024:0615|2025:0605|2026:0526|2027:0515|2028:0504|2029:0423|2030:0412|2031:0401|2032:0321|2033:0310|2034:0228|2035:0217|2036:0206|2037:0125|2038:0115|2039:0104,1225|2040:1213|2041:1203|2042:1122|2043:1111|2044:1030|2045:1020|2046:1009|2047:0929|2048:0918|2049:0907|2050:0827|2051:0816|2052:0804|2053:0725|2054:0714|2055:0704|2056:0622|2057:0611|2058:0531|2059:0521|2060:0509","estimated":"y|2010:1115|2011:1105|2012:1025|2026:0526|2027:0515|2028:0504|2029:0423|2030:0412|2031:0401|2032:0321|2033:0310|2034:0228|2035:0217|2036:0206|2037:0125|2038:0115|2039:0104,1225|2040:1213|2041:1203|2042:1122|2043:1111|2044:1030|2045:1020|2046:1009|2047:0929|2048:0918|2049:0907|2050:0827|2051:0816|2052:0804|2053:0725|2054:0714|2055:0704|2056:0622|2057:0611|2058:0531|2059:0521|2060:0509"},{"id":"islamic_new_year","name":"رأس السنة الهجرية","en":"Islamic New Year","category":"public","dates":"y|2010:1207|2011:1126|2012:1115|2013:1105|2014:1025|2015:1015|2016:1003|2017:0922|2018:0911|2019:0831|2020:0821|2021:0809|2022:0730|2023:0719|2024:0707|2025:0626|2026:0616|2027:0606|2028:0525|2029:0514|2030:0503|2031:0423|2032:0411|2033:0401|2034:0321|2035:0311|2036:0228|2037:0216|2038:0205|2039:0126|2040:0115|2041:0104,1224|2042:1214|2043:1203|2044:1121|2045:1110|2046:1031|2047:1020|2048:1009|2049:0928|2050:0917|2051:0906|2052:0826|2053:0815|2054:0805|2055:0725|2056:0714|2057:0703|2058:0622|2059:0611|2060:0531","estimated":"y|2010:1207|2011:1126|2012:1115|2026:0616|2027:0606|2028:0525|2029:0514|2030:0503|2031:0423|2032:0411|2033:0401|2034:0321|2035:0311|2036:0228|2037:0216|2038:0205|2039:0126|2040:0115|2041:0104,1224|2042:1214|2043:1203|2044:1121|2045:1110|2046:1031|2047:1020|2048:1009|2049:0928|2050:0917|2051:0906|2052:0826|2053:0815|2054:0805|2055:0725|2056:0714|2057:0703|2058:0622|2059:0611|2060:0531"},{"id":"eid_al_adha","name":"عيد الأضحى","en":"Eid al-Adha","category":"public","dates":"y|2010:1116-1118|2011:1106-1108|2012:1026-1028|2013:1015-1017|2014:1004-1006|2015:0924-0926|2016:0913-0915|2017:0902-0904|2018:0822-0824|2019:0812-0814|2020:0731-0802|2021:0720-0722|2022:0709-0711|2023:0628-0630|2024:0616-0618|2025:0606-0608|2026:0527-0529|2027:0516-0518|2028:0505-0507|2029:0424-0426|2030:0413-0415|2031:0402-0404|2032:0322-0324|2033:0311-0313|2034:0301-0303|2035:0218-0220|2036:0207-0209|2037:0126-0128|2038:0116-0118|2039:0105-0107,1226-1228|2040:1214-1216|2041:1204-1206|2042:1123-1125|2043:1112-1114|2044:1031-1102|2045:1021-1023|2046:1010-1012|2047:0930-1002|2048:0919-0921|2049:0908-0910|2050:0828-0830|2051:0817-0819|2052:0805-0807|2053:0726-0728|2054:0715-0717|2055:0705-0707|2056:0623-0625|2057:0612-0614|2058:0601-0603|2059:0522-0524|2060:0510-0512","estimated":"y|2010:1116-1118|2011:1106-1108|2012:1026-1028|2026:0527-0529|2027:0516-0518|2028:0505-0507|2029:0424-0426|2030:0413-0415|2031:0402-0404|2032:0322-0324|2033:0311-0313|2034:0301-0303|2035:0218-0220|2036:0207-0209|2037:0126-0128|2038:0116-0118|2039:0105-0107,1226-1228|2040:1214-1216|2041:1204-1206|2042:1123-1125|2043:1112-1114|2044:1031-1102|2045:1021-1023|2046:1010-1012|2047:0930-1002|2048:0919-0921|2049:0908-0910|2050:0828-0830|2051:0817-0819|2052:0805-0807|2053:0726-0728|2054:0715-0717|2055:0705-0707|2056:0623-0625|2057:0612-0614|2058:0601-0603|2059:0522-0524|2060:0510-0512"},{"id":"army_day","name":"عيد الجيش","en":"Army Day","category":"public","dates":"d|0106:2010-2060"},{"id":"commemoration_of_the_saddam_baath_crimes_against","name":"ذكرى جرائم البعث والأنفال والهجوم على حلبجة","en":"Commemoration of the Saddam Baath crimes against the Iraqi people","category":"public","dates":"d|0316:2025-2060"},{"id":"nowruz","name":"عيد نوروز","en":"Nowruz","category":"public","dates":"d|0321:2010-2060"},{"id":"labor_day","name":"عيد العمال العالمي","en":"Labor Day","category":"public","dates":"d|0501:2010-2060"},{"id":"feast_of_creation_sabian","name":"عيد الخليقة","en":"Feast of Creation","category":"sabian","dates":"d|0304:2060|0305:2056-2060|0306:2052-2060|0307:2048-2060|0308:2044-2060|0309:2040-2059|0310:2036-2055|0311:2032-2051|0312:2028-2047|0313:2024-2043|0314:2020-2039|0315:2016-2035|0316:2012-2019,2024-2031|0317:2010-2015,2024-2027|0318:2010-2011"},{"id":"easter_sunday_christian","name":"أحد الفصح","en":"Easter Sunday","category":"christian","dates":"d|0325:2035,2046|0327:2016|0328:2027,2032|0329:2043,2054|0330:2059|0331:2013,2024|0401:2018,2029,2040|0402:2051,2056|0404:2010,2021|0405:2015,2026,2037,2048|0406:2042,2053|0408:2012|0409:2023,2034,2045|0410:2039,2050|0412:2020|0413:2031,2036|0414:2047,2058|0416:2017,2028|0417:2022,2033,2044|0418:2049,2055,2060|0420:2014,2025|0421:2019,2030,2041,2052|0422:2057|0424:2011|0425:2038"},{"id":"easter_monday_christian","name":"إثنين الفصح","en":"Easter Monday","category":"christian","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"little_feast_sabian","name":"عيد الصغير","en":"Little Feast","category":"sabian","dates":"d|0331:2060|0401:2056-2060|0402:2052-2059|0403:2048-2055|0404:2044-2051|0405:2040-2047|0406:2036-2043|0407:2032-2039|0408:2028-2035|0409:2024-2031|0410:2020-2027|0411:2016-2019|0412:2012-2015|0413:2010-2011"},{"id":"yazidi_new_year_yazidi","name":"رأس السنة الإيزيدية","en":"Yazidi New Year","category":"yazidi","dates":"d|0414:2010,2021,2027,2032,2038,2049,2055,2060|0415:2015,2020,2026,2037,2043,2048,2054|0416:2014,2025,2031,2036,2042,2053,2059|0417:2013,2019,2024,2030,2041,2047,2052,2058|0418:2012,2018,2029,2035,2040,2046,2057|0419:2017,2023,2028,2034,2045,2051,2056|0420:2011,2016,2022,2033,2039,2044,2050"},{"id":"prophet_yahya_s_birthday_sabian","name":"مولد النبي يحيى عليه السلام","en":"Prophet Yahya's Birthday","category":"sabian","dates":"d|0508:2060|0509:2056-2059|0510:2052-2055|0511:2048-2051|0512:2044-2047|0513:2040-2043|0514:2036-2039|0515:2032-2035|0516:2028-2031|0517:2024-2027|0518:2020-2023|0519:2016-2019|0520:2012-2015|0521:2010-2011"},{"id":"great_feast_sabian","name":"يوما عيد البنجة","en":"Great Feast","category":"sabian","dates":"d|0712:2060|0713:2056-2060|0714:2052-2060|0715:2048-2060|0716:2044-2059|0717:2040-2055|0718:2036-2051|0719:2032-2047|0720:2028-2043|0721:2024-2039|0722:2020-2035|0723:2016-2031|0724:2012-2019,2024-2027|0725:2010-2015|0726:2010-2011"},{"id":"yazidi_summer_festival_yazidi","name":"مهرجان الصيف اليزيدي","en":"Yazidi Summer Festival","category":"yazidi","dates":"d|0731:2010-2023|0801:2010-2023|0802:2010-2060|0803:2010-2060"},{"id":"feast_of_the_assembly_yazidi","name":"عيد الجمعية","en":"Feast of the Assembly","category":"yazidi","dates":"d|1006:2010-2060|1007:2010-2060|1008:2010-2060|1009:2010-2060|1010:2010-2060|1011:2010-2060|1012:2010-2060|1013:2010-2060"},{"id":"feast_of_ezi_yazidi","name":"عيد إيزي","en":"Feast of Êzî","category":"yazidi","dates":"d|1214:2012,2018,2029,2035,2040,2046,2057|1215:2017,2023,2028,2034,2045,2051,2056|1216:2011,2016,2022,2033,2039,2044,2050|1217:2010,2021,2027,2032,2038,2049,2055,2060|1218:2015,2020,2026,2037,2043,2048,2054|1219:2014,2025,2031,2036,2042,2053,2059|1220:2013,2019,2024,2030,2041,2047,2052,2058"},{"id":"christmas_day_christian","name":"عيد الميلاد","en":"Christmas Day","category":"christian","dates":"d|1225:2010-2060"}]} diff --git a/app/Support/Holidays/data/IR.json b/app/Support/Holidays/data/IR.json new file mode 100644 index 000000000..33c1599a4 --- /dev/null +++ b/app/Support/Holidays/data/IR.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"IR","language":"fa_IR","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"birthday_of_mahdi","name":"ولادت حضرت قائم عجل الله تعالی فرجه و جشن نیمه شعبان","en":"Birthday of Mahdi","category":"public","dates":"y|2010:0727|2011:0717|2012:0705|2013:0624|2014:0613|2015:0603|2016:0522|2017:0512|2018:0502|2019:0421|2020:0409|2021:0329|2022:0318|2023:0308|2024:0225|2025:0214|2026:0204|2027:0124|2028:0113|2029:0101,1222|2030:1211|2031:1201|2032:1119|2033:1108|2034:1028|2035:1017|2036:1006|2037:0926|2038:0915|2039:0905|2040:0824|2041:0813|2042:0802|2043:0723|2044:0711|2045:0701|2046:0620|2047:0610|2048:0529|2049:0518|2050:0507|2051:0427|2052:0416|2053:0405|2054:0325|2055:0314|2056:0303|2057:0220|2058:0209|2059:0130|2060:0120","estimated":"y|2027:0124|2028:0113|2029:0101,1222|2030:1211|2031:1201|2032:1119|2033:1108|2034:1028|2035:1017|2036:1006|2037:0926|2038:0915|2039:0905|2040:0824|2041:0813|2042:0802|2043:0723|2044:0711|2045:0701|2046:0620|2047:0610|2048:0529|2049:0518|2050:0507|2051:0427|2052:0416|2053:0405|2054:0325|2055:0314|2056:0303|2057:0220|2058:0209|2059:0130|2060:0120"},{"id":"isra_and_mi_raj","name":"مبعث رسول اکرم (ص)","en":"Isra' and Mi'raj","category":"public","dates":"y|2010:0710|2011:0630|2012:0618|2013:0607|2014:0527|2015:0516|2016:0505|2017:0425|2018:0414|2019:0403|2020:0322|2021:0311|2022:0301|2023:0218|2024:0208|2025:0128|2026:0117|2027:0106,1226|2028:1215|2029:1204|2030:1124|2031:1113|2032:1102|2033:1022|2034:1011|2035:0930|2036:0919|2037:0908|2038:0829|2039:0818|2040:0806|2041:0726|2042:0716|2043:0705|2044:0624|2045:0614|2046:0603|2047:0523|2048:0511|2049:0430|2050:0420|2051:0410|2052:0329|2053:0319|2054:0308|2055:0225|2056:0214|2057:0202|2058:0123|2059:0113|2060:0102,1221","estimated":"y|2027:0106,1226|2028:1215|2029:1204|2030:1124|2031:1113|2032:1102|2033:1022|2034:1011|2035:0930|2036:0919|2037:0908|2038:0829|2039:0818|2040:0806|2041:0726|2042:0716|2043:0705|2044:0624|2045:0614|2046:0603|2047:0523|2048:0511|2049:0430|2050:0420|2051:0410|2052:0329|2053:0319|2054:0308|2055:0225|2056:0214|2057:0202|2058:0123|2059:0113|2060:0102,1221"},{"id":"martyrdom_of_ali_al_rida","name":"شهادت امام رضا علیه السلام","en":"Martyrdom of Ali al-Rida","category":"public","dates":"y|2010:0215|2011:0204|2012:0124|2013:0112|2014:0102,1223|2015:1212|2016:1130|2017:1119|2018:1108|2019:1029|2020:1017|2021:1007|2022:0927|2023:0916|2024:0904|2025:0824|2026:0813|2027:0803|2028:0723|2029:0713|2030:0702|2031:0621|2032:0609|2033:0529|2034:0519|2035:0509|2036:0427|2037:0417|2038:0406|2039:0326|2040:0314|2041:0304|2042:0221|2043:0211|2044:0131|2045:0119|2046:0108,1228|2047:1218|2048:1207|2049:1126|2050:1115|2051:1105|2052:1024|2053:1013|2054:1002|2055:0922|2056:0911|2057:0831|2058:0820|2059:0809|2060:0728","estimated":"y|2027:0803|2028:0723|2029:0713|2030:0702|2031:0621|2032:0609|2033:0529|2034:0519|2035:0509|2036:0427|2037:0417|2038:0406|2039:0326|2040:0314|2041:0304|2042:0221|2043:0211|2044:0131|2045:0119|2046:0108,1228|2047:1218|2048:1207|2049:1126|2050:1115|2051:1105|2052:1024|2053:1013|2054:1002|2055:0922|2056:0911|2057:0831|2058:0820|2059:0809|2060:0728"},{"id":"martyrdom_of_fatima","name":"شهادت حضرت فاطمه زهرا سلام الله علیها","en":"Martyrdom of Fatima","category":"public","dates":"y|2010:0517|2011:0507|2012:0425|2013:0414|2014:0403|2015:0324|2016:0313|2017:0302|2018:0220|2019:0209|2020:0129|2021:0117|2022:0106,1227|2023:1217|2024:1205|2025:1124|2026:1113|2027:1103|2028:1022|2029:1012|2030:1002|2031:0921|2032:0909|2033:0829|2034:0818|2035:0808|2036:0727|2037:0717|2038:0706|2039:0626|2040:0614|2041:0603|2042:0523|2043:0513|2044:0502|2045:0421|2046:0410|2047:0330|2048:0319|2049:0308|2050:0226|2051:0215|2052:0205|2053:0124|2054:0113|2055:0102,1222|2056:1211|2057:1130|2058:1120|2059:1109|2060:1028","estimated":"y|2027:1103|2028:1022|2029:1012|2030:1002|2031:0921|2032:0909|2033:0829|2034:0818|2035:0808|2036:0727|2037:0717|2038:0706|2039:0626|2040:0614|2041:0603|2042:0523|2043:0513|2044:0502|2045:0421|2046:0410|2047:0330|2048:0319|2049:0308|2050:0226|2051:0215|2052:0205|2053:0124|2054:0113|2055:0102,1222|2056:1211|2057:1130|2058:1120|2059:1109|2060:1028"},{"id":"tasua","name":"تاسوعای حسینی","en":"Tasua","category":"public","dates":"y|2010:1215|2011:1205|2012:1124|2013:1113|2014:1103|2015:1023|2016:1011|2017:0930|2018:0919|2019:0909|2020:0829|2021:0818|2022:0807|2023:0727|2024:0715|2025:0705|2026:0624|2027:0615|2028:0603|2029:0523|2030:0512|2031:0502|2032:0420|2033:0410|2034:0330|2035:0320|2036:0308|2037:0225|2038:0214|2039:0204|2040:0124|2041:0113|2042:0102,1223|2043:1212|2044:1130|2045:1119|2046:1109|2047:1029|2048:1018|2049:1007|2050:0926|2051:0915|2052:0904|2053:0824|2054:0814|2055:0803|2056:0723|2057:0712|2058:0701|2059:0620|2060:0609","estimated":"y|2027:0615|2028:0603|2029:0523|2030:0512|2031:0502|2032:0420|2033:0410|2034:0330|2035:0320|2036:0308|2037:0225|2038:0214|2039:0204|2040:0124|2041:0113|2042:0102,1223|2043:1212|2044:1130|2045:1119|2046:1109|2047:1029|2048:1018|2049:1007|2050:0926|2051:0915|2052:0904|2053:0824|2054:0814|2055:0803|2056:0723|2057:0712|2058:0701|2059:0620|2060:0609"},{"id":"arbaeen","name":"اربعین حسینی","en":"Arbaeen","category":"public","dates":"y|2010:0205|2011:0125|2012:0114|2013:0103,1223|2014:1213|2015:1202|2016:1120|2017:1109|2018:1030|2019:1019|2020:1008|2021:0927|2022:0917|2023:0906|2024:0825|2025:0814|2026:0804|2027:0725|2028:0714|2029:0703|2030:0622|2031:0611|2032:0530|2033:0520|2034:0510|2035:0429|2036:0418|2037:0407|2038:0327|2039:0316|2040:0305|2041:0222|2042:0212|2043:0201|2044:0122|2045:0110,1230|2046:1219|2047:1209|2048:1127|2049:1117|2050:1106|2051:1026|2052:1014|2053:1003|2054:0923|2055:0913|2056:0901|2057:0821|2058:0810|2059:0731|2060:0719","estimated":"y|2027:0725|2028:0714|2029:0703|2030:0622|2031:0611|2032:0530|2033:0520|2034:0510|2035:0429|2036:0418|2037:0407|2038:0327|2039:0316|2040:0305|2041:0222|2042:0212|2043:0201|2044:0122|2045:0110,1230|2046:1219|2047:1209|2048:1127|2049:1117|2050:1106|2051:1026|2052:1014|2053:1003|2054:0923|2055:0913|2056:0901|2057:0821|2058:0810|2059:0731|2060:0719"},{"id":"ashura","name":"عاشورای حسینی","en":"Ashura","category":"public","dates":"y|2010:1216|2011:1206|2012:1125|2013:1114|2014:1104|2015:1024|2016:1012|2017:1001|2018:0920|2019:0910|2020:0830|2021:0819|2022:0808|2023:0728|2024:0716|2025:0706|2026:0625|2027:0616|2028:0604|2029:0524|2030:0513|2031:0503|2032:0421|2033:0411|2034:0331|2035:0321|2036:0309|2037:0226|2038:0215|2039:0205|2040:0125|2041:0114|2042:0103,1224|2043:1213|2044:1201|2045:1120|2046:1110|2047:1030|2048:1019|2049:1008|2050:0927|2051:0916|2052:0905|2053:0825|2054:0815|2055:0804|2056:0724|2057:0713|2058:0702|2059:0621|2060:0610","estimated":"y|2027:0616|2028:0604|2029:0524|2030:0513|2031:0503|2032:0421|2033:0411|2034:0331|2035:0321|2036:0309|2037:0226|2038:0215|2039:0205|2040:0125|2041:0114|2042:0103,1224|2043:1213|2044:1201|2045:1120|2046:1110|2047:1030|2048:1019|2049:1008|2050:0927|2051:0916|2052:0905|2053:0825|2054:0815|2055:0804|2056:0724|2057:0713|2058:0702|2059:0621|2060:0610"},{"id":"birthday_of_imam_ali","name":"ولادت امام علی علیه السلام و روز پدر","en":"Birthday of Imam Ali","category":"public","dates":"y|2010:0626|2011:0616|2012:0604|2013:0524|2014:0513|2015:0502|2016:0421|2017:0411|2018:0331|2019:0320|2020:0308|2021:0225|2022:0215|2023:0204|2024:0125|2025:0114|2026:0103,1223|2027:1212|2028:1201|2029:1120|2030:1110|2031:1030|2032:1019|2033:1008|2034:0927|2035:0916|2036:0905|2037:0825|2038:0815|2039:0804|2040:0723|2041:0712|2042:0702|2043:0621|2044:0610|2045:0531|2046:0520|2047:0509|2048:0427|2049:0416|2050:0406|2051:0327|2052:0315|2053:0305|2054:0222|2055:0211|2056:0131|2057:0119|2058:0109,1230|2059:1219|2060:1207","estimated":"y|2027:1212|2028:1201|2029:1120|2030:1110|2031:1030|2032:1019|2033:1008|2034:0927|2035:0916|2036:0905|2037:0825|2038:0815|2039:0804|2040:0723|2041:0712|2042:0702|2043:0621|2044:0610|2045:0531|2046:0520|2047:0509|2048:0427|2049:0416|2050:0406|2051:0327|2052:0315|2053:0305|2054:0222|2055:0211|2056:0131|2057:0119|2058:0109,1230|2059:1219|2060:1207"},{"id":"eid_al_fitr","name":"عید سعید فطر","en":"Eid al-Fitr","category":"public","dates":"y|2010:0910|2011:0831|2012:0819|2013:0809|2014:0729|2015:0718|2016:0706|2017:0626|2018:0615|2019:0605|2020:0524|2021:0513|2022:0503|2023:0422|2024:0410|2025:0331|2026:0321|2027:0310|2028:0227|2029:0215|2030:0205|2031:0125|2032:0115|2033:0103,1224|2034:1213|2035:1202|2036:1120|2037:1109|2038:1030|2039:1020|2040:1008|2041:0927|2042:0916|2043:0905|2044:0825|2045:0815|2046:0804|2047:0725|2048:0713|2049:0702|2050:0621|2051:0611|2052:0530|2053:0520|2054:0510|2055:0429|2056:0417|2057:0406|2058:0326|2059:0316|2060:0305","estimated":"y|2027:0310|2028:0227|2029:0215|2030:0205|2031:0125|2032:0115|2033:0103,1224|2034:1213|2035:1202|2036:1120|2037:1109|2038:1030|2039:1020|2040:1008|2041:0927|2042:0916|2043:0905|2044:0825|2045:0815|2046:0804|2047:0725|2048:0713|2049:0702|2050:0621|2051:0611|2052:0530|2053:0520|2054:0510|2055:0429|2056:0417|2057:0406|2058:0326|2059:0316|2060:0305"},{"id":"birthday_of_muhammad_and_imam_ja_far_al_sadiq","name":"میلاد رسول اکرم و امام جعفر صادق علیه السلام","en":"Birthday of Muhammad and Imam Ja'far al-Sadiq","category":"public","dates":"y|2010:0304|2011:0221|2012:0210|2013:0129|2014:0119|2015:0109,1229|2016:1217|2017:1206|2018:1125|2019:1115|2020:1103|2021:1024|2022:1014|2023:1003|2024:0921|2025:0910|2026:0830|2027:0820|2028:0809|2029:0730|2030:0719|2031:0708|2032:0626|2033:0615|2034:0605|2035:0526|2036:0514|2037:0504|2038:0423|2039:0412|2040:0331|2041:0321|2042:0310|2043:0228|2044:0217|2045:0205|2046:0125|2047:0114|2048:0104,1224|2049:1213|2050:1202|2051:1122|2052:1110|2053:1030|2054:1019|2055:1009|2056:0928|2057:0917|2058:0906|2059:0826|2060:0814","estimated":"y|2027:0820|2028:0809|2029:0730|2030:0719|2031:0708|2032:0626|2033:0615|2034:0605|2035:0526|2036:0514|2037:0504|2038:0423|2039:0412|2040:0331|2041:0321|2042:0310|2043:0228|2044:0217|2045:0205|2046:0125|2047:0114|2048:0104,1224|2049:1213|2050:1202|2051:1122|2052:1110|2053:1030|2054:1019|2055:1009|2056:0928|2057:0917|2058:0906|2059:0826|2060:0814"},{"id":"eid_al_fitr_holiday","name":"تعطیل به مناسبت عید سعید فطر","en":"Eid al-Fitr Holiday","category":"public","dates":"y|2010:0911|2011:0901|2012:0820|2013:0810|2014:0730|2015:0719|2016:0707|2017:0627|2018:0616|2019:0606|2020:0525|2021:0514|2022:0504|2023:0423|2024:0411|2025:0401|2026:0322|2027:0311|2028:0228|2029:0216|2030:0206|2031:0126|2032:0116|2033:0104,1225|2034:1214|2035:1203|2036:1121|2037:1110|2038:1031|2039:1021|2040:1009|2041:0928|2042:0917|2043:0906|2044:0826|2045:0816|2046:0805|2047:0726|2048:0714|2049:0703|2050:0622|2051:0612|2052:0531|2053:0521|2054:0511|2055:0430|2056:0418|2057:0407|2058:0327|2059:0317|2060:0306","estimated":"y|2027:0311|2028:0228|2029:0216|2030:0206|2031:0126|2032:0116|2033:0104,1225|2034:1214|2035:1203|2036:1121|2037:1110|2038:1031|2039:1021|2040:1009|2041:0928|2042:0917|2043:0906|2044:0826|2045:0816|2046:0805|2047:0726|2048:0714|2049:0703|2050:0622|2051:0612|2052:0531|2053:0521|2054:0511|2055:0430|2056:0418|2057:0407|2058:0327|2059:0317|2060:0306"},{"id":"eid_al_ghadir","name":"عید سعید غدیر خم","en":"Eid al-Ghadir","category":"public","dates":"y|2010:1125|2011:1115|2012:1103|2013:1024|2014:1013|2015:1002|2016:0920|2017:0909|2018:0830|2019:0820|2020:0808|2021:0729|2022:0718|2023:0707|2024:0625|2025:0614|2026:0604|2027:0525|2028:0514|2029:0503|2030:0422|2031:0411|2032:0331|2033:0320|2034:0310|2035:0227|2036:0216|2037:0204|2038:0125|2039:0114|2040:0104,1223|2041:1213|2042:1202|2043:1121|2044:1109|2045:1030|2046:1019|2047:1009|2048:0928|2049:0917|2050:0906|2051:0826|2052:0814|2053:0804|2054:0724|2055:0714|2056:0702|2057:0621|2058:0610|2059:0531|2060:0519","estimated":"y|2027:0525|2028:0514|2029:0503|2030:0422|2031:0411|2032:0331|2033:0320|2034:0310|2035:0227|2036:0216|2037:0204|2038:0125|2039:0114|2040:0104,1223|2041:1213|2042:1202|2043:1121|2044:1109|2045:1030|2046:1019|2047:1009|2048:0928|2049:0917|2050:0906|2051:0826|2052:0814|2053:0804|2054:0724|2055:0714|2056:0702|2057:0621|2058:0610|2059:0531|2060:0519"},{"id":"martyrdom_of_hasan_al_askari","name":"شهادت امام حسن عسکری علیه السلام","en":"Martyrdom of Hasan al-Askari","category":"public","dates":"y|2010:0223|2011:0212|2012:0201|2013:0120|2014:0110,1231|2015:1220|2016:1208|2017:1127|2018:1116|2019:1106|2020:1025|2021:1015|2022:1005|2023:0924|2024:0912|2025:0901|2026:0821|2027:0811|2028:0731|2029:0721|2030:0710|2031:0629|2032:0617|2033:0606|2034:0527|2035:0517|2036:0505|2037:0425|2038:0414|2039:0403|2040:0322|2041:0312|2042:0301|2043:0219|2044:0208|2045:0127|2046:0116|2047:0105,1226|2048:1215|2049:1204|2050:1123|2051:1113|2052:1101|2053:1021|2054:1010|2055:0930|2056:0919|2057:0908|2058:0828|2059:0817|2060:0805","estimated":"y|2027:0811|2028:0731|2029:0721|2030:0710|2031:0629|2032:0617|2033:0606|2034:0527|2035:0517|2036:0505|2037:0425|2038:0414|2039:0403|2040:0322|2041:0312|2042:0301|2043:0219|2044:0208|2045:0127|2046:0116|2047:0105,1226|2048:1215|2049:1204|2050:1123|2051:1113|2052:1101|2053:1021|2054:1010|2055:0930|2056:0919|2057:0908|2058:0828|2059:0817|2060:0805"},{"id":"martyrdom_of_imam_ali","name":"شهادت حضرت علی علیه السلام","en":"Martyrdom of Imam Ali","category":"public","dates":"y|2010:0901|2011:0821|2012:0810|2013:0730|2014:0719|2015:0708|2016:0627|2017:0616|2018:0606|2019:0527|2020:0515|2021:0504|2022:0423|2023:0412|2024:0401|2025:0322|2026:0311|2027:0301|2028:0218|2029:0206|2030:0126|2031:0116|2032:0105,1225|2033:1214|2034:1203|2035:1122|2036:1110|2037:1031|2038:1021|2039:1010|2040:0928|2041:0918|2042:0907|2043:0827|2044:0816|2045:0805|2046:0726|2047:0715|2048:0703|2049:0623|2050:0612|2051:0601|2052:0521|2053:0511|2054:0430|2055:0419|2056:0407|2057:0327|2058:0317|2059:0307|2060:0224","estimated":"y|2027:0301|2028:0218|2029:0206|2030:0126|2031:0116|2032:0105,1225|2033:1214|2034:1203|2035:1122|2036:1110|2037:1031|2038:1021|2039:1010|2040:0928|2041:0918|2042:0907|2043:0827|2044:0816|2045:0805|2046:0726|2047:0715|2048:0703|2049:0623|2050:0612|2051:0601|2052:0521|2053:0511|2054:0430|2055:0419|2056:0407|2057:0327|2058:0317|2059:0307|2060:0224"},{"id":"eid_al_adha","name":"عید سعید قربان","en":"Eid al-Adha","category":"public","dates":"y|2010:1117|2011:1107|2012:1026|2013:1016|2014:1005|2015:0924|2016:0912|2017:0901|2018:0822|2019:0812|2020:0731|2021:0721|2022:0710|2023:0629|2024:0617|2025:0606|2026:0527|2027:0517|2028:0506|2029:0425|2030:0414|2031:0403|2032:0323|2033:0312|2034:0302|2035:0219|2036:0208|2037:0127|2038:0117|2039:0106,1227|2040:1215|2041:1205|2042:1124|2043:1113|2044:1101|2045:1022|2046:1011|2047:1001|2048:0920|2049:0909|2050:0829|2051:0818|2052:0806|2053:0727|2054:0716|2055:0706|2056:0624|2057:0613|2058:0602|2059:0523|2060:0511","estimated":"y|2027:0517|2028:0506|2029:0425|2030:0414|2031:0403|2032:0323|2033:0312|2034:0302|2035:0219|2036:0208|2037:0127|2038:0117|2039:0106,1227|2040:1215|2041:1205|2042:1124|2043:1113|2044:1101|2045:1022|2046:1011|2047:1001|2048:0920|2049:0909|2050:0829|2051:0818|2052:0806|2053:0727|2054:0716|2055:0706|2056:0624|2057:0613|2058:0602|2059:0523|2060:0511"},{"id":"martyrdom_of_imam_ja_far_al_sadiq","name":"شهادت امام جعفر صادق علیه السلام","en":"Martyrdom of Imam Ja'far al-Sadiq","category":"public","dates":"y|2010:1004|2011:0924|2012:0912|2013:0902|2014:0822|2015:0811|2016:0730|2017:0720|2018:0709|2019:0629|2020:0617|2021:0606|2022:0527|2023:0516|2024:0504|2025:0424|2026:0414|2027:0403|2028:0322|2029:0311|2030:0301|2031:0218|2032:0208|2033:0127|2034:0117|2035:0106,1226|2036:1214|2037:1203|2038:1123|2039:1113|2040:1101|2041:1021|2042:1010|2043:0929|2044:0918|2045:0908|2046:0828|2047:0818|2048:0806|2049:0726|2050:0715|2051:0705|2052:0623|2053:0613|2054:0603|2055:0523|2056:0511|2057:0430|2058:0419|2059:0409|2060:0329","estimated":"y|2027:0403|2028:0322|2029:0311|2030:0301|2031:0218|2032:0208|2033:0127|2034:0117|2035:0106,1226|2036:1214|2037:1203|2038:1123|2039:1113|2040:1101|2041:1021|2042:1010|2043:0929|2044:0918|2045:0908|2046:0828|2047:0818|2048:0806|2049:0726|2050:0715|2051:0705|2052:0623|2053:0613|2054:0603|2055:0523|2056:0511|2057:0430|2058:0419|2059:0409|2060:0329"},{"id":"death_of_prophet_muhammad_and_martyrdom_of_hasan","name":"رحلت رسول اکرم؛شهادت امام حسن مجتبی علیه السلام","en":"Death of Prophet Muhammad and Martyrdom of Hasan ibn Ali","category":"public","dates":"y|2010:0213|2011:0202|2012:0122|2013:0111,1231|2014:1221|2015:1210|2016:1128|2017:1117|2018:1107|2019:1027|2020:1016|2021:1005|2022:0925|2023:0914|2024:0902|2025:0822|2026:0812|2027:0802|2028:0722|2029:0711|2030:0630|2031:0619|2032:0607|2033:0528|2034:0518|2035:0507|2036:0426|2037:0415|2038:0404|2039:0324|2040:0313|2041:0302|2042:0220|2043:0209|2044:0130|2045:0118|2046:0107,1227|2047:1217|2048:1205|2049:1125|2050:1114|2051:1103|2052:1022|2053:1011|2054:1001|2055:0921|2056:0909|2057:0829|2058:0818|2059:0808|2060:0727","estimated":"y|2027:0802|2028:0722|2029:0711|2030:0630|2031:0619|2032:0607|2033:0528|2034:0518|2035:0507|2036:0426|2037:0415|2038:0404|2039:0324|2040:0313|2041:0302|2042:0220|2043:0209|2044:0130|2045:0118|2046:0107,1227|2047:1217|2048:1205|2049:1125|2050:1114|2051:1103|2052:1022|2053:1011|2054:1001|2055:0921|2056:0909|2057:0829|2058:0818|2059:0808|2060:0727"},{"id":"islamic_revolution_day","name":"پیروزی انقلاب اسلامی","en":"Islamic Revolution Day","category":"public","dates":"d|0210:2013,2017,2021,2025,2029-2030,2033-2034,2037-2038,2041-2042,2045-2046,2049-2050,2053-2054,2057-2058|0211:2010-2012,2014-2016,2018-2020,2022-2024,2026-2028,2031-2032,2035-2036,2039-2040,2043-2044,2047-2048,2051-2052,2055-2056,2059-2060"},{"id":"public_holiday","name":"تعطیلی عمومی","en":"Public Holiday","category":"public","dates":"y|2023:0801-0802|2026:0301-0307"},{"id":"iranian_oil_industry_nationalization_day","name":"روز ملی شدن صنعت نفت ایران","en":"Iranian Oil Industry Nationalization Day","category":"public","dates":"d|0319:2012-2013,2016-2017,2020-2021,2024-2025,2028-2030,2032-2034,2036-2038,2040-2042,2044-2046,2048-2050,2052-2054,2056-2058,2060|0320:2010-2011,2014-2015,2018-2019,2022-2023,2026-2027,2031,2035,2039,2043,2047,2051,2055,2059"},{"id":"last_day_of_year","name":"آخرین روز سال","en":"Last Day of Year","category":"public","dates":"d|0320:2013,2017,2021,2025,2030,2034,2038,2042,2046,2050,2054,2058"},{"id":"nowruz","name":"جشن نوروز","en":"Nowruz","category":"public","dates":"d|0320:2012,2016,2020,2024,2028-2029,2032-2033,2036-2037,2040-2041,2044-2045,2048-2049,2052-2053,2056-2057,2060|0321:2010-2011,2013-2015,2017-2019,2021-2023,2025-2027,2030-2031,2034-2035,2038-2039,2042-2043,2046-2047,2050-2051,2054-2055,2058-2059"},{"id":"nowruz_holiday","name":"عیدنوروز","en":"Nowruz Holiday","category":"public","dates":"d|0321:2012,2016,2020,2024,2028-2029,2032-2033,2036-2037,2040-2041,2044-2045,2048-2049,2052-2053,2056-2057,2060|0322:2010-2060|0323:2010-2060|0324:2010-2011,2013-2015,2017-2019,2021-2023,2025-2027,2030-2031,2034-2035,2038-2039,2042-2043,2046-2047,2050-2051,2054-2055,2058-2059"},{"id":"islamic_republic_day","name":"روز جمهوری اسلامی","en":"Islamic Republic Day","category":"public","dates":"d|0331:2012,2016,2020,2024,2028-2029,2032-2033,2036-2037,2040-2041,2044-2045,2048-2049,2052-2053,2056-2057,2060|0401:2010-2011,2013-2015,2017-2019,2021-2023,2025-2027,2030-2031,2034-2035,2038-2039,2042-2043,2046-2047,2050-2051,2054-2055,2058-2059"},{"id":"nature_s_day","name":"روز طبیعت","en":"Nature's Day","category":"public","dates":"d|0401:2012,2016,2020,2024,2028-2029,2032-2033,2036-2037,2040-2041,2044-2045,2048-2049,2052-2053,2056-2057,2060|0402:2010-2011,2013-2015,2017-2019,2021-2023,2025-2027,2030-2031,2034-2035,2038-2039,2042-2043,2046-2047,2050-2051,2054-2055,2058-2059"},{"id":"death_of_imam_khomeini","name":"رحلت حضرت امام خمینی","en":"Death of Imam Khomeini","category":"public","dates":"d|0603:2012,2016,2020,2024,2028-2029,2032-2033,2036-2037,2040-2041,2044-2045,2048-2049,2052-2053,2056-2057,2060|0604:2010-2011,2013-2015,2017-2019,2021-2023,2025-2027,2030-2031,2034-2035,2038-2039,2042-2043,2046-2047,2050-2051,2054-2055,2058-2059"},{"id":"15_khordad_uprising","name":"قیام 15 خرداد","en":"15 Khordad Uprising","category":"public","dates":"d|0604:2012,2016,2020,2024,2028-2029,2032-2033,2036-2037,2040-2041,2044-2045,2048-2049,2052-2053,2056-2057,2060|0605:2010-2011,2013-2015,2017-2019,2021-2023,2025-2027,2030-2031,2034-2035,2038-2039,2042-2043,2046-2047,2050-2051,2054-2055,2058-2059"}]} diff --git a/app/Support/Holidays/data/IS.json b/app/Support/Holidays/data/IS.json new file mode 100644 index 000000000..e3905bc2f --- /dev/null +++ b/app/Support/Holidays/data/IS.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"IS","language":"is","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Nýársdagur","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"maundy_thursday","name":"Skírdagur","en":"Maundy Thursday","category":"public","dates":"d|0322:2035,2046|0324:2016|0325:2027,2032|0326:2043,2054|0327:2059|0328:2013,2024|0329:2018,2029,2040|0330:2051,2056|0401:2010,2021|0402:2015,2026,2037,2048|0403:2042,2053|0405:2012|0406:2023,2034,2045|0407:2039,2050|0409:2020|0410:2031,2036|0411:2047,2058|0413:2017,2028|0414:2022,2033,2044|0415:2049,2055,2060|0417:2014,2025|0418:2019,2030,2041,2052|0419:2057|0421:2011|0422:2038"},{"id":"good_friday","name":"Föstudagurinn langi","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_sunday","name":"Páskadagur","en":"Easter Sunday","category":"public","dates":"d|0325:2035,2046|0327:2016|0328:2027,2032|0329:2043,2054|0330:2059|0331:2013,2024|0401:2018,2029,2040|0402:2051,2056|0404:2010,2021|0405:2015,2026,2037,2048|0406:2042,2053|0408:2012|0409:2023,2034,2045|0410:2039,2050|0412:2020|0413:2031,2036|0414:2047,2058|0416:2017,2028|0417:2022,2033,2044|0418:2049,2055,2060|0420:2014,2025|0421:2019,2030,2041,2052|0422:2057|0424:2011|0425:2038"},{"id":"easter_monday","name":"Annar í páskum","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"first_day_of_summer","name":"Sumardagurinn fyrsti","en":"First Day of Summer","category":"public","dates":"d|0419:2012,2018,2029,2035,2040,2046,2057|0420:2017,2023,2028,2034,2045,2051,2056|0421:2011,2016,2022,2033,2039,2044,2050|0422:2010,2021,2027,2032,2038,2049,2055,2060|0423:2015,2020,2026,2037,2043,2048,2054|0424:2014,2025,2031,2036,2042,2053,2059|0425:2013,2019,2024,2030,2041,2047,2052,2058"},{"id":"labor_day","name":"Verkalýðsdagurinn","en":"Labor Day","category":"public","dates":"d|0501:2010-2060"},{"id":"ascension_day","name":"Uppstigningardagur","en":"Ascension Day","category":"public","dates":"d|0503:2035,2046|0505:2016|0506:2027,2032|0507:2043,2054|0508:2059|0509:2013,2024|0510:2018,2029,2040|0511:2051,2056|0513:2010,2021|0514:2015,2026,2037,2048|0515:2042,2053|0517:2012|0518:2023,2034,2045|0519:2039,2050|0521:2020|0522:2031,2036|0523:2047,2058|0525:2017,2028|0526:2022,2033,2044|0527:2049,2055,2060|0529:2014,2025|0530:2019,2030,2041,2052|0531:2057|0602:2011|0603:2038"},{"id":"pentecost","name":"Hvítasunnudagur","en":"Pentecost","category":"public","dates":"d|0513:2035,2046|0515:2016|0516:2027,2032|0517:2043,2054|0518:2059|0519:2013,2024|0520:2018,2029,2040|0521:2051,2056|0523:2010,2021|0524:2015,2026,2037,2048|0525:2042,2053|0527:2012|0528:2023,2034,2045|0529:2039,2050|0531:2020|0601:2031,2036|0602:2047,2058|0604:2017,2028|0605:2022,2033,2044|0606:2049,2055,2060|0608:2014,2025|0609:2019,2030,2041,2052|0610:2057|0612:2011|0613:2038"},{"id":"pentecost_monday","name":"Annar í hvítasunnu","en":"Pentecost Monday","category":"public","dates":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038"},{"id":"national_day","name":"Þjóðhátíðardagurinn","en":"National Day","category":"public","dates":"d|0617:2010-2060"},{"id":"commerce_day","name":"Frídagur verslunarmanna","en":"Commerce Day","category":"public","dates":"d|0801:2011,2016,2022,2033,2039,2044,2050|0802:2010,2021,2027,2032,2038,2049,2055,2060|0803:2015,2020,2026,2037,2043,2048,2054|0804:2014,2025,2031,2036,2042,2053,2059|0805:2013,2019,2024,2030,2041,2047,2052,2058|0806:2012,2018,2029,2035,2040,2046,2057|0807:2017,2023,2028,2034,2045,2051,2056"},{"id":"christmas_day","name":"Jóladagur","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"},{"id":"second_day_of_christmas","name":"Annar í jólum","en":"Second Day of Christmas","category":"public","dates":"d|1226:2010-2060"},{"id":"christmas_eve_half_day","name":"Aðfangadagur","en":"Christmas Eve","category":"half_day","dates":"d|1224:2010-2060"},{"id":"new_year_s_eve_half_day","name":"Gamlársdagur","en":"New Year's Eve","category":"half_day","dates":"d|1231:2010-2060"}]} diff --git a/app/Support/Holidays/data/IT.json b/app/Support/Holidays/data/IT.json new file mode 100644 index 000000000..ff3893a6b --- /dev/null +++ b/app/Support/Holidays/data/IT.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"IT","language":"it_IT","years":[2010,2060],"subdivisions":{"AG":"Agrigento","AL":"Alessandria","AN":"Ancona","AO":"Aosta","AP":"Ascoli Piceno","AQ":"L'Aquila","AR":"Arezzo","AT":"Asti","AV":"Avellino","BA":"Bari","BG":"Bergamo","BI":"Biella","BL":"Belluno","BN":"Benevento","BO":"Bologna","BR":"Brindisi","BS":"Brescia","BT":"Barletta-Andria-Trani","BZ":"Bolzano","CA":"Cagliari","CB":"Campobasso","CE":"Caserta","CH":"Chieti","CL":"Caltanissetta","CN":"Cuneo","CO":"Como","CR":"Cremona","CS":"Cosenza","CT":"Catania","CZ":"Catanzaro","EN":"Enna","FC":"Forli-Cesena","FE":"Ferrara","FG":"Foggia","FI":"Firenze","FM":"Fermo","FR":"Frosinone","GE":"Genova","GO":"Gorizia","GR":"Grosseto","IM":"Imperia","IS":"Isernia","KR":"Crotone","LC":"Lecco","LE":"Lecce","LI":"Livorno","LO":"Lodi","LT":"Latina","LU":"Lucca","MB":"Monza e Brianza","MC":"Macerata","ME":"Messina","MI":"Milano","MN":"Mantova","MO":"Modena","MS":"Massa-Carrara","MT":"Matera","NA":"Napoli","NO":"Novara","NU":"Nuoro","OR":"Oristano","PA":"Palermo","PC":"Piacenza","PD":"Padova","PE":"Pescara","PG":"Perugia","PI":"Pisa","PN":"Pordenone","PO":"Prato","PR":"Parma","PT":"Pistoia","PU":"Pesaro e Urbino","PV":"Pavia","PZ":"Potenza","RA":"Ravenna","RC":"Reggio Calabria","RE":"Reggio Emilia","RG":"Ragusa","RI":"Rieti","RM":"Roma","RN":"Rimini","RO":"Rovigo","SA":"Salerno","SI":"Siena","SO":"Sondrio","SP":"La Spezia","SR":"Siracusa","SS":"Sassari","SU":"Sud Sardegna","SV":"Savona","TA":"Taranto","TE":"Teramo","TN":"Trento","TO":"Torino","TP":"Trapani","TR":"Terni","TS":"Trieste","TV":"Treviso","UD":"Udine","VA":"Varese","VB":"Verbano-Cusio-Ossola","VC":"Vercelli","VE":"Venezia","VI":"Vicenza","VR":"Verona","VT":"Viterbo","VV":"Vibo Valentia","Andria":"Andria","Barletta":"Barletta","Cesena":"Cesena","Forli":"Forlì","Pesaro":"Pesaro","Trani":"Trani","Urbino":"Urbino"},"holidays":[{"id":"new_year_s_day","name":"Capodanno","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"epiphany","name":"Epifania","en":"Epiphany","category":"public","dates":"d|0106:2010-2060"},{"id":"saint_hilary_of_poitiers_s_day","name":"Sant'Ilario di Poitiers","en":"Saint Hilary of Poitiers's Day","category":"public","regional":{"PR":"d|0113:2010-2060"}},{"id":"saint_bassianus_s_day","name":"San Bassiano","en":"Saint Bassianus's Day","category":"public","regional":{"LO":"d|0119:2010-2060"}},{"id":"saint_sebastian_s_day","name":"San Sebastiano","en":"Saint Sebastian's Day","category":"public","regional":{"CE":"d|0120:2010-2060"}},{"id":"saint_gaudentius_s_day","name":"San Gaudenzio","en":"Saint Gaudentius's Day","category":"public","regional":{"NO":"d|0122:2010-2060","RN":"d|1014:2010-2060"}},{"id":"saint_geminianus_s_day","name":"San Geminiano","en":"Saint Geminianus's Day","category":"public","regional":{"MO":"d|0131:2010-2060"}},{"id":"our_lady_of_the_fire","name":"Madonna del Fuoco","en":"Our Lady of the Fire","category":"public","regional":{"FC":"d|0204:2010-2060","Forli":"d|0204:2010-2060"}},{"id":"saint_agatha_s_day","name":"Sant'Agata","en":"Saint Agatha's Day","category":"public","regional":{"CT":"d|0205:2010-2060"}},{"id":"our_lady_of_the_pilerio","name":"Madonna del Pilerio","en":"Our Lady of the Pilerio","category":"public","regional":{"CS":"d|0212:2010-2060"}},{"id":"saint_archelaus_s_day","name":"Sant'Archelao","en":"Saint Archelaus's Day","category":"public","regional":{"OR":"d|0213:2010-2060"}},{"id":"saint_modestinus_s_day","name":"San Modestino","en":"Saint Modestinus's Day","category":"public","regional":{"AV":"d|0214:2010-2060"}},{"id":"saint_valentine_s_day","name":"San Valentino","en":"Saint Valentine's Day","category":"public","regional":{"TR":"d|0214:2010-2060"}},{"id":"saints_faustinus_and_jovita_s_day","name":"Santi Faustino e Giovita","en":"Saints Faustinus and Jovita's Day","category":"public","regional":{"BS":"d|0215:2010-2060"}},{"id":"saint_gerland_s_day","name":"San Gerlando","en":"Saint Gerland's Day","category":"public","regional":{"AG":"d|0225:2010-2060"}},{"id":"saint_leoluca_s_day","name":"San Leoluca","en":"Saint Leoluca's Day","category":"public","regional":{"VV":"d|0301:2010-2060"}},{"id":"saints_hilary_and_tatian_s_day","name":"Santi Ilario e Taziano","en":"Saints Hilary and Tatian's Day","category":"public","regional":{"GO":"d|0316:2010-2060"}},{"id":"our_lady_of_mercy","name":"Nostra Signora della Misericordia","en":"Our Lady of Mercy","category":"public","regional":{"SV":"d|0318:2010-2060"}},{"id":"saint_anselm_of_baggio_s_day","name":"Sant'Anselmo da Baggio","en":"Saint Anselm of Baggio's Day","category":"public","regional":{"MN":"d|0318:2010-2060"}},{"id":"saint_joseph_s_day","name":"San Giuseppe","en":"Saint Joseph's Day","category":"public","regional":{"SP":"d|0319:2010-2060"}},{"id":"our_lady_of_the_seven_veils","name":"Madonna dei Sette Veli","en":"Our Lady of the Seven Veils","category":"public","regional":{"FG":"d|0322:2010-2060"}},{"id":"easter_sunday","name":"Pasqua","en":"Easter Sunday","category":"public","dates":"d|0325:2035,2046|0327:2016|0328:2027,2032|0329:2043,2054|0330:2059|0331:2013,2024|0401:2018,2029,2040|0402:2051,2056|0404:2010,2021|0405:2015,2026,2037,2048|0406:2042,2053|0408:2012|0409:2023,2034,2045|0410:2039,2050|0412:2020|0413:2031,2036|0414:2047,2058|0416:2017,2028|0417:2022,2033,2044|0418:2049,2055,2060|0420:2014,2025|0421:2019,2030,2041,2052|0422:2057|0424:2011|0425:2038"},{"id":"easter_monday","name":"Lunedì dell'Angelo","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"saint_george_s_day","name":"San Giorgio","en":"Saint George's Day","category":"public","regional":{"CB":"d|0423:2010-2060","FE":"d|0423:2010-2060","RC":"d|0423:2010-2060","RG":"d|0423:2010-2060"}},{"id":"liberation_day","name":"Anniversario della Liberazione","en":"Liberation Day","category":"public","dates":"d|0425:2010-2060"},{"id":"saint_mark_s_day","name":"San Marco Evangelista","en":"Saint Mark's Day","category":"public","regional":{"LT":"d|0425:2010-2060","PN":"d|0425:2010-2060","VE":"d|0425:2010-2060"}},{"id":"saint_liberal_s_day","name":"San Liberale","en":"Saint Liberal's Day","category":"public","regional":{"TV":"d|0427:2010-2060"}},{"id":"labor_day","name":"Festa del Lavoro","en":"Labor Day","category":"public","dates":"d|0501:2010-2060"},{"id":"saint_secundus_of_asti_s_day","name":"San Secondo di Asti","en":"Saint Secundus of Asti's Day","category":"public","regional":{"AT":"d|0501:2012,2018,2029,2035,2040,2046,2057|0502:2017,2023,2028,2034,2045,2051,2056|0503:2011,2016,2022,2033,2039,2044,2050|0504:2010,2021,2027,2032,2038,2049,2055,2060|0505:2015,2020,2026,2037,2043,2048,2054|0506:2014,2025,2031,2036,2042,2053,2059|0507:2013,2019,2024,2030,2041,2047,2052,2058"}},{"id":"saint_nicholas_the_pilgrim_s_day","name":"San Nicola Pellegrino","en":"Saint Nicholas the Pilgrim's Day","category":"public","regional":{"BT":"d|0503:2010-2060","Trani":"d|0503:2010-2060"}},{"id":"saint_cyriacus_s_day","name":"San Ciriaco","en":"Saint Cyriacus's Day","category":"public","regional":{"AN":"d|0504:2010-2060"}},{"id":"saint_victor_the_moor_s_day","name":"San Vittore il Moro","en":"Saint Victor the Moor's Day","category":"public","regional":{"VA":"d|0508:2010-2060","VB":"d|0508:2010-2060"}},{"id":"saint_catald_s_day","name":"San Cataldo","en":"Saint Catald's Day","category":"public","regional":{"TA":"d|0510:2010-2060"}},{"id":"saint_justin_of_chieti_s_day","name":"San Giustino di Chieti","en":"Saint Justin of Chieti's Day","category":"public","regional":{"CH":"d|0511:2010-2060"}},{"id":"saint_pontian_s_day","name":"San Ponziano","en":"Saint Pontian's Day","category":"public","regional":{"SU":"d|0512:2011,2016,2022,2033,2039,2044,2050|0513:2010,2021,2027,2032,2038,2049,2055,2060|0514:2015,2020,2026,2037,2043,2048,2054|0515:2014,2025,2031,2036,2042,2053,2059|0516:2013,2019,2024,2030,2041,2047,2052,2058|0517:2012,2018,2029,2035,2040,2046,2057|0518:2017,2023,2028,2034,2045,2051,2056"}},{"id":"pentecost_monday","name":"Lunedì di Pentecoste","en":"Pentecost Monday","category":"public","regional":{"BZ":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038"}},{"id":"saint_peter_celestine_s_day","name":"San Pietro Celestino","en":"Saint Peter Celestine's Day","category":"public","regional":{"IS":"d|0519:2010-2060"}},{"id":"saint_zeno_s_day","name":"San Zeno","en":"Saint Zeno's Day","category":"public","regional":{"VR":"d|0521:2010-2060"}},{"id":"saint_julia_s_day","name":"Santa Giulia","en":"Saint Julia's Day","category":"public","regional":{"LI":"d|0522:2010-2060"}},{"id":"saint_gerard_of_potenza_s_day","name":"San Gerardo di Potenza","en":"Saint Gerard of Potenza's Day","category":"public","regional":{"PZ":"d|0530:2010-2060"}},{"id":"saint_crescentinus_s_day","name":"San Crescentino","en":"Saint Crescentinus's Day","category":"public","regional":{"PU":"d|0601:2010-2060","Urbino":"d|0601:2010-2060"}},{"id":"republic_day","name":"Festa della Repubblica","en":"Republic Day","category":"public","dates":"d|0602:2010-2060"},{"id":"our_lady_of_the_letter","name":"Madonna della Lettera","en":"Our Lady of the Letter","category":"public","regional":{"ME":"d|0603:2010-2060"}},{"id":"saint_maximus_of_aveia_s_day","name":"San Massimo d'Aveia","en":"Saint Maximus of Aveia's Day","category":"public","regional":{"AQ":"d|0610:2010-2060"}},{"id":"saint_anthony_of_padua_s_day","name":"Sant'Antonio di Padova","en":"Saint Anthony of Padua's Day","category":"public","regional":{"PD":"d|0613:2010-2060"}},{"id":"saint_ranieri_s_day","name":"San Ranieri","en":"Saint Ranieri's Day","category":"public","regional":{"PI":"d|0617:2010-2060"}},{"id":"saints_gervasius_and_protasius_s_day","name":"San Gervasio e San Protasio","en":"Saints Gervasius and Protasius's Day","category":"public","regional":{"SO":"d|0619:2010-2060"}},{"id":"saint_silverius_s_day","name":"San Silverio","en":"Saint Silverius's Day","category":"public","regional":{"FR":"d|0620:2010-2060"}},{"id":"saint_john_s_day","name":"San Giovanni Battista","en":"Saint John's Day","category":"public","regional":{"Cesena":"d|0624:2010-2060","FC":"d|0624:2010-2060","FI":"d|0624:2010-2060","GE":"d|0624:2010-2060","MB":"d|0624:2010-2060","RG":"d|0829:2010-2060","TO":"d|0624:2010-2060"}},{"id":"saint_vigilius_s_day","name":"San Vigilio","en":"Saint Vigilius's Day","category":"public","regional":{"TN":"d|0626:2010-2060"}},{"id":"saints_peter_and_paul_s_day","name":"Santi Pietro e Paolo","en":"Saints Peter and Paul's Day","category":"public","regional":{"RM":"d|0629:2010-2060"}},{"id":"our_lady_of_the_bruna","name":"Madonna della Bruna","en":"Our Lady of the Bruna","category":"public","regional":{"MT":"d|0702:2010-2060"}},{"id":"visitation_of_mary_day","name":"Madonna della Visitazione","en":"Visitation of Mary Day","category":"public","regional":{"EN":"d|0702:2010-2060"}},{"id":"saint_antoninus_of_piacenza_s_day","name":"Sant'Antonino di Piacenza","en":"Saint Antoninus of Piacenza's Day","category":"public","regional":{"PC":"d|0704:2010-2060"}},{"id":"saint_maria_goretti_s_day","name":"Santa Maria Goretti","en":"Saint Maria Goretti's Day","category":"public","regional":{"LT":"d|0706:2010-2060"}},{"id":"saint_paulinus_of_lucca_s_day","name":"San Paolino di Lucca","en":"Saint Paulinus of Lucca's Day","category":"public","regional":{"LU":"d|0712:2010-2060"}},{"id":"saints_hermagoras_and_fortunatus_s_day","name":"Santi Ermacora e Fortunato","en":"Saints Hermagoras and Fortunatus's Day","category":"public","regional":{"UD":"d|0712:2010-2060"}},{"id":"saint_rosalia_s_day","name":"Santa Rosalia","en":"Saint Rosalia's Day","category":"public","regional":{"PA":"d|0715:2010-2060"}},{"id":"saint_vitalian_s_day","name":"San Vitaliano","en":"Saint Vitalian's Day","category":"public","regional":{"CZ":"d|0716:2010-2060"}},{"id":"saint_apollinaris_s_day","name":"Sant'Apollinare","en":"Saint Apollinaris's Day","category":"public","regional":{"RA":"d|0723:2010-2060"}},{"id":"saint_james_s_day","name":"San Jacopo","en":"Saint James's Day","category":"public","regional":{"PT":"d|0725:2010-2060"}},{"id":"saint_eusebius_of_vercelli_s_day","name":"Sant'Eusebio di Vercelli","en":"Saint Eusebius of Vercelli's Day","category":"public","regional":{"VC":"d|0801:2010-2060"}},{"id":"our_lady_of_the_snows","name":"Nostra Signora della Neve","en":"Our Lady of the Snows","category":"public","regional":{"NU":"d|0805:2010-2060"}},{"id":"saint_emidius_s_day","name":"Sant'Emidio","en":"Saint Emidius's Day","category":"public","regional":{"AP":"d|0805:2010-2060"}},{"id":"saint_albert_of_trapani_s_day","name":"Sant'Alberto degli Abati","en":"Saint Albert of Trapani's Day","category":"public","regional":{"TP":"d|0807:2010-2060"}},{"id":"saint_donatus_of_arezzo_s_day","name":"San Donato d'Arezzo","en":"Saint Donatus of Arezzo's Day","category":"public","regional":{"AR":"d|0807:2010-2060"}},{"id":"saint_lawrence_s_day","name":"San Lorenzo","en":"Saint Lawrence's Day","category":"public","regional":{"GR":"d|0810:2010-2060"}},{"id":"saint_clare_of_assisi_s_day","name":"Santa Chiara d'Assisi","en":"Saint Clare of Assisi's Day","category":"public","regional":{"PG":"d|0811:2010-2060"}},{"id":"assumption_day","name":"Assunzione della Beata Vergine Maria","en":"Assumption Day","category":"public","regional":{"FM":"d|0815:2010-2060|0816:2010-2060"}},{"id":"assumption_of_mary_day","name":"Assunzione della Beata Vergine Maria","en":"Assumption Of Mary Day","category":"public","dates":"d|0815:2010-2060"},{"id":"saint_bartholomew_s_day","name":"San Bartolomeo apostolo","en":"Saint Bartholomew's Day","category":"public","regional":{"BN":"d|0824:2010-2060"}},{"id":"saint_alexander_of_bergamo_s_day","name":"Sant'Alessandro di Bergamo","en":"Saint Alexander of Bergamo's Day","category":"public","regional":{"BG":"d|0826:2010-2060"}},{"id":"saint_orontius_s_day","name":"Sant'Oronzo","en":"Saint Orontius's Day","category":"public","regional":{"LE":"d|0826:2010-2060"}},{"id":"saint_abbondius_s_day","name":"Sant'Abbondio","en":"Saint Abbondius's Day","category":"public","regional":{"CO":"d|0831:2010-2060"}},{"id":"saint_julian_the_hospitaller_s_day","name":"San Giuliano l'ospitaliere","en":"Saint Julian the Hospitaller's Day","category":"public","regional":{"MC":"d|0831:2010-2060"}},{"id":"saint_theodore_of_amasea_and_saint_lawrence_of_b","name":"San Teodoro d'Amasea e San Lorenzo da Brindisi","en":"Saint Theodore of Amasea and Saint Lawrence of Brindisi's Day","category":"public","regional":{"BR":"d|0901:2013,2019,2024,2030,2041,2047,2052,2058|0902:2012,2018,2029,2035,2040,2046,2057|0903:2017,2023,2028,2034,2045,2051,2056|0904:2011,2016,2022,2033,2039,2044,2050|0905:2010,2021,2027,2032,2038,2049,2055,2060|0906:2015,2020,2026,2037,2043,2048,2054|0907:2014,2025,2031,2036,2042,2053,2059"}},{"id":"saint_rose_of_viterbo_s_day","name":"Santa Rosa da Viterbo","en":"Saint Rose of Viterbo's Day","category":"public","regional":{"VT":"d|0904:2010-2060"}},{"id":"saint_grat_s_day","name":"San Grato","en":"Saint Grat's Day","category":"public","regional":{"AO":"d|0907:2010-2060"}},{"id":"our_lady_of_graces","name":"Madonna delle Grazie","en":"Our Lady of Graces","category":"public","regional":{"PN":"d|0908:2010-2060"}},{"id":"our_lady_of_monte_berico","name":"Madonna di Monte Berico","en":"Our Lady of Monte Berico","category":"public","regional":{"VI":"d|0908:2010-2060"}},{"id":"saint_richard_of_andria_s_day","name":"San Riccardo di Andria","en":"Saint Richard of Andria's Day","category":"public","regional":{"Andria":"d|0915:2013,2019,2024,2030,2041,2047,2052,2058|0916:2012,2018,2029,2035,2040,2046,2057|0917:2017,2023,2028,2034,2045,2051,2056|0918:2011,2016,2022,2033,2039,2044,2050|0919:2010,2021,2027,2032,2038,2049,2055,2060|0920:2015,2020,2026,2037,2043,2048,2054|0921:2014,2025,2031,2036,2042,2053,2059","BT":"d|0915:2013,2019,2024,2030,2041,2047,2052,2058|0916:2012,2018,2029,2035,2040,2046,2057|0917:2017,2023,2028,2034,2045,2051,2056|0918:2011,2016,2022,2033,2039,2044,2050|0919:2010,2021,2027,2032,2038,2049,2055,2060|0920:2015,2020,2026,2037,2043,2048,2054|0921:2014,2025,2031,2036,2042,2053,2059"}},{"id":"saint_januarius_s_day","name":"San Gennaro","en":"Saint Januarius's Day","category":"public","regional":{"NA":"d|0919:2010-2060"}},{"id":"saint_matthew_s_day","name":"San Matteo Evangelista","en":"Saint Matthew's Day","category":"public","regional":{"SA":"d|0921:2010-2060"}},{"id":"saint_terentius_of_pesaro_s_day","name":"San Terenzio di Pesaro","en":"Saint Terentius of Pesaro's Day","category":"public","regional":{"PU":"d|0924:2010-2060","Pesaro":"d|0924:2010-2060"}},{"id":"saint_michael_the_archangel_s_day","name":"San Michele Arcangelo","en":"Saint Michael the Archangel's Day","category":"public","regional":{"CL":"d|0929:2010-2060","CN":"d|0929:2010-2060"}},{"id":"saint_francis_of_assisi_patron_saint_of_italy","name":"Festa nazionale di San Francesco d'Assisi, patrono d'Italia","en":"Saint Francis of Assisi, Patron Saint of Italy","category":"public","dates":"d|1004:2026-2060"},{"id":"saint_francis_of_assisi_s_day","name":"San Francesco d'Assisi","en":"Saint Francis of Assisi's Day","category":"public","regional":{"MS":"d|1004:2010-2060","PG":"d|1004:2010-2060"}},{"id":"saint_petronius_s_day","name":"San Petronio","en":"Saint Petronius's Day","category":"public","regional":{"BO":"d|1004:2010-2060"}},{"id":"saint_dionysius_s_day","name":"San Dionigi","en":"Saint Dionysius's Day","category":"public","regional":{"KR":"d|1009:2010-2060"}},{"id":"saint_cetteus_s_day","name":"San Cetteo","en":"Saint Cetteus's Day","category":"public","regional":{"PE":"d|1010:2010-2060"}},{"id":"saint_saturninus_of_cagliari_s_day","name":"San Saturnino di Cagliari","en":"Saint Saturninus of Cagliari's Day","category":"public","regional":{"CA":"d|1030:2010-2060"}},{"id":"all_saints_day","name":"Ognissanti","en":"All Saints' Day","category":"public","dates":"d|1101:2010-2060"},{"id":"national_unity_day","name":"Giorno dell'unità nazionale","en":"National Unity Day","category":"public","dates":"d|1101:2015,2020,2026,2037,2043,2048,2054|1102:2014,2025,2031,2036,2042,2053,2059|1103:2013,2019,2024,2030,2041,2047,2052,2058|1104:2012,2018,2029,2035,2040,2046,2057|1105:2017,2023,2028,2034,2045,2051,2056|1106:2011,2016,2022,2033,2039,2044,2050|1107:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"saint_justus_s_day","name":"San Giusto","en":"Saint Justus's Day","category":"public","regional":{"TS":"d|1103:2010-2060"}},{"id":"saint_baudolino_s_day","name":"San Baudolino","en":"Saint Baudolino's Day","category":"public","regional":{"AL":"d|1110:2010-2060"}},{"id":"saint_martin_s_day","name":"San Martino","en":"Saint Martin's Day","category":"public","regional":{"BL":"d|1111:2010-2060"}},{"id":"saint_homobonus_s_day","name":"Sant'Omobono","en":"Saint Homobonus's Day","category":"public","regional":{"CR":"d|1113:2010-2060"}},{"id":"our_lady_of_health","name":"Madonna della Salute","en":"Our Lady of Health","category":"public","regional":{"VE":"d|1121:2010-2060"}},{"id":"saint_prosper_of_reggio_s_day","name":"San Prospero Vescovo","en":"Saint Prosper of Reggio's Day","category":"public","regional":{"RE":"d|1124:2010-2060"}},{"id":"saint_bellinus_s_day","name":"San Bellino","en":"Saint Bellinus's Day","category":"public","regional":{"RO":"d|1126:2010-2060"}},{"id":"saint_leonard_of_porto_maurizio_s_day","name":"San Leonardo da Porto Maurizio","en":"Saint Leonard of Porto Maurizio's Day","category":"public","regional":{"IM":"d|1126:2010-2060"}},{"id":"saint_ansanus_s_day","name":"Sant'Ansano","en":"Saint Ansanus's Day","category":"public","regional":{"SI":"d|1201:2010-2060"}},{"id":"saint_barbara_s_day","name":"Santa Barbara","en":"Saint Barbara's Day","category":"public","regional":{"RI":"d|1204:2010-2060"}},{"id":"saint_nicholas_s_day","name":"San Nicola","en":"Saint Nicholas's Day","category":"public","regional":{"BA":"d|1206:2010-2060","LC":"d|1206:2010-2060","SS":"d|1206:2010-2060"}},{"id":"saint_ambrose_s_day","name":"Sant'Ambrogio","en":"Saint Ambrose's Day","category":"public","regional":{"MI":"d|1207:2010-2060"}},{"id":"immaculate_conception","name":"Immacolata Concezione","en":"Immaculate Conception","category":"public","dates":"d|1208:2010-2060"},{"id":"saint_syrus_s_day","name":"San Siro","en":"Saint Syrus's Day","category":"public","regional":{"PV":"d|1209:2010-2060"}},{"id":"saint_lucy_s_day","name":"Santa Lucia","en":"Saint Lucy's Day","category":"public","regional":{"SR":"d|1213:2010-2060"}},{"id":"saint_berardo_s_day","name":"San Berardo da Pagliara","en":"Saint Berardo's Day","category":"public","regional":{"TE":"d|1219:2010-2060"}},{"id":"christmas_day","name":"Natale","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"},{"id":"saint_stephen_s_day","name":"Santo Stefano","en":"Saint Stephen's Day","category":"public","dates":"d|1226:2010-2060"},{"id":"saint_roger_s_day","name":"San Ruggero","en":"Saint Roger's Day","category":"public","regional":{"BT":"d|1230:2010-2060","Barletta":"d|1230:2010-2060"}},{"id":"fat_thursday_half_day","name":"Giovedì grasso","en":"Fat Thursday","category":"half_day","regional":{"BZ":"d|0201:2035,2046|0204:2016,2027|0205:2032,2043,2054|0206:2059|0207:2013|0208:2018,2024,2029|0209:2040,2051|0210:2056|0211:2010,2021|0212:2015,2026,2037|0213:2042,2048,2053|0216:2012,2023,2034,2045|0217:2039,2050|0220:2020,2031|0221:2036,2047,2058|0223:2017|0224:2022,2028,2033|0225:2044,2049,2055|0226:2060|0227:2014,2025|0228:2019,2030,2041|0229:2052|0301:2057|0303:2011|0304:2038"}},{"id":"last_day_of_carnival_half_day","name":"Ultimo giorno di carnevale","en":"Last Day of Carnival","category":"half_day","regional":{"BZ":"d|0206:2035,2046|0209:2016,2027|0210:2032,2043,2054|0211:2059|0212:2013|0213:2018,2024,2029|0214:2040,2051|0215:2056|0216:2010,2021|0217:2015,2026,2037|0218:2042,2048,2053|0221:2012,2023,2034,2045|0222:2039,2050|0225:2020,2031|0226:2036,2047,2058|0228:2017|0229:2028|0301:2022,2033,2044|0302:2049,2055,2060|0304:2014,2025|0305:2019,2030,2041,2052|0306:2057|0308:2011|0309:2038"}},{"id":"good_friday_half_day","name":"Venerdì santo","en":"Good Friday","category":"half_day","regional":{"BZ":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"}},{"id":"christmas_eve_half_day","name":"Vigilia di Natale","en":"Christmas Eve","category":"half_day","regional":{"BZ":"d|1224:2010-2060"}},{"id":"last_day_of_the_year_half_day","name":"Ultimo giorno dell'anno","en":"Last Day of the Year","category":"half_day","regional":{"BZ":"d|1231:2010-2060"}}]} diff --git a/app/Support/Holidays/data/JE.json b/app/Support/Holidays/data/JE.json new file mode 100644 index 000000000..7b82474ac --- /dev/null +++ b/app/Support/Holidays/data/JE.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"JE","language":null,"years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051|0103:2011,2022,2028,2033,2039,2050,2056"},{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_monday","name":"Easter Monday","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"early_may_bank_holiday","name":"Early May Bank Holiday","en":"Early May Bank Holiday","category":"public","dates":"d|0501:2017,2023,2028,2034,2045,2051,2056|0502:2011,2016,2022,2033,2039,2044,2050|0503:2010,2021,2027,2032,2038,2049,2055,2060|0504:2015,2020,2026,2037,2043,2048,2054|0505:2014,2025,2031,2036,2042,2053,2059|0506:2013,2019,2030,2041,2047,2052,2058|0507:2012,2018,2029,2035,2040,2046,2057"},{"id":"liberation_day","name":"Liberation Day","en":"Liberation Day","category":"public","dates":"d|0509:2010-2020,2022-2026,2028-2031,2033-2037,2039-2048,2050-2054,2056-2059"},{"id":"spring_bank_holiday","name":"Spring Bank Holiday","en":"Spring Bank Holiday","category":"public","dates":"d|0525:2015,2020,2026,2037,2043,2048,2054|0526:2014,2025,2031,2036,2042,2053,2059|0527:2013,2019,2024,2030,2041,2047,2052,2058|0528:2018,2029,2035,2040,2046,2057|0529:2017,2023,2028,2034,2045,2051,2056|0530:2011,2016,2033,2039,2044,2050|0531:2010,2021,2027,2032,2038,2049,2055,2060|0602:2022|0604:2012"},{"id":"summer_bank_holiday","name":"Summer Bank Holiday","en":"Summer Bank Holiday","category":"public","dates":"d|0825:2014,2025,2031,2036,2042,2053,2059|0826:2013,2019,2024,2030,2041,2047,2052,2058|0827:2012,2018,2029,2035,2040,2046,2057|0828:2017,2023,2028,2034,2045,2051,2056|0829:2011,2016,2022,2033,2039,2044,2050|0830:2010,2021,2027,2032,2038,2049,2055,2060|0831:2015,2020,2026,2037,2043,2048,2054"},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060|1227:2010-2011,2016,2021-2022,2027,2032-2033,2038-2039,2044,2049-2050,2055,2060"},{"id":"boxing_day","name":"Boxing Day","en":"Boxing Day","category":"public","dates":"d|1226:2010-2060|1228:2010,2015,2020-2021,2026-2027,2032,2037-2038,2043,2048-2049,2054-2055,2060"}]} diff --git a/app/Support/Holidays/data/JM.json b/app/Support/Holidays/data/JM.json new file mode 100644 index 000000000..142fe6a86 --- /dev/null +++ b/app/Support/Holidays/data/JM.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"JM","language":null,"years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051"},{"id":"ash_wednesday","name":"Ash Wednesday","en":"Ash Wednesday","category":"public","dates":"d|0207:2035,2046|0210:2016,2027|0211:2032,2043,2054|0212:2059|0213:2013|0214:2018,2024,2029|0215:2040,2051|0216:2056|0217:2010,2021|0218:2015,2026,2037|0219:2042,2048,2053|0222:2012,2023,2034,2045|0223:2039,2050|0226:2020,2031|0227:2036,2047,2058|0301:2017,2028|0302:2022,2033,2044|0303:2049,2055,2060|0305:2014,2025|0306:2019,2030,2041,2052|0307:2057|0309:2011|0310:2038"},{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_monday","name":"Easter Monday","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"national_labour_day","name":"National Labour Day","en":"National Labour Day","category":"public","dates":"d|0523:2010-2060|0524:2010,2021,2027,2032,2038,2049,2055,2060|0525:2015,2020,2026,2037,2043,2048,2054"},{"id":"emancipation_day","name":"Emancipation Day","en":"Emancipation Day","category":"public","dates":"d|0801:2010-2060|0802:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"independence_day","name":"Independence Day","en":"Independence Day","category":"public","dates":"d|0806:2010-2060|0807:2017,2023,2028,2034,2045,2051,2056"},{"id":"national_heroes_day","name":"National Heroes Day","en":"National Heroes Day","category":"public","dates":"d|1015:2012,2018,2029,2035,2040,2046,2057|1016:2017,2023,2028,2034,2045,2051,2056|1017:2011,2016,2022,2033,2039,2044,2050|1018:2010,2021,2027,2032,2038,2049,2055,2060|1019:2015,2020,2026,2037,2043,2048,2054|1020:2014,2025,2031,2036,2042,2053,2059|1021:2013,2019,2024,2030,2041,2047,2052,2058"},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060|1227:2011,2016,2022,2033,2039,2044,2050"},{"id":"boxing_day","name":"Boxing Day","en":"Boxing Day","category":"public","dates":"d|1226:2010-2060|1227:2010,2021,2027,2032,2038,2049,2055,2060"}]} diff --git a/app/Support/Holidays/data/JO.json b/app/Support/Holidays/data/JO.json new file mode 100644 index 000000000..b58ce6323 --- /dev/null +++ b/app/Support/Holidays/data/JO.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"JO","language":"ar","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"isra_and_mi_raj","name":"ليلة المعراج","en":"Isra' and Mi'raj","category":"public","dates":"y|2010:0709|2011:0629|2012:0617|2013:0606|2014:0526|2015:0516|2016:0504|2017:0424|2018:0413|2019:0403|2020:0322|2021:0311|2022:0228|2023:0218|2024:0208|2025:0127|2026:0116|2027:0105,1225|2028:1214|2029:1203|2030:1123|2031:1112|2032:1101|2033:1021|2034:1010|2035:0929|2036:0918|2037:0907|2038:0828|2039:0817|2040:0805|2041:0725|2042:0715|2043:0704|2044:0623|2045:0613|2046:0602|2047:0522|2048:0510|2049:0429|2050:0419|2051:0409|2052:0328|2053:0318|2054:0307|2055:0224|2056:0213|2057:0201|2058:0122|2059:0112|2060:0101,1220","estimated":"y|2010:0709|2011:0629|2012:0617|2013:0606|2014:0526|2015:0516|2016:0504|2017:0424|2018:0413|2019:0403|2020:0322|2021:0311|2022:0228|2023:0218|2024:0208|2025:0127|2026:0116|2027:0105,1225|2028:1214|2029:1203|2030:1123|2031:1112|2032:1101|2033:1021|2034:1010|2035:0929|2036:0918|2037:0907|2038:0828|2039:0817|2040:0805|2041:0725|2042:0715|2043:0704|2044:0623|2045:0613|2046:0602|2047:0522|2048:0510|2049:0429|2050:0419|2051:0409|2052:0328|2053:0318|2054:0307|2055:0224|2056:0213|2057:0201|2058:0122|2059:0112|2060:0101,1220"},{"id":"new_year_s_day","name":"رأس السنة الميلادية","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"eid_al_fitr","name":"عيد الفطر","en":"Eid al-Fitr","category":"public","dates":"y|2010:0910|2011:0830|2012:0819|2013:0808|2014:0728|2015:0717|2016:0706|2017:0625|2018:0615|2019:0604|2020:0524|2021:0513|2022:0502|2023:0421|2024:0410|2025:0330|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304","estimated":"y|2010:0910|2011:0830|2012:0819|2013:0808|2014:0728|2015:0717|2016:0706|2017:0625|2018:0615|2019:0604|2020:0524|2021:0513|2022:0502|2023:0421|2024:0410|2025:0330|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304"},{"id":"eid_al_fitr_holiday","name":"عطلة عيد الفطر","en":"Eid al-Fitr Holiday","category":"public","dates":"y|2010:0911-0912|2011:0831-0901|2012:0820-0821|2013:0809-0810|2014:0729-0730|2015:0718-0719|2016:0707-0708|2017:0626-0627|2018:0616-0617|2019:0605-0606|2020:0525-0526|2021:0514-0515|2022:0503-0504|2023:0422-0423|2024:0411-0412|2025:0331-0401|2026:0321-0322|2027:0310-0311|2028:0227-0228|2029:0215-0216|2030:0205-0206|2031:0125-0126|2032:0115-0116|2033:0103-0104,1224-1225|2034:1213-1214|2035:1202-1203|2036:1120-1121|2037:1109-1110|2038:1030-1031|2039:1020-1021|2040:1008-1009|2041:0927-0928|2042:0916-0917|2043:0905-0906|2044:0825-0826|2045:0815-0816|2046:0804-0805|2047:0725-0726|2048:0713-0714|2049:0702-0703|2050:0621-0622|2051:0611-0612|2052:0530-0531|2053:0520-0521|2054:0510-0511|2055:0429-0430|2056:0417-0418|2057:0406-0407|2058:0326-0327|2059:0316-0317|2060:0305-0306","estimated":"y|2010:0911-0912|2011:0831-0901|2012:0820-0821|2013:0809-0810|2014:0729-0730|2015:0718-0719|2016:0707-0708|2017:0626-0627|2018:0616-0617|2019:0605-0606|2020:0525-0526|2021:0514-0515|2022:0503-0504|2023:0422-0423|2024:0411-0412|2025:0331-0401|2026:0321-0322|2027:0310-0311|2028:0227-0228|2029:0215-0216|2030:0205-0206|2031:0125-0126|2032:0115-0116|2033:0103-0104,1224-1225|2034:1213-1214|2035:1202-1203|2036:1120-1121|2037:1109-1110|2038:1030-1031|2039:1020-1021|2040:1008-1009|2041:0927-0928|2042:0916-0917|2043:0905-0906|2044:0825-0826|2045:0815-0816|2046:0804-0805|2047:0725-0726|2048:0713-0714|2049:0702-0703|2050:0621-0622|2051:0611-0612|2052:0530-0531|2053:0520-0521|2054:0510-0511|2055:0429-0430|2056:0417-0418|2057:0406-0407|2058:0326-0327|2059:0316-0317|2060:0305-0306"},{"id":"prophet_s_birthday","name":"عيد المولد النبوي","en":"Prophet's Birthday","category":"public","dates":"y|2010:0226|2011:0215|2012:0204|2013:0124|2014:0113|2015:0103,1223|2016:1211|2017:1130|2018:1120|2019:1109|2020:1029|2021:1018|2022:1008|2023:0927|2024:0915|2025:0904|2026:0825|2027:0814|2028:0803|2029:0724|2030:0713|2031:0702|2032:0620|2033:0609|2034:0530|2035:0520|2036:0508|2037:0428|2038:0417|2039:0406|2040:0325|2041:0315|2042:0304|2043:0222|2044:0211|2045:0130|2046:0119|2047:0108,1229|2048:1218|2049:1207|2050:1126|2051:1116|2052:1104|2053:1024|2054:1013|2055:1003|2056:0922|2057:0911|2058:0831|2059:0820|2060:0808","estimated":"y|2010:0226|2011:0215|2012:0204|2013:0124|2014:0113|2015:0103,1223|2016:1211|2017:1130|2018:1120|2019:1109|2020:1029|2021:1018|2022:1008|2023:0927|2024:0915|2025:0904|2026:0825|2027:0814|2028:0803|2029:0724|2030:0713|2031:0702|2032:0620|2033:0609|2034:0530|2035:0520|2036:0508|2037:0428|2038:0417|2039:0406|2040:0325|2041:0315|2042:0304|2043:0222|2044:0211|2045:0130|2046:0119|2047:0108,1229|2048:1218|2049:1207|2050:1126|2051:1116|2052:1104|2053:1024|2054:1013|2055:1003|2056:0922|2057:0911|2058:0831|2059:0820|2060:0808"},{"id":"day_of_arafah","name":"يوم عرفة","en":"Day of Arafah","category":"public","dates":"y|2010:1115|2011:1105|2012:1025|2013:1014|2014:1003|2015:0922|2016:0910|2017:0831|2018:0820|2019:0810|2020:0730|2021:0719|2022:0708|2023:0627|2024:0615|2025:0605|2026:0526|2027:0515|2028:0504|2029:0423|2030:0412|2031:0401|2032:0321|2033:0310|2034:0228|2035:0217|2036:0206|2037:0125|2038:0115|2039:0104,1225|2040:1213|2041:1203|2042:1122|2043:1111|2044:1030|2045:1020|2046:1009|2047:0929|2048:0918|2049:0907|2050:0827|2051:0816|2052:0804|2053:0725|2054:0714|2055:0704|2056:0622|2057:0611|2058:0531|2059:0521|2060:0509","estimated":"y|2010:1115|2011:1105|2012:1025|2013:1014|2014:1003|2015:0922|2016:0910|2017:0831|2018:0820|2019:0810|2020:0730|2021:0719|2022:0708|2023:0627|2024:0615|2025:0605|2026:0526|2027:0515|2028:0504|2029:0423|2030:0412|2031:0401|2032:0321|2033:0310|2034:0228|2035:0217|2036:0206|2037:0125|2038:0115|2039:0104,1225|2040:1213|2041:1203|2042:1122|2043:1111|2044:1030|2045:1020|2046:1009|2047:0929|2048:0918|2049:0907|2050:0827|2051:0816|2052:0804|2053:0725|2054:0714|2055:0704|2056:0622|2057:0611|2058:0531|2059:0521|2060:0509"},{"id":"islamic_new_year","name":"رأس السنة الهجرية","en":"Islamic New Year","category":"public","dates":"y|2010:1207|2011:1126|2012:1115|2013:1104|2014:1025|2015:1014|2016:1002|2017:0921|2018:0911|2019:0831|2020:0820|2021:0809|2022:0730|2023:0719|2024:0707|2025:0626|2026:0616|2027:0606|2028:0525|2029:0514|2030:0503|2031:0423|2032:0411|2033:0401|2034:0321|2035:0311|2036:0228|2037:0216|2038:0205|2039:0126|2040:0115|2041:0104,1224|2042:1214|2043:1203|2044:1121|2045:1110|2046:1031|2047:1020|2048:1009|2049:0928|2050:0917|2051:0906|2052:0826|2053:0815|2054:0805|2055:0725|2056:0714|2057:0703|2058:0622|2059:0611|2060:0531","estimated":"y|2010:1207|2011:1126|2012:1115|2013:1104|2014:1025|2015:1014|2016:1002|2017:0921|2018:0911|2019:0831|2020:0820|2021:0809|2022:0730|2023:0719|2024:0707|2025:0626|2026:0616|2027:0606|2028:0525|2029:0514|2030:0503|2031:0423|2032:0411|2033:0401|2034:0321|2035:0311|2036:0228|2037:0216|2038:0205|2039:0126|2040:0115|2041:0104,1224|2042:1214|2043:1203|2044:1121|2045:1110|2046:1031|2047:1020|2048:1009|2049:0928|2050:0917|2051:0906|2052:0826|2053:0815|2054:0805|2055:0725|2056:0714|2057:0703|2058:0622|2059:0611|2060:0531"},{"id":"eid_al_adha","name":"عيد الأضحى","en":"Eid al-Adha","category":"public","dates":"y|2010:1116|2011:1106|2012:1026|2013:1015|2014:1004|2015:0923|2016:0911|2017:0901|2018:0821|2019:0811|2020:0731|2021:0720|2022:0709|2023:0628|2024:0616|2025:0606|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510","estimated":"y|2010:1116|2011:1106|2012:1026|2013:1015|2014:1004|2015:0923|2016:0911|2017:0901|2018:0821|2019:0811|2020:0731|2021:0720|2022:0709|2023:0628|2024:0616|2025:0606|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510"},{"id":"eid_al_adha_holiday","name":"عطلة عيد الأضحى","en":"Eid al-Adha Holiday","category":"public","dates":"y|2010:1117-1118|2011:1107-1108|2012:1027-1028|2013:1016-1017|2014:1005-1006|2015:0924-0925|2016:0912-0913|2017:0902-0903|2018:0822-0823|2019:0812-0813|2020:0801-0802|2021:0721-0722|2022:0710-0711|2023:0629-0630|2024:0617-0618|2025:0607-0608|2026:0528-0529|2027:0517-0518|2028:0506-0507|2029:0425-0426|2030:0414-0415|2031:0403-0404|2032:0323-0324|2033:0312-0313|2034:0302-0303|2035:0219-0220|2036:0208-0209|2037:0127-0128|2038:0117-0118|2039:0106-0107,1227-1228|2040:1215-1216|2041:1205-1206|2042:1124-1125|2043:1113-1114|2044:1101-1102|2045:1022-1023|2046:1011-1012|2047:1001-1002|2048:0920-0921|2049:0909-0910|2050:0829-0830|2051:0818-0819|2052:0806-0807|2053:0727-0728|2054:0716-0717|2055:0706-0707|2056:0624-0625|2057:0613-0614|2058:0602-0603|2059:0523-0524|2060:0511-0512","estimated":"y|2010:1117-1118|2011:1107-1108|2012:1027-1028|2013:1016-1017|2014:1005-1006|2015:0924-0925|2016:0912-0913|2017:0902-0903|2018:0822-0823|2019:0812-0813|2020:0801-0802|2021:0721-0722|2022:0710-0711|2023:0629-0630|2024:0617-0618|2025:0607-0608|2026:0528-0529|2027:0517-0518|2028:0506-0507|2029:0425-0426|2030:0414-0415|2031:0403-0404|2032:0323-0324|2033:0312-0313|2034:0302-0303|2035:0219-0220|2036:0208-0209|2037:0127-0128|2038:0117-0118|2039:0106-0107,1227-1228|2040:1215-1216|2041:1205-1206|2042:1124-1125|2043:1113-1114|2044:1101-1102|2045:1022-1023|2046:1011-1012|2047:1001-1002|2048:0920-0921|2049:0909-0910|2050:0829-0830|2051:0818-0819|2052:0806-0807|2053:0727-0728|2054:0716-0717|2055:0706-0707|2056:0624-0625|2057:0613-0614|2058:0602-0603|2059:0523-0524|2060:0511-0512"},{"id":"labor_day","name":"عيد العمال","en":"Labor Day","category":"public","dates":"d|0501:2010-2060"},{"id":"independence_day","name":"عيد الاستقلال","en":"Independence Day","category":"public","dates":"d|0525:2010-2060"},{"id":"christmas_day","name":"عيد الميلاد المجيد","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"}]} diff --git a/app/Support/Holidays/data/JP.json b/app/Support/Holidays/data/JP.json new file mode 100644 index 000000000..962ed45cf --- /dev/null +++ b/app/Support/Holidays/data/JP.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"JP","language":"ja","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"元日","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"substitute_holiday","name":"振替休日","en":"Substitute Holiday","category":"public","dates":"d|0102:2012,2017,2023,2034,2040,2045,2051|0212:2018,2024,2029,2035,2046,2052,2057|0224:2020,2025,2031,2042,2048,2053,2059|0321:2016,2033,2044,2050|0322:2010,2027,2055|0430:2012,2018,2029,2035,2040,2046,2057|0506:2013-2015,2019-2020,2024-2026,2030-2031,2036-2037,2041-2043,2047-2048,2052-2054,2058-2059|0809:2021|0812:2019,2024,2030,2041,2047,2052,2058|0923:2024,2052|0924:2018,2029,2035,2046|1104:2013,2019,2024,2030,2041,2047,2052,2058|1124:2014,2025,2031,2036,2042,2053,2059|1224:2012,2018"},{"id":"coming_of_age_day","name":"成人の日","en":"Coming of Age Day","category":"public","dates":"d|0108:2018,2024,2029,2035,2046,2052,2057|0109:2012,2017,2023,2034,2040,2045,2051|0110:2011,2022,2028,2033,2039,2050,2056|0111:2010,2016,2021,2027,2038,2044,2049,2055|0112:2015,2026,2032,2037,2043,2054,2060|0113:2014,2020,2025,2031,2042,2048,2053,2059|0114:2013,2019,2030,2036,2041,2047,2058"},{"id":"foundation_day","name":"建国記念の日","en":"Foundation Day","category":"public","dates":"d|0211:2010-2060"},{"id":"emperor_s_birthday","name":"天皇誕生日","en":"Emperor's Birthday","category":"public","dates":"d|0223:2020-2060|1223:2010-2018"},{"id":"vernal_equinox_day","name":"春分の日","en":"Vernal Equinox Day","category":"public","dates":"d|0320:2012-2013,2016-2017,2020-2021,2024-2026,2028-2030,2032-2034,2036-2038,2040-2042,2044-2046,2048-2050,2052-2054,2056-2060|0321:2010-2011,2014-2015,2018-2019,2022-2023,2027,2031,2035,2039,2043,2047,2051,2055"},{"id":"showa_day","name":"昭和の日","en":"Showa Day","category":"public","dates":"d|0429:2010-2060"},{"id":"national_holiday","name":"国民の休日","en":"National Holiday","category":"public","dates":"d|0430:2019|0502:2019|0921:2032,2049,2060|0922:2015,2026,2037,2043,2054"},{"id":"constitution_day","name":"憲法記念日","en":"Constitution Day","category":"public","dates":"d|0503:2010-2060"},{"id":"greenery_day","name":"みどりの日","en":"Greenery Day","category":"public","dates":"d|0504:2010-2060"},{"id":"children_s_day","name":"こどもの日","en":"Children's Day","category":"public","dates":"d|0505:2010-2060"},{"id":"marine_day","name":"海の日","en":"Marine Day","category":"public","dates":"d|0715:2013,2019,2024,2030,2041,2047,2052,2058|0716:2012,2018,2029,2035,2040,2046,2057|0717:2017,2023,2028,2034,2045,2051,2056|0718:2011,2016,2022,2033,2039,2044,2050|0719:2010,2027,2032,2038,2049,2055,2060|0720:2015,2026,2037,2043,2048,2054|0721:2014,2025,2031,2036,2042,2053,2059|0722:2021|0723:2020"},{"id":"sports_day","name":"スポーツの日","en":"Sports Day","category":"public","dates":"d|0723:2021|0724:2020|1008:2029,2035,2040,2046,2057|1009:2023,2028,2034,2045,2051,2056|1010:2022,2033,2039,2044,2050|1011:2027,2032,2038,2049,2055,2060|1012:2026,2037,2043,2048,2054|1013:2025,2031,2036,2042,2053,2059|1014:2024,2030,2041,2047,2052,2058"},{"id":"mountain_day","name":"山の日","en":"Mountain Day","category":"public","dates":"d|0808:2021|0810:2020|0811:2016-2019,2022-2060"},{"id":"respect_for_the_aged_day","name":"敬老の日","en":"Respect for the Aged Day","category":"public","dates":"d|0915:2014,2025,2031,2036,2042,2053,2059|0916:2013,2019,2024,2030,2041,2047,2052,2058|0917:2012,2018,2029,2035,2040,2046,2057|0918:2017,2023,2028,2034,2045,2051,2056|0919:2011,2016,2022,2033,2039,2044,2050|0920:2010,2021,2027,2032,2038,2049,2055,2060|0921:2015,2020,2026,2037,2043,2048,2054"},{"id":"autumnal_equinox_day","name":"秋分の日","en":"Autumnal Equinox Day","category":"public","dates":"d|0922:2012,2016,2020,2024,2028,2032,2036,2040,2044-2045,2048-2049,2052-2053,2056-2057,2060|0923:2010-2011,2013-2015,2017-2019,2021-2023,2025-2027,2029-2031,2033-2035,2037-2039,2041-2043,2046-2047,2050-2051,2054-2055,2058-2059"},{"id":"culture_day","name":"文化の日","en":"Culture Day","category":"public","dates":"d|1103:2010-2060"},{"id":"labor_thanksgiving_day","name":"勤労感謝の日","en":"Labor Thanksgiving Day","category":"public","dates":"d|1123:2010-2060"},{"id":"bank_holiday_bank","name":"銀行休業日","en":"Bank Holiday","category":"bank","dates":"d|0101:2010-2060|0102:2010-2060|0103:2010-2060|1231:2010-2060"}]} diff --git a/app/Support/Holidays/data/KE.json b/app/Support/Holidays/data/KE.json new file mode 100644 index 000000000..dd5efd375 --- /dev/null +++ b/app/Support/Holidays/data/KE.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"KE","language":"en_KE","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051"},{"id":"eid_al_fitr","name":"Idd-ul-Fitr","en":"Eid al-Fitr","category":"public","dates":"y|2010:0910|2011:0830|2012:0819-0820|2013:0808|2014:0729|2015:0718|2016:0707|2017:0626|2018:0615|2019:0605|2020:0525|2021:0514|2022:0503|2023:0421|2024:0410|2025:0330-0331|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102-0103,1223|2034:1212|2035:1201|2036:1119|2037:1108-1109|2038:1029|2039:1019|2040:1007-1008|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712-0713|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416-0417|2057:0405|2058:0325|2059:0315|2060:0304","estimated":"y|2010:0910|2011:0830|2012:0819|2013:0808|2025:0330|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304"},{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_monday","name":"Easter Monday","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"labor_day","name":"Labour Day","en":"Labor Day","category":"public","dates":"d|0501:2010-2060|0502:2011,2016,2022,2033,2039,2044,2050"},{"id":"madaraka_day","name":"Madaraka Day","en":"Madaraka Day","category":"public","dates":"d|0601:2011-2060|0602:2014,2025,2031,2036,2042,2053,2059"},{"id":"mazingira_day","name":"Mazingira Day","en":"Mazingira Day","category":"public","dates":"d|1010:2025-2060|1011:2027,2032,2038,2049,2055,2060"},{"id":"mashujaa_day","name":"Mashujaa Day","en":"Mashujaa Day","category":"public","dates":"d|1020:2011-2060|1021:2013,2019,2024,2030,2041,2047,2052,2058"},{"id":"jamhuri_day","name":"Jamhuri Day","en":"Jamhuri Day","category":"public","dates":"d|1212:2011-2060|1213:2021,2027,2032,2038,2049,2055,2060"},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060|1227:2011,2016,2022,2033,2039,2044,2050"},{"id":"boxing_day","name":"Boxing Day","en":"Boxing Day","category":"public","dates":"d|1226:2010-2060|1227:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"eid_al_adha_islamic","name":"Idd-ul-Azha","en":"Eid al-Adha","category":"islamic","dates":"y|2010:1116|2011:1106|2012:1026|2013:1015|2014:1006|2015:0924|2016:0912|2017:0901|2018:0821|2019:0812|2020:0731|2021:0720|2022:0711|2023:0628|2024:0617|2025:0606|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510","estimated":"y|2010:1116|2011:1106|2012:1026|2013:1015|2025:0606|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510"},{"id":"diwali_hindu","name":"Diwali","en":"Diwali","category":"hindu","dates":"d|1018:2017|1022:2014,2033|1023:2060|1024:2022,2041|1026:2011,2030,2049|1027:2038,2057|1028:2019,2027,2046|1029:2016|1030:2035,2054|1031:2024|1101:2043|1102:2013,2032,2051|1104:2021,2040,2059|1105:2010,2029,2048|1106:2018,2037,2056|1108:2026,2045|1109:2053|1110:2015,2034|1112:2023,2042|1113:2012,2050|1114:2020,2031|1115:2028,2039,2058|1116:2047|1117:2036|1118:2044,2055|1119:2025|1120:2052"}]} diff --git a/app/Support/Holidays/data/KG.json b/app/Support/Holidays/data/KG.json new file mode 100644 index 000000000..f367fb1e5 --- /dev/null +++ b/app/Support/Holidays/data/KG.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"KG","language":"ky","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Жаңы жыл","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2012,2017,2023|0103:2011,2022"},{"id":"eid_al_fitr","name":"Орозо айт","en":"Eid al-Fitr","category":"public","dates":"y|2010:0910|2011:0830|2012:0819-0820|2013:0808|2014:0729|2015:0717|2016:0705|2017:0626|2018:0615|2019:0605|2020:0524-0525|2021:0513|2022:0503|2023:0421|2024:0410|2025:0330|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304","estimated":"y|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304"},{"id":"new_year_holidays","name":"Жаңы жылдык каникулдар","en":"New Year Holidays","category":"public","dates":"d|0102:2026-2060|0103:2026-2060|0104:2026-2060|0105:2026-2060|0106:2026-2060"},{"id":"eid_al_adha","name":"Курман айт","en":"Eid al-Adha","category":"public","dates":"y|2010:1116|2011:1106,1108|2012:1026|2013:1015|2014:1004,1006|2015:0924|2016:0912|2017:0901|2018:0821|2019:0811-0812|2020:0731|2021:0720|2022:0709,0711|2023:0628|2024:0617|2025:0606|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510","estimated":"y|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510"},{"id":"christmas_day","name":"Ыйса Пайгамбардын туулган күнү","en":"Christmas Day","category":"public","dates":"d|0107:2010-2060|0109:2017"},{"id":"international_women_s_day","name":"Аялдардын эл аралык күнү","en":"International Women's Day","category":"public","dates":"d|0308:2010-2060|0309:2015|0310:2014"},{"id":"nooruz_holiday","name":"Элдик Нооруз майрамы","en":"Nooruz Holiday","category":"public","dates":"d|0321:2010-2060|0322:2010,2021"},{"id":"labor_day","name":"Эмгек майрамы","en":"Labor Day","category":"public","dates":"d|0501:2010-2060|0502:2011,2016,2022|0503:2010"},{"id":"may_holidays","name":"Май каникулдары","en":"May Holidays","category":"public","dates":"d|0502:2025-2060|0503:2025-2060|0504:2025-2060|0506:2025-2060|0507:2025-2060|0508:2025-2060"},{"id":"constitution_day","name":"Кыргыз Республикасынын Конституция күнү","en":"Constitution Day","category":"public","dates":"d|0505:2010-2060|0507:2012"},{"id":"victory_day","name":"Жеңиш күнү","en":"Victory Day","category":"public","dates":"d|0509:2010-2060|0510:2010|0511:2015"},{"id":"independence_day","name":"Кыргыз Республикасынын Көз карандысыздыгынын күнү","en":"Independence Day","category":"public","dates":"d|0831:2010-2060|0902:2013"}]} diff --git a/app/Support/Holidays/data/KH.json b/app/Support/Holidays/data/KH.json new file mode 100644 index 000000000..be62625cb --- /dev/null +++ b/app/Support/Holidays/data/KH.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"KH","language":"km","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"international_new_year_day","name":"ទិវាចូលឆ្នាំសាកល","en":"International New Year Day","category":"public","dates":"d|0101:2010-2060"},{"id":"day_of_victory_over_the_genocidal_regime","name":"ទិវាជ័យជម្នះលើរបបប្រល័យពូជសាសន៍","en":"Day of Victory over the Genocidal Regime","category":"public","dates":"d|0107:2010-2060"},{"id":"international_women_s_rights_day","name":"ទិវាអន្តរជាតិនារី","en":"International Women's Rights Day","category":"public","dates":"d|0308:2010-2060"},{"id":"khmer_new_year_s_day","name":"ពិធីបុណ្យចូលឆ្នាំថ្មីប្រពៃណីជាតិ","en":"Khmer New Year's Day","category":"public","dates":"d|0413:2010-2016,2019,2024,2028,2032-2060|0414:2010-2019,2021-2060|0415:2010-2019,2021-2060|0416:2017-2018,2021-2027,2029-2031"},{"id":"visaka_bochea_day","name":"ពិធីបុណ្យវិសាខបូជា","en":"Visaka Bochea Day","category":"public","dates":"d|0425:2040|0426:2021|0427:2029,2048,2059|0428:2010|0429:2018,2037,2056|0430:2045|0501:2026|0502:2015,2034,2053|0503:2042|0504:2023|0505:2012,2031,2050|0506:2020|0507:2039,2058|0508:2028|0509:2047|0510:2017,2036,2055|0511:2025,2044|0512:2052|0513:2014,2033|0514:2041|0515:2022,2060|0516:2030,2049|0517:2011|0518:2019,2038,2057|0519:2046|0520:2016,2027|0521:2035,2054|0522:2024,2043|0523:2032|0524:2013,2051"},{"id":"royal_ploughing_ceremony","name":"ព្រះរាជពិធីច្រត់ព្រះនង្គ័ល","en":"Royal Ploughing Ceremony","category":"public","dates":"d|0429:2040|0430:2021|0501:2029,2048,2059|0502:2010|0503:2018,2037,2056|0504:2045|0505:2026|0506:2015,2034,2053|0507:2042|0508:2023|0509:2012,2031,2050|0510:2020|0511:2039,2058|0512:2028|0513:2047|0514:2017,2036,2055|0515:2025,2044|0516:2052|0517:2014,2033|0518:2041|0519:2022,2060|0520:2030,2049|0521:2011|0522:2019,2038,2057|0523:2046|0524:2016,2027|0525:2035,2054|0526:2024,2043|0527:2032|0528:2013,2051"},{"id":"international_labor_day","name":"ទិវាពលកម្មអន្តរជាតិ","en":"International Labor Day","category":"public","dates":"d|0501:2010-2060"},{"id":"hm_king_norodom_sihamoni_s_birthday","name":"ព្រះរាជពិធីបុណ្យចម្រើនព្រះជន្ម ព្រះករុណា ព្រះបាទសម្តេចព្រះបរមនាថ នរោត្តម សីហមុនី","en":"HM King Norodom Sihamoni's Birthday","category":"public","dates":"d|0513:2010-2019|0514:2010-2060|0515:2010-2019"},{"id":"hm_queen_norodom_monineath_sihanouk_the_queen_mo","name":"ព្រះរាជពិធីបុណ្យចម្រើនព្រះជន្ម សម្តេចព្រះមហាក្សត្រី ព្រះវររាជមាតា នរោត្តម មុនិនាថ សីហនុ","en":"HM Queen Norodom Monineath Sihanouk the Queen-Mother's Birthday","category":"public","dates":"d|0618:2010-2060"},{"id":"pchum_ben_day","name":"ពិធីបុណ្យភ្ផុំបិណ្ឌ","en":"Pchum Ben Day","category":"public","dates":"y|2010:1007-1008|2011:0926-0927|2012:1014-1015|2013:1003-1004|2014:0922-0923|2015:1011-1012|2016:0930-1001|2017:0919-0921|2018:1008-1010|2019:0927-0929|2020:0916-0918|2021:1005-1007|2022:0924-0926|2023:1013-1015|2024:1001-1003|2025:0921-0923|2026:1010-1012|2027:0929-1001|2028:0917-0919|2029:1006-1008|2030:0925-0927|2031:1014-1016|2032:1003-1005|2033:0922-0924|2034:1011-1013|2035:1001-1003|2036:0919-0921|2037:1008-1010|2038:0927-0929|2039:0916-0918|2040:1004-1006|2041:0923-0925|2042:1012-1014|2043:1002-1004|2044:0920-0922|2045:1009-1011|2046:0929-1001|2047:0918-0920|2048:1006-1008|2049:0925-0927|2050:1014-1016|2051:1003-1005|2052:0922-0924|2053:1011-1013|2054:0930-1002|2055:0920-0922|2056:1008-1010|2057:0927-0929|2058:0917-0919|2059:1006-1008|2060:0924-0926"},{"id":"constitution_day","name":"ទិវាប្រកាសរដ្ឋធម្មនុញ្ញ","en":"Constitution Day","category":"public","dates":"d|0924:2010-2060"},{"id":"hm_king_norodom_sihanouk_mourning_day","name":"ទិវាប្រារព្ឋពិធីគោរពព្រះវិញ្ញាណក្ខន្ឋ ព្រះករុណា ព្រះបាទសម្តេចព្រះ នរោត្តម សីហនុ ព្រះមហាវីរក្សត្រ ព្រះវររាជបិតាឯករាជ្យ បូរណភាពទឹកដី និងឯកភាពជាតិខ្មែរ ព្រះបរមរតនកោដ្ឋ","en":"HM King Norodom Sihanouk Mourning Day","category":"public","dates":"d|1015:2012-2060"},{"id":"hm_king_norodom_sihamoni_s_coronation_day","name":"ព្រះរាជពិធីគ្រងព្រះបរមរាជសម្បត្តិ របស់ ព្រះករុណា ព្រះបាទសម្តេចព្រះបរមនាថ នរោត្តម សីហមុនី ព្រះមហាក្សត្រនៃព្រះរាជាណាចក្រកម្ពុជា","en":"HM King Norodom Sihamoni's Coronation Day","category":"public","dates":"d|1029:2010-2060"},{"id":"water_festival","name":"ព្រះរាជពិធីបុណ្យអុំទូក បណ្តែតប្រទីប និងសំពះព្រះខែអកអំបុក","en":"Water Festival","category":"public","dates":"y|2010:1120-1122|2011:1109-1111|2012:1127-1129|2013:1116-1118|2014:1105-1107|2015:1124-1126|2016:1113-1115|2017:1102-1104|2018:1121-1123|2019:1110-1112|2020:1030-1101|2021:1118-1120|2022:1107-1109|2023:1126-1128|2024:1114-1116|2025:1104-1106|2026:1123-1125|2027:1112-1114|2028:1031-1102|2029:1119-1121|2030:1108-1110|2031:1127-1129|2032:1116-1118|2033:1105-1107|2034:1124-1126|2035:1114-1116|2036:1102-1104|2037:1121-1123|2038:1110-1112|2039:1030-1101|2040:1117-1119|2041:1106-1108|2042:1125-1127|2043:1115-1117|2044:1103-1105|2045:1122-1124|2046:1112-1114|2047:1101-1103|2048:1119-1121|2049:1108-1110|2050:1127-1129|2051:1116-1118|2052:1105-1107|2053:1124-1126|2054:1113-1115|2055:1103-1105|2056:1121-1123|2057:1110-1112|2058:1031-1102|2059:1119-1121|2060:1107-1109"},{"id":"national_independence_day","name":"ពិធីបុណ្យឯករាជ្យជាតិ","en":"National Independence Day","category":"public","dates":"d|1109:2010-2060"},{"id":"peace_day_in_cambodia","name":"ទិវាសន្តិភាពនៅកម្ពុជា","en":"Peace Day in Cambodia","category":"public","dates":"d|1229:2024-2060"}]} diff --git a/app/Support/Holidays/data/KI.json b/app/Support/Holidays/data/KI.json new file mode 100644 index 000000000..c83dc8409 --- /dev/null +++ b/app/Support/Holidays/data/KI.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"KI","language":null,"years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051|0103:2011,2022,2028,2033,2039,2050,2056"},{"id":"international_women_s_day","name":"International Women's Day","en":"International Women's Day","category":"public","dates":"d|0308:2010-2060|0309:2015,2020,2026,2037,2043,2048,2054|0310:2014,2025,2031,2036,2042,2053,2059"},{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_monday","name":"Easter Monday","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"national_health_day","name":"National Health Day","en":"National Health Day","category":"public","dates":"d|0419:2010-2060|0420:2015,2020,2026,2037,2043,2048,2054|0421:2014,2025,2031,2036,2042,2053,2059"},{"id":"gospel_day","name":"Gospel Day","en":"Gospel Day","category":"public","dates":"d|0711:2010-2060"},{"id":"national_day_independence_anniversary","name":"National Day - Independence Anniversary","en":"National Day - Independence Anniversary","category":"public","dates":"d|0712:2010-2060|0713:2015,2020,2026,2037,2043,2048,2054|0714:2014,2025,2031,2036,2042,2053,2059"},{"id":"national_day_in_honor_of_unimwane","name":"National Day (in honor of Unimwane)","en":"National Day (in honor of Unimwane)","category":"public","dates":"d|0715:2010-2060"},{"id":"national_day_in_honor_of_unaine","name":"National Day (in honor of Unaine)","en":"National Day (in honor of Unaine)","category":"public","dates":"d|0716:2010-2060|0717:2017,2023,2028,2034,2045,2051,2056|0718:2011,2016,2022,2033,2039,2044,2050"},{"id":"youth_day","name":"Youth Day","en":"Youth Day","category":"public","dates":"d|0805:2010-2060|0806:2012,2018,2029,2035,2040,2046,2057|0807:2017,2023,2028,2034,2045,2051,2056"},{"id":"human_rights_and_peace_day","name":"Human Rights and Peace Day","en":"Human Rights and Peace Day","category":"public","dates":"d|1209:2010-2060|1210:2012,2018,2029,2035,2040,2046,2057|1211:2017,2023,2028,2034,2045,2051,2056"},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060|1227:2010-2011,2016,2021-2022,2027,2032-2033,2038-2039,2044,2049-2050,2055,2060"},{"id":"boxing_day","name":"Boxing Day","en":"Boxing Day","category":"public","dates":"d|1226:2010-2060|1228:2010,2015,2020-2021,2026-2027,2032,2037-2038,2043,2048-2049,2054-2055,2060"}]} diff --git a/app/Support/Holidays/data/KM.json b/app/Support/Holidays/data/KM.json new file mode 100644 index 000000000..18625b392 --- /dev/null +++ b/app/Support/Holidays/data/KM.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"KM","language":null,"years":[2010,2060],"subdivisions":{},"holidays":[{"id":"isra_and_mi_raj","name":"Isra' and Mi'raj","en":"Isra' and Mi'raj","category":"public","dates":"y|2010:0709|2011:0629|2012:0617|2013:0606|2014:0526|2015:0516|2016:0504|2017:0424|2018:0413|2019:0403|2020:0322|2021:0311|2022:0228|2023:0218|2024:0208|2025:0127|2026:0116|2027:0105,1225|2028:1214|2029:1203|2030:1123|2031:1112|2032:1101|2033:1021|2034:1010|2035:0929|2036:0918|2037:0907|2038:0828|2039:0817|2040:0805|2041:0725|2042:0715|2043:0704|2044:0623|2045:0613|2046:0602|2047:0522|2048:0510|2049:0429|2050:0419|2051:0409|2052:0328|2053:0318|2054:0307|2055:0224|2056:0213|2057:0201|2058:0122|2059:0112|2060:0101,1220","estimated":"y|2010:0709|2011:0629|2012:0617|2013:0606|2014:0526|2015:0516|2016:0504|2017:0424|2018:0413|2019:0403|2020:0322|2021:0311|2022:0228|2023:0218|2024:0208|2025:0127|2026:0116|2027:0105,1225|2028:1214|2029:1203|2030:1123|2031:1112|2032:1101|2033:1021|2034:1010|2035:0929|2036:0918|2037:0907|2038:0828|2039:0817|2040:0805|2041:0725|2042:0715|2043:0704|2044:0623|2045:0613|2046:0602|2047:0522|2048:0510|2049:0429|2050:0419|2051:0409|2052:0328|2053:0318|2054:0307|2055:0224|2056:0213|2057:0201|2058:0122|2059:0112|2060:0101,1220"},{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"eid_al_fitr","name":"Eid al-Fitr","en":"Eid al-Fitr","category":"public","dates":"y|2010:0910-0912|2011:0830-0901|2012:0819-0821|2013:0808-0810|2014:0728-0730|2015:0717-0719|2016:0706-0708|2017:0625-0627|2018:0615-0617|2019:0604-0606|2020:0524-0526|2021:0513-0515|2022:0502-0504|2023:0421-0423|2024:0410-0412|2025:0330-0401|2026:0320-0322|2027:0309-0311|2028:0226-0228|2029:0214-0216|2030:0204-0206|2031:0124-0126|2032:0114-0116|2033:0102-0104,1223-1225|2034:1212-1214|2035:1201-1203|2036:1119-1121|2037:1108-1110|2038:1029-1031|2039:1019-1021|2040:1007-1009|2041:0926-0928|2042:0915-0917|2043:0904-0906|2044:0824-0826|2045:0814-0816|2046:0803-0805|2047:0724-0726|2048:0712-0714|2049:0701-0703|2050:0620-0622|2051:0610-0612|2052:0529-0531|2053:0519-0521|2054:0509-0511|2055:0428-0430|2056:0416-0418|2057:0405-0407|2058:0325-0327|2059:0315-0317|2060:0304-0306","estimated":"y|2010:0910-0912|2011:0830-0901|2012:0819-0821|2013:0808-0810|2014:0728-0730|2015:0717-0719|2016:0706-0708|2017:0625-0627|2018:0615-0617|2019:0604-0606|2020:0524-0526|2021:0513-0515|2022:0502-0504|2023:0421-0423|2024:0410-0412|2025:0330-0401|2026:0320-0322|2027:0309-0311|2028:0226-0228|2029:0214-0216|2030:0204-0206|2031:0124-0126|2032:0114-0116|2033:0102-0104,1223-1225|2034:1212-1214|2035:1201-1203|2036:1119-1121|2037:1108-1110|2038:1029-1031|2039:1019-1021|2040:1007-1009|2041:0926-0928|2042:0915-0917|2043:0904-0906|2044:0824-0826|2045:0814-0816|2046:0803-0805|2047:0724-0726|2048:0712-0714|2049:0701-0703|2050:0620-0622|2051:0610-0612|2052:0529-0531|2053:0519-0521|2054:0509-0511|2055:0428-0430|2056:0416-0418|2057:0405-0407|2058:0325-0327|2059:0315-0317|2060:0304-0306"},{"id":"prophet_s_birthday","name":"Prophet's Birthday","en":"Prophet's Birthday","category":"public","dates":"y|2010:0226|2011:0215|2012:0204|2013:0124|2014:0113|2015:0103,1223|2016:1211|2017:1130|2018:1120|2019:1109|2020:1029|2021:1019|2022:1008|2023:0927|2024:0915|2025:0904|2026:0825|2027:0814|2028:0803|2029:0724|2030:0713|2031:0702|2032:0620|2033:0609|2034:0530|2035:0520|2036:0508|2037:0428|2038:0417|2039:0406|2040:0325|2041:0315|2042:0304|2043:0222|2044:0211|2045:0130|2046:0119|2047:0108,1229|2048:1218|2049:1207|2050:1126|2051:1116|2052:1104|2053:1024|2054:1013|2055:1003|2056:0922|2057:0911|2058:0831|2059:0820|2060:0808","estimated":"y|2010:0226|2011:0215|2012:0204|2013:0124|2014:0113|2015:0103,1223|2016:1211|2017:1130|2018:1120|2019:1109|2020:1029|2022:1008|2023:0927|2024:0915|2025:0904|2026:0825|2027:0814|2028:0803|2029:0724|2030:0713|2031:0702|2032:0620|2033:0609|2034:0530|2035:0520|2036:0508|2037:0428|2038:0417|2039:0406|2040:0325|2041:0315|2042:0304|2043:0222|2044:0211|2045:0130|2046:0119|2047:0108,1229|2048:1218|2049:1207|2050:1126|2051:1116|2052:1104|2053:1024|2054:1013|2055:1003|2056:0922|2057:0911|2058:0831|2059:0820|2060:0808"},{"id":"islamic_new_year","name":"Islamic New Year","en":"Islamic New Year","category":"public","dates":"y|2010:1207|2011:1126|2012:1115|2013:1104|2014:1025|2015:1014|2016:1002|2017:0921|2018:0911|2019:0831|2020:0820|2021:0809|2022:0730|2023:0719|2024:0707|2025:0626|2026:0616|2027:0606|2028:0525|2029:0514|2030:0503|2031:0423|2032:0411|2033:0401|2034:0321|2035:0311|2036:0228|2037:0216|2038:0205|2039:0126|2040:0115|2041:0104,1224|2042:1214|2043:1203|2044:1121|2045:1110|2046:1031|2047:1020|2048:1009|2049:0928|2050:0917|2051:0906|2052:0826|2053:0815|2054:0805|2055:0725|2056:0714|2057:0703|2058:0622|2059:0611|2060:0531","estimated":"y|2010:1207|2011:1126|2012:1115|2013:1104|2014:1025|2015:1014|2016:1002|2017:0921|2018:0911|2019:0831|2020:0820|2021:0809|2022:0730|2023:0719|2024:0707|2025:0626|2026:0616|2027:0606|2028:0525|2029:0514|2030:0503|2031:0423|2032:0411|2033:0401|2034:0321|2035:0311|2036:0228|2037:0216|2038:0205|2039:0126|2040:0115|2041:0104,1224|2042:1214|2043:1203|2044:1121|2045:1110|2046:1031|2047:1020|2048:1009|2049:0928|2050:0917|2051:0906|2052:0826|2053:0815|2054:0805|2055:0725|2056:0714|2057:0703|2058:0622|2059:0611|2060:0531"},{"id":"eid_al_adha","name":"Eid al-Adha","en":"Eid al-Adha","category":"public","dates":"y|2010:1116-1117|2011:1106-1107|2012:1026-1027|2013:1015-1016|2014:1004-1005|2015:0923-0924|2016:0911-0912|2017:0901-0902|2018:0821-0822|2019:0811-0812|2020:0731-0801|2021:0720-0721|2022:0709-0710|2023:0628-0629|2024:0616-0617|2025:0606-0607|2026:0527-0528|2027:0516-0517|2028:0505-0506|2029:0424-0425|2030:0413-0414|2031:0402-0403|2032:0322-0323|2033:0311-0312|2034:0301-0302|2035:0218-0219|2036:0207-0208|2037:0126-0127|2038:0116-0117|2039:0105-0106,1226-1227|2040:1214-1215|2041:1204-1205|2042:1123-1124|2043:1112-1113|2044:1031-1101|2045:1021-1022|2046:1010-1011|2047:0930-1001|2048:0919-0920|2049:0908-0909|2050:0828-0829|2051:0817-0818|2052:0805-0806|2053:0726-0727|2054:0715-0716|2055:0705-0706|2056:0623-0624|2057:0612-0613|2058:0601-0602|2059:0522-0523|2060:0510-0511","estimated":"y|2010:1116-1117|2011:1106-1107|2012:1026-1027|2013:1015-1016|2014:1004-1005|2015:0923-0924|2016:0911-0912|2017:0901-0902|2018:0821-0822|2019:0811-0812|2020:0731-0801|2021:0720-0721|2022:0709-0710|2023:0628-0629|2024:0616-0617|2025:0606-0607|2026:0527-0528|2027:0516-0517|2028:0505-0506|2029:0424-0425|2030:0413-0414|2031:0402-0403|2032:0322-0323|2033:0311-0312|2034:0301-0302|2035:0218-0219|2036:0207-0208|2037:0126-0127|2038:0116-0117|2039:0105-0106,1226-1227|2040:1214-1215|2041:1204-1205|2042:1123-1124|2043:1112-1113|2044:1031-1101|2045:1021-1022|2046:1010-1011|2047:0930-1001|2048:0919-0920|2049:0908-0909|2050:0828-0829|2051:0817-0818|2052:0805-0806|2053:0726-0727|2054:0715-0716|2055:0705-0706|2056:0623-0624|2057:0612-0613|2058:0601-0602|2059:0522-0523|2060:0510-0511"},{"id":"cheikh_al_maarouf_day","name":"Cheikh al Maarouf Day","en":"Cheikh al Maarouf Day","category":"public","dates":"d|0318:2010-2060"},{"id":"labour_day","name":"Labour Day","en":"Labour Day","category":"public","dates":"d|0501:2010-2060"},{"id":"national_day","name":"National Day","en":"National Day","category":"public","dates":"d|0706:2010-2060"},{"id":"maore_day","name":"Maore Day","en":"Maore Day","category":"public","dates":"d|1112:2010-2060"},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"}]} diff --git a/app/Support/Holidays/data/KN.json b/app/Support/Holidays/data/KN.json new file mode 100644 index 000000000..c89210ea9 --- /dev/null +++ b/app/Support/Holidays/data/KN.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"KN","language":null,"years":[2010,2060],"subdivisions":{},"holidays":[{"id":"carnival_day","name":"Carnival Day","en":"Carnival Day","category":"public","dates":"d|0101:2010-2060|0103:2012,2017,2023,2034,2040,2045,2051"},{"id":"carnival_day_last_lap","name":"Carnival Day - Last Lap","en":"Carnival Day - Last Lap","category":"public","dates":"d|0102:2010-2060|0103:2011,2022,2028,2033,2039,2050,2056"},{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_monday","name":"Easter Monday","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"labour_day","name":"Labour Day","en":"Labour Day","category":"public","dates":"d|0501:2017,2023,2028,2034,2045,2051,2056|0502:2011,2016,2022,2033,2039,2044,2050|0503:2010,2021,2027,2032,2038,2049,2055,2060|0504:2015,2020,2026,2037,2043,2048,2054|0505:2014,2025,2031,2036,2042,2053,2059|0506:2013,2019,2024,2030,2041,2047,2052,2058|0507:2012,2018,2029,2035,2040,2046,2057"},{"id":"whit_monday","name":"Whit Monday","en":"Whit Monday","category":"public","dates":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038"},{"id":"emancipation_day","name":"Emancipation Day","en":"Emancipation Day","category":"public","dates":"d|0801:2011,2016,2022,2033,2039,2044,2050|0802:2010,2021,2027,2032,2038,2049,2055,2060|0803:2015,2020,2026,2037,2043,2048,2054|0804:2014,2025,2031,2036,2042,2053,2059|0805:2013,2019,2024,2030,2041,2047,2052,2058|0806:2012,2018,2029,2035,2040,2046,2057|0807:2017,2023,2028,2034,2045,2051,2056"},{"id":"culturama_day_last_lap","name":"Culturama Day - Last Lap","en":"Culturama Day - Last Lap","category":"public","dates":"d|0802:2011,2016,2022,2033,2039,2044,2050|0803:2010,2021,2027,2032,2038,2049,2055,2060|0804:2015,2020,2026,2037,2043,2048,2054|0805:2014,2025,2031,2036,2042,2053,2059|0806:2013,2019,2024,2030,2041,2047,2052,2058|0807:2012,2018,2029,2035,2040,2046,2057|0808:2017,2023,2028,2034,2045,2051,2056"},{"id":"national_heroes_day","name":"National Heroes Day","en":"National Heroes Day","category":"public","dates":"d|0916:2010-2060|0917:2012,2018,2029,2035,2040,2046,2057"},{"id":"independence_day","name":"Independence Day","en":"Independence Day","category":"public","dates":"d|0919:2010-2060|0920:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060|1227:2011,2016,2022,2033,2039,2044,2050"},{"id":"boxing_day","name":"Boxing Day","en":"Boxing Day","category":"public","dates":"d|1226:2010-2060|1227:2010,2021,2027,2032,2038,2049,2055,2060"}]} diff --git a/app/Support/Holidays/data/KP.json b/app/Support/Holidays/data/KP.json new file mode 100644 index 000000000..41b4d216d --- /dev/null +++ b/app/Support/Holidays/data/KP.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"KP","language":"ko_KP","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"양력설","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"korean_new_year","name":"설명절","en":"Korean New Year","category":"public","dates":"d|0122:2023,2042|0123:2012,2031,2050|0124:2039,2058|0125:2020|0126:2047|0127:2028|0128:2017,2036,2055|0129:2025|0130:2044|0131:2014,2033|0201:2022,2041,2052|0202:2049,2060|0203:2011,2030|0204:2038,2057|0205:2019|0206:2046|0207:2027|0208:2016,2035,2054|0210:2013,2024,2043|0211:2032,2051|0212:2021,2040,2059|0213:2029|0214:2010,2048|0215:2037,2056|0216:2018|0217:2026,2045|0219:2015,2034,2053"},{"id":"daeboreum","name":"대보름","en":"Daeboreum","category":"public","dates":"d|0205:2023,2042|0206:2012,2031,2050|0207:2039,2058|0208:2020|0209:2047|0210:2028|0211:2017,2036,2055|0212:2025|0213:2044|0214:2014,2033|0215:2022,2041,2052|0216:2049,2060|0217:2011,2030|0218:2038,2057|0219:2019|0220:2046|0221:2027|0222:2016,2035,2054|0224:2013,2024,2043|0225:2032,2051|0226:2021,2040,2059|0227:2029|0228:2010,2048|0229:2056|0301:2037|0302:2018|0303:2026,2045|0305:2015,2034,2053"},{"id":"founding_day_of_the_korean_people_s_army","name":"조선인민군창건일","en":"Founding Day of the Korean People's Army","category":"public","dates":"d|0208:2018-2060"},{"id":"day_of_the_shining_star","name":"광명성절","en":"Day of the Shining Star","category":"public","dates":"d|0216:2012-2060|0217:2012-2060"},{"id":"international_women_s_day","name":"국제부녀절","en":"International Women's Day","category":"public","dates":"d|0308:2010-2060"},{"id":"cheongmyeong_festival","name":"청명","en":"Cheongmyeong Festival","category":"public","dates":"d|0404:2012,2016-2017,2020-2021,2024-2025,2028-2029,2032-2033,2036-2037,2040-2041,2044-2046,2048-2050,2052-2054,2056-2058,2060|0405:2013-2015,2018-2019,2022-2023,2026-2027,2030-2031,2034-2035,2038-2039,2042-2043,2047,2051,2055,2059"},{"id":"day_of_the_sun","name":"태양절","en":"Day of the Sun","category":"public","dates":"d|0415:2010-2060|0416:2010-2060"},{"id":"founding_day_of_the_korean_people_s_revolutionar","name":"조선인민혁명군 창건일","en":"Founding Day of the Korean People's Revolutionary Army","category":"public","dates":"d|0425:2018-2060"},{"id":"international_workers_day","name":"전세계근로자들의 국제적명절","en":"International Workers' Day","category":"public","dates":"d|0501:2010-2060"},{"id":"foundation_day_of_the_korean_children_s_union","name":"조선소년단 창립절","en":"Foundation Day of the Korean Children's Union","category":"public","dates":"d|0606:2010-2060"},{"id":"day_of_victory_in_the_great_fatherland_liberatio","name":"조국해방전쟁승리기념일","en":"Day of Victory in the Great Fatherland Liberation War","category":"public","dates":"d|0727:2010-2060"},{"id":"liberation_day","name":"조국해방절","en":"Liberation Day","category":"public","dates":"d|0815:2010-2060"},{"id":"day_of_songun","name":"선군절","en":"Day of Songun","category":"public","dates":"d|0825:2013-2060"},{"id":"youth_day","name":"청년절","en":"Youth Day","category":"public","dates":"d|0828:2010-2060"},{"id":"chuseok","name":"추석","en":"Chuseok","category":"public","dates":"d|0907:2052|0908:2014,2033|0909:2060|0910:2022,2041|0911:2049|0912:2011,2030|0913:2019,2038,2057|0915:2016,2027,2046|0916:2035,2054|0917:2024,2043|0919:2013,2032,2051|0921:2021,2040,2059|0922:2010,2029,2048|0924:2018,2037,2056|0925:2026,2045|0926:2053|0927:2015,2034|0928:2042|0929:2023|0930:2012,2050|1001:2020,2031|1002:2039,2058|1003:2028|1004:2017,2036,2047|1005:2044,2055|1006:2025"},{"id":"founding_day_of_the_dprk","name":"조선민주주의인민공화국창건일","en":"Founding Day of the DPRK","category":"public","dates":"d|0909:2010-2060"},{"id":"foundation_day_of_the_workers_party_of_korea","name":"조선로동당창건일","en":"Foundation Day of the Workers' Party of Korea","category":"public","dates":"d|1010:2010-2060"},{"id":"mother_s_day","name":"어머니날","en":"Mother's Day","category":"public","dates":"d|1116:2012-2060"},{"id":"socialist_constitution_day","name":"사회주의헌법절","en":"Socialist Constitution Day","category":"public","dates":"d|1227:2010-2060"}]} diff --git a/app/Support/Holidays/data/KR.json b/app/Support/Holidays/data/KR.json new file mode 100644 index 000000000..030c44d6b --- /dev/null +++ b/app/Support/Holidays/data/KR.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"KR","language":"ko","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"신정연휴","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"the_day_preceding_korean_new_year","name":"설날 전날","en":"The day preceding Korean New Year","category":"public","dates":"d|0121:2023,2042|0122:2012,2031,2050|0123:2039,2058|0124:2020|0125:2047|0126:2028|0127:2017,2036,2055|0128:2025|0129:2044|0130:2014,2033|0131:2022,2041,2052|0201:2049,2060|0202:2011,2030|0203:2038,2057|0204:2019|0205:2046|0206:2027|0207:2016,2035,2054|0209:2013,2024,2043|0210:2032,2051|0211:2021,2040,2059|0212:2029|0213:2010,2048|0214:2037,2056|0215:2018|0216:2026,2045|0218:2015,2034,2053"},{"id":"korean_new_year","name":"설날","en":"Korean New Year","category":"public","dates":"d|0122:2023,2042|0123:2012,2031,2050|0124:2039,2058|0125:2020|0126:2047|0127:2028|0128:2017,2036,2055|0129:2025|0130:2044|0131:2014,2033|0201:2022,2041,2052|0202:2049,2060|0203:2011,2030|0204:2038,2057|0205:2019|0206:2046|0207:2027|0208:2016,2035,2054|0210:2013,2024,2043|0211:2032,2051|0212:2021,2040,2059|0213:2029|0214:2010,2048|0215:2037,2056|0216:2018|0217:2026,2045|0219:2015,2034,2053"},{"id":"the_second_day_of_korean_new_year","name":"설날 다음날","en":"The second day of Korean New Year","category":"public","dates":"d|0123:2023,2042|0124:2012,2031,2050|0125:2039,2058|0126:2020|0127:2047|0128:2028|0129:2017,2036,2055|0130:2025|0131:2044|0201:2014,2033|0202:2022,2041,2052|0203:2049,2060|0204:2011,2030|0205:2038,2057|0206:2019|0207:2046|0208:2027|0209:2016,2035,2054|0211:2013,2024,2043|0212:2032,2051|0213:2021,2040,2059|0214:2029|0215:2010,2048|0216:2037,2056|0217:2018|0218:2026,2045|0220:2015,2034,2053"},{"id":"alternative_holiday_for_korean_new_year","name":"설날 대체 휴일","en":"Alternative holiday for Korean New Year","category":"public","dates":"d|0124:2023|0125:2050|0126:2039|0127:2020|0128:2047|0130:2017,2036|0201:2044|0202:2033|0204:2060|0205:2030|0206:2057|0209:2027|0210:2016,2054|0212:2024|0213:2051|0214:2040|0217:2037|0221:2034"},{"id":"independence_movement_day","name":"삼일절","en":"Independence Movement Day","category":"public","dates":"d|0301:2010-2060"},{"id":"alternative_holiday_for_independence_movement_da","name":"삼일절 대체 휴일","en":"Alternative holiday for Independence Movement Day","category":"public","dates":"d|0302:2026,2037,2043,2048,2054|0303:2025,2031,2036,2042,2053,2059"},{"id":"presidential_election_day","name":"대통령 선거일","en":"Presidential Election Day","category":"public","dates":"d|0309:2022|0403:2030|0404:2035,2040|0405:2045|0406:2050|0407:2055,2060|0509:2017|0603:2025|1219:2012"},{"id":"national_assembly_election_day","name":"국회의원 선거일","en":"National Assembly Election Day","category":"public","dates":"d|0408:2048|0409:2036|0410:2024,2052|0411:2012,2040|0412:2028,2056|0413:2016,2044|0414:2032,2060|0415:2020"},{"id":"buddha_s_birthday","name":"석가탄신일","en":"Buddha's Birthday","category":"public","dates":"d|0430:2020,2039,2058|0502:2028,2047|0503:2017,2036|0504:2055|0505:2025,2044|0506:2014,2033,2052|0507:2041,2060|0508:2022|0509:2030,2049|0510:2011|0511:2038,2057|0512:2019|0513:2027,2046|0514:2016|0515:2024,2035,2054|0516:2032,2043|0517:2013,2051|0518:2040|0519:2021,2059|0520:2029,2048|0521:2010|0522:2018,2037,2056|0524:2026,2045|0525:2015,2034,2053|0526:2042|0527:2023|0528:2012,2031,2050"},{"id":"labor_day","name":"노동절","en":"Labor Day","category":"public","dates":"d|0501:2026-2060"},{"id":"alternative_holiday_for_buddha_s_birthday","name":"부처님오신날 대체 휴일","en":"Alternative holiday for Buddha's Birthday","category":"public","dates":"d|0502:2039|0506:2025,2036,2044|0510:2049|0514:2046|0517:2032|0518:2043|0521:2029|0525:2026|0526:2053|0529:2023|0530:2050"},{"id":"alternative_holiday_for_labor_day","name":"노동절 대체 휴일","en":"Alternative holiday for Labor Day","category":"public","dates":"d|0502:2033,2044,2050|0503:2027,2032,2038-2039,2049,2055,2060"},{"id":"children_s_day","name":"어린이날","en":"Children's Day","category":"public","dates":"d|0505:2010-2060"},{"id":"alternative_holiday_for_children_s_day","name":"어린이날 대체 휴일","en":"Alternative holiday for Children's Day","category":"public","dates":"d|0506:2019,2024-2025,2030,2041,2044,2047,2058|0507:2018,2029,2035,2040,2046,2052,2057"},{"id":"local_election_day","name":"지방선거일","en":"Local Election Day","category":"public","dates":"d|0601:2022,2050|0602:2010,2038|0603:2026,2054|0604:2014,2042|0612:2030,2058|0613:2018,2046|0614:2034"},{"id":"memorial_day","name":"현충일","en":"Memorial Day","category":"public","dates":"d|0606:2010-2060"},{"id":"constitution_day","name":"제헌절","en":"Constitution Day","category":"public","dates":"d|0717:2026-2060"},{"id":"alternative_holiday_for_constitution_day","name":"제헌절 대체 휴일","en":"Alternative holiday for Constitution Day","category":"public","dates":"d|0718:2033,2039,2044,2050|0719:2027,2032,2038,2049,2055,2060"},{"id":"liberation_day","name":"광복절","en":"Liberation Day","category":"public","dates":"d|0815:2010-2060"},{"id":"alternative_holiday_for_liberation_day","name":"광복절 대체 휴일","en":"Alternative holiday for Liberation Day","category":"public","dates":"d|0816:2021,2027,2032,2038,2049,2055,2060|0817:2026,2037,2043,2048,2054"},{"id":"the_day_preceding_chuseok","name":"추석 전날","en":"The day preceding Chuseok","category":"public","dates":"d|0906:2052|0907:2014,2033|0908:2060|0909:2022,2041|0910:2049|0911:2011,2030|0912:2019,2038,2057|0914:2016,2027,2046|0915:2035,2054|0916:2024,2043|0918:2013,2032,2051|0920:2021,2040,2059|0921:2010,2029,2048|0923:2018,2037,2056|0924:2026,2045|0925:2053|0926:2015,2034|0927:2042|0928:2023|0929:2012,2050|0930:2020,2031|1001:2039,2058|1002:2028|1003:2017,2036,2047|1004:2044,2055|1005:2025"},{"id":"chuseok","name":"추석","en":"Chuseok","category":"public","dates":"d|0907:2052|0908:2014,2033|0909:2060|0910:2022,2041|0911:2049|0912:2011,2030|0913:2019,2038,2057|0915:2016,2027,2046|0916:2035,2054|0917:2024,2043|0919:2013,2032,2051|0921:2021,2040,2059|0922:2010,2029,2048|0924:2018,2037,2056|0925:2026,2045|0926:2053|0927:2015,2034|0928:2042|0929:2023|0930:2012,2050|1001:2020,2031|1002:2039,2058|1003:2028|1004:2017,2036,2047|1005:2044,2055|1006:2025"},{"id":"the_second_day_of_chuseok","name":"추석 다음날","en":"The second day of Chuseok","category":"public","dates":"d|0908:2052|0909:2014,2033|0910:2060|0911:2022,2041|0912:2049|0913:2011,2030|0914:2019,2038,2057|0916:2016,2027,2046|0917:2035,2054|0918:2024,2043|0920:2013,2032,2051|0922:2021,2040,2059|0923:2010,2029,2048|0925:2018,2037,2056|0926:2026,2045|0927:2053|0928:2015,2034|0929:2042|0930:2023|1001:2012,2050|1002:2020,2031|1003:2039,2058|1004:2028|1005:2017,2036,2047|1006:2044,2055|1007:2025"},{"id":"alternative_holiday_for_chuseok","name":"추석 대체 휴일","en":"Alternative holiday for Chuseok","category":"public","dates":"d|0909:2052|0910:2014|0912:2022|0913:2049|0915:2038|0917:2046|0918:2035|0921:2032|0923:2059|0924:2029|0926:2018,2056|0927:2045|0929:2015|0930:2042|1004:2039,2058|1005:2028,2039|1006:2017,2036|1007:2036,2047|1008:2025"},{"id":"national_foundation_day","name":"개천절","en":"National Foundation Day","category":"public","dates":"d|1003:2010-2060"},{"id":"alternative_holiday_for_national_foundation_day","name":"개천절 대체 휴일","en":"Alternative holiday for National Foundation Day","category":"public","dates":"d|1004:2021,2027,2032,2038,2049,2060|1005:2026,2037,2043,2048,2054|1007:2055"},{"id":"hangul_day","name":"한글날","en":"Hangul Day","category":"public","dates":"d|1009:2013-2060"},{"id":"alternative_holiday_for_hangul_day","name":"한글날 대체 휴일","en":"Alternative holiday for Hangul Day","category":"public","dates":"d|1010:2022,2033,2039,2044,2050|1011:2021,2027,2032,2038,2049,2055,2060"},{"id":"christmas_day","name":"기독탄신일","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"},{"id":"alternative_holiday_for_christmas_day","name":"기독탄신일 대체 휴일","en":"Alternative holiday for Christmas Day","category":"public","dates":"d|1226:2033,2039,2044,2050|1227:2027,2032,2038,2049,2055,2060"}]} diff --git a/app/Support/Holidays/data/KW.json b/app/Support/Holidays/data/KW.json new file mode 100644 index 000000000..1d4b137bb --- /dev/null +++ b/app/Support/Holidays/data/KW.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"KW","language":"ar","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"isra_and_mi_raj","name":"ليلة المعراج","en":"Isra' and Mi'raj","category":"public","dates":"y|2010:0709|2011:0629|2012:0617|2013:0606|2014:0526|2015:0516|2016:0504|2017:0424|2018:0413|2019:0403|2020:0322|2021:0311|2022:0228|2023:0218|2024:0208|2025:0127|2026:0116|2027:0105,1225|2028:1214|2029:1203|2030:1123|2031:1112|2032:1101|2033:1021|2034:1010|2035:0929|2036:0918|2037:0907|2038:0828|2039:0817|2040:0805|2041:0725|2042:0715|2043:0704|2044:0623|2045:0613|2046:0602|2047:0522|2048:0510|2049:0429|2050:0419|2051:0409|2052:0328|2053:0318|2054:0307|2055:0224|2056:0213|2057:0201|2058:0122|2059:0112|2060:0101,1220","estimated":"y|2010:0709|2011:0629|2012:0617|2013:0606|2014:0526|2015:0516|2016:0504|2017:0424|2018:0413|2019:0403|2020:0322|2021:0311|2022:0228|2023:0218|2024:0208|2025:0127|2026:0116|2027:0105,1225|2028:1214|2029:1203|2030:1123|2031:1112|2032:1101|2033:1021|2034:1010|2035:0929|2036:0918|2037:0907|2038:0828|2039:0817|2040:0805|2041:0725|2042:0715|2043:0704|2044:0623|2045:0613|2046:0602|2047:0522|2048:0510|2049:0429|2050:0419|2051:0409|2052:0328|2053:0318|2054:0307|2055:0224|2056:0213|2057:0201|2058:0122|2059:0112|2060:0101,1220"},{"id":"new_year_s_day","name":"رأس السنة الميلادية","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"eid_al_fitr","name":"عيد الفطر","en":"Eid al-Fitr","category":"public","dates":"y|2010:0910|2011:0830|2012:0819|2013:0808|2014:0728|2015:0717|2016:0706|2017:0625|2018:0615|2019:0604|2020:0524|2021:0513|2022:0502|2023:0421|2024:0410|2025:0330|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304","estimated":"y|2010:0910|2011:0830|2012:0819|2013:0808|2014:0728|2015:0717|2016:0706|2017:0625|2018:0615|2019:0604|2020:0524|2021:0513|2022:0502|2023:0421|2024:0410|2025:0330|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304"},{"id":"eid_al_fitr_holiday","name":"عطلة عيد الفطر","en":"Eid al-Fitr Holiday","category":"public","dates":"y|2010:0911-0912|2011:0831-0901|2012:0820-0821|2013:0809-0810|2014:0729-0730|2015:0718-0719|2016:0707-0708|2017:0626-0627|2018:0616-0617|2019:0605-0606|2020:0525-0526|2021:0514-0515|2022:0503-0504|2023:0422-0423|2024:0411-0412|2025:0331-0401|2026:0321-0322|2027:0310-0311|2028:0227-0228|2029:0215-0216|2030:0205-0206|2031:0125-0126|2032:0115-0116|2033:0103-0104,1224-1225|2034:1213-1214|2035:1202-1203|2036:1120-1121|2037:1109-1110|2038:1030-1031|2039:1020-1021|2040:1008-1009|2041:0927-0928|2042:0916-0917|2043:0905-0906|2044:0825-0826|2045:0815-0816|2046:0804-0805|2047:0725-0726|2048:0713-0714|2049:0702-0703|2050:0621-0622|2051:0611-0612|2052:0530-0531|2053:0520-0521|2054:0510-0511|2055:0429-0430|2056:0417-0418|2057:0406-0407|2058:0326-0327|2059:0316-0317|2060:0305-0306","estimated":"y|2010:0911-0912|2011:0831-0901|2012:0820-0821|2013:0809-0810|2014:0729-0730|2015:0718-0719|2016:0707-0708|2017:0626-0627|2018:0616-0617|2019:0605-0606|2020:0525-0526|2021:0514-0515|2022:0503-0504|2023:0422-0423|2024:0411-0412|2025:0331-0401|2026:0321-0322|2027:0310-0311|2028:0227-0228|2029:0215-0216|2030:0205-0206|2031:0125-0126|2032:0115-0116|2033:0103-0104,1224-1225|2034:1213-1214|2035:1202-1203|2036:1120-1121|2037:1109-1110|2038:1030-1031|2039:1020-1021|2040:1008-1009|2041:0927-0928|2042:0916-0917|2043:0905-0906|2044:0825-0826|2045:0815-0816|2046:0804-0805|2047:0725-0726|2048:0713-0714|2049:0702-0703|2050:0621-0622|2051:0611-0612|2052:0530-0531|2053:0520-0521|2054:0510-0511|2055:0429-0430|2056:0417-0418|2057:0406-0407|2058:0326-0327|2059:0316-0317|2060:0305-0306"},{"id":"prophet_s_birthday","name":"يوم التحرير","en":"Prophet's Birthday","category":"public","dates":"y|2010:0226|2011:0215|2012:0204|2013:0124|2014:0113|2015:0103,1223|2016:1211|2017:1130|2018:1120|2019:1109|2020:1029|2021:1018|2022:1008|2023:0927|2024:0915|2025:0904|2026:0825|2027:0814|2028:0803|2029:0724|2030:0713|2031:0702|2032:0620|2033:0609|2034:0530|2035:0520|2036:0508|2037:0428|2038:0417|2039:0406|2040:0325|2041:0315|2042:0304|2043:0222|2044:0211|2045:0130|2046:0119|2047:0108,1229|2048:1218|2049:1207|2050:1126|2051:1116|2052:1104|2053:1024|2054:1013|2055:1003|2056:0922|2057:0911|2058:0831|2059:0820|2060:0808","estimated":"y|2010:0226|2011:0215|2012:0204|2013:0124|2014:0113|2015:0103,1223|2016:1211|2017:1130|2018:1120|2019:1109|2020:1029|2021:1018|2022:1008|2023:0927|2024:0915|2025:0904|2026:0825|2027:0814|2028:0803|2029:0724|2030:0713|2031:0702|2032:0620|2033:0609|2034:0530|2035:0520|2036:0508|2037:0428|2038:0417|2039:0406|2040:0325|2041:0315|2042:0304|2043:0222|2044:0211|2045:0130|2046:0119|2047:0108,1229|2048:1218|2049:1207|2050:1126|2051:1116|2052:1104|2053:1024|2054:1013|2055:1003|2056:0922|2057:0911|2058:0831|2059:0820|2060:0808"},{"id":"day_of_arafah","name":"يوم عرفة","en":"Day of Arafah","category":"public","dates":"y|2010:1115|2011:1105|2012:1025|2013:1014|2014:1003|2015:0922|2016:0910|2017:0831|2018:0820|2019:0810|2020:0730|2021:0719|2022:0708|2023:0627|2024:0615|2025:0605|2026:0526|2027:0515|2028:0504|2029:0423|2030:0412|2031:0401|2032:0321|2033:0310|2034:0228|2035:0217|2036:0206|2037:0125|2038:0115|2039:0104,1225|2040:1213|2041:1203|2042:1122|2043:1111|2044:1030|2045:1020|2046:1009|2047:0929|2048:0918|2049:0907|2050:0827|2051:0816|2052:0804|2053:0725|2054:0714|2055:0704|2056:0622|2057:0611|2058:0531|2059:0521|2060:0509","estimated":"y|2010:1115|2011:1105|2012:1025|2013:1014|2014:1003|2015:0922|2016:0910|2017:0831|2018:0820|2019:0810|2020:0730|2021:0719|2022:0708|2023:0627|2024:0615|2025:0605|2026:0526|2027:0515|2028:0504|2029:0423|2030:0412|2031:0401|2032:0321|2033:0310|2034:0228|2035:0217|2036:0206|2037:0125|2038:0115|2039:0104,1225|2040:1213|2041:1203|2042:1122|2043:1111|2044:1030|2045:1020|2046:1009|2047:0929|2048:0918|2049:0907|2050:0827|2051:0816|2052:0804|2053:0725|2054:0714|2055:0704|2056:0622|2057:0611|2058:0531|2059:0521|2060:0509"},{"id":"islamic_new_year","name":"رأس السنة الهجرية","en":"Islamic New Year","category":"public","dates":"y|2010:1207|2011:1126|2012:1115|2013:1104|2014:1025|2015:1014|2016:1002|2017:0921|2018:0911|2019:0831|2020:0820|2021:0809|2022:0730|2023:0719|2024:0707|2025:0626|2026:0616|2027:0606|2028:0525|2029:0514|2030:0503|2031:0423|2032:0411|2033:0401|2034:0321|2035:0311|2036:0228|2037:0216|2038:0205|2039:0126|2040:0115|2041:0104,1224|2042:1214|2043:1203|2044:1121|2045:1110|2046:1031|2047:1020|2048:1009|2049:0928|2050:0917|2051:0906|2052:0826|2053:0815|2054:0805|2055:0725|2056:0714|2057:0703|2058:0622|2059:0611|2060:0531","estimated":"y|2010:1207|2011:1126|2012:1115|2013:1104|2014:1025|2015:1014|2016:1002|2017:0921|2018:0911|2019:0831|2020:0820|2021:0809|2022:0730|2023:0719|2024:0707|2025:0626|2026:0616|2027:0606|2028:0525|2029:0514|2030:0503|2031:0423|2032:0411|2033:0401|2034:0321|2035:0311|2036:0228|2037:0216|2038:0205|2039:0126|2040:0115|2041:0104,1224|2042:1214|2043:1203|2044:1121|2045:1110|2046:1031|2047:1020|2048:1009|2049:0928|2050:0917|2051:0906|2052:0826|2053:0815|2054:0805|2055:0725|2056:0714|2057:0703|2058:0622|2059:0611|2060:0531"},{"id":"eid_al_adha","name":"عيد الأضحى","en":"Eid al-Adha","category":"public","dates":"y|2010:1116|2011:1106|2012:1026|2013:1015|2014:1004|2015:0923|2016:0911|2017:0901|2018:0821|2019:0811|2020:0731|2021:0720|2022:0709|2023:0628|2024:0616|2025:0606|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510","estimated":"y|2010:1116|2011:1106|2012:1026|2013:1015|2014:1004|2015:0923|2016:0911|2017:0901|2018:0821|2019:0811|2020:0731|2021:0720|2022:0709|2023:0628|2024:0616|2025:0606|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510"},{"id":"eid_al_adha_holiday","name":"عطلة عيد الأضحى","en":"Eid al-Adha Holiday","category":"public","dates":"y|2010:1117-1118|2011:1107-1108|2012:1027-1028|2013:1016-1017|2014:1005-1006|2015:0924-0925|2016:0912-0913|2017:0902-0903|2018:0822-0823|2019:0812-0813|2020:0801-0802|2021:0721-0722|2022:0710-0711|2023:0629-0630|2024:0617-0618|2025:0607-0608|2026:0528-0529|2027:0517-0518|2028:0506-0507|2029:0425-0426|2030:0414-0415|2031:0403-0404|2032:0323-0324|2033:0312-0313|2034:0302-0303|2035:0219-0220|2036:0208-0209|2037:0127-0128|2038:0117-0118|2039:0106-0107,1227-1228|2040:1215-1216|2041:1205-1206|2042:1124-1125|2043:1113-1114|2044:1101-1102|2045:1022-1023|2046:1011-1012|2047:1001-1002|2048:0920-0921|2049:0909-0910|2050:0829-0830|2051:0818-0819|2052:0806-0807|2053:0727-0728|2054:0716-0717|2055:0706-0707|2056:0624-0625|2057:0613-0614|2058:0602-0603|2059:0523-0524|2060:0511-0512","estimated":"y|2010:1117-1118|2011:1107-1108|2012:1027-1028|2013:1016-1017|2014:1005-1006|2015:0924-0925|2016:0912-0913|2017:0902-0903|2018:0822-0823|2019:0812-0813|2020:0801-0802|2021:0721-0722|2022:0710-0711|2023:0629-0630|2024:0617-0618|2025:0607-0608|2026:0528-0529|2027:0517-0518|2028:0506-0507|2029:0425-0426|2030:0414-0415|2031:0403-0404|2032:0323-0324|2033:0312-0313|2034:0302-0303|2035:0219-0220|2036:0208-0209|2037:0127-0128|2038:0117-0118|2039:0106-0107,1227-1228|2040:1215-1216|2041:1205-1206|2042:1124-1125|2043:1113-1114|2044:1101-1102|2045:1022-1023|2046:1011-1012|2047:1001-1002|2048:0920-0921|2049:0909-0910|2050:0829-0830|2051:0818-0819|2052:0806-0807|2053:0727-0728|2054:0716-0717|2055:0706-0707|2056:0624-0625|2057:0613-0614|2058:0602-0603|2059:0523-0524|2060:0511-0512"},{"id":"national_day","name":"اليوم الوطني","en":"National Day","category":"public","dates":"d|0225:2010-2060"},{"id":"liberation_day","name":"عيد المولد النبوي (تقديري)","en":"Liberation Day","category":"public","dates":"d|0226:2010-2060"}]} diff --git a/app/Support/Holidays/data/KY.json b/app/Support/Holidays/data/KY.json new file mode 100644 index 000000000..d353d295a --- /dev/null +++ b/app/Support/Holidays/data/KY.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"KY","language":"en_GB","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051|0103:2011,2022,2028,2033,2039,2050,2056"},{"id":"national_heroes_day","name":"National Heroes Day","en":"National Heroes Day","category":"public","dates":"d|0122:2018,2024,2029,2035,2046,2052,2057|0123:2012,2017,2023,2034,2040,2045,2051|0124:2011,2022,2028,2033,2039,2050,2056|0125:2010,2016,2021,2027,2038,2044,2049,2055|0126:2015,2026,2032,2037,2043,2054,2060|0127:2014,2020,2025,2031,2042,2048,2053,2059|0128:2013,2019,2030,2036,2041,2047,2058"},{"id":"ash_wednesday","name":"Ash Wednesday","en":"Ash Wednesday","category":"public","dates":"d|0207:2035,2046|0210:2016,2027|0211:2032,2043,2054|0212:2059|0213:2013|0214:2018,2024,2029|0215:2040,2051|0216:2056|0217:2010,2021|0218:2015,2026,2037|0219:2042,2048,2053|0222:2012,2023,2034,2045|0223:2039,2050|0226:2020,2031|0227:2036,2047,2058|0301:2017,2028|0302:2022,2033,2044|0303:2049,2055,2060|0305:2014,2025|0306:2019,2030,2041,2052|0307:2057|0309:2011|0310:2038"},{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_monday","name":"Easter Monday","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"emancipation_day","name":"Emancipation Day","en":"Emancipation Day","category":"public","dates":"d|0501:2028,2034,2045,2051,2056|0502:2033,2039,2044,2050|0503:2027,2032,2038,2049,2055,2060|0504:2026,2037,2043,2048,2054|0505:2025,2031,2036,2042,2053,2059|0506:2024,2030,2041,2047,2052,2058|0507:2029,2035,2040,2046,2057"},{"id":"discovery_day","name":"Discovery Day","en":"Discovery Day","category":"public","dates":"d|0515:2017,2023,2028,2034,2045,2051,2056|0516:2011,2016,2022,2033,2039,2044,2050|0517:2010,2021,2027,2032,2038,2049,2055,2060|0518:2015,2020,2026,2037,2043,2048,2054|0519:2014,2025,2031,2036,2042,2053,2059|0520:2013,2019,2024,2030,2041,2047,2052,2058|0521:2012,2018,2029,2035,2040,2046,2057"},{"id":"king_s_birthday","name":"King's Birthday","en":"King's Birthday","category":"public","dates":"d|0617:2024,2030,2041,2047,2052,2058|0618:2029,2035,2040,2046,2057|0619:2023,2028,2034,2045,2051,2056|0620:2033,2039,2044,2050|0621:2027,2032,2038,2049,2055,2060|0622:2026,2037,2043,2048,2054|0623:2025,2031,2036,2042,2053,2059"},{"id":"constitution_day","name":"Constitution Day","en":"Constitution Day","category":"public","dates":"d|0701:2013,2019,2024,2030,2041,2047,2052,2058|0702:2012,2018,2029,2035,2040,2046,2057|0703:2017,2023,2028,2034,2045,2051,2056|0704:2011,2016,2022,2033,2039,2044,2050|0705:2010,2021,2027,2032,2038,2049,2055,2060|0706:2015,2020,2026,2037,2043,2048,2054|0707:2014,2025,2031,2036,2042,2053,2059"},{"id":"remembrance_day","name":"Remembrance Day","en":"Remembrance Day","category":"public","dates":"d|1108:2010,2021,2027,2032,2038,2049,2055,2060|1109:2015,2020,2026,2037,2043,2048,2054|1110:2014,2025,2031,2036,2042,2053,2059|1111:2013,2019,2024,2030,2041,2047,2052,2058|1112:2012,2018,2029,2035,2040,2046,2057|1113:2017,2023,2028,2034,2045,2051,2056|1114:2011,2016,2022,2033,2039,2044,2050"},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060|1227:2010-2011,2016,2021-2022,2027,2032-2033,2038-2039,2044,2049-2050,2055,2060"},{"id":"boxing_day","name":"Boxing Day","en":"Boxing Day","category":"public","dates":"d|1226:2010-2060|1228:2010,2015,2020-2021,2026-2027,2032,2037-2038,2043,2048-2049,2054-2055,2060"}]} diff --git a/app/Support/Holidays/data/KZ.json b/app/Support/Holidays/data/KZ.json new file mode 100644 index 000000000..d08ded710 --- /dev/null +++ b/app/Support/Holidays/data/KZ.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"KZ","language":"kk","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Жаңа жыл","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2010-2060|0103:2011-2012,2017,2022-2023,2028,2033-2034,2039-2040,2045,2050-2051,2056|0104:2010-2011,2016,2021-2022,2027-2028,2033,2038-2039,2044,2049-2050,2055-2056"},{"id":"eid_al_adha","name":"Құрбан айт","en":"Eid al-Adha","category":"public","dates":"y|2010:1116|2011:1106|2012:1026|2013:1015|2014:1004|2015:0924|2016:0912|2017:0901|2018:0821|2019:0811|2020:0731|2021:0720|2022:0709|2023:0628|2024:0616|2025:0606|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510","estimated":"y|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510"},{"id":"orthodox_christmas","name":"Православиелік Рождество","en":"Orthodox Christmas","category":"public","dates":"d|0107:2010-2060"},{"id":"international_women_s_day","name":"Халықаралық әйелдер күні","en":"International Women's Day","category":"public","dates":"d|0308:2010-2060|0309:2015,2020,2026,2037,2043,2048,2054|0310:2014,2025,2031,2036,2042,2053,2059"},{"id":"constitution_day","name":"Қазақстан Республикасының Конституциясы күні","en":"Constitution Day","category":"public","dates":"d|0315:2027-2060|0316:2037,2043,2048,2054|0317:2031,2036,2042,2053,2059|0830:2010-2025|0831:2015,2020|0901:2014,2025"},{"id":"nowruz_holiday","name":"Наурыз мейрамы","en":"Nowruz Holiday","category":"public","dates":"d|0321:2010-2060|0322:2010-2060|0323:2010-2060|0324:2010,2014-2015,2020-2021,2025-2027,2031-2032,2036-2038,2042-2043,2048-2049,2053-2055,2059-2060|0325:2013-2015,2019-2020,2024-2026,2030-2031,2036-2037,2041-2043,2047-2048,2052-2054,2058-2059"},{"id":"kazakhstan_s_people_solidarity_holiday","name":"Қазақстан халқының бірлігі мерекесі","en":"Kazakhstan's People Solidarity Holiday","category":"public","dates":"d|0501:2010-2060|0502:2011,2016,2022,2033,2039,2044,2050|0503:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"defender_of_the_fatherland_day","name":"Отан Қорғаушы күні","en":"Defender of the Fatherland Day","category":"public","dates":"d|0507:2013-2060|0508:2017,2023,2028,2034,2045,2051,2056|0510:2016,2022,2033,2039,2044,2050"},{"id":"victory_day","name":"Жеңіс күні","en":"Victory Day","category":"public","dates":"d|0509:2010-2060|0510:2010,2021,2027,2032,2038,2049,2055,2060|0511:2015,2026,2037,2043,2048,2054"},{"id":"capital_day","name":"Астана күні","en":"Capital Day","category":"public","dates":"d|0706:2010-2060|0707:2014,2025,2031,2036,2042,2053,2059|0708:2013,2019,2024,2030,2041,2047,2052,2058"},{"id":"republic_day","name":"Республика күні","en":"Republic Day","category":"public","dates":"d|1025:2022-2060|1026:2026,2037,2043,2048,2054|1027:2025,2031,2036,2042,2053,2059"},{"id":"independence_day","name":"Тəуелсіздік күні","en":"Independence Day","category":"public","dates":"d|1216:2010-2060|1217:2010-2021,2029,2035,2040,2046,2057|1218:2012,2017-2018,2023,2028,2034,2045,2051,2056|1219:2011,2016-2017"}]} diff --git a/app/Support/Holidays/data/LA.json b/app/Support/Holidays/data/LA.json new file mode 100644 index 000000000..3aa511ec4 --- /dev/null +++ b/app/Support/Holidays/data/LA.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"LA","language":"lo","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"ວັນປີໃໝ່ສາກົນ","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051|0103:2022,2028,2033,2039,2050,2056"},{"id":"international_women_s_rights_day","name":"ວັນແມ່ຍິງສາກົນ","en":"International Women's Rights Day","category":"public","dates":"d|0308:2010-2060|0309:2015,2020,2026,2037,2043,2048,2054|0310:2025,2031,2036,2042,2053,2059"},{"id":"lao_new_year_s_day","name":"ບຸນປີໃໝ່ລາວ","en":"Lao New Year's Day","category":"public","dates":"d|0413:2011-2012,2016-2017,2020,2024|0414:2010-2060|0415:2010-2060|0416:2010-2016,2018-2060|0417:2012-2013,2017-2020,2023-2024,2028-2030,2034-2035,2040-2041,2045-2047,2051-2052,2056-2058|0418:2016,2018,2022-2024,2028-2029,2033-2035,2039-2040,2044-2046,2050-2051,2056-2057"},{"id":"international_labor_day","name":"ວັນກຳມະກອນສາກົນ","en":"International Labor Day","category":"public","dates":"d|0501:2010-2060|0502:2016,2022,2033,2039,2044,2050|0503:2021,2027,2032,2038,2049,2055,2060"},{"id":"lao_national_day","name":"ວັນຊາດ","en":"Lao National Day","category":"public","dates":"d|1202:2010-2060|1203:2012,2018,2029,2035,2040,2046,2057|1204:2017,2023,2028,2034,2045,2051,2056"},{"id":"makha_bousa_festival_school","name":"ວັນບຸນມາຂະບູຊາ","en":"Makha Bousa Festival","category":"school","dates":"d|0128:2021,2040|0129:2029,2059|0130:2010,2048|0131:2018,2037|0201:2045,2056|0202:2026|0203:2015,2034,2053|0204:2042|0205:2023|0206:2031,2050|0207:2012|0208:2020,2039,2058|0210:2028,2047|0211:2017,2055|0212:2025,2036|0213:2044|0214:2014,2033,2052|0215:2041|0216:2022|0217:2030,2049,2060|0218:2011|0219:2019,2038,2057|0220:2046|0221:2027|0222:2016,2035,2054|0223:2043|0224:2024|0225:2013,2032,2051"},{"id":"visakha_bousa_festival_school","name":"ວັນບຸນວິສາຂະບູຊາ","en":"Visakha Bousa Festival","category":"school","dates":"d|0425:2040|0426:2021|0427:2029,2048,2059|0428:2010|0429:2018,2037,2056|0430:2045|0501:2026|0502:2015,2034,2053|0503:2042|0504:2023|0505:2012,2031,2050|0506:2020|0507:2039,2058|0508:2028|0509:2047|0510:2017,2036,2055|0511:2025,2044|0512:2052|0513:2014,2033|0514:2041|0515:2022,2060|0516:2030,2049|0517:2011|0518:2019,2038,2057|0519:2046|0520:2016,2027|0521:2035,2054|0522:2024,2043|0523:2032|0524:2013,2051"},{"id":"begin_of_buddhist_lent_school","name":"ວັນບຸນເຂົ້າພັນສາ","en":"Begin of Buddhist Lent","category":"school","dates":"d|0705:2020,2039|0706:2028,2058|0707:2047|0708:2017,2036|0709:2044,2055|0710:2025|0711:2014,2033,2052|0712:2041|0713:2022,2060|0714:2030,2049|0715:2011|0716:2019,2038,2057|0718:2027,2046|0719:2016,2054|0720:2024,2035|0721:2043|0722:2013,2032,2051|0723:2040|0724:2021|0725:2029,2048,2059|0726:2010|0727:2018,2037,2056|0728:2045|0729:2026|0730:2015,2034,2053|0731:2042|0801:2023|0802:2012,2031,2050"},{"id":"boun_haw_khao_padapdin_school","name":"ວັນບຸນຫໍ່ເຂົ້າປະດັບດິນ","en":"Boun Haw Khao Padapdin","category":"school","dates":"d|0818:2020,2039|0819:2028,2058|0820:2047|0821:2017,2036|0822:2044,2055|0823:2025|0824:2014,2033,2052|0825:2041|0826:2022,2060|0827:2030,2049|0828:2011|0829:2019,2038,2057|0831:2027,2046|0901:2016,2054|0902:2024,2035|0903:2043|0904:2013,2032,2051|0905:2040|0906:2021|0907:2029,2048,2059|0908:2010|0909:2018,2037,2056|0910:2045|0911:2026|0912:2015,2034,2053|0913:2042|0914:2023|0915:2012,2031,2050"},{"id":"boun_haw_khao_salark_school","name":"ວັນບຸນຫໍ່ເຂົ້າສະຫຼາກ","en":"Boun Haw Khao Salark","category":"school","dates":"d|0902:2020,2039|0903:2028,2058|0904:2047|0905:2017,2036|0906:2044,2055|0907:2025|0908:2014,2033,2052|0909:2041|0910:2022,2060|0911:2030,2049|0912:2011|0913:2019,2038,2057|0915:2027,2046|0916:2016,2054|0917:2024,2035|0918:2043|0919:2013,2032,2051|0920:2040|0921:2021|0922:2029,2048,2059|0923:2010|0924:2018,2037,2056|0925:2045|0926:2026|0927:2015,2034,2053|0928:2042|0929:2023|0930:2012,2031,2050"},{"id":"end_of_buddhist_lent_school","name":"ວັນບຸນອອກພັນສາ","en":"End of Buddhist Lent","category":"school","dates":"d|1002:2020,2039|1003:2028,2058|1004:2047|1005:2017,2036|1006:2044,2055|1007:2025|1008:2014,2033,2052|1009:2041|1010:2022,2060|1011:2030,2049|1012:2011|1013:2019,2038,2057|1015:2027,2046|1016:2016,2054|1017:2024,2035|1018:2043|1019:2013,2032,2051|1020:2040|1021:2021|1022:2029,2048,2059|1023:2010|1024:2018,2037,2056|1025:2045|1026:2026|1027:2015,2034,2053|1028:2042|1029:2023|1030:2012,2031,2050"},{"id":"vientiane_boat_racing_festival_school","name":"ວັນບຸນຊ່ວງເຮືອ ນະຄອນຫຼວງວຽງຈັນ","en":"Vientiane Boat Racing Festival","category":"school","dates":"d|1003:2020,2039|1004:2028,2058|1005:2047|1006:2017,2036|1007:2044,2055|1008:2025|1009:2014,2033,2052|1010:2041|1011:2022,2060|1012:2030,2049|1013:2011|1014:2019,2038,2057|1016:2027,2046|1017:2016,2054|1018:2024,2035|1019:2043|1020:2013,2032,2051|1021:2040|1022:2021|1023:2029,2048,2059|1024:2010|1025:2018,2037,2056|1026:2045|1027:2026|1028:2015,2034,2053|1029:2042|1030:2023|1031:2012,2031,2050"},{"id":"establishment_day_of_the_bol_bank","name":"ວັນສ້າງຕັ້ງທະນາຄານແຫ່ງ ສປປ ລາວ","en":"Establishment Day of the BOL","category":"bank","dates":"d|1007:2010-2060|1008:2012,2018,2029,2035,2040,2046,2057|1009:2017,2023,2028,2034,2045,2051,2056"},{"id":"national_teacher_day_school","name":"ວັນຄູແຫ່ງຊາດ","en":"National Teacher Day","category":"school","dates":"d|1007:2010-2060"},{"id":"boun_that_luang_festival_school","name":"ວັນບຸນທາດຫລວງ","en":"Boun That Luang Festival","category":"school","dates":"d|1031:2020,2039|1101:2028,2058|1102:2047|1103:2017,2036|1104:2044,2055|1105:2025|1106:2014,2033,2052|1107:2041|1108:2022,2060|1109:2030,2049|1110:2011|1111:2019,2038,2057|1113:2027,2046|1114:2016,2054|1115:2024,2035|1116:2043|1117:2013,2032,2051|1118:2040|1119:2021|1120:2029,2048,2059|1121:2010|1122:2018,2037,2056|1123:2045|1124:2026|1125:2015,2034,2053|1126:2042|1127:2023|1128:2012,2031,2050"},{"id":"lao_year_end_bank_holiday_bank","name":"ສາມວັນລັດຖະການສຸດທ້າຍຂອງທຸກໆປີ","en":"Lao Year-End Bank Holiday","category":"bank","dates":"d|1227:2012-2013,2017-2019,2023-2024,2028-2030,2034-2035,2040-2041,2045-2047,2051-2052,2056-2058|1228:2011-2012,2016-2018,2022-2023,2028-2029,2033-2035,2039-2040,2044-2046,2050-2051,2056-2057|1229:2010-2011,2014-2017,2020-2023,2025-2028,2031-2034,2036-2039,2042-2045,2048-2051,2053-2056,2059-2060|1230:2010-2011,2013-2016,2019-2022,2024-2027,2030-2033,2036-2039,2041-2044,2047-2050,2052-2055,2058-2060|1231:2010,2012-2015,2018-2021,2024-2027,2029-2032,2035-2038,2040-2043,2046-2049,2052-2055,2057-2060"}]} diff --git a/app/Support/Holidays/data/LB.json b/app/Support/Holidays/data/LB.json new file mode 100644 index 000000000..86a254503 --- /dev/null +++ b/app/Support/Holidays/data/LB.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"LB","language":"ar","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"رأس السنة الميلادية","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"ashura","name":"عاشوراء","en":"Ashura","category":"public","dates":"y|2010:1217|2011:1206|2012:1124|2013:1114|2014:1103|2015:1024|2016:1012|2017:1001|2018:0921|2019:0910|2020:0830|2021:0819|2022:0809|2023:0728|2024:0716|2025:0705|2026:0625|2027:0615|2028:0603|2029:0523|2030:0512|2031:0502|2032:0420|2033:0410|2034:0330|2035:0320|2036:0308|2037:0225|2038:0214|2039:0204|2040:0124|2041:0113|2042:0102,1223|2043:1212|2044:1130|2045:1119|2046:1109|2047:1029|2048:1018|2049:1007|2050:0926|2051:0915|2052:0904|2053:0824|2054:0814|2055:0803|2056:0723|2057:0712|2058:0701|2059:0620|2060:0609","estimated":"y|2025:0705|2026:0625|2027:0615|2028:0603|2029:0523|2030:0512|2031:0502|2032:0420|2033:0410|2034:0330|2035:0320|2036:0308|2037:0225|2038:0214|2039:0204|2040:0124|2041:0113|2042:0102,1223|2043:1212|2044:1130|2045:1119|2046:1109|2047:1029|2048:1018|2049:1007|2050:0926|2051:0915|2052:0904|2053:0824|2054:0814|2055:0803|2056:0723|2057:0712|2058:0701|2059:0620|2060:0609"},{"id":"eid_al_fitr","name":"عيد الفطر","en":"Eid al-Fitr","category":"public","dates":"y|2010:0910-0911|2011:0831-0901|2012:0819-0820|2013:0808-0809|2014:0729-0730|2015:0717-0718|2016:0707-0708|2017:0626-0627|2018:0615-0616|2019:0604-0605|2020:0524-0525|2021:0513-0514|2022:0502-0503|2023:0421-0422|2024:0410-0411|2025:0330-0331|2026:0320-0321|2027:0309-0310|2028:0226-0227|2029:0214-0215|2030:0204-0205|2031:0124-0125|2032:0114-0115|2033:0102-0103,1223-1224|2034:1212-1213|2035:1201-1202|2036:1119-1120|2037:1108-1109|2038:1029-1030|2039:1019-1020|2040:1007-1008|2041:0926-0927|2042:0915-0916|2043:0904-0905|2044:0824-0825|2045:0814-0815|2046:0803-0804|2047:0724-0725|2048:0712-0713|2049:0701-0702|2050:0620-0621|2051:0610-0611|2052:0529-0530|2053:0519-0520|2054:0509-0510|2055:0428-0429|2056:0416-0417|2057:0405-0406|2058:0325-0326|2059:0315-0316|2060:0304-0305","estimated":"y|2026:0320-0321|2027:0309-0310|2028:0226-0227|2029:0214-0215|2030:0204-0205|2031:0124-0125|2032:0114-0115|2033:0102-0103,1223-1224|2034:1212-1213|2035:1201-1202|2036:1119-1120|2037:1108-1109|2038:1029-1030|2039:1019-1020|2040:1007-1008|2041:0926-0927|2042:0915-0916|2043:0904-0905|2044:0824-0825|2045:0814-0815|2046:0803-0804|2047:0724-0725|2048:0712-0713|2049:0701-0702|2050:0620-0621|2051:0610-0611|2052:0529-0530|2053:0519-0520|2054:0509-0510|2055:0428-0429|2056:0416-0417|2057:0405-0406|2058:0325-0326|2059:0315-0316|2060:0304-0305"},{"id":"prophet_s_birthday","name":"ذكرى المولد النبوي الشريف","en":"Prophet's Birthday","category":"public","dates":"y|2010:0226|2011:0216|2012:0205|2013:0124|2014:0114|2015:0103,1224|2016:1212|2017:1201|2018:1120|2019:1109|2020:1029|2021:1018|2022:1008|2023:0927|2024:0915|2025:0904|2026:0825|2027:0814|2028:0803|2029:0724|2030:0713|2031:0702|2032:0620|2033:0609|2034:0530|2035:0520|2036:0508|2037:0428|2038:0417|2039:0406|2040:0325|2041:0315|2042:0304|2043:0222|2044:0211|2045:0130|2046:0119|2047:0108,1229|2048:1218|2049:1207|2050:1126|2051:1116|2052:1104|2053:1024|2054:1013|2055:1003|2056:0922|2057:0911|2058:0831|2059:0820|2060:0808","estimated":"y|2025:0904|2026:0825|2027:0814|2028:0803|2029:0724|2030:0713|2031:0702|2032:0620|2033:0609|2034:0530|2035:0520|2036:0508|2037:0428|2038:0417|2039:0406|2040:0325|2041:0315|2042:0304|2043:0222|2044:0211|2045:0130|2046:0119|2047:0108,1229|2048:1218|2049:1207|2050:1126|2051:1116|2052:1104|2053:1024|2054:1013|2055:1003|2056:0922|2057:0911|2058:0831|2059:0820|2060:0808"},{"id":"islamic_new_year_day","name":"عيد رأس السنة الهجرية","en":"Islamic New Year Day","category":"public","dates":"y|2010:1208|2011:1127|2012:1115|2013:1105|2014:1025|2015:1014|2016:1003|2017:0922|2018:0911|2019:0831|2020:0820|2021:0809|2022:0730|2023:0719|2024:0707|2025:0626|2026:0616|2027:0606|2028:0525|2029:0514|2030:0503|2031:0423|2032:0411|2033:0401|2034:0321|2035:0311|2036:0228|2037:0216|2038:0205|2039:0126|2040:0115|2041:0104,1224|2042:1214|2043:1203|2044:1121|2045:1110|2046:1031|2047:1020|2048:1009|2049:0928|2050:0917|2051:0906|2052:0826|2053:0815|2054:0805|2055:0725|2056:0714|2057:0703|2058:0622|2059:0611|2060:0531","estimated":"y|2026:0616|2027:0606|2028:0525|2029:0514|2030:0503|2031:0423|2032:0411|2033:0401|2034:0321|2035:0311|2036:0228|2037:0216|2038:0205|2039:0126|2040:0115|2041:0104,1224|2042:1214|2043:1203|2044:1121|2045:1110|2046:1031|2047:1020|2048:1009|2049:0928|2050:0917|2051:0906|2052:0826|2053:0815|2054:0805|2055:0725|2056:0714|2057:0703|2058:0622|2059:0611|2060:0531"},{"id":"eid_al_adha","name":"عيد الأضحى","en":"Eid al-Adha","category":"public","dates":"y|2010:1117-1118|2011:1107-1108|2012:1026-1027|2013:1015-1016|2014:1005-1006|2015:0924-0925|2016:0913-0914|2017:0902-0903|2018:0822-0823|2019:0812-0813|2020:0731-0801|2021:0720-0721|2022:0709-0710|2023:0628-0629|2024:0616-0617|2025:0606-0607|2026:0527-0528|2027:0516-0517|2028:0505-0506|2029:0424-0425|2030:0413-0414|2031:0402-0403|2032:0322-0323|2033:0311-0312|2034:0301-0302|2035:0218-0219|2036:0207-0208|2037:0126-0127|2038:0116-0117|2039:0105-0106,1226-1227|2040:1214-1215|2041:1204-1205|2042:1123-1124|2043:1112-1113|2044:1031-1101|2045:1021-1022|2046:1010-1011|2047:0930-1001|2048:0919-0920|2049:0908-0909|2050:0828-0829|2051:0817-0818|2052:0805-0806|2053:0726-0727|2054:0715-0716|2055:0705-0706|2056:0623-0624|2057:0612-0613|2058:0601-0602|2059:0522-0523|2060:0510-0511","estimated":"y|2026:0527-0528|2027:0516-0517|2028:0505-0506|2029:0424-0425|2030:0413-0414|2031:0402-0403|2032:0322-0323|2033:0311-0312|2034:0301-0302|2035:0218-0219|2036:0207-0208|2037:0126-0127|2038:0116-0117|2039:0105-0106,1226-1227|2040:1214-1215|2041:1204-1205|2042:1123-1124|2043:1112-1113|2044:1031-1101|2045:1021-1022|2046:1010-1011|2047:0930-1001|2048:0919-0920|2049:0908-0909|2050:0828-0829|2051:0817-0818|2052:0805-0806|2053:0726-0727|2054:0715-0716|2055:0705-0706|2056:0623-0624|2057:0612-0613|2058:0601-0602|2059:0522-0523|2060:0510-0511"},{"id":"armenian_orthodox_christmas_day","name":"عيد الميلاد عند الطوائف الارمنية الارثوذكسية","en":"Armenian Orthodox Christmas Day","category":"public","dates":"d|0106:2010-2060"},{"id":"saint_maron_s_day","name":"عيد مار مارون","en":"Saint Maron's Day","category":"public","dates":"d|0209:2010-2060"},{"id":"catholic_good_friday","name":"الجمعة العظيمة عند الطوائف الأرثوذكسية","en":"Catholic Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"feast_of_the_annunciation","name":"عيد بشارة السيدة مريم العذراء","en":"Feast of the Annunciation","category":"public","dates":"d|0325:2010-2060"},{"id":"orthodox_good_friday","name":"الجمعة العظيمة عند الطوائف الكاثوليكية","en":"Orthodox Good Friday","category":"public","dates":"d|0402:2010|0403:2037,2048|0406:2018,2029|0407:2034,2045,2056|0410:2015,2026|0411:2031,2042,2053|0412:2058|0413:2012|0414:2017,2023,2028|0415:2039,2050|0416:2055|0417:2020|0418:2014,2025,2036|0419:2041,2047,2052|0422:2011,2022,2033,2044|0423:2038,2049,2060|0426:2019,2030|0427:2035,2046,2057|0429:2016|0430:2021,2027,2032|0501:2043,2054|0502:2059|0503:2013,2024|0504:2040|0505:2051"},{"id":"orthodox_holy_saturday","name":"سبت النور للطائفة الأرثوذكسية","en":"Orthodox Holy Saturday","category":"public","dates":"d|0403:2010|0404:2037,2048|0408:2034,2045|0412:2031|0413:2058|0415:2017,2028|0417:2055|0419:2014,2025|0420:2041,2052|0423:2011|0424:2038"},{"id":"labor_day","name":"عيد العمل","en":"Labor Day","category":"public","dates":"d|0501:2010-2060|0502:2011,2016,2022,2033,2039,2044,2050"},{"id":"martyrs_day","name":"عيد الشهداء","en":"Martyrs' Day","category":"public","dates":"d|0501:2011,2016,2022,2033,2039,2044,2050|0502:2010,2021,2027,2032,2038,2049,2055,2060|0503:2015,2020,2026,2037,2043,2048,2054|0504:2014,2025,2031,2036,2042,2053,2059|0505:2013,2019,2024,2030,2041,2047,2052,2058|0506:2012,2018,2029,2035,2040,2046,2057|0507:2017,2023,2028,2034,2045,2051,2056"},{"id":"resistance_and_liberation_day","name":"عيد المقاومة والتحرير","en":"Resistance and Liberation Day","category":"public","dates":"d|0508:2011,2016,2022,2033,2039,2044,2050|0509:2010,2021,2027,2032,2038,2049,2055,2060|0510:2015,2020,2026,2037,2043,2048,2054|0511:2014,2025,2031,2036,2042,2053,2059|0512:2013,2019,2024,2030,2041,2047,2052,2058|0513:2012,2018,2029,2035,2040,2046,2057|0514:2017,2023,2028,2034,2045,2051,2056"},{"id":"assumption_day","name":"عيد انتقال العذراء","en":"Assumption Day","category":"public","dates":"d|0815:2010-2060"},{"id":"independence_day","name":"ذكرى الاستقلال","en":"Independence Day","category":"public","dates":"d|1122:2010-2060"},{"id":"christmas_day","name":"عيد الميلاد","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"},{"id":"rafik_hariri_memorial_day_bank","name":"يوم ذكرى رفيق الحريري","en":"Rafik Hariri Memorial Day","category":"bank","dates":"d|0214:2020-2060"},{"id":"rafik_hariri_memorial_day_government","name":"يوم ذكرى رفيق الحريري","en":"Rafik Hariri Memorial Day","category":"government","dates":"d|0214:2020-2060"},{"id":"catholic_easter_monday_bank","name":"اثنين الفصح عند الطوائف الكاثوليكية","en":"Catholic Easter Monday","category":"bank","dates":"d|0326:2035,2046|0329:2027,2032|0330:2043,2054|0331:2059|0401:2024|0402:2029,2040|0403:2051,2056|0405:2021|0406:2026,2037,2048|0407:2042,2053|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2025|0422:2030,2041,2052|0423:2057|0426:2038"},{"id":"orthodox_easter_monday_bank","name":"اثنين الفصح عند الطوائف الأرثوذكسية","en":"Orthodox Easter Monday","category":"bank","dates":"d|0406:2037,2048|0409:2029|0410:2034,2045,2056|0413:2026|0414:2031,2042,2053|0415:2058|0417:2023,2028|0418:2039,2050|0419:2055|0420:2020|0421:2025,2036|0422:2041,2047,2052|0425:2022,2033,2044|0426:2038,2049,2060|0429:2030|0430:2035,2046,2057|0503:2021,2027,2032|0504:2043,2054|0505:2059|0506:2024|0507:2040|0508:2051"},{"id":"anniversary_of_the_tragedy_of_beirut_port_explos_government","name":"ذكرى مأساة انفجار مرفأ بيروت","en":"Anniversary of the tragedy of Beirut port explosion","category":"government","dates":"d|0804:2021-2060"}]} diff --git a/app/Support/Holidays/data/LC.json b/app/Support/Holidays/data/LC.json new file mode 100644 index 000000000..8f3349b57 --- /dev/null +++ b/app/Support/Holidays/data/LC.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"LC","language":"en_LC","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0103:2012,2017,2023,2034,2040,2045,2051"},{"id":"new_year_s_holiday","name":"New Year's Holiday","en":"New Year's Holiday","category":"public","dates":"d|0102:2010-2060|0103:2011,2022,2028,2033,2039,2050,2056"},{"id":"independence_day","name":"Independence Day","en":"Independence Day","category":"public","dates":"d|0222:2010-2060|0223:2015,2026,2032,2037,2043,2054,2060"},{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_monday","name":"Easter Monday","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"labor_day","name":"Labour Day","en":"Labor Day","category":"public","dates":"d|0501:2010-2060|0502:2011,2016,2022,2033,2039,2044,2050"},{"id":"pentecost_monday","name":"Whit Monday","en":"Pentecost Monday","category":"public","dates":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038"},{"id":"corpus_christi","name":"Corpus Christi","en":"Corpus Christi","category":"public","dates":"d|0524:2035,2046|0526:2016|0527:2027,2032|0528:2043,2054|0529:2059|0530:2013,2024|0531:2018,2029,2040|0601:2051,2056|0603:2010,2021|0604:2015,2026,2037,2048|0605:2042,2053|0607:2012|0608:2023,2034,2045|0609:2039,2050|0611:2020|0612:2031,2036|0613:2047,2058|0615:2017,2028|0616:2022,2033,2044|0617:2049,2055,2060|0619:2014,2025|0620:2019,2030,2041,2052|0621:2057|0623:2011|0624:2038"},{"id":"emancipation_day","name":"Emancipation Day","en":"Emancipation Day","category":"public","dates":"d|0801:2010-2060|0802:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"thanksgiving_day","name":"Thanksgiving Day","en":"Thanksgiving Day","category":"public","dates":"d|1001:2012,2018,2029,2035,2040,2046,2057|1002:2017,2023,2028,2034,2045,2051,2056|1003:2011,2016,2022,2033,2039,2044,2050|1004:2010,2021,2027,2032,2038,2049,2055,2060|1005:2015,2020,2026,2037,2043,2048,2054|1006:2014,2025,2031,2036,2042,2053,2059|1007:2013,2019,2024,2030,2041,2047,2052,2058"},{"id":"national_day","name":"National Day","en":"National Day","category":"public","dates":"d|1213:2010-2060|1214:2015,2020,2026,2037,2043,2048,2054"},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060|1227:2011,2016,2022,2033,2039,2044,2050"},{"id":"boxing_day","name":"Boxing Day","en":"Boxing Day","category":"public","dates":"d|1226:2010-2060|1227:2010,2021,2027,2032,2038,2049,2055,2060"}]} diff --git a/app/Support/Holidays/data/LI.json b/app/Support/Holidays/data/LI.json new file mode 100644 index 000000000..0da4cbe9b --- /dev/null +++ b/app/Support/Holidays/data/LI.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"LI","language":"de","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Neujahr","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"epiphany","name":"Heilige Drei Könige","en":"Epiphany","category":"public","dates":"d|0106:2010-2060"},{"id":"candlemas","name":"Mariä Lichtmess","en":"Candlemas","category":"public","dates":"d|0202:2010-2060"},{"id":"saint_joseph_s_day","name":"Josefstag","en":"Saint Joseph's Day","category":"public","dates":"d|0319:2010-2060"},{"id":"easter_sunday","name":"Ostersonntag","en":"Easter Sunday","category":"public","dates":"d|0325:2035,2046|0327:2016|0328:2027,2032|0329:2043,2054|0330:2059|0331:2013,2024|0401:2018,2029,2040|0402:2051,2056|0404:2010,2021|0405:2015,2026,2037,2048|0406:2042,2053|0408:2012|0409:2023,2034,2045|0410:2039,2050|0412:2020|0413:2031,2036|0414:2047,2058|0416:2017,2028|0417:2022,2033,2044|0418:2049,2055,2060|0420:2014,2025|0421:2019,2030,2041,2052|0422:2057|0424:2011|0425:2038"},{"id":"easter_monday","name":"Ostermontag","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"labor_day","name":"Tag der Arbeit","en":"Labor Day","category":"public","dates":"d|0501:2010-2060"},{"id":"ascension_day","name":"Auffahrt","en":"Ascension Day","category":"public","dates":"d|0503:2035,2046|0505:2016|0506:2027,2032|0507:2043,2054|0508:2059|0509:2013,2024|0510:2018,2029,2040|0511:2051,2056|0513:2010,2021|0514:2015,2026,2037,2048|0515:2042,2053|0517:2012|0518:2023,2034,2045|0519:2039,2050|0521:2020|0522:2031,2036|0523:2047,2058|0525:2017,2028|0526:2022,2033,2044|0527:2049,2055,2060|0529:2014,2025|0530:2019,2030,2041,2052|0531:2057|0602:2011|0603:2038"},{"id":"pentecost","name":"Pfingstsonntag","en":"Pentecost","category":"public","dates":"d|0513:2035,2046|0515:2016|0516:2027,2032|0517:2043,2054|0518:2059|0519:2013,2024|0520:2018,2029,2040|0521:2051,2056|0523:2010,2021|0524:2015,2026,2037,2048|0525:2042,2053|0527:2012|0528:2023,2034,2045|0529:2039,2050|0531:2020|0601:2031,2036|0602:2047,2058|0604:2017,2028|0605:2022,2033,2044|0606:2049,2055,2060|0608:2014,2025|0609:2019,2030,2041,2052|0610:2057|0612:2011|0613:2038"},{"id":"pentecost_monday","name":"Pfingstmontag","en":"Pentecost Monday","category":"public","dates":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038"},{"id":"corpus_christi","name":"Fronleichnam","en":"Corpus Christi","category":"public","dates":"d|0524:2035,2046|0526:2016|0527:2027,2032|0528:2043,2054|0529:2059|0530:2013,2024|0531:2018,2029,2040|0601:2051,2056|0603:2010,2021|0604:2015,2026,2037,2048|0605:2042,2053|0607:2012|0608:2023,2034,2045|0609:2039,2050|0611:2020|0612:2031,2036|0613:2047,2058|0615:2017,2028|0616:2022,2033,2044|0617:2049,2055,2060|0619:2014,2025|0620:2019,2030,2041,2052|0621:2057|0623:2011|0624:2038"},{"id":"national_day","name":"Staatsfeiertag","en":"National Day","category":"public","dates":"d|0815:2010-2060"},{"id":"nativity_of_mary","name":"Mariä Geburt","en":"Nativity of Mary","category":"public","dates":"d|0908:2010-2060"},{"id":"all_saints_day","name":"Allerheiligen","en":"All Saints' Day","category":"public","dates":"d|1101:2010-2060"},{"id":"immaculate_conception","name":"Mariä Empfängnis","en":"Immaculate Conception","category":"public","dates":"d|1208:2010-2060"},{"id":"christmas_day","name":"Weihnachten","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"},{"id":"saint_stephen_s_day","name":"Stephanstag","en":"Saint Stephen's Day","category":"public","dates":"d|1226:2010-2060"},{"id":"saint_berchtold_s_day_bank","name":"Berchtoldstag","en":"Saint Berchtold's Day","category":"bank","dates":"d|0102:2010-2060"},{"id":"shrove_tuesday_bank","name":"Fasnachtsdienstag","en":"Shrove Tuesday","category":"bank","dates":"d|0206:2035,2046|0209:2016,2027|0210:2032,2043,2054|0211:2059|0212:2013|0213:2018,2024,2029|0214:2040,2051|0215:2056|0216:2010,2021|0217:2015,2026,2037|0218:2042,2048,2053|0221:2012,2023,2034,2045|0222:2039,2050|0225:2020,2031|0226:2036,2047,2058|0228:2017|0229:2028|0301:2022,2033,2044|0302:2049,2055,2060|0304:2014,2025|0305:2019,2030,2041,2052|0306:2057|0308:2011|0309:2038"},{"id":"good_friday_bank","name":"Karfreitag","en":"Good Friday","category":"bank","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"christmas_eve_bank","name":"Heiligabend","en":"Christmas Eve","category":"bank","dates":"d|1224:2010-2060"},{"id":"new_year_s_eve_bank","name":"Silvester","en":"New Year's Eve","category":"bank","dates":"d|1231:2010-2060"}]} diff --git a/app/Support/Holidays/data/LICENSE-python-holidays b/app/Support/Holidays/data/LICENSE-python-holidays new file mode 100644 index 000000000..e00f477cc --- /dev/null +++ b/app/Support/Holidays/data/LICENSE-python-holidays @@ -0,0 +1,23 @@ +Copyright (c) Vacanza Team and individual contributors (see CONTRIBUTORS file) +Copyright (c) dr-prodigy , 2017-2023 +Copyright (c) ryanss , 2014-2017 + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/app/Support/Holidays/data/LK.json b/app/Support/Holidays/data/LK.json new file mode 100644 index 000000000..ed348bce5 --- /dev/null +++ b/app/Support/Holidays/data/LK.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"LK","language":"si_LK","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"duruthu_full_moon_poya_day","name":"දුරුතු පුර පසළොස්වක පෝය දිනය","en":"Duruthu Full Moon Poya Day","category":"public","dates":"d|0101:2018|0103:2026|0104:2015|0106:2023|0108:2012|0110:2020|0112:2017|0113:2025|0115:2014|0117:2022|0119:2011|0120:2019|0123:2016|0125:2024|0126:2013|0128:2021"},{"id":"prophet_s_birthday","name":"නබි නායකතුමාගේ උපන් දිනය","en":"Prophet's Birthday","category":"public","dates":"d|0104:2015|0114:2014|0125:2013|0205:2012|0216:2011|0227:2010|0826:2026|0905:2025|0916:2024|0928:2023|1009:2022|1019:2021|1030:2020|1110:2019|1120:2018|1201:2017|1212:2016"},{"id":"tamil_thai_pongal_day","name":"දෙමළ තෛපොංැලල් දිනය","en":"Tamil Thai Pongal Day","category":"public","dates":"d|0114:2010,2013-2014,2017-2018,2021-2022,2025|0115:2011-2012,2015-2016,2019-2020,2023-2024,2026"},{"id":"nawam_full_moon_poya_day","name":"නවම් පුර පසළොස්වක පෝය දිනය","en":"Nawam Full Moon Poya Day","category":"public","dates":"d|0129:2010|0131:2018|0201:2026|0203:2015|0205:2023|0207:2012|0208:2020|0210:2017|0212:2025|0214:2014|0216:2022|0217:2011|0219:2019|0222:2016|0223:2024|0225:2013|0226:2021"},{"id":"independence_day","name":"නිදහස් සමරු දිනය","en":"Independence Day","category":"public","dates":"d|0204:2010-2026"},{"id":"maha_sivarathri_day","name":"මහ සිවරාත්රි දිනය","en":"Maha Sivarathri Day","category":"public","dates":"d|0213:2018|0215:2026|0217:2015|0218:2023|0220:2012|0221:2020|0224:2017|0226:2025|0227:2014|0301:2022|0302:2011|0304:2019|0307:2016|0308:2024|0310:2013|0311:2021|0313:2010"},{"id":"medin_full_moon_poya_day","name":"මැදින් පුර පසළොස්වක පෝය දිනය","en":"Medin Full Moon Poya Day","category":"public","dates":"d|0228:2010|0301:2018|0302:2026|0305:2015|0306:2023|0307:2012|0309:2020|0312:2017|0313:2025|0316:2014|0317:2022|0319:2011|0320:2019|0322:2016|0324:2024|0326:2013|0328:2021"},{"id":"eid_al_fitr","name":"ඊදුල් ෆීතර්","en":"Eid al-Fitr","category":"public","dates":"d|0321:2026|0331:2025|0411:2024|0422:2023|0503:2022|0514:2021|0525:2020|0605:2019|0615:2018|0626:2017|0706:2016|0718:2015|0729:2014|0809:2013|0819:2012|0831:2011|0910:2010"},{"id":"good_friday","name":"මහ සිකුරාදා දිනය","en":"Good Friday","category":"public","dates":"d|0325:2016|0329:2013,2024|0330:2018|0402:2010,2021|0403:2015,2026|0406:2012|0407:2023|0410:2020|0414:2017|0415:2022|0418:2014,2025|0419:2019|0422:2011"},{"id":"bak_full_moon_poya_day","name":"බක් පුර පසළොස්වක පෝය දිනය","en":"Bak Full Moon Poya Day","category":"public","dates":"d|0329:2010|0331:2018|0401:2026|0403:2015|0405:2023|0406:2012|0407:2020|0410:2017|0412:2025|0414:2014|0416:2022|0417:2011|0419:2019|0421:2016|0423:2024|0425:2013|0426:2021"},{"id":"day_before_sinhala_and_tamil_new_year","name":"සිංහල හා දෙමළ අලුත් අවුරුදු දිනට පෙර දිනය","en":"Day Before Sinhala and Tamil New Year","category":"public","dates":"d|0412:2012,2020,2024|0413:2010-2011,2013-2019,2021-2023,2025-2026"},{"id":"sinhala_and_tamil_new_year","name":"සිංහල හා දෙමළ අලුත් අවුරුදු දිනය","en":"Sinhala and Tamil New Year","category":"public","dates":"d|0413:2012,2020,2024|0414:2010-2011,2013-2019,2021-2023,2025-2026"},{"id":"vesak_full_moon_poya_day","name":"වෙසක් පුර පසළොස්වක පෝය දිනය","en":"Vesak Full Moon Poya Day","category":"public","dates":"d|0429:2018|0501:2026|0503:2015|0505:2012,2023|0507:2020|0510:2017|0512:2025|0514:2014|0515:2022|0517:2011|0518:2019|0521:2016|0523:2024|0524:2013|0526:2021|0527:2010"},{"id":"day_following_vesak_full_moon_poya_day","name":"වෙසක් පුර පසළොස්වක පෝය දිනට පසු දිනය","en":"Day Following Vesak Full Moon Poya Day","category":"public","dates":"d|0430:2018|0502:2026|0504:2015|0506:2012,2023|0508:2020|0511:2017|0513:2025|0515:2014|0516:2022|0518:2011|0519:2019|0522:2016|0524:2024|0525:2013|0527:2021|0528:2010"},{"id":"international_workers_day","name":"ලොක කම්කරු දිනය","en":"International Workers' Day","category":"public","dates":"d|0501:2010-2026"},{"id":"eid_al_adha","name":"ඊදුල් අල්හා","en":"Eid al-Adha","category":"public","dates":"d|0528:2026|0607:2025|0617:2024|0629:2023|0710:2022|0721:2021|0801:2020|0812:2019|0822:2018|0901:2017|0912:2016|0924:2015|1005:2014|1016:2013|1027:2012|1107:2011|1117:2010"},{"id":"adhi_poson_full_moon_poya_day","name":"අධි පොසොන් පුර පසළොස්වක පෝය දිනය","en":"Adhi Poson Full Moon Poya Day","category":"public","dates":"d|0529:2018|0530:2026"},{"id":"poson_full_moon_poya_day","name":"පොසොන් පුර පසළොස්වක පෝය දිනය","en":"Poson Full Moon Poya Day","category":"public","dates":"d|0602:2015|0603:2023|0604:2012|0605:2020|0608:2017|0610:2025|0612:2014|0614:2022|0615:2011|0616:2019|0619:2016|0621:2024|0623:2013|0624:2021|0625:2010|0627:2018|0629:2026"},{"id":"esala_full_moon_poya_day","name":"ඇසල පුර පසළොස්වක පෝය දිනය","en":"Esala Full Moon Poya Day","category":"public","dates":"d|0703:2012|0704:2020|0708:2017|0710:2025|0712:2014|0713:2022|0714:2011|0716:2019|0719:2016|0720:2024|0722:2013|0723:2021|0725:2010|0727:2018|0729:2026|0731:2015|0801:2023"},{"id":"nikini_full_moon_poya_day","name":"නිකිණි පුර පසළොස්වක පෝය දිනය","en":"Nikini Full Moon Poya Day","category":"public","dates":"d|0801:2012|0803:2020|0807:2017|0808:2025|0810:2014|0811:2022|0813:2011|0814:2019|0817:2016|0819:2024|0820:2013|0822:2021|0824:2010|0825:2018|0827:2026|0829:2015|0830:2023"},{"id":"binara_full_moon_poya_day","name":"බිනර පුර පසළොස්වක පෝය දිනය","en":"Binara Full Moon Poya Day","category":"public","dates":"d|0901:2020|0905:2017|0907:2025|0908:2014|0910:2022|0911:2011|0913:2019|0916:2016|0917:2024|0919:2013|0920:2021|0922:2010|0924:2018|0926:2026|0927:2015|0929:2012,2023"},{"id":"vap_full_moon_poya_day","name":"වප් පුර පසළොස්වක පෝය දිනය","en":"Vap Full Moon Poya Day","category":"public","dates":"d|1005:2017|1006:2025|1008:2014|1009:2022|1011:2011|1013:2019|1015:2016|1017:2024|1018:2013|1020:2021|1022:2010|1024:2018|1025:2026|1027:2015|1028:2023|1029:2012|1030:2020"},{"id":"diwali","name":"දීපවාලි උත්සව දිනය","en":"Diwali","category":"public","dates":"d|1018:2017|1020:2025|1022:2014|1024:2022|1026:2011|1027:2019|1029:2016|1031:2024|1102:2013|1104:2021|1105:2010|1106:2018|1108:2026|1110:2015|1112:2023|1113:2012|1114:2020"},{"id":"il_full_moon_poya_day","name":"ඉල් පුර පසළොස්වක පෝය දිනය","en":"Il Full Moon Poya Day","category":"public","dates":"d|1103:2017|1105:2025|1106:2014|1107:2022|1110:2011|1112:2019|1114:2016|1115:2024|1117:2013|1118:2021|1121:2010|1122:2018|1124:2026|1125:2015|1126:2023|1127:2012|1129:2020"},{"id":"unduvap_full_moon_poya_day","name":"උඳුවප් පුර පසළොස්වක පෝය දිනය","en":"Unduvap Full Moon Poya Day","category":"public","dates":"d|1203:2017|1204:2025|1206:2014|1207:2022|1210:2011|1211:2019|1213:2016|1214:2024|1216:2013|1218:2021|1220:2010|1222:2018|1223:2026|1224:2015|1226:2023|1227:2012|1229:2020"},{"id":"christmas_day","name":"නත්තල් උත්සව දිනය","en":"Christmas Day","category":"public","dates":"d|1225:2010-2026"}]} diff --git a/app/Support/Holidays/data/LR.json b/app/Support/Holidays/data/LR.json new file mode 100644 index 000000000..d71adbf00 --- /dev/null +++ b/app/Support/Holidays/data/LR.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"LR","language":null,"years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"armed_forces_day","name":"Armed Forces Day","en":"Armed Forces Day","category":"public","dates":"d|0211:2010-2060"},{"id":"decoration_day","name":"Decoration Day","en":"Decoration Day","category":"public","dates":"d|0308:2017,2023,2028,2034,2045,2051,2056|0309:2011,2016,2022,2033,2039,2044,2050|0310:2010,2021,2027,2032,2038,2049,2055,2060|0311:2015,2020,2026,2037,2043,2048,2054|0312:2014,2025,2031,2036,2042,2053,2059|0313:2013,2019,2024,2030,2041,2047,2052,2058|0314:2012,2018,2029,2035,2040,2046,2057"},{"id":"j_j_roberts_memorial_birthday","name":"J. J. Roberts Memorial Birthday","en":"J. J. Roberts Memorial Birthday","category":"public","dates":"d|0315:2010-2060"},{"id":"fasting_and_prayer_day","name":"Fasting and Prayer Day","en":"Fasting and Prayer Day","category":"public","dates":"d|0408:2011,2016,2022,2033,2039,2044,2050|0409:2010,2021,2027,2032,2038,2049,2055,2060|0410:2015,2020,2026,2037,2043,2048,2054|0411:2014,2025,2031,2036,2042,2053,2059|0412:2013,2019,2024,2030,2041,2047,2052,2058|0413:2012,2018,2029,2035,2040,2046,2057|0414:2017,2023,2028,2034,2045,2051,2056"},{"id":"national_unification_and_integration_day","name":"National Unification and Integration Day","en":"National Unification and Integration Day","category":"public","dates":"d|0514:2010-2060"},{"id":"independence_day","name":"Independence Day","en":"Independence Day","category":"public","dates":"d|0726:2010-2060"},{"id":"national_flag_day","name":"National Flag Day","en":"National Flag Day","category":"public","dates":"d|0824:2010-2060"},{"id":"thanksgiving_day","name":"Thanksgiving Day","en":"Thanksgiving Day","category":"public","dates":"d|1101:2012,2018,2029,2035,2040,2046,2057|1102:2017,2023,2028,2034,2045,2051,2056|1103:2011,2016,2022,2033,2039,2044,2050|1104:2010,2021,2027,2032,2038,2049,2055,2060|1105:2015,2020,2026,2037,2043,2048,2054|1106:2014,2025,2031,2036,2042,2053,2059|1107:2013,2019,2024,2030,2041,2047,2052,2058"},{"id":"tubman_administration_goodwill_day","name":"Tubman Administration Goodwill Day","en":"Tubman Administration Goodwill Day","category":"public","dates":"d|1129:2010-2060"},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"}]} diff --git a/app/Support/Holidays/data/LS.json b/app/Support/Holidays/data/LS.json new file mode 100644 index 000000000..bd44a5009 --- /dev/null +++ b/app/Support/Holidays/data/LS.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"LS","language":null,"years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"moshoeshoe_s_day","name":"Moshoeshoe's Day","en":"Moshoeshoe's Day","category":"public","dates":"d|0311:2010-2060"},{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_monday","name":"Easter Monday","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"workers_day","name":"Workers' Day","en":"Workers' Day","category":"public","dates":"d|0501:2010-2060"},{"id":"ascension_day","name":"Ascension Day","en":"Ascension Day","category":"public","dates":"d|0503:2035,2046|0505:2016|0506:2027,2032|0507:2043,2054|0508:2059|0509:2013,2024|0510:2018,2029,2040|0511:2051,2056|0513:2010,2021|0514:2015,2026,2037,2048|0515:2042,2053|0517:2012|0518:2023,2034,2045|0519:2039,2050|0521:2020|0522:2031,2036|0523:2047,2058|0525:2017,2028|0526:2022,2033,2044|0527:2049,2055,2060|0529:2014,2025|0530:2019,2030,2041,2052|0531:2057|0602:2011|0603:2038"},{"id":"africa_heroes_day","name":"Africa/Heroes Day","en":"Africa/Heroes Day","category":"public","dates":"d|0525:2010-2060"},{"id":"king_s_birthday","name":"King's Birthday","en":"King's Birthday","category":"public","dates":"d|0717:2010-2060"},{"id":"independence_day","name":"Independence Day","en":"Independence Day","category":"public","dates":"d|1004:2010-2060"},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"},{"id":"boxing_day","name":"Boxing Day","en":"Boxing Day","category":"public","dates":"d|1226:2010-2060"}]} diff --git a/app/Support/Holidays/data/LT.json b/app/Support/Holidays/data/LT.json new file mode 100644 index 000000000..8fa603842 --- /dev/null +++ b/app/Support/Holidays/data/LT.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"LT","language":"lt","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Naujųjų metų diena","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"day_of_restoration_of_the_state_of_lithuania","name":"Lietuvos valstybės atkūrimo diena","en":"Day of Restoration of the State of Lithuania","category":"public","dates":"d|0216:2010-2060"},{"id":"day_of_restoration_of_independence_of_lithuania","name":"Lietuvos nepriklausomybės atkūrimo diena","en":"Day of Restoration of Independence of Lithuania","category":"public","dates":"d|0311:2010-2060"},{"id":"easter_sunday","name":"Šv. Velykos","en":"Easter Sunday","category":"public","dates":"d|0325:2035,2046|0327:2016|0328:2027,2032|0329:2043,2054|0330:2059|0331:2013,2024|0401:2018,2029,2040|0402:2051,2056|0404:2010,2021|0405:2015,2026,2037,2048|0406:2042,2053|0408:2012|0409:2023,2034,2045|0410:2039,2050|0412:2020|0413:2031,2036|0414:2047,2058|0416:2017,2028|0417:2022,2033,2044|0418:2049,2055,2060|0420:2014,2025|0421:2019,2030,2041,2052|0422:2057|0424:2011|0425:2038"},{"id":"easter_monday","name":"Antroji šv. Velykų diena","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"international_workers_day","name":"Tarptautinė darbo diena","en":"International Workers' Day","category":"public","dates":"d|0501:2010-2060"},{"id":"mother_s_day","name":"Motinos diena","en":"Mother's Day","category":"public","dates":"d|0501:2011,2016,2022,2033,2039,2044,2050|0502:2010,2021,2027,2032,2038,2049,2055,2060|0503:2015,2020,2026,2037,2043,2048,2054|0504:2014,2025,2031,2036,2042,2053,2059|0505:2013,2019,2024,2030,2041,2047,2052,2058|0506:2012,2018,2029,2035,2040,2046,2057|0507:2017,2023,2028,2034,2045,2051,2056"},{"id":"father_s_day","name":"Tėvo diena","en":"Father's Day","category":"public","dates":"d|0601:2014,2025,2031,2036,2042,2053,2059|0602:2013,2019,2024,2030,2041,2047,2052,2058|0603:2012,2018,2029,2035,2040,2046,2057|0604:2017,2023,2028,2034,2045,2051,2056|0605:2011,2016,2022,2033,2039,2044,2050|0606:2010,2021,2027,2032,2038,2049,2055,2060|0607:2015,2020,2026,2037,2043,2048,2054"},{"id":"day_of_dew_and_saint_john","name":"Rasos ir Joninių diena","en":"Day of Dew and Saint John","category":"public","dates":"d|0624:2010-2060"},{"id":"statehood_day","name":"Valstybės (Lietuvos karaliaus Mindaugo karūnavimo) ir Tautiškos giesmės diena","en":"Statehood Day","category":"public","dates":"d|0706:2010-2060"},{"id":"assumption_day","name":"Žolinė (Švč. Mergelės Marijos ėmimo į dangų diena)","en":"Assumption Day","category":"public","dates":"d|0815:2010-2060"},{"id":"all_saints_day","name":"Visų Šventųjų diena","en":"All Saints' Day","category":"public","dates":"d|1101:2010-2060"},{"id":"all_souls_day","name":"Mirusiųjų atminimo (Vėlinių) diena","en":"All Souls' Day","category":"public","dates":"d|1102:2020-2060"},{"id":"christmas_eve","name":"Kūčių diena","en":"Christmas Eve","category":"public","dates":"d|1224:2010-2060"},{"id":"christmas_day","name":"Šv. Kalėdų pirma diena","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"},{"id":"second_day_of_christmas","name":"Šv. Kalėdų antra diena","en":"Second Day of Christmas","category":"public","dates":"d|1226:2010-2060"}]} diff --git a/app/Support/Holidays/data/LU.json b/app/Support/Holidays/data/LU.json new file mode 100644 index 000000000..bee978f84 --- /dev/null +++ b/app/Support/Holidays/data/LU.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"LU","language":"lb","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Neijoerschdag","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"easter_monday","name":"Ouschterméindeg","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"labor_day","name":"Dag vun der Aarbecht","en":"Labor Day","category":"public","dates":"d|0501:2010-2060"},{"id":"ascension_day","name":"Christi Himmelfaart","en":"Ascension Day","category":"public","dates":"d|0503:2035,2046|0505:2016|0506:2027,2032|0507:2043,2054|0508:2059|0509:2013,2024|0510:2018,2029,2040|0511:2051,2056|0513:2010,2021|0514:2015,2026,2037,2048|0515:2042,2053|0517:2012|0518:2023,2034,2045|0519:2039,2050|0521:2020|0522:2031,2036|0523:2047,2058|0525:2017,2028|0526:2022,2033,2044|0527:2049,2055,2060|0529:2014,2025|0530:2019,2030,2041,2052|0531:2057|0602:2011|0603:2038"},{"id":"europe_day","name":"Europadag","en":"Europe Day","category":"public","dates":"d|0509:2019-2060"},{"id":"pentecost_monday","name":"Péngschtméindeg","en":"Pentecost Monday","category":"public","dates":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038"},{"id":"national_day","name":"Nationalfeierdag","en":"National Day","category":"public","dates":"d|0623:2010-2060"},{"id":"assumption_day","name":"Léiffrawëschdag","en":"Assumption Day","category":"public","dates":"d|0815:2010-2060"},{"id":"all_saints_day","name":"Allerhellgen","en":"All Saints' Day","category":"public","dates":"d|1101:2010-2060"},{"id":"christmas_day","name":"Chrëschtdag","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"},{"id":"saint_stephen_s_day","name":"Stiefesdag","en":"Saint Stephen's Day","category":"public","dates":"d|1226:2010-2060"},{"id":"good_friday_bank","name":"Karfreideg","en":"Good Friday","category":"bank","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"christmas_eve_afternoon_bank","name":"Hellegowend (nomëtteg)","en":"Christmas Eve (afternoon)","category":"bank","dates":"d|1224:2010-2060"},{"id":"new_year_s_eve_bank","name":"Silvester","en":"New Year's Eve","category":"bank","dates":"d|1231:2010-2060"}]} diff --git a/app/Support/Holidays/data/LV.json b/app/Support/Holidays/data/LV.json new file mode 100644 index 000000000..620eab5ff --- /dev/null +++ b/app/Support/Holidays/data/LV.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"LV","language":"lv","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Jaungada diena","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"day_off","name":"Brīvdiena","en":"Day off","category":"public","dates":"d|0102:2015,2026|0430:2012,2018|0502:2014,2025|0503:2010,2021|0505:2017,2023|0622:2015,2020,2026|0625:2010,2021|1117:2014,2025|1119:2010,2021|1223:2013,2024|1230:2013,2024"},{"id":"good_friday","name":"Lielā Piektdiena","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_sunday","name":"Pirmās Lieldienas","en":"Easter Sunday","category":"public","dates":"d|0325:2035,2046|0327:2016|0328:2027,2032|0329:2043,2054|0330:2059|0331:2013,2024|0401:2018,2029,2040|0402:2051,2056|0404:2010,2021|0405:2015,2026,2037,2048|0406:2042,2053|0408:2012|0409:2023,2034,2045|0410:2039,2050|0412:2020|0413:2031,2036|0414:2047,2058|0416:2017,2028|0417:2022,2033,2044|0418:2049,2055,2060|0420:2014,2025|0421:2019,2030,2041,2052|0422:2057|0424:2011|0425:2038"},{"id":"easter_monday","name":"Otrās Lieldienas","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"constitutional_assembly_convocation_day","name":"Darba svētki","en":"Constitutional Assembly Convocation Day","category":"public","dates":"d|0501:2010-2060"},{"id":"labor_day","name":"Latvijas Republikas Satversmes sapulces sasaukšanas diena","en":"Labor Day","category":"public","dates":"d|0501:2010-2060"},{"id":"restoration_of_independence_day","name":"Latvijas Republikas Neatkarības atjaunošanas diena","en":"Restoration of Independence Day","category":"public","dates":"d|0504:2012-2060|0505:2014,2025,2031,2036,2042,2053,2059|0506:2013,2019,2024,2030,2041,2047,2052,2058"},{"id":"mother_s_day","name":"Mātes diena","en":"Mother's Day","category":"public","dates":"d|0508:2011,2016,2022,2033,2039,2044,2050|0509:2010,2021,2027,2032,2038,2049,2055,2060|0510:2015,2020,2026,2037,2043,2048,2054|0511:2014,2025,2031,2036,2042,2053,2059|0512:2013,2019,2024,2030,2041,2047,2052,2058|0513:2012,2018,2029,2035,2040,2046,2057|0514:2017,2023,2028,2034,2045,2051,2056"},{"id":"pentecost","name":"Vasarsvētki","en":"Pentecost","category":"public","dates":"d|0513:2035,2046|0515:2016|0516:2027,2032|0517:2043,2054|0518:2059|0519:2013,2024|0520:2018,2029,2040|0521:2051,2056|0523:2010,2021|0524:2015,2026,2037,2048|0525:2042,2053|0527:2012|0528:2023,2034,2045|0529:2039,2050|0531:2020|0601:2031,2036|0602:2047,2058|0604:2017,2028|0605:2022,2033,2044|0606:2049,2055,2060|0608:2014,2025|0609:2019,2030,2041,2052|0610:2057|0612:2011|0613:2038"},{"id":"midsummer_eve","name":"Līgo diena","en":"Midsummer Eve","category":"public","dates":"d|0623:2010-2060"},{"id":"midsummer_day","name":"Jāņu diena","en":"Midsummer Day","category":"public","dates":"d|0624:2010-2060"},{"id":"republic_of_latvia_proclamation_day","name":"Latvijas Republikas Proklamēšanas diena","en":"Republic of Latvia Proclamation Day","category":"public","dates":"d|1118:2010-2060|1119:2012,2018,2029,2035,2040,2046,2057|1120:2017,2023,2028,2034,2045,2051,2056"},{"id":"christmas_eve","name":"Ziemassvētku vakars","en":"Christmas Eve","category":"public","dates":"d|1224:2010-2060"},{"id":"christmas_day","name":"Pirmie Ziemassvētki","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"},{"id":"second_day_of_christmas","name":"Otrie Ziemassvētki","en":"Second Day of Christmas","category":"public","dates":"d|1226:2010-2060"},{"id":"new_year_s_eve","name":"Vecgada diena","en":"New Year's Eve","category":"public","dates":"d|1231:2010-2060"}]} diff --git a/app/Support/Holidays/data/LY.json b/app/Support/Holidays/data/LY.json new file mode 100644 index 000000000..becf56bc3 --- /dev/null +++ b/app/Support/Holidays/data/LY.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"LY","language":"ar","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"eid_al_fitr","name":"عيد الفطر","en":"Eid al-Fitr","category":"public","dates":"y|2010:0910-0912|2011:0830-0901|2012:0819-0821|2013:0808-0810|2014:0729-0731|2015:0718-0720|2016:0707-0709|2017:0626-0628|2018:0615-0617|2019:0604-0606|2020:0524-0526|2021:0513-0515|2022:0502-0504|2023:0421-0423|2024:0410-0412|2025:0331-0402|2026:0320-0322|2027:0309-0311|2028:0226-0228|2029:0214-0216|2030:0204-0206|2031:0124-0126|2032:0114-0116|2033:0102-0104,1223-1225|2034:1212-1214|2035:1201-1203|2036:1119-1121|2037:1108-1110|2038:1029-1031|2039:1019-1021|2040:1007-1009|2041:0926-0928|2042:0915-0917|2043:0904-0906|2044:0824-0826|2045:0814-0816|2046:0803-0805|2047:0724-0726|2048:0712-0714|2049:0701-0703|2050:0620-0622|2051:0610-0612|2052:0529-0531|2053:0519-0521|2054:0509-0511|2055:0428-0430|2056:0416-0418|2057:0405-0407|2058:0325-0327|2059:0315-0317|2060:0304-0306","estimated":"y|2010:0910-0912|2011:0830-0901|2026:0320-0322|2027:0309-0311|2028:0226-0228|2029:0214-0216|2030:0204-0206|2031:0124-0126|2032:0114-0116|2033:0102-0104,1223-1225|2034:1212-1214|2035:1201-1203|2036:1119-1121|2037:1108-1110|2038:1029-1031|2039:1019-1021|2040:1007-1009|2041:0926-0928|2042:0915-0917|2043:0904-0906|2044:0824-0826|2045:0814-0816|2046:0803-0805|2047:0724-0726|2048:0712-0714|2049:0701-0703|2050:0620-0622|2051:0610-0612|2052:0529-0531|2053:0519-0521|2054:0509-0511|2055:0428-0430|2056:0416-0418|2057:0405-0407|2058:0325-0327|2059:0315-0317|2060:0304-0306"},{"id":"prophet_s_birthday","name":"ذكرى المولد النبوي الشريف","en":"Prophet's Birthday","category":"public","dates":"y|2010:0226|2011:0215|2012:0205|2013:0124|2014:0114|2015:0103,1223|2016:1212|2017:1201|2018:1121|2019:1109|2020:1029|2021:1019|2022:1008|2023:0927|2024:0915|2025:0904|2026:0825|2027:0814|2028:0803|2029:0724|2030:0713|2031:0702|2032:0620|2033:0609|2034:0530|2035:0520|2036:0508|2037:0428|2038:0417|2039:0406|2040:0325|2041:0315|2042:0304|2043:0222|2044:0211|2045:0130|2046:0119|2047:0108,1229|2048:1218|2049:1207|2050:1126|2051:1116|2052:1104|2053:1024|2054:1013|2055:1003|2056:0922|2057:0911|2058:0831|2059:0820|2060:0808","estimated":"y|2010:0226|2011:0215|2025:0904|2026:0825|2027:0814|2028:0803|2029:0724|2030:0713|2031:0702|2032:0620|2033:0609|2034:0530|2035:0520|2036:0508|2037:0428|2038:0417|2039:0406|2040:0325|2041:0315|2042:0304|2043:0222|2044:0211|2045:0130|2046:0119|2047:0108,1229|2048:1218|2049:1207|2050:1126|2051:1116|2052:1104|2053:1024|2054:1013|2055:1003|2056:0922|2057:0911|2058:0831|2059:0820|2060:0808"},{"id":"day_of_arafah","name":"يوم عرفة","en":"Day of Arafah","category":"public","dates":"y|2010:1115|2011:1105|2012:1025|2013:1014|2014:1004|2015:0922|2016:0910|2017:0831|2018:0821|2019:0810|2020:0730|2021:0719|2022:0708|2023:0627|2024:0615|2025:0605|2026:0526|2027:0515|2028:0504|2029:0423|2030:0412|2031:0401|2032:0321|2033:0310|2034:0228|2035:0217|2036:0206|2037:0125|2038:0115|2039:0104,1225|2040:1213|2041:1203|2042:1122|2043:1111|2044:1030|2045:1020|2046:1009|2047:0929|2048:0918|2049:0907|2050:0827|2051:0816|2052:0804|2053:0725|2054:0714|2055:0704|2056:0622|2057:0611|2058:0531|2059:0521|2060:0509","estimated":"y|2010:1115|2011:1105|2026:0526|2027:0515|2028:0504|2029:0423|2030:0412|2031:0401|2032:0321|2033:0310|2034:0228|2035:0217|2036:0206|2037:0125|2038:0115|2039:0104,1225|2040:1213|2041:1203|2042:1122|2043:1111|2044:1030|2045:1020|2046:1009|2047:0929|2048:0918|2049:0907|2050:0827|2051:0816|2052:0804|2053:0725|2054:0714|2055:0704|2056:0622|2057:0611|2058:0531|2059:0521|2060:0509"},{"id":"islamic_new_year_day","name":"عيد رأس السنة الهجرية","en":"Islamic New Year Day","category":"public","dates":"y|2012:1115|2013:1105|2014:1025|2015:1015|2016:1003|2017:0922|2018:0912|2019:0831|2020:0820|2021:0810|2022:0730|2023:0719|2024:0707|2025:0626|2026:0616|2027:0606|2028:0525|2029:0514|2030:0503|2031:0423|2032:0411|2033:0401|2034:0321|2035:0311|2036:0228|2037:0216|2038:0205|2039:0126|2040:0115|2041:0104,1224|2042:1214|2043:1203|2044:1121|2045:1110|2046:1031|2047:1020|2048:1009|2049:0928|2050:0917|2051:0906|2052:0826|2053:0815|2054:0805|2055:0725|2056:0714|2057:0703|2058:0622|2059:0611|2060:0531","estimated":"y|2026:0616|2027:0606|2028:0525|2029:0514|2030:0503|2031:0423|2032:0411|2033:0401|2034:0321|2035:0311|2036:0228|2037:0216|2038:0205|2039:0126|2040:0115|2041:0104,1224|2042:1214|2043:1203|2044:1121|2045:1110|2046:1031|2047:1020|2048:1009|2049:0928|2050:0917|2051:0906|2052:0826|2053:0815|2054:0805|2055:0725|2056:0714|2057:0703|2058:0622|2059:0611|2060:0531"},{"id":"eid_al_adha","name":"عيد الأضحى","en":"Eid al-Adha","category":"public","dates":"y|2010:1116-1118|2011:1106-1108|2012:1026-1028|2013:1015-1017|2014:1005-1007|2015:0923-0925|2016:0911-0913|2017:0901-0903|2018:0822-0824|2019:0811-0813|2020:0731-0802|2021:0720-0722|2022:0709-0711|2023:0628-0630|2024:0616-0618|2025:0606-0608|2026:0527-0529|2027:0516-0518|2028:0505-0507|2029:0424-0426|2030:0413-0415|2031:0402-0404|2032:0322-0324|2033:0311-0313|2034:0301-0303|2035:0218-0220|2036:0207-0209|2037:0126-0128|2038:0116-0118|2039:0105-0107,1226-1228|2040:1214-1216|2041:1204-1206|2042:1123-1125|2043:1112-1114|2044:1031-1102|2045:1021-1023|2046:1010-1012|2047:0930-1002|2048:0919-0921|2049:0908-0910|2050:0828-0830|2051:0817-0819|2052:0805-0807|2053:0726-0728|2054:0715-0717|2055:0705-0707|2056:0623-0625|2057:0612-0614|2058:0601-0603|2059:0522-0524|2060:0510-0512","estimated":"y|2010:1116-1118|2011:1106-1108|2026:0527-0529|2027:0516-0518|2028:0505-0507|2029:0424-0426|2030:0413-0415|2031:0402-0404|2032:0322-0324|2033:0311-0313|2034:0301-0303|2035:0218-0220|2036:0207-0209|2037:0126-0128|2038:0116-0118|2039:0105-0107,1226-1228|2040:1214-1216|2041:1204-1206|2042:1123-1125|2043:1112-1114|2044:1031-1102|2045:1021-1023|2046:1010-1012|2047:0930-1002|2048:0919-0921|2049:0908-0910|2050:0828-0830|2051:0817-0819|2052:0805-0807|2053:0726-0728|2054:0715-0717|2055:0705-0707|2056:0623-0625|2057:0612-0614|2058:0601-0603|2059:0522-0524|2060:0510-0512"},{"id":"anniversary_of_the_february_17_revolution","name":"ثورة 17 فبراير","en":"Anniversary of the February 17 Revolution","category":"public","dates":"d|0217:2012-2060"},{"id":"labor_day","name":"عيد العمال","en":"Labor Day","category":"public","dates":"d|0501:2012-2060"},{"id":"national_environmental_sanitation_day","name":"يوم وطني للإصحاح البيئي","en":"National Environmental Sanitation Day","category":"public","dates":"d|0814:2022-2060"},{"id":"martyrs_day","name":"يوم الشهيد","en":"Martyrs' Day","category":"public","dates":"d|0916:2012-2060"},{"id":"liberation_day","name":"يوم التحرير","en":"Liberation Day","category":"public","dates":"d|1023:2012-2060"},{"id":"independence_day","name":"عيد الاستقلال","en":"Independence Day","category":"public","dates":"d|1224:2012-2060"}]} diff --git a/app/Support/Holidays/data/MA.json b/app/Support/Holidays/data/MA.json new file mode 100644 index 000000000..24336e52c --- /dev/null +++ b/app/Support/Holidays/data/MA.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"MA","language":"ar","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"رأس السنة الميلادية","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"eid_al_fitr","name":"عيد الفطر","en":"Eid al-Fitr","category":"public","dates":"y|2010:0910-0911|2011:0830-0831|2012:0819-0820|2013:0808-0809|2014:0728-0729|2015:0717-0718|2016:0706-0707|2017:0625-0626|2018:0615-0616|2019:0604-0605|2020:0524-0525|2021:0513-0514|2022:0502-0503|2023:0421-0422|2024:0410-0411|2025:0330-0331|2026:0320-0321|2027:0309-0310|2028:0226-0227|2029:0214-0215|2030:0204-0205|2031:0124-0125|2032:0114-0115|2033:0102-0103,1223-1224|2034:1212-1213|2035:1201-1202|2036:1119-1120|2037:1108-1109|2038:1029-1030|2039:1019-1020|2040:1007-1008|2041:0926-0927|2042:0915-0916|2043:0904-0905|2044:0824-0825|2045:0814-0815|2046:0803-0804|2047:0724-0725|2048:0712-0713|2049:0701-0702|2050:0620-0621|2051:0610-0611|2052:0529-0530|2053:0519-0520|2054:0509-0510|2055:0428-0429|2056:0416-0417|2057:0405-0406|2058:0325-0326|2059:0315-0316|2060:0304-0305","estimated":"y|2010:0910-0911|2011:0830-0831|2012:0819-0820|2013:0808-0809|2014:0728-0729|2015:0717-0718|2016:0706-0707|2017:0625-0626|2018:0615-0616|2019:0604-0605|2020:0524-0525|2021:0513-0514|2022:0502-0503|2023:0421-0422|2024:0410-0411|2025:0330-0331|2026:0320-0321|2027:0309-0310|2028:0226-0227|2029:0214-0215|2030:0204-0205|2031:0124-0125|2032:0114-0115|2033:0102-0103,1223-1224|2034:1212-1213|2035:1201-1202|2036:1119-1120|2037:1108-1109|2038:1029-1030|2039:1019-1020|2040:1007-1008|2041:0926-0927|2042:0915-0916|2043:0904-0905|2044:0824-0825|2045:0814-0815|2046:0803-0804|2047:0724-0725|2048:0712-0713|2049:0701-0702|2050:0620-0621|2051:0610-0611|2052:0529-0530|2053:0519-0520|2054:0509-0510|2055:0428-0429|2056:0416-0417|2057:0405-0406|2058:0325-0326|2059:0315-0316|2060:0304-0305"},{"id":"prophet_s_birthday","name":"عيد المولد النبوي","en":"Prophet's Birthday","category":"public","dates":"y|2010:0226-0227|2011:0215-0216|2012:0204-0205|2013:0124-0125|2014:0113-0114|2015:0103-0104,1223-1224|2016:1211-1212|2017:1130-1201|2018:1120-1121|2019:1109-1110|2020:1029-1030|2021:1018-1019|2022:1008-1009|2023:0927-0928|2024:0915-0916|2025:0904-0905|2026:0825-0826|2027:0814-0815|2028:0803-0804|2029:0724-0725|2030:0713-0714|2031:0702-0703|2032:0620-0621|2033:0609-0610|2034:0530-0531|2035:0520-0521|2036:0508-0509|2037:0428-0429|2038:0417-0418|2039:0406-0407|2040:0325-0326|2041:0315-0316|2042:0304-0305|2043:0222-0223|2044:0211-0212|2045:0130-0131|2046:0119-0120|2047:0108-0109,1229-1230|2048:1218-1219|2049:1207-1208|2050:1126-1127|2051:1116-1117|2052:1104-1105|2053:1024-1025|2054:1013-1014|2055:1003-1004|2056:0922-0923|2057:0911-0912|2058:0831-0901|2059:0820-0821|2060:0808-0809","estimated":"y|2010:0226-0227|2011:0215-0216|2012:0204-0205|2013:0124-0125|2014:0113-0114|2015:0103-0104,1223-1224|2016:1211-1212|2017:1130-1201|2018:1120-1121|2019:1109-1110|2020:1029-1030|2021:1018-1019|2022:1008-1009|2023:0927-0928|2024:0915-0916|2025:0904-0905|2026:0825-0826|2027:0814-0815|2028:0803-0804|2029:0724-0725|2030:0713-0714|2031:0702-0703|2032:0620-0621|2033:0609-0610|2034:0530-0531|2035:0520-0521|2036:0508-0509|2037:0428-0429|2038:0417-0418|2039:0406-0407|2040:0325-0326|2041:0315-0316|2042:0304-0305|2043:0222-0223|2044:0211-0212|2045:0130-0131|2046:0119-0120|2047:0108-0109,1229-1230|2048:1218-1219|2049:1207-1208|2050:1126-1127|2051:1116-1117|2052:1104-1105|2053:1024-1025|2054:1013-1014|2055:1003-1004|2056:0922-0923|2057:0911-0912|2058:0831-0901|2059:0820-0821|2060:0808-0809"},{"id":"islamic_new_year","name":"رأس السنة الهجرية","en":"Islamic New Year","category":"public","dates":"y|2010:1207|2011:1126|2012:1115|2013:1104|2014:1025|2015:1014|2016:1002|2017:0921|2018:0911|2019:0831|2020:0820|2021:0809|2022:0730|2023:0719|2024:0707|2025:0626|2026:0616|2027:0606|2028:0525|2029:0514|2030:0503|2031:0423|2032:0411|2033:0401|2034:0321|2035:0311|2036:0228|2037:0216|2038:0205|2039:0126|2040:0115|2041:0104,1224|2042:1214|2043:1203|2044:1121|2045:1110|2046:1031|2047:1020|2048:1009|2049:0928|2050:0917|2051:0906|2052:0826|2053:0815|2054:0805|2055:0725|2056:0714|2057:0703|2058:0622|2059:0611|2060:0531","estimated":"y|2010:1207|2011:1126|2012:1115|2013:1104|2014:1025|2015:1014|2016:1002|2017:0921|2018:0911|2019:0831|2020:0820|2021:0809|2022:0730|2023:0719|2024:0707|2025:0626|2026:0616|2027:0606|2028:0525|2029:0514|2030:0503|2031:0423|2032:0411|2033:0401|2034:0321|2035:0311|2036:0228|2037:0216|2038:0205|2039:0126|2040:0115|2041:0104,1224|2042:1214|2043:1203|2044:1121|2045:1110|2046:1031|2047:1020|2048:1009|2049:0928|2050:0917|2051:0906|2052:0826|2053:0815|2054:0805|2055:0725|2056:0714|2057:0703|2058:0622|2059:0611|2060:0531"},{"id":"eid_al_adha","name":"عيد الأضحى","en":"Eid al-Adha","category":"public","dates":"y|2010:1116-1117|2011:1106-1107|2012:1026-1027|2013:1015-1016|2014:1004-1005|2015:0923-0924|2016:0911-0912|2017:0901-0902|2018:0821-0822|2019:0811-0812|2020:0731-0801|2021:0720-0721|2022:0709-0710|2023:0628-0629|2024:0616-0617|2025:0606-0607|2026:0527-0528|2027:0516-0517|2028:0505-0506|2029:0424-0425|2030:0413-0414|2031:0402-0403|2032:0322-0323|2033:0311-0312|2034:0301-0302|2035:0218-0219|2036:0207-0208|2037:0126-0127|2038:0116-0117|2039:0105-0106,1226-1227|2040:1214-1215|2041:1204-1205|2042:1123-1124|2043:1112-1113|2044:1031-1101|2045:1021-1022|2046:1010-1011|2047:0930-1001|2048:0919-0920|2049:0908-0909|2050:0828-0829|2051:0817-0818|2052:0805-0806|2053:0726-0727|2054:0715-0716|2055:0705-0706|2056:0623-0624|2057:0612-0613|2058:0601-0602|2059:0522-0523|2060:0510-0511","estimated":"y|2010:1116-1117|2011:1106-1107|2012:1026-1027|2013:1015-1016|2014:1004-1005|2015:0923-0924|2016:0911-0912|2017:0901-0902|2018:0821-0822|2019:0811-0812|2020:0731-0801|2021:0720-0721|2022:0709-0710|2023:0628-0629|2024:0616-0617|2025:0606-0607|2026:0527-0528|2027:0516-0517|2028:0505-0506|2029:0424-0425|2030:0413-0414|2031:0402-0403|2032:0322-0323|2033:0311-0312|2034:0301-0302|2035:0218-0219|2036:0207-0208|2037:0126-0127|2038:0116-0117|2039:0105-0106,1226-1227|2040:1214-1215|2041:1204-1205|2042:1123-1124|2043:1112-1113|2044:1031-1101|2045:1021-1022|2046:1010-1011|2047:0930-1001|2048:0919-0920|2049:0908-0909|2050:0828-0829|2051:0817-0818|2052:0805-0806|2053:0726-0727|2054:0715-0716|2055:0705-0706|2056:0623-0624|2057:0612-0613|2058:0601-0602|2059:0522-0523|2060:0510-0511"},{"id":"proclamation_of_independence_day","name":"ذكرى تقديم وثيقة الاستقلال","en":"Proclamation of Independence Day","category":"public","dates":"d|0111:2010-2060"},{"id":"amazigh_new_year","name":"رأس السنة الأمازيغية","en":"Amazigh New Year","category":"public","dates":"d|0114:2024-2060"},{"id":"labor_day","name":"عيد العمال","en":"Labor Day","category":"public","dates":"d|0501:2010-2060"},{"id":"throne_day","name":"عيد العرش","en":"Throne Day","category":"public","dates":"d|0730:2010-2060"},{"id":"oued_ed_dahab_day","name":"ذكرى استرجاع إقليم وادي الذهب","en":"Oued Ed-Dahab Day","category":"public","dates":"d|0814:2010-2060"},{"id":"revolution_day","name":"ذكرى ثورة الملك و الشعب","en":"Revolution Day","category":"public","dates":"d|0820:2010-2060"},{"id":"youth_day","name":"عيد الشباب","en":"Youth Day","category":"public","dates":"d|0821:2010-2060"},{"id":"unity_day","name":"عيد الوحدة","en":"Unity Day","category":"public","dates":"d|1031:2026-2060"},{"id":"green_march","name":"ذكرى المسيرة الخضراء","en":"Green March","category":"public","dates":"d|1106:2010-2060"},{"id":"independence_day","name":"عيد الاستقلال","en":"Independence Day","category":"public","dates":"d|1118:2010-2060"}]} diff --git a/app/Support/Holidays/data/MC.json b/app/Support/Holidays/data/MC.json new file mode 100644 index 000000000..056e7206f --- /dev/null +++ b/app/Support/Holidays/data/MC.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"MC","language":"fr_MC","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Le jour de l'An","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051"},{"id":"saint_devote_s_day","name":"Le jour de la Sainte-Dévote","en":"Saint Devote's Day","category":"public","dates":"d|0127:2010-2060"},{"id":"easter_monday","name":"Le Lundi de Pâques","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"labor_day","name":"Le jour de la Fête du Travail","en":"Labor Day","category":"public","dates":"d|0501:2010-2060|0502:2011,2016,2022,2033,2039,2044,2050"},{"id":"ascension_day","name":"Le jour de l'Ascension","en":"Ascension Day","category":"public","dates":"d|0503:2035,2046|0505:2016|0506:2027,2032|0507:2043,2054|0508:2059|0509:2013,2024|0510:2018,2029,2040|0511:2051,2056|0513:2010,2021|0514:2015,2026,2037,2048|0515:2042,2053|0517:2012|0518:2023,2034,2045|0519:2039,2050|0521:2020|0522:2031,2036|0523:2047,2058|0525:2017,2028|0526:2022,2033,2044|0527:2049,2055,2060|0529:2014,2025|0530:2019,2030,2041,2052|0531:2057|0602:2011|0603:2038"},{"id":"pentecost_monday","name":"Le Lundi de Pentecôte","en":"Pentecost Monday","category":"public","dates":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038"},{"id":"corpus_christi","name":"Le jour de la Fête Dieu","en":"Corpus Christi","category":"public","dates":"d|0524:2035,2046|0526:2016|0527:2027,2032|0528:2043,2054|0529:2059|0530:2013,2024|0531:2018,2029,2040|0601:2051,2056|0603:2010,2021|0604:2015,2026,2037,2048|0605:2042,2053|0607:2012|0608:2023,2034,2045|0609:2039,2050|0611:2020|0612:2031,2036|0613:2047,2058|0615:2017,2028|0616:2022,2033,2044|0617:2049,2055,2060|0619:2014,2025|0620:2019,2030,2041,2052|0621:2057|0623:2011|0624:2038"},{"id":"assumption_day","name":"Le jour de l'Assomption","en":"Assumption Day","category":"public","dates":"d|0815:2010-2060|0816:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"all_saints_day","name":"Le jour de la Toussaint","en":"All Saints' Day","category":"public","dates":"d|1101:2010-2060|1102:2015,2020,2026,2037,2043,2048,2054"},{"id":"prince_s_day","name":"Le jour de la Fête de S.A.S. le Prince Souverain","en":"Prince's Day","category":"public","dates":"d|1119:2010-2060|1120:2017,2023,2028,2034,2045,2051,2056"},{"id":"immaculate_conception","name":"Le jour de l'Immaculée Conception","en":"Immaculate Conception","category":"public","dates":"d|1208:2010-2060|1209:2019,2024,2030,2041,2047,2052,2058"},{"id":"christmas_day","name":"Le jour de Noël","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060|1226:2011,2016,2022,2033,2039,2044,2050"}]} diff --git a/app/Support/Holidays/data/MD.json b/app/Support/Holidays/data/MD.json new file mode 100644 index 000000000..10685b285 --- /dev/null +++ b/app/Support/Holidays/data/MD.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"MD","language":"ro","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Anul Nou","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"christmas_day_by_old_style","name":"Nașterea lui Iisus Hristos (Crăciunul pe stil vechi)","en":"Christmas Day (by old style)","category":"public","dates":"d|0107:2014-2060|0108:2014-2060"},{"id":"international_women_s_day","name":"Ziua internațională a femeii","en":"International Women's Day","category":"public","dates":"d|0308:2010-2060"},{"id":"easter","name":"Paștele","en":"Easter","category":"public","dates":"d|0404:2010|0405:2010,2037,2048|0406:2037,2048|0408:2018,2029|0409:2018,2029,2034,2045,2056|0410:2034,2045,2056|0412:2015,2026|0413:2015,2026,2031,2042,2053|0414:2031,2042,2053,2058|0415:2012,2058|0416:2012,2017,2023,2028|0417:2017,2023,2028,2039,2050|0418:2039,2050,2055|0419:2020,2055|0420:2014,2020,2025,2036|0421:2014,2025,2036,2041,2047,2052|0422:2041,2047,2052|0424:2011,2022,2033,2044|0425:2011,2022,2033,2038,2044,2049,2060|0426:2038,2049,2060|0428:2019,2030|0429:2019,2030,2035,2046,2057|0430:2035,2046,2057|0501:2016|0502:2016,2021,2027,2032|0503:2021,2027,2032,2043,2054|0504:2043,2054,2059|0505:2013,2024,2059|0506:2013,2024,2040|0507:2040,2051|0508:2051"},{"id":"day_of_rejoicing","name":"Paștele blajinilor","en":"Day of Rejoicing","category":"public","dates":"d|0412:2010|0413:2037,2048|0416:2018,2029|0417:2034,2045,2056|0420:2015,2026|0421:2031,2042,2053|0422:2058|0423:2012|0424:2017,2023,2028|0425:2039,2050|0426:2055|0427:2020|0428:2014,2025,2036|0429:2041,2047,2052|0502:2011,2022,2033,2044|0503:2038,2049,2060|0506:2019,2030|0507:2035,2046,2057|0509:2016|0510:2021,2027,2032|0511:2043,2054|0512:2059|0513:2013,2024|0514:2040|0515:2051"},{"id":"international_workers_solidarity_day","name":"Ziua internațională a solidarității oamenilor muncii","en":"International Workers' Solidarity Day","category":"public","dates":"d|0501:2010-2060"},{"id":"europe_day","name":"Ziua Europei","en":"Europe Day","category":"public","dates":"d|0509:2017-2060"},{"id":"victory_day_and_commemoration_of_the_heroes_fall","name":"Ziua Victoriei și a comemorării eroilor căzuți pentru Independența Patriei","en":"Victory Day and Commemoration of the heroes fallen for Independence of Fatherland","category":"public","dates":"d|0509:2010-2060"},{"id":"international_children_s_day","name":"Ziua Ocrotirii Copilului","en":"International Children's Day","category":"public","dates":"d|0601:2016-2060"},{"id":"republic_of_moldova_independence_day","name":"Ziua independenței Republicii Moldova","en":"Republic of Moldova Independence Day","category":"public","dates":"d|0827:2010-2060"},{"id":"national_language_day","name":"Limba noastră","en":"National Language Day","category":"public","dates":"d|0831:2010-2060"},{"id":"christmas_day_by_new_style","name":"Nașterea lui Iisus Hristos (Crăciunul pe stil nou)","en":"Christmas Day (by new style)","category":"public","dates":"d|1225:2013-2060"}]} diff --git a/app/Support/Holidays/data/ME.json b/app/Support/Holidays/data/ME.json new file mode 100644 index 000000000..5b21825b5 --- /dev/null +++ b/app/Support/Holidays/data/ME.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"ME","language":"cnr","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Nova godina","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2010-2060|0103:2011-2012,2017,2022-2023,2028,2033-2034,2039-2040,2045,2050-2051,2056"},{"id":"labor_day","name":"Praznik rada","en":"Labor Day","category":"public","dates":"d|0501:2010-2060|0502:2010-2060|0503:2010-2011,2016,2021-2022,2027,2032-2033,2038-2039,2044,2049-2050,2055,2060"},{"id":"independence_day","name":"Dan nezavisnosti","en":"Independence Day","category":"public","dates":"d|0521:2010-2060|0522:2010-2060|0523:2011,2016-2017,2022-2023,2028,2033-2034,2039,2044-2045,2050-2051,2056"},{"id":"statehood_day","name":"Dan državnosti","en":"Statehood Day","category":"public","dates":"d|0713:2010-2060|0714:2010-2060|0715:2013-2014,2019,2024-2025,2030-2031,2036,2041-2042,2047,2052-2053,2058-2059"},{"id":"njegos_day","name":"Njegošev dan","en":"Njegos Day","category":"public","dates":"d|1113:2022-2060|1114:2022,2024,2033,2039,2044,2050"},{"id":"eid_al_fitr_islamic","name":"Ramazanski bajram","en":"Eid al-Fitr","category":"islamic","dates":"y|2010:0910-0912|2011:0830-0901|2012:0819-0821|2013:0808-0810|2014:0729-0731|2015:0718-0720|2016:0707-0709|2017:0626-0628|2018:0615-0617|2019:0604-0606|2020:0524-0526|2021:0513-0515|2022:0502-0504|2023:0421-0423|2024:0410-0412|2025:0330-0401|2026:0320-0322|2027:0309-0311|2028:0226-0228|2029:0214-0216|2030:0204-0206|2031:0124-0126|2032:0114-0116|2033:0102-0104,1223-1225|2034:1212-1214|2035:1201-1203|2036:1119-1121|2037:1108-1110|2038:1029-1031|2039:1019-1021|2040:1007-1009|2041:0926-0928|2042:0915-0917|2043:0904-0906|2044:0824-0826|2045:0814-0816|2046:0803-0805|2047:0724-0726|2048:0712-0714|2049:0701-0703|2050:0620-0622|2051:0610-0612|2052:0529-0531|2053:0519-0521|2054:0509-0511|2055:0428-0430|2056:0416-0418|2057:0405-0407|2058:0325-0327|2059:0315-0317|2060:0304-0306","estimated":"y|2010:0910-0912|2011:0830-0901|2025:0330-0401|2026:0320-0322|2027:0309-0311|2028:0226-0228|2029:0214-0216|2030:0204-0206|2031:0124-0126|2032:0114-0116|2033:0102-0104,1223-1225|2034:1212-1214|2035:1201-1203|2036:1119-1121|2037:1108-1110|2038:1029-1031|2039:1019-1021|2040:1007-1009|2041:0926-0928|2042:0915-0917|2043:0904-0906|2044:0824-0826|2045:0814-0816|2046:0803-0805|2047:0724-0726|2048:0712-0714|2049:0701-0703|2050:0620-0622|2051:0610-0612|2052:0529-0531|2053:0519-0521|2054:0509-0511|2055:0428-0430|2056:0416-0418|2057:0405-0407|2058:0325-0327|2059:0315-0317|2060:0304-0306"},{"id":"eid_al_adha_islamic","name":"Kurbanski bajram","en":"Eid al-Adha","category":"islamic","dates":"y|2010:1116-1118|2011:1106-1108|2012:1026-1028|2013:1015-1017|2014:1005-1007|2015:0924-0926|2016:0913-0915|2017:0902-0904|2018:0822-0824|2019:0811-0813|2020:0731-0802|2021:0720-0722|2022:0709-0711|2023:0628-0630|2024:0616-0618|2025:0606-0608|2026:0527-0529|2027:0516-0518|2028:0505-0507|2029:0424-0426|2030:0413-0415|2031:0402-0404|2032:0322-0324|2033:0311-0313|2034:0301-0303|2035:0218-0220|2036:0207-0209|2037:0126-0128|2038:0116-0118|2039:0105-0107,1226-1228|2040:1214-1216|2041:1204-1206|2042:1123-1125|2043:1112-1114|2044:1031-1102|2045:1021-1023|2046:1010-1012|2047:0930-1002|2048:0919-0921|2049:0908-0910|2050:0828-0830|2051:0817-0819|2052:0805-0807|2053:0726-0728|2054:0715-0717|2055:0705-0707|2056:0623-0625|2057:0612-0614|2058:0601-0603|2059:0522-0524|2060:0510-0512","estimated":"y|2010:1116-1118|2011:1106-1108|2025:0606-0608|2026:0527-0529|2027:0516-0518|2028:0505-0507|2029:0424-0426|2030:0413-0415|2031:0402-0404|2032:0322-0324|2033:0311-0313|2034:0301-0303|2035:0218-0220|2036:0207-0209|2037:0126-0128|2038:0116-0118|2039:0105-0107,1226-1228|2040:1214-1216|2041:1204-1206|2042:1123-1125|2043:1112-1114|2044:1031-1102|2045:1021-1023|2046:1010-1012|2047:0930-1002|2048:0919-0921|2049:0908-0910|2050:0828-0830|2051:0817-0819|2052:0805-0807|2053:0726-0728|2054:0715-0717|2055:0705-0707|2056:0623-0625|2057:0612-0614|2058:0601-0603|2059:0522-0524|2060:0510-0512"},{"id":"christmas_eve_orthodox","name":"Badnji dan","en":"Christmas Eve","category":"orthodox","dates":"d|0106:2010-2060"},{"id":"christmas_orthodox","name":"Božić","en":"Christmas","category":"orthodox","dates":"d|0107:2010-2060|0108:2010-2060"},{"id":"good_friday_catholic","name":"Veliki petak","en":"Good Friday","category":"catholic","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_catholic","name":"Uskrs","en":"Easter","category":"catholic","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"pesach_hebrew","name":"Pasha","en":"Pesach","category":"hebrew","dates":"d|0326:2013|0327:2013,2032|0328:2021,2032,2051|0329:2021,2040,2048,2051,2059|0330:2010,2040,2048,2059|0331:2010,2018,2029,2037|0401:2018,2029,2037,2056|0402:2026,2045,2056|0403:2026,2045,2053|0404:2015,2034,2053|0405:2015,2034,2042|0406:2023,2042|0407:2012,2023,2050|0408:2012,2031,2050|0409:2020,2031,2039,2058|0410:2020,2039,2058|0411:2017,2028,2047|0412:2017,2028,2036,2044,2047|0413:2025,2036,2044,2055|0414:2025,2033,2052,2055|0415:2014,2033,2052,2060|0416:2014,2022,2041,2060|0417:2022,2041,2049|0418:2030,2049|0419:2011,2030,2057|0420:2011,2019,2038,2057|0421:2019,2038,2046|0422:2027,2046|0423:2016,2024,2027,2054|0424:2016,2024,2035,2054|0425:2035,2043|0426:2043"},{"id":"good_friday_orthodox","name":"Veliki petak","en":"Good Friday","category":"orthodox","dates":"d|0402:2010|0403:2037,2048|0406:2018,2029|0407:2034,2045,2056|0410:2015,2026|0411:2031,2042,2053|0412:2058|0413:2012|0414:2017,2023,2028|0415:2039,2050|0416:2055|0417:2020|0418:2014,2025,2036|0419:2041,2047,2052|0422:2011,2022,2033,2044|0423:2038,2049,2060|0426:2019,2030|0427:2035,2046,2057|0429:2016|0430:2021,2027,2032|0501:2043,2054|0502:2059|0503:2013,2024|0504:2040|0505:2051"},{"id":"easter_orthodox","name":"Uskrs","en":"Easter","category":"orthodox","dates":"d|0405:2010|0406:2037,2048|0409:2018,2029|0410:2034,2045,2056|0413:2015,2026|0414:2031,2042,2053|0415:2058|0416:2012|0417:2017,2023,2028|0418:2039,2050|0419:2055|0420:2020|0421:2014,2025,2036|0422:2041,2047,2052|0425:2011,2022,2033,2044|0426:2038,2049,2060|0429:2019,2030|0430:2035,2046,2057|0502:2016|0503:2021,2027,2032|0504:2043,2054|0505:2059|0506:2013,2024|0507:2040|0508:2051"},{"id":"yom_kippur_hebrew","name":"Jom Kipur","en":"Yom Kippur","category":"hebrew","dates":"d|0914:2013|0915:2013,2032|0916:2021,2032,2051|0917:2021,2040,2048,2051,2059|0918:2010,2040,2048,2059|0919:2010,2018,2029,2037|0920:2018,2029,2037,2056|0921:2026,2045,2056|0922:2026,2045,2053|0923:2015,2034,2053|0924:2015,2034,2042|0925:2023,2042|0926:2012,2023,2050|0927:2012,2031,2050|0928:2020,2031,2039,2058|0929:2020,2039,2058|0930:2017,2028,2047|1001:2017,2028,2036,2044,2047|1002:2025,2036,2044,2055|1003:2025,2033,2052,2055|1004:2014,2033,2052,2060|1005:2014,2022,2041,2060|1006:2022,2041,2049|1007:2030,2049|1008:2011,2030,2057|1009:2011,2019,2038,2057|1010:2019,2038,2046|1011:2027,2046|1012:2016,2024,2027,2054|1013:2016,2024,2035,2054|1014:2035,2043|1015:2043"},{"id":"all_saints_day_catholic","name":"Svi Sveti","en":"All Saints' Day","category":"catholic","dates":"d|1101:2010-2060"},{"id":"christmas_eve_catholic","name":"Badnji dan","en":"Christmas Eve","category":"catholic","dates":"d|1224:2010-2060"},{"id":"christmas_catholic","name":"Božić","en":"Christmas","category":"catholic","dates":"d|1225:2010-2060|1226:2010-2060"}]} diff --git a/app/Support/Holidays/data/MF.json b/app/Support/Holidays/data/MF.json new file mode 100644 index 000000000..56a7cd36b --- /dev/null +++ b/app/Support/Holidays/data/MF.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"MF","language":"fr","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Jour de l'an","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"easter_monday","name":"Lundi de Pâques","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"labor_day","name":"Fête du Travail","en":"Labor Day","category":"public","dates":"d|0501:2010-2060"},{"id":"ascension_day","name":"Ascension","en":"Ascension Day","category":"public","dates":"d|0503:2035,2046|0505:2016|0506:2027,2032|0507:2043,2054|0508:2059|0509:2013,2024|0510:2018,2029,2040|0511:2051,2056|0513:2010,2021|0514:2015,2026,2037,2048|0515:2042,2053|0517:2012|0518:2023,2034,2045|0519:2039,2050|0521:2020|0522:2031,2036|0523:2047,2058|0525:2017,2028|0526:2022,2033,2044|0527:2049,2055,2060|0529:2014,2025|0530:2019,2030,2041,2052|0531:2057|0602:2011|0603:2038"},{"id":"victory_day","name":"Fête de la Victoire","en":"Victory Day","category":"public","dates":"d|0508:2010-2060"},{"id":"pentecost_monday","name":"Lundi de Pentecôte","en":"Pentecost Monday","category":"public","dates":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038"},{"id":"abolition_of_slavery","name":"Abolition de l'esclavage","en":"Abolition of Slavery","category":"public","dates":"d|0528:2012-2060"},{"id":"national_day","name":"Fête nationale","en":"National Day","category":"public","dates":"d|0714:2010-2060"},{"id":"victor_schoelcher_day","name":"Fête de Victor Schoelcher","en":"Victor Schoelcher Day","category":"public","dates":"d|0721:2012-2060"},{"id":"assumption_day","name":"Assomption","en":"Assumption Day","category":"public","dates":"d|0815:2010-2060"},{"id":"all_saints_day","name":"Toussaint","en":"All Saints' Day","category":"public","dates":"d|1101:2010-2060"},{"id":"armistice_day","name":"Armistice","en":"Armistice Day","category":"public","dates":"d|1111:2010-2060"},{"id":"christmas_day","name":"Noël","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"}]} diff --git a/app/Support/Holidays/data/MG.json b/app/Support/Holidays/data/MG.json new file mode 100644 index 000000000..b88599336 --- /dev/null +++ b/app/Support/Holidays/data/MG.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"MG","language":"mg","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Taom-baovao","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"women_s_day","name":"Fetin'ny vehivavy","en":"Women's Day","category":"public","dates":"d|0308:2010-2060"},{"id":"easter_sunday","name":"Fetin'ny paska","en":"Easter Sunday","category":"public","dates":"d|0325:2035,2046|0327:2016|0328:2027,2032|0329:2043,2054|0330:2059|0331:2013,2024|0401:2018,2029,2040|0402:2051,2056|0404:2010,2021|0405:2015,2026,2037,2048|0406:2042,2053|0408:2012|0409:2023,2034,2045|0410:2039,2050|0412:2020|0413:2031,2036|0414:2047,2058|0416:2017,2028|0417:2022,2033,2044|0418:2049,2055,2060|0420:2014,2025|0421:2019,2030,2041,2052|0422:2057|0424:2011|0425:2038"},{"id":"easter_monday","name":"Alatsinain'ny paska","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"martyrs_day","name":"Fetin'ny mahery fo","en":"Martyrs' Day","category":"public","dates":"d|0329:2010-2060"},{"id":"labor_day","name":"Fetin'ny asa","en":"Labor Day","category":"public","dates":"d|0501:2010-2060"},{"id":"ascension_day","name":"Fiakaran'ny Jesosy kristy tany an-danitra","en":"Ascension Day","category":"public","dates":"d|0503:2035,2046|0505:2016|0506:2027,2032|0507:2043,2054|0508:2059|0509:2013,2024|0510:2018,2029,2040|0511:2051,2056|0513:2010,2021|0514:2015,2026,2037,2048|0515:2042,2053|0517:2012|0518:2023,2034,2045|0519:2039,2050|0521:2020|0522:2031,2036|0523:2047,2058|0525:2017,2028|0526:2022,2033,2044|0527:2049,2055,2060|0529:2014,2025|0530:2019,2030,2041,2052|0531:2057|0602:2011|0603:2038"},{"id":"pentecost","name":"Pentekosta","en":"Pentecost","category":"public","dates":"d|0513:2035,2046|0515:2016|0516:2027,2032|0517:2043,2054|0518:2059|0519:2013,2024|0520:2018,2029,2040|0521:2051,2056|0523:2010,2021|0524:2015,2026,2037,2048|0525:2042,2053|0527:2012|0528:2023,2034,2045|0529:2039,2050|0531:2020|0601:2031,2036|0602:2047,2058|0604:2017,2028|0605:2022,2033,2044|0606:2049,2055,2060|0608:2014,2025|0609:2019,2030,2041,2052|0610:2057|0612:2011|0613:2038"},{"id":"pentecost_monday","name":"Alatsinain'ny pentekosta","en":"Pentecost Monday","category":"public","dates":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038"},{"id":"mother_s_day","name":"Fetin'ny reny","en":"Mother's Day","category":"public","dates":"d|0525:2014,2025,2031,2036,2059|0526:2013,2019,2024,2030,2041,2047,2052,2058|0527:2018,2029,2035,2040,2046,2057|0528:2017,2028,2051,2056|0529:2011,2016,2022,2033,2044|0530:2010,2021,2027,2032,2038,2049,2055,2060|0531:2015,2026,2037,2043,2048,2054|0601:2042,2053|0603:2012|0604:2023,2034,2045|0605:2039,2050|0607:2020"},{"id":"father_s_day","name":"Fetin'ny ray","en":"Father's Day","category":"public","dates":"d|0615:2014,2025,2031,2036,2042,2053,2059|0616:2013,2019,2024,2030,2041,2047,2052,2058|0617:2012,2018,2029,2035,2040,2046,2057|0618:2017,2023,2028,2034,2045,2051,2056|0619:2011,2016,2022,2033,2039,2044,2050|0620:2010,2021,2027,2032,2038,2049,2055,2060|0621:2015,2020,2026,2037,2043,2048,2054"},{"id":"independence_day","name":"Fetin'ny fahaleovantena","en":"Independence Day","category":"public","dates":"d|0626:2010-2060"},{"id":"assumption_day","name":"Fiakaran'ny Masina Maria tany an-danitra","en":"Assumption Day","category":"public","dates":"d|0815:2010-2060"},{"id":"all_saints_day","name":"Fetin'ny olo-masina","en":"All Saints' Day","category":"public","dates":"d|1101:2010-2060"},{"id":"republic_day","name":"Fetin'ny Repoblika","en":"Republic Day","category":"public","dates":"d|1211:2011-2060"},{"id":"christmas_day","name":"Fetin'ny noely","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"}]} diff --git a/app/Support/Holidays/data/MH.json b/app/Support/Holidays/data/MH.json new file mode 100644 index 000000000..a90aff62f --- /dev/null +++ b/app/Support/Holidays/data/MH.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"MH","language":null,"years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"new_year_s_day_holiday","name":"New Year's Day Holiday","en":"New Year's Day Holiday","category":"public","dates":"d|0102:2012,2017,2023,2034,2040,2045,2051|1231:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"nuclear_victims_remembrance_day_holiday","name":"Nuclear Victims Remembrance Day Holiday","en":"Nuclear Victims Remembrance Day Holiday","category":"public","dates":"d|0228:2014,2025,2031,2042,2053,2059|0229:2036|0302:2015,2020,2026,2037,2043,2048,2054"},{"id":"nuclear_victims_remembrance_day","name":"Nuclear Victims Remembrance Day","en":"Nuclear Victims Remembrance Day","category":"public","dates":"d|0301:2010-2060"},{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"constitution_day_holiday","name":"Constitution Day Holiday","en":"Constitution Day Holiday","category":"public","dates":"d|0430:2010,2021,2027,2032,2038,2049,2055,2060|0502:2011,2016,2022,2033,2039,2044,2050"},{"id":"constitution_day","name":"Constitution Day","en":"Constitution Day","category":"public","dates":"d|0501:2010-2060"},{"id":"fisherman_s_day","name":"Fisherman's Day","en":"Fisherman's Day","category":"public","dates":"d|0701:2011,2016,2022,2033,2039,2044,2050|0702:2010,2021,2027,2032,2038,2049,2055,2060|0703:2015,2020,2026,2037,2043,2048,2054|0704:2014,2025,2031,2036,2042,2053,2059|0705:2013,2019,2024,2030,2041,2047,2052,2058|0706:2012,2018,2029,2035,2040,2046,2057|0707:2017,2023,2028,2034,2045,2051,2056"},{"id":"dri_jerbal_day","name":"Dri-jerbal Day","en":"Dri-jerbal Day","category":"public","dates":"d|0901:2017,2023,2028,2034,2045,2051,2056|0902:2011,2016,2022,2033,2039,2044,2050|0903:2010,2021,2027,2032,2038,2049,2055,2060|0904:2015,2020,2026,2037,2043,2048,2054|0905:2014,2025,2031,2036,2042,2053,2059|0906:2013,2019,2024,2030,2041,2047,2052,2058|0907:2012,2018,2029,2035,2040,2046,2057"},{"id":"manit_day","name":"Manit Day","en":"Manit Day","category":"public","dates":"d|0924:2010,2021,2027,2032,2038,2049,2055,2060|0925:2015,2020,2026,2037,2043,2048,2054|0926:2014,2025,2031,2036,2042,2053,2059|0927:2013,2019,2024,2030,2041,2047,2052,2058|0928:2012,2018,2029,2035,2040,2046,2057|0929:2017,2023,2028,2034,2045,2051,2056|0930:2011,2016,2022,2033,2039,2044,2050"},{"id":"general_election_day","name":"General Election Day","en":"General Election Day","category":"public","dates":"d|1115:2027,2055|1116:2015,2043|1117:2031,2059|1118:2019,2047|1119:2035|1120:2023,2051|1121:2039"},{"id":"president_s_day_holiday","name":"President's Day Holiday","en":"President's Day Holiday","category":"public","dates":"d|1116:2012,2018,2029,2035,2040,2046,2057|1118:2013,2019,2024,2030,2041,2047,2052,2058"},{"id":"president_s_day","name":"President's Day","en":"President's Day","category":"public","dates":"d|1117:2010-2060"},{"id":"gospel_day","name":"Gospel Day","en":"Gospel Day","category":"public","dates":"d|1201:2017,2023,2028,2034,2045,2051,2056|1202:2011,2016,2022,2033,2039,2044,2050|1203:2010,2021,2027,2032,2038,2049,2055,2060|1204:2015,2020,2026,2037,2043,2048,2054|1205:2014,2025,2031,2036,2042,2053,2059|1206:2013,2019,2024,2030,2041,2047,2052,2058|1207:2012,2018,2029,2035,2040,2046,2057"},{"id":"christmas_day_holiday","name":"Christmas Day Holiday","en":"Christmas Day Holiday","category":"public","dates":"d|1224:2010,2021,2027,2032,2038,2049,2055,2060|1226:2011,2016,2022,2033,2039,2044,2050"},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"}]} diff --git a/app/Support/Holidays/data/MK.json b/app/Support/Holidays/data/MK.json new file mode 100644 index 000000000..fb4cd7bca --- /dev/null +++ b/app/Support/Holidays/data/MK.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"MK","language":"mk","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Нова Година","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051"},{"id":"eid_al_fitr","name":"Рамазан Бајрам","en":"Eid al-Fitr","category":"public","dates":"y|2010:0909|2011:0830|2012:0819-0820|2013:0808|2014:0728|2015:0717|2016:0705|2017:0625-0626|2018:0615|2019:0604|2020:0524-0525|2021:0513|2022:0502|2023:0421|2024:0410|2025:0330-0331|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102-0103,1223|2034:1212|2035:1201|2036:1119|2037:1108-1109|2038:1029|2039:1019|2040:1007-1008|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712-0713|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416-0417|2057:0405|2058:0325|2059:0315|2060:0304","estimated":"y|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304"},{"id":"christmas_day","name":"Божиќ","en":"Christmas Day","category":"public","dates":"d|0107:2010-2060|0108:2018,2024,2029,2035,2046,2052,2057"},{"id":"easter_monday","name":"Велигден","en":"Easter Monday","category":"public","dates":"d|0405:2010|0406:2037,2048|0409:2018,2029|0410:2034,2045,2056|0413:2015,2026|0414:2031,2042,2053|0415:2058|0416:2012|0417:2017,2023,2028|0418:2039,2050|0419:2055|0420:2020|0421:2014,2025,2036|0422:2041,2047,2052|0425:2011,2022,2033,2044|0426:2038,2049,2060|0429:2019,2030|0430:2035,2046,2057|0502:2016|0503:2021,2027,2032|0504:2043,2054|0505:2059|0506:2013,2024|0507:2040|0508:2051"},{"id":"labor_day","name":"Ден на трудот","en":"Labor Day","category":"public","dates":"d|0501:2010-2060|0502:2011,2016,2022,2033,2039,2044,2050"},{"id":"saints_cyril_and_methodius_day","name":"Светите Кирил и Методиј - Ден на сесловенските просветители","en":"Saints Cyril and Methodius Day","category":"public","dates":"d|0524:2010-2060|0525:2015,2020,2026,2037,2043,2048,2054"},{"id":"republic_day","name":"Ден на Републиката","en":"Republic Day","category":"public","dates":"d|0802:2010-2060|0803:2015,2020,2026,2037,2043,2048,2054"},{"id":"independence_day","name":"Ден на независноста","en":"Independence Day","category":"public","dates":"d|0908:2010-2060|0909:2013,2019,2024,2030,2041,2047,2052,2058"},{"id":"national_uprising_day","name":"Ден на народното востание","en":"National Uprising Day","category":"public","dates":"d|1011:2010-2060|1012:2015,2020,2026,2037,2043,2048,2054"},{"id":"macedonian_revolutionary_struggle_day","name":"Ден на македонската револуционерна борба","en":"Macedonian Revolutionary Struggle Day","category":"public","dates":"d|1023:2010-2060|1024:2011,2016,2022,2033,2039,2044,2050"},{"id":"saint_clement_of_ohrid_day","name":"Свети Климент Охридски","en":"Saint Clement of Ohrid Day","category":"public","dates":"d|1208:2010-2060|1209:2013,2019,2024,2030,2041,2047,2052,2058"},{"id":"eid_al_adha_islamic","name":"Курбан Бајрам","en":"Eid al-Adha","category":"islamic","dates":"y|2010:1116|2011:1106|2012:1025|2013:1015|2014:1004|2015:0924|2016:0912|2017:0901|2018:0821|2019:0811|2020:0731|2021:0720|2022:0709|2023:0628|2024:0616|2025:0606|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510","estimated":"y|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510"},{"id":"christmas_eve_orthodox","name":"Бадник","en":"Christmas Eve","category":"orthodox","dates":"d|0106:2010-2060"},{"id":"epiphany_orthodox","name":"Богојавление","en":"Epiphany","category":"orthodox","dates":"d|0119:2010-2060"},{"id":"saint_sava_s_day_serbian","name":"Свети Сава","en":"Saint Sava's Day","category":"serbian","dates":"d|0127:2010-2060"},{"id":"easter_monday_catholic","name":"Велигден","en":"Easter Monday","category":"catholic","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"good_friday_orthodox","name":"Велики Петок","en":"Good Friday","category":"orthodox","dates":"d|0402:2010|0403:2037,2048|0406:2018,2029|0407:2034,2045,2056|0410:2015,2026|0411:2031,2042,2053|0412:2058|0413:2012|0414:2017,2023,2028|0415:2039,2050|0416:2055|0417:2020|0418:2014,2025,2036|0419:2041,2047,2052|0422:2011,2022,2033,2044|0423:2038,2049,2060|0426:2019,2030|0427:2035,2046,2057|0429:2016|0430:2021,2027,2032|0501:2043,2054|0502:2059|0503:2013,2024|0504:2040|0505:2051"},{"id":"international_romani_day_roma","name":"Меѓународен ден на Ромите","en":"International Romani Day","category":"roma","dates":"d|0408:2010-2060"},{"id":"pentecost_orthodox","name":"Духовден","en":"Pentecost","category":"orthodox","dates":"d|0521:2010|0522:2037,2048|0525:2018,2029|0526:2034,2045,2056|0529:2015,2026|0530:2031,2042,2053|0531:2058|0601:2012|0602:2017,2023,2028|0603:2039,2050|0604:2055|0605:2020|0606:2014,2025,2036|0607:2041,2047,2052|0610:2011,2022,2033,2044|0611:2038,2049,2060|0614:2019,2030|0615:2035,2046,2057|0617:2016|0618:2021,2027,2032|0619:2043,2054|0620:2059|0621:2013,2024|0622:2040|0623:2051"},{"id":"vlachs_national_day_vlach","name":"Национален ден на Власите","en":"Vlachs National Day","category":"vlach","dates":"d|0523:2010-2060"},{"id":"dormition_of_the_mother_of_god_orthodox","name":"Успение на Пресвета Богородица","en":"Dormition of the Mother of God","category":"orthodox","dates":"d|0828:2010-2060"},{"id":"yom_kippur_hebrew","name":"Јом Кипур","en":"Yom Kippur","category":"hebrew","dates":"d|0914:2013|0915:2032|0916:2021,2051|0917:2040,2048,2059|0918:2010|0919:2018,2029,2037|0920:2056|0921:2026,2045|0922:2053|0923:2015,2034|0924:2042|0925:2023|0926:2012,2050|0927:2031|0928:2020,2039,2058|0930:2017,2028,2047|1001:2036,2044|1002:2025,2055|1003:2033,2052|1004:2014,2060|1005:2022,2041|1006:2049|1007:2030|1008:2011,2057|1009:2019,2038|1010:2046|1011:2027|1012:2016,2024,2054|1013:2035|1014:2043"},{"id":"international_bosniaks_day_bosnian","name":"Меѓународен ден на Бошњаците","en":"International Bosniaks Day","category":"bosnian","dates":"d|0928:2010-2060"},{"id":"all_saints_day_catholic","name":"Сите Светци","en":"All Saints' Day","category":"catholic","dates":"d|1101:2010-2060"},{"id":"albanian_alphabet_day_albanian","name":"Ден на Албанската азбука","en":"Albanian Alphabet Day","category":"albanian","dates":"d|1122:2010-2060"},{"id":"turkish_language_teaching_day_turkish","name":"Ден на настава на турски јазик","en":"Turkish Language Teaching Day","category":"turkish","dates":"d|1221:2010-2060"},{"id":"christmas_day_catholic","name":"Божиќ","en":"Christmas Day","category":"catholic","dates":"d|1225:2010-2060"}]} diff --git a/app/Support/Holidays/data/ML.json b/app/Support/Holidays/data/ML.json new file mode 100644 index 000000000..568c2bf1d --- /dev/null +++ b/app/Support/Holidays/data/ML.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"ML","language":"fr","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Jour de l'An","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"eid_al_fitr","name":"Journée de la Fête du Ramadan","en":"Eid al-Fitr","category":"public","dates":"y|2010:0910|2011:0830|2012:0819|2013:0808|2014:0728|2015:0718|2016:0707|2017:0626|2018:0615|2019:0603|2020:0523|2021:0512|2022:0501|2023:0421|2024:0409|2025:0330|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304","estimated":"y|2010:0910|2011:0830|2012:0819|2013:0808|2014:0728|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304"},{"id":"prophet_s_birthday","name":"Journée du Maouloud (Naissance du Prophète)","en":"Prophet's Birthday","category":"public","dates":"y|2010:0226|2011:0215|2012:0204|2013:0124|2014:0113|2015:0103,1224|2016:1212|2017:1201|2018:1121|2019:1110|2020:1029|2021:1019|2022:1009|2023:0928|2024:0916|2025:0904|2026:0825|2027:0814|2028:0803|2029:0724|2030:0713|2031:0702|2032:0620|2033:0609|2034:0530|2035:0520|2036:0508|2037:0428|2038:0417|2039:0406|2040:0325|2041:0315|2042:0304|2043:0222|2044:0211|2045:0130|2046:0119|2047:0108,1229|2048:1218|2049:1207|2050:1126|2051:1116|2052:1104|2053:1024|2054:1013|2055:1003|2056:0922|2057:0911|2058:0831|2059:0820|2060:0808","estimated":"y|2010:0226|2011:0215|2012:0204|2013:0124|2014:0113|2025:0904|2026:0825|2027:0814|2028:0803|2029:0724|2030:0713|2031:0702|2032:0620|2033:0609|2034:0530|2035:0520|2036:0508|2037:0428|2038:0417|2039:0406|2040:0325|2041:0315|2042:0304|2043:0222|2044:0211|2045:0130|2046:0119|2047:0108,1229|2048:1218|2049:1207|2050:1126|2051:1116|2052:1104|2053:1024|2054:1013|2055:1003|2056:0922|2057:0911|2058:0831|2059:0820|2060:0808"},{"id":"eid_al_adha","name":"Journée de la Tabaski","en":"Eid al-Adha","category":"public","dates":"y|2010:1116|2011:1106|2012:1026|2013:1015|2014:1004|2015:0924|2016:0913|2017:0902|2018:0822|2019:0811|2020:0731|2021:0721|2022:0709|2023:0628|2024:0617|2025:0607|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510","estimated":"y|2010:1116|2011:1106|2012:1026|2013:1015|2014:1004|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510"},{"id":"prophet_s_baptism","name":"Journée du Maouloud (Baptême du Prophète)","en":"Prophet's Baptism","category":"public","dates":"y|2010:0305|2011:0222|2012:0211|2013:0131|2014:0120|2015:0110,1231|2016:1219|2017:1208|2018:1128|2019:1117|2020:1105|2021:1026|2022:1016|2023:1005|2024:0923|2025:0911|2026:0901|2027:0821|2028:0810|2029:0731|2030:0720|2031:0709|2032:0627|2033:0616|2034:0606|2035:0527|2036:0515|2037:0505|2038:0424|2039:0413|2040:0401|2041:0322|2042:0311|2043:0301|2044:0218|2045:0206|2046:0126|2047:0115|2048:0105,1225|2049:1214|2050:1203|2051:1123|2052:1111|2053:1031|2054:1020|2055:1010|2056:0929|2057:0918|2058:0907|2059:0827|2060:0815","estimated":"y|2010:0305|2011:0222|2012:0211|2013:0131|2014:0120|2025:0911|2026:0901|2027:0821|2028:0810|2029:0731|2030:0720|2031:0709|2032:0627|2033:0616|2034:0606|2035:0527|2036:0515|2037:0505|2038:0424|2039:0413|2040:0401|2041:0322|2042:0311|2043:0301|2044:0218|2045:0206|2046:0126|2047:0115|2048:0105,1225|2049:1214|2050:1203|2051:1123|2052:1111|2053:1031|2054:1020|2055:1010|2056:0929|2057:0918|2058:0907|2059:0827|2060:0815"},{"id":"armed_forces_day","name":"Journée de l'Armée","en":"Armed Forces Day","category":"public","dates":"d|0120:2010-2060"},{"id":"easter_monday","name":"Lundi de Pâques","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"martyrs_day","name":"Journée du 26 mars","en":"Martyrs' Day","category":"public","dates":"d|0326:2010-2060"},{"id":"labor_day","name":"Fête du Travail","en":"Labor Day","category":"public","dates":"d|0501:2010-2060"},{"id":"africa_day","name":"Journée de l'Afrique","en":"Africa Day","category":"public","dates":"d|0525:2010-2060"},{"id":"national_day_of_the_republic_of_mali","name":"Fête Nationale de la République du Mali","en":"National Day of the Republic of Mali","category":"public","dates":"d|0922:2010-2060"},{"id":"christmas_day","name":"Fête de Noël","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"}]} diff --git a/app/Support/Holidays/data/MM.json b/app/Support/Holidays/data/MM.json new file mode 100644 index 000000000..4d37a5fbf --- /dev/null +++ b/app/Support/Holidays/data/MM.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"MM","language":"my","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"karen_new_year","name":"ကရင်နှစ်သစ်ကူးနေ့","en":"Karen New Year","category":"public","dates":"d|0101:2014,2033,2052|0102:2022,2041|0103:2060|0104:2030,2049|0105:2011,2057|0106:2019,2038|0107:2046|0108:2027|0109:2035,2054|0110:2016|0111:2024,2043|0112:2013,2051|0113:2021,2032|0114:2040,2059|1216:2028,2047|1217:2036|1218:2017,2055|1219:2025,2044|1220:2052|1221:2014,2033|1222:2022,2041,2060|1224:2030,2049|1225:2011,2057|1226:2019,2038|1227:2046|1228:2027|1229:2016,2035,2054|1230:2024|1231:2043"},{"id":"new_year_s_day","name":"နိုင်ငံတကာနှစ်သစ်ကူးနေ့","en":"New Year's Day","category":"public","dates":"d|0101:2025-2060"},{"id":"independence_day","name":"လွတ်လပ်ရေးနေ့","en":"Independence Day","category":"public","dates":"d|0104:2010-2060"},{"id":"eid_al_adha","name":"အီဒုလ်အဿွဟာနေ့","en":"Eid al-Adha","category":"public","dates":"y|2010:1117|2011:1107|2012:1027|2013:1016|2014:1005|2015:0924|2016:0912|2017:0902|2018:0822|2019:0812|2020:0801|2021:0721|2022:0710|2023:0629|2024:0617|2025:0607|2026:0528|2027:0517|2028:0506|2029:0425|2030:0414|2031:0403|2032:0323|2033:0312|2034:0302|2035:0219|2036:0208|2037:0127|2038:0117|2039:0106,1227|2040:1215|2041:1205|2042:1124|2043:1113|2044:1101|2045:1022|2046:1011|2047:1001|2048:0920|2049:0909|2050:0829|2051:0818|2052:0806|2053:0727|2054:0716|2055:0706|2056:0624|2057:0613|2058:0602|2059:0523|2060:0511","estimated":"y|2010:1117|2011:1107|2012:1027|2013:1016|2014:1005|2015:0924|2016:0912|2017:0902|2018:0822|2019:0812|2026:0528|2027:0517|2028:0506|2029:0425|2030:0414|2031:0403|2032:0323|2033:0312|2034:0302|2035:0219|2036:0208|2037:0127|2038:0117|2039:0106,1227|2040:1215|2041:1205|2042:1124|2043:1113|2044:1101|2045:1022|2046:1011|2047:1001|2048:0920|2049:0909|2050:0829|2051:0818|2052:0806|2053:0727|2054:0716|2055:0706|2056:0624|2057:0613|2058:0602|2059:0523|2060:0511"},{"id":"chinese_new_year","name":"တရုတ်နှစ်သစ်ကူးနေ့","en":"Chinese New Year","category":"public","dates":"d|0122:2042|0123:2031,2050|0124:2039,2058|0126:2028,2047|0128:2036,2055|0129:2025|0130:2044|0131:2033|0201:2041,2052|0202:2049,2060|0203:2030|0204:2038,2057|0206:2027,2046|0208:2035,2054|0210:2043|0211:2032,2051|0212:2040,2059|0213:2029|0214:2048|0215:2037,2056|0217:2026,2045|0219:2034,2053"},{"id":"union_day","name":"ပြည်ထောင်စုနေ့","en":"Union Day","category":"public","dates":"d|0212:2010-2060"},{"id":"full_moon_day_of_tabaung","name":"တပေါင်းလပြည့်နေ့","en":"Full Moon Day of Tabaung","category":"public","dates":"d|0227:2010,2029,2048|0228:2037|0229:2056|0301:2018|0302:2026,2045|0303:2053|0304:2015,2034|0305:2023,2042|0307:2012,2031,2050|0308:2020,2058|0309:2039|0310:2028,2047|0311:2036|0312:2017,2055|0313:2025,2044|0314:2052|0315:2014,2033|0316:2022,2041,2060|0318:2030,2049|0319:2011,2057|0320:2019,2038|0321:2046|0322:2027|0323:2016,2035,2054|0324:2024|0325:2043|0326:2013,2032,2051|0327:2021,2040|0328:2059"},{"id":"peasants_day","name":"တောင်သူလယ်သမားနေ့","en":"Peasants' Day","category":"public","dates":"d|0302:2010-2060"},{"id":"armed_forces_day","name":"တပ်မတော်နေ့","en":"Armed Forces Day","category":"public","dates":"d|0327:2010-2060"},{"id":"myanmar_new_year","name":"မြန်မာနှစ်သစ်ကူး ရုံးပိတ်ရက်များ","en":"Myanmar New Year","category":"public","dates":"d|0409:2022-2023|0410:2022-2023|0411:2012,2016,2022-2023|0412:2010-2016,2020,2022-2023|0413:2010-2050,2052-2054,2056-2058,2060|0414:2010-2060|0415:2010-2060|0416:2010-2060|0417:2010-2016,2024-2060|0418:2010-2016,2024-2060|0419:2010-2016,2024-2060|0420:2010-2016,2024-2060|0421:2010-2011,2013-2015,2024-2060|0422:2051,2055,2059"},{"id":"full_moon_day_of_kason","name":"ကဆုန်လပြည့်နေ့","en":"Full Moon Day of Kason","category":"public","dates":"d|0426:2048|0427:2010,2029|0428:2037,2056|0429:2018|0430:2026,2045|0501:2053|0502:2015,2034|0503:2023,2042|0505:2012,2031,2050|0506:2020,2058|0507:2039|0508:2028,2047|0509:2036|0510:2017,2055|0511:2025,2044|0512:2052|0513:2014,2033|0514:2022,2041,2060|0516:2030,2049|0517:2011,2057|0518:2019,2038|0519:2046|0520:2027|0521:2016,2035,2054|0522:2024|0523:2043|0524:2013,2032,2051|0525:2021,2040|0526:2059"},{"id":"may_day","name":"မေဒေးနေ့","en":"May Day","category":"public","dates":"d|0501:2010-2060"},{"id":"full_moon_day_of_waso","name":"ဝါဆိုလပြည့်နေ့","en":"Full Moon Day of Waso","category":"public","dates":"d|0706:2028,2047|0707:2036|0708:2017,2055|0709:2025,2044|0710:2052|0711:2014,2033|0712:2022,2041,2060|0714:2030,2049|0715:2011,2057|0716:2019,2038|0717:2046|0718:2027|0719:2016,2035,2054|0720:2024|0721:2043|0722:2013,2032,2051|0723:2021,2040|0724:2059|0725:2029,2048|0726:2010,2056|0727:2018,2037|0728:2045|0729:2026|0730:2034,2053|0731:2015|0801:2023,2042|0802:2012,2050|0803:2020,2031|0804:2039,2058"},{"id":"martyrs_day","name":"အာဇာနည်နေ့","en":"Martyrs' Day","category":"public","dates":"d|0719:2010-2060"},{"id":"thadingyut_holidays","name":"သီတင်းကျွတ်ပိတ်ရက်များ","en":"Thadingyut Holidays","category":"public","dates":"y|2010:1022-1024|2011:1011-1013|2012:1029-1031|2013:1018-1020|2014:1007-1009|2015:1027-1029|2016:1015-1017|2017:1004-1006|2018:1023-1025|2019:1012-1014|2020:1030-1101|2021:1019-1021|2022:1008-1010|2023:1028-1030|2024:1016-1018|2025:1005-1007|2026:1025-1027|2027:1014-1016|2028:1002-1004|2029:1021-1023|2030:1010-1012|2031:1030-1101|2032:1018-1020|2033:1007-1009|2034:1026-1028|2035:1015-1017|2036:1003-1005|2037:1023-1025|2038:1012-1014|2039:1031-1102|2040:1019-1021|2041:1008-1010|2042:1028-1030|2043:1017-1019|2044:1005-1007|2045:1024-1026|2046:1013-1015|2047:1002-1004|2048:1021-1023|2049:1010-1012|2050:1029-1031|2051:1018-1020|2052:1006-1008|2053:1026-1028|2054:1015-1017|2055:1004-1006|2056:1022-1024|2057:1011-1013|2058:1031-1102|2059:1020-1022|2060:1008-1010"},{"id":"diwali","name":"ဒီပါဝလီနေ့","en":"Diwali","category":"public","dates":"d|1018:2028,2047|1019:2036|1020:2017,2055|1021:2025,2044|1022:2052|1023:2014,2033|1024:2022,2041,2060|1026:2030,2049|1027:2011,2057|1028:2019,2038|1029:2046|1030:2027|1031:2016,2035,2054|1101:2024|1102:2043|1103:2013,2032,2051|1104:2021,2040|1105:2059|1106:2029,2048|1107:2010,2056|1108:2018,2037|1109:2045|1110:2026|1111:2034,2053|1112:2015|1113:2023,2042|1114:2012,2050|1115:2020,2031|1116:2039,2058"},{"id":"full_moon_day_of_tazaungmon","name":"တန်ဆောင်တိုင်လပြည့်နေ့","en":"Full Moon Day of Tazaungmon","category":"public","dates":"d|1101:2028,2047|1102:2036|1103:2017,2055|1104:2025,2044|1105:2052|1106:2014,2033|1107:2022,2041,2060|1109:2030,2049|1110:2011,2057|1111:2019,2038|1112:2046|1113:2027|1114:2016,2035,2054|1115:2024|1116:2043|1117:2013,2032,2051|1118:2021,2040|1119:2059|1120:2029,2048|1121:2010,2056|1122:2018,2037|1123:2045|1124:2026|1125:2034,2053|1126:2015|1127:2023,2042|1128:2012,2050|1129:2020,2031|1130:2039,2058"},{"id":"national_day","name":"အမျိုးသားနေ့","en":"National Day","category":"public","dates":"d|1111:2028,2047|1112:2036|1113:2017,2055|1114:2025,2044|1115:2052|1116:2014,2033|1117:2022,2041,2060|1119:2030,2049|1120:2011,2057|1121:2019,2038|1122:2046|1123:2027|1124:2016,2035,2054|1125:2024|1126:2043|1127:2013,2032,2051|1128:2021,2040|1129:2059|1130:2029,2048|1201:2010,2056|1202:2018,2037|1203:2045|1204:2026|1205:2034,2053|1206:2015|1207:2023,2042|1208:2012,2050|1209:2020,2031|1210:2039,2058"},{"id":"christmas_day","name":"ခရစ္စမတ်နေ့","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"}]} diff --git a/app/Support/Holidays/data/MN.json b/app/Support/Holidays/data/MN.json new file mode 100644 index 000000000..c316198d3 --- /dev/null +++ b/app/Support/Holidays/data/MN.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"MN","language":"mn","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Шинэ жил","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"lunar_new_year","name":"Цагаан сар","en":"Lunar New Year","category":"public","dates":"y|2010:0214-0215|2011:0203-0204|2012:0222-0223|2013:0211-0212|2014:0131-0202|2015:0219-0221|2016:0209-0211|2017:0227-0301|2018:0216-0218|2019:0205-0207|2020:0224-0226|2021:0212-0214|2022:0202-0204|2023:0221-0223|2024:0210-0212|2025:0301-0303|2026:0218-0220|2027:0207-0209|2028:0226-0228|2029:0214-0216|2030:0203-0205|2031:0222-0224|2032:0212-0214|2033:0131-0202|2034:0219-0221|2035:0209-0211|2036:0228-0301|2037:0216-0218|2038:0205-0207|2039:0224-0226|2040:0213-0215|2041:0202-0204|2042:0221-0223|2043:0210-0212|2044:0229-0302|2045:0217-0219|2046:0206-0208|2047:0225-0227|2048:0214-0216|2049:0203-0205|2050:0222-0224|2051:0212-0214|2052:0201-0203|2053:0219-0221|2054:0208-0210|2055:0227-0301|2056:0216-0218|2057:0204-0206|2058:0223-0225|2059:0213-0215|2060:0203-0205"},{"id":"international_women_s_day","name":"Олон улсын эмэгтэйчүүдийн өдөр","en":"International Women's Day","category":"public","dates":"d|0308:2010-2060"},{"id":"the_buddha_s_birthday","name":"Бурхан багшийн Их дүйчин өдөр","en":"The Buddha's Birthday","category":"public","dates":"d|0517:2030,2049|0518:2038,2057|0520:2027,2046|0521:2054|0522:2035|0523:2024,2043|0525:2032,2051|0526:2021,2040|0527:2029,2048,2059|0529:2037,2056|0530:2045|0531:2026|0601:2053|0602:2034|0603:2042|0604:2023|0605:2020,2031,2050|0606:2039,2058|0607:2028|0608:2036,2047|0609:2055|0610:2044|0611:2025|0612:2033,2052|0614:2022,2041,2060"},{"id":"children_s_day","name":"Хүүхдийн баяр","en":"Children's Day","category":"public","dates":"d|0601:2010-2060"},{"id":"national_festival_and_people_s_revolution_annive","name":"Үндэсний их баяр наадам, Ардын хувьсгалын ойн баяр","en":"National Festival and People's Revolution Anniversary","category":"public","dates":"d|0710:2023-2060|0711:2010-2060|0712:2010-2060|0713:2010-2060|0714:2014-2060|0715:2014-2060"},{"id":"genghis_khan_s_birthday","name":"Их Эзэн Чингис хааны өдөр","en":"Genghis Khan's Birthday","category":"public","dates":"d|1031:2016|1102:2024,2043|1104:2013,2032,2051|1105:2021,2040|1106:2059|1107:2029,2048|1108:2018,2037,2056|1109:2045|1110:2026|1111:2034,2053|1112:2015|1113:2042|1114:2012,2023|1115:2031,2050|1116:2020|1117:2028,2039,2058|1118:2036,2047|1119:2017,2055|1120:2044|1121:2025|1122:2033,2052|1123:2014|1124:2022,2041,2060|1126:2030,2049|1127:2019,2038,2057|1128:2046|1129:2027|1130:2035,2054"},{"id":"republic_day","name":"Бүгд Найрамдах Улс тунхагласан өдөр","en":"Republic Day","category":"public","dates":"d|1126:2016-2060"},{"id":"national_freedom_and_independence_day","name":"Үндэсний эрх чөлөө, тусгаар тогтнолоо сэргээсний баярын өдөр","en":"National Freedom and Independence Day","category":"public","dates":"d|1229:2011-2060"}]} diff --git a/app/Support/Holidays/data/MO.json b/app/Support/Holidays/data/MO.json new file mode 100644 index 000000000..e4b032705 --- /dev/null +++ b/app/Support/Holidays/data/MO.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"MO","language":"zh_MO","years":[2010,2060],"subdivisions":{"I":"Concelho das Ilhas","M":"Concelho de Macau"},"holidays":[{"id":"new_year_s_day","name":"元旦","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"chinese_new_year_s_day","name":"農曆正月初一","en":"Chinese New Year's Day","category":"public","dates":"d|0122:2023,2042|0123:2012,2031,2050|0124:2039,2058|0125:2020|0126:2028,2047|0128:2017,2036,2055|0129:2025|0130:2044|0131:2014,2033|0201:2022,2041,2052|0202:2049,2060|0203:2011,2030|0204:2038,2057|0205:2019|0206:2027,2046|0208:2016,2035,2054|0210:2013,2024,2043|0211:2032,2051|0212:2021,2040,2059|0213:2029|0214:2010,2048|0215:2037,2056|0216:2018|0217:2026,2045|0219:2015,2034,2053"},{"id":"the_second_day_of_chinese_new_year","name":"農曆正月初二","en":"The second day of Chinese New Year","category":"public","dates":"d|0123:2023,2042|0124:2012,2031,2050|0125:2039,2058|0126:2020|0127:2028,2047|0129:2017,2036,2055|0130:2025|0131:2044|0201:2014,2033|0202:2022,2041,2052|0203:2049,2060|0204:2011,2030|0205:2038,2057|0206:2019|0207:2027,2046|0209:2016,2035,2054|0211:2013,2024,2043|0212:2032,2051|0213:2021,2040,2059|0214:2029|0215:2010,2048|0216:2037,2056|0217:2018|0218:2026,2045|0220:2015,2034,2053"},{"id":"the_third_day_of_chinese_new_year","name":"農曆正月初三","en":"The third day of Chinese New Year","category":"public","dates":"d|0124:2023,2042|0125:2012,2031,2050|0126:2039,2058|0127:2020|0128:2028,2047|0130:2017,2036,2055|0131:2025|0201:2044|0202:2014,2033|0203:2022,2041,2052|0204:2049,2060|0205:2011,2030|0206:2038,2057|0207:2019|0208:2027,2046|0210:2016,2035,2054|0212:2013,2024,2043|0213:2032,2051|0214:2021,2040,2059|0215:2029|0216:2010,2048|0217:2037,2056|0218:2018|0219:2026,2045|0221:2015,2034,2053"},{"id":"tomb_sweeping_day","name":"清明節","en":"Tomb-Sweeping Day","category":"public","dates":"d|0404:2012-2013,2016-2017,2020-2021,2024-2025,2028-2029,2032-2033,2036-2037,2040-2042,2044-2046,2048-2050,2052-2054,2056-2058,2060|0405:2010-2011,2014-2015,2018-2019,2022-2023,2026-2027,2030-2031,2034-2035,2038-2039,2043,2047,2051,2055,2059"},{"id":"labor_day","name":"勞動節","en":"Labor Day","category":"public","dates":"d|0501:2010-2060"},{"id":"the_day_following_mid_autumn_festival","name":"中秋節翌日","en":"The Day following Mid-Autumn Festival","category":"public","dates":"d|0908:2052|0909:2014,2033|0910:2060|0911:2022,2041|0912:2049|0913:2011,2030|0914:2019,2038,2057|0916:2016,2027,2046|0917:2035,2054|0918:2024,2043|0920:2013,2032,2051|0921:2040|0922:2021,2059|0923:2010,2029,2048|0925:2018,2037,2056|0926:2026,2045|0927:2053|0928:2015,2034|0929:2042|0930:2023|1001:2012,2050|1002:2020,2031|1003:2039,2058|1004:2028|1005:2017,2036,2047|1006:2044,2055|1007:2025"},{"id":"double_ninth_festival","name":"重陽節","en":"Double Ninth Festival","category":"public","dates":"d|1001:2033|1002:2014,2060|1003:2041|1004:2022|1005:2011,2030,2049|1007:2019,2038,2057|1008:2027,2046|1009:2016,2035,2054|1011:2024,2043|1012:2032|1013:2013,2051|1014:2021,2040,2059|1016:2010,2029,2048|1017:2018,2037,2056|1018:2026,2045|1020:2034,2053|1021:2015|1022:2042|1023:2012,2023|1024:2031,2050|1025:2020,2058|1026:2028,2039|1027:2036,2047|1028:2017,2055|1029:2025,2044|1030:2052"},{"id":"national_day_of_the_people_s_republic_of_china","name":"中華人民共和國國慶日","en":"National Day of the People's Republic of China","category":"public","dates":"d|1001:2010-2060"},{"id":"macao_s_a_r_establishment_day","name":"澳門特別行政區成立紀念日","en":"Macao S.A.R. Establishment Day","category":"public","dates":"d|1220:2010-2060"},{"id":"new_year_s_day_government","name":"元旦","en":"New Year's Day","category":"government","dates":"d|0101:2010-2060"},{"id":"new_year_s_day_optional","name":"元旦","en":"New Year's Day","category":"optional","dates":"d|0101:2010-2060"},{"id":"compensatory_rest_day_for_new_year_s_day_government","name":"元旦的補假","en":"Compensatory rest day for New Year's Day","category":"government","dates":"d|0102:2023,2034,2040,2045,2051|0103:2022,2028,2033,2039,2050,2056"},{"id":"chinese_new_year_s_eve_afternoon_government","name":"農曆除夕(下午)","en":"Chinese New Year's Eve (Afternoon)","category":"government","dates":"d|0121:2042|0122:2031|0123:2058|0124:2020|0125:2028,2047|0127:2017,2055|0128:2025|0129:2044|0130:2014|0131:2022,2041,2052|0201:2049|0202:2011|0203:2038|0204:2019|0205:2027,2046|0207:2035|0209:2024,2043|0210:2032,2051|0211:2021,2059|0212:2029|0213:2048|0214:2056|0215:2018|0216:2026,2045|0218:2015,2053"},{"id":"chinese_new_year_s_day_government","name":"農曆正月初一","en":"Chinese New Year's Day","category":"government","dates":"d|0122:2023,2042|0123:2031,2050|0124:2039,2058|0125:2020|0126:2028,2047|0128:2036,2055|0129:2025|0130:2044|0131:2033|0201:2022,2041,2052|0202:2049,2060|0203:2030|0204:2038,2057|0205:2019|0206:2027,2046|0208:2035,2054|0210:2024,2043|0211:2032,2051|0212:2021,2040,2059|0213:2029|0214:2048|0215:2037,2056|0217:2026,2045|0219:2034,2053"},{"id":"chinese_new_year_s_day_optional","name":"農曆正月初一","en":"Chinese New Year's Day","category":"optional","dates":"d|0122:2023,2042|0123:2012,2031,2050|0124:2039,2058|0125:2020|0126:2028,2047|0128:2017,2036,2055|0129:2025|0130:2044|0131:2014,2033|0201:2022,2041,2052|0202:2049,2060|0203:2011,2030|0204:2038,2057|0205:2019|0206:2027,2046|0208:2016,2035,2054|0210:2013,2024,2043|0211:2032,2051|0212:2021,2040,2059|0213:2029|0214:2010,2048|0215:2037,2056|0216:2018|0217:2026,2045|0219:2015,2034,2053"},{"id":"the_second_day_of_chinese_new_year_government","name":"農曆正月初二","en":"The second day of Chinese New Year","category":"government","dates":"d|0123:2023,2042|0124:2031,2050|0125:2039,2058|0126:2020|0127:2028,2047|0129:2036,2055|0130:2025|0131:2044|0201:2033|0202:2022,2041,2052|0203:2049,2060|0204:2030|0205:2038,2057|0206:2019|0207:2027,2046|0209:2035,2054|0211:2024,2043|0212:2032,2051|0213:2021,2040,2059|0214:2029|0215:2048|0216:2037,2056|0218:2026,2045|0220:2034,2053"},{"id":"the_second_day_of_chinese_new_year_optional","name":"農曆正月初二","en":"The second day of Chinese New Year","category":"optional","dates":"d|0123:2023,2042|0124:2012,2031,2050|0125:2039,2058|0126:2020|0127:2028,2047|0129:2017,2036,2055|0130:2025|0131:2044|0201:2014,2033|0202:2022,2041,2052|0203:2049,2060|0204:2011,2030|0205:2038,2057|0206:2019|0207:2027,2046|0209:2016,2035,2054|0211:2013,2024,2043|0212:2032,2051|0213:2021,2040,2059|0214:2029|0215:2010,2048|0216:2037,2056|0217:2018|0218:2026,2045|0220:2015,2034,2053"},{"id":"the_third_day_of_chinese_new_year_government","name":"農曆正月初三","en":"The third day of Chinese New Year","category":"government","dates":"d|0124:2023,2042|0125:2031,2050|0126:2039,2058|0127:2020|0128:2028,2047|0130:2036,2055|0131:2025|0201:2044|0202:2033|0203:2022,2041,2052|0204:2049,2060|0205:2030|0206:2038,2057|0207:2019|0208:2027,2046|0210:2035,2054|0212:2024,2043|0213:2032,2051|0214:2021,2040,2059|0215:2029|0216:2048|0217:2037,2056|0219:2026,2045|0221:2034,2053"},{"id":"the_third_day_of_chinese_new_year_optional","name":"農曆正月初三","en":"The third day of Chinese New Year","category":"optional","dates":"d|0124:2023,2042|0125:2012,2031,2050|0126:2039,2058|0127:2020|0128:2028,2047|0130:2017,2036,2055|0131:2025|0201:2044|0202:2014,2033|0203:2022,2041,2052|0204:2049,2060|0205:2011,2030|0206:2038,2057|0207:2019|0208:2027,2046|0210:2016,2035,2054|0212:2013,2024,2043|0213:2032,2051|0214:2021,2040,2059|0215:2029|0216:2010,2048|0217:2037,2056|0218:2018|0219:2026,2045|0221:2015,2034,2053"},{"id":"compensatory_rest_day_for_chinese_new_year_s_day_government","name":"農曆正月初一的補假","en":"Compensatory rest day for Chinese New Year's Day","category":"government","dates":"d|0125:2023|0126:2050|0128:2020|0129:2047|0202:2044|0206:2030|0207:2057|0209:2027|0211:2054|0213:2024|0214:2051|0215:2040|0218:2037|0222:2034"},{"id":"compensatory_rest_day_for_the_third_day_of_chine_government","name":"農曆正月初三的補假","en":"Compensatory rest day for The third day of Chinese New Year","category":"government","dates":"d|0127:2031|0128:2058|0201:2055|0205:2041,2052|0208:2038|0212:2035|0216:2021|0218:2048|0221:2045"},{"id":"compensatory_rest_day_for_the_second_day_of_chin_government","name":"農曆正月初二的補假","en":"Compensatory rest day for The second day of Chinese New Year","category":"government","dates":"d|0129:2020|0130:2047|0203:2044|0204:2041|0210:2027|0214:2024|0215:2021,2051|0217:2048|0220:2045"},{"id":"good_friday_optional","name":"耶穌受難日","en":"Good Friday","category":"optional","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"the_day_before_easter_government","name":"復活節前日","en":"The Day before Easter","category":"government","dates":"d|0324:2035,2046|0326:2016|0327:2027,2032|0328:2043,2054|0329:2059|0330:2013,2024|0331:2018,2029,2040|0401:2051,2056|0403:2010,2021|0404:2015,2026,2037,2048|0405:2042,2053|0407:2012|0408:2023,2034,2045|0409:2039,2050|0411:2020|0412:2031,2036|0413:2047,2058|0415:2017,2028|0416:2022,2033,2044|0417:2049,2055,2060|0419:2014,2025|0420:2019,2030,2041,2052|0421:2057|0423:2011|0424:2038"},{"id":"the_day_before_easter_optional","name":"復活節前日","en":"The Day before Easter","category":"optional","dates":"d|0324:2035,2046|0326:2016|0327:2027,2032|0328:2043,2054|0329:2059|0330:2013,2024|0331:2018,2029,2040|0401:2051,2056|0403:2010,2021|0404:2015,2026,2037,2048|0405:2042,2053|0407:2012|0408:2023,2034,2045|0409:2039,2050|0411:2020|0412:2031,2036|0413:2047,2058|0415:2017,2028|0416:2022,2033,2044|0417:2049,2055,2060|0419:2014,2025|0420:2019,2030,2041,2052|0421:2057|0423:2011|0424:2038"},{"id":"compensatory_rest_day_for_the_day_before_easter_government","name":"復活節前日的補假","en":"Compensatory rest day for The Day before Easter","category":"government","dates":"d|0326:2035,2046|0329:2027,2032|0330:2043,2054|0331:2059|0401:2024|0402:2029,2040|0403:2051,2056|0405:2021|0406:2026,2037,2048|0407:2042,2053|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2025|0422:2030,2041,2052|0423:2057|0426:2038"},{"id":"tomb_sweeping_day_government","name":"清明節","en":"Tomb-Sweeping Day","category":"government","dates":"d|0404:2012-2013,2016-2017,2020-2021,2024-2025,2028-2029,2032-2033,2036-2037,2040-2042,2044-2046,2048-2050,2052-2054,2056-2058,2060|0405:2010-2011,2014-2015,2018-2019,2022-2023,2026-2027,2030-2031,2034-2035,2038-2039,2043,2047,2051,2055,2059"},{"id":"tomb_sweeping_day_optional","name":"清明節","en":"Tomb-Sweeping Day","category":"optional","dates":"d|0404:2012-2013,2016-2017,2020-2021,2024-2025,2028-2029,2032-2033,2036-2037,2040-2042,2044-2046,2048-2050,2052-2054,2056-2058,2060|0405:2010-2011,2014-2015,2018-2019,2022-2023,2026-2027,2030-2031,2034-2035,2038-2039,2043,2047,2051,2055,2059"},{"id":"compensatory_rest_day_for_tomb_sweeping_day_government","name":"清明節的補假","en":"Compensatory rest day for Tomb-Sweeping Day","category":"government","dates":"d|0405:2032,2049,2060|0406:2020-2021,2043,2054|0407:2026,2031,2037,2048,2059"},{"id":"the_buddha_s_birthday_government","name":"佛誕節","en":"The Buddha's Birthday","category":"government","dates":"d|0428:2012|0430:2020,2039,2058|0502:2028,2047|0503:2017,2036|0504:2055|0505:2025,2044|0506:2014,2033,2052|0507:2041,2060|0508:2022|0509:2030,2049|0510:2011|0511:2038,2057|0512:2019|0513:2027,2046|0514:2016|0515:2024,2035,2054|0516:2032,2043|0517:2013,2051|0518:2040|0519:2021,2059|0520:2029,2048|0521:2010|0522:2018,2037,2056|0524:2026,2045|0525:2015,2034,2053|0526:2023,2042|0528:2031,2050"},{"id":"the_buddha_s_birthday_optional","name":"佛誕節","en":"The Buddha's Birthday","category":"optional","dates":"d|0428:2012|0430:2020,2039,2058|0502:2028,2047|0503:2017,2036|0504:2055|0505:2025,2044|0506:2014,2033,2052|0507:2041,2060|0508:2022|0509:2030,2049|0510:2011|0511:2038,2057|0512:2019|0513:2027,2046|0514:2016|0515:2024,2035,2054|0516:2032,2043|0517:2013,2051|0518:2040|0519:2021,2059|0520:2029,2048|0521:2010|0522:2018,2037,2056|0524:2026,2045|0525:2015,2034,2053|0526:2023,2042|0528:2031,2050"},{"id":"labor_day_government","name":"勞動節","en":"Labor Day","category":"government","dates":"d|0501:2010-2060"},{"id":"labor_day_optional","name":"勞動節","en":"Labor Day","category":"optional","dates":"d|0501:2010-2060"},{"id":"compensatory_rest_day_for_labor_day_government","name":"勞動節的補假","en":"Compensatory rest day for Labor Day","category":"government","dates":"d|0502:2022,2033,2044,2050|0503:2021,2027,2032,2038-2039,2049,2055,2060"},{"id":"compensatory_rest_day_for_the_buddha_s_birthday_government","name":"佛誕節的補假","en":"Compensatory rest day for The Buddha's Birthday","category":"government","dates":"d|0502:2039|0505:2036|0509:2022|0510:2049|0514:2046|0517:2032|0518:2043|0521:2029|0525:2026|0526:2053|0530:2050"},{"id":"dragon_boat_festival_government","name":"端午節","en":"Dragon Boat Festival","category":"government","dates":"d|0527:2039|0528:2028|0529:2047|0530:2017,2036,2055|0531:2025,2044|0601:2033,2052|0602:2014|0603:2022,2041,2060|0604:2049|0605:2030|0606:2011,2057|0607:2019,2038|0608:2046|0609:2016,2027|0610:2024,2035,2054|0611:2043|0612:2013,2032|0613:2051|0614:2021,2040,2059|0615:2048|0616:2010,2029|0617:2056|0618:2018,2037|0619:2026,2045|0620:2015,2034,2053|0622:2023,2042|0623:2012,2050|0624:2031|0625:2020,2058"},{"id":"dragon_boat_festival_optional","name":"端午節","en":"Dragon Boat Festival","category":"optional","dates":"d|0527:2039|0528:2028|0529:2047|0530:2017,2036,2055|0531:2025,2044|0601:2033,2052|0602:2014|0603:2022,2041,2060|0604:2049|0605:2030|0606:2011,2057|0607:2019,2038|0608:2046|0609:2016,2027|0610:2024,2035,2054|0611:2043|0612:2013,2032|0613:2051|0614:2021,2040,2059|0615:2048|0616:2010,2029|0617:2056|0618:2018,2037|0619:2026,2045|0620:2015,2034,2053|0622:2023,2042|0623:2012,2050|0624:2031|0625:2020,2058"},{"id":"compensatory_rest_day_for_dragon_boat_festival_government","name":"端午節的補假","en":"Compensatory rest day for Dragon Boat Festival","category":"government","dates":"d|0529:2028|0531:2055|0602:2025|0603:2052|0611:2035|0614:2032|0616:2059|0618:2029|0619:2056|0623:2042"},{"id":"the_day_following_mid_autumn_festival_government","name":"中秋節翌日","en":"The Day following Mid-Autumn Festival","category":"government","dates":"d|0908:2052|0909:2014,2033|0910:2060|0911:2022,2041|0912:2049|0913:2011,2030|0914:2019,2038,2057|0916:2016,2027,2046|0917:2035,2054|0918:2024,2043|0920:2013,2032,2051|0921:2040|0922:2021,2059|0923:2010,2029,2048|0925:2018,2037,2056|0926:2026,2045|0927:2053|0928:2015,2034|0929:2042|0930:2023|1001:2012,2050|1002:2020,2031|1003:2039,2058|1004:2028|1005:2017,2036,2047|1006:2044,2055|1007:2025"},{"id":"the_day_following_mid_autumn_festival_optional","name":"中秋節翌日","en":"The Day following Mid-Autumn Festival","category":"optional","dates":"d|0908:2052|0909:2014,2033|0910:2060|0911:2022,2041|0912:2049|0913:2011,2030|0914:2019,2038,2057|0916:2016,2027,2046|0917:2035,2054|0918:2024,2043|0920:2013,2032,2051|0921:2040|0922:2021,2059|0923:2010,2029,2048|0925:2018,2037,2056|0926:2026,2045|0927:2053|0928:2015,2034|0929:2042|0930:2023|1001:2012,2050|1002:2020,2031|1003:2039,2058|1004:2028|1005:2017,2036,2047|1006:2044,2055|1007:2025"},{"id":"compensatory_rest_day_for_the_day_following_mid__government","name":"中秋節翌日的補假","en":"Compensatory rest day for The Day following Mid-Autumn Festival","category":"government","dates":"d|0909:2052|0912:2022|0913:2049|0917:2046|0924:2029|0928:2026|0929:2053|1003:2023|1004:2050|1006:2036|1007:2047"},{"id":"double_ninth_festival_government","name":"重陽節","en":"Double Ninth Festival","category":"government","dates":"d|1001:2033|1002:2014,2060|1003:2041|1004:2022|1005:2011,2030,2049|1007:2019,2038,2057|1008:2027,2046|1009:2016,2035,2054|1011:2024,2043|1012:2032|1013:2013,2051|1014:2021,2040,2059|1016:2010,2029,2048|1017:2018,2037,2056|1018:2026,2045|1020:2034,2053|1021:2015|1022:2042|1023:2012,2023|1024:2031,2050|1025:2020,2058|1026:2028,2039|1027:2036,2047|1028:2017,2055|1029:2025,2044|1030:2052"},{"id":"double_ninth_festival_optional","name":"重陽節","en":"Double Ninth Festival","category":"optional","dates":"d|1001:2033|1002:2014,2060|1003:2041|1004:2022|1005:2011,2030,2049|1007:2019,2038,2057|1008:2027,2046|1009:2016,2035,2054|1011:2024,2043|1012:2032|1013:2013,2051|1014:2021,2040,2059|1016:2010,2029,2048|1017:2018,2037,2056|1018:2026,2045|1020:2034,2053|1021:2015|1022:2042|1023:2012,2023|1024:2031,2050|1025:2020,2058|1026:2028,2039|1027:2036,2047|1028:2017,2055|1029:2025,2044|1030:2052"},{"id":"national_day_of_the_people_s_republic_of_china_government","name":"中華人民共和國國慶日","en":"National Day of the People's Republic of China","category":"government","dates":"d|1001:2010-2060"},{"id":"national_day_of_the_people_s_republic_of_china_optional","name":"中華人民共和國國慶日","en":"National Day of the People's Republic of China","category":"optional","dates":"d|1001:2010-2060"},{"id":"the_day_following_national_day_of_the_people_s_r_government","name":"中華人民共和國國慶日翌日","en":"The day following National Day of the People's Republic of China","category":"government","dates":"d|1002:2010-2060"},{"id":"the_day_following_national_day_of_the_people_s_r_optional","name":"中華人民共和國國慶日翌日","en":"The day following National Day of the People's Republic of China","category":"optional","dates":"d|1002:2010-2060"},{"id":"compensatory_rest_day_for_double_ninth_festival_government","name":"重陽節的補假","en":"Compensatory rest day for Double Ninth Festival","category":"government","dates":"d|1003:2033|1004:2060|1007:2030|1008:2057|1012:2043|1015:2040|1019:2026,2037|1026:2020|1028:2047|1031:2044"},{"id":"compensatory_rest_day_for_national_day_of_the_pe_government","name":"中華人民共和國國慶日的補假","en":"Compensatory rest day for National Day of the People's Republic of China","category":"government","dates":"d|1003:2022,2028,2034,2044-2045,2050-2051,2056|1004:2023,2033,2039"},{"id":"compensatory_rest_day_for_the_day_following_nati_government","name":"中華人民共和國國慶日翌日的補假","en":"Compensatory rest day for The day following National Day of the People's Republic of China","category":"government","dates":"d|1004:2021,2027,2032,2038,2044,2049,2055|1005:2022,2033,2039,2050,2060"},{"id":"all_souls_day_government","name":"追思節","en":"All Souls' Day","category":"government","dates":"d|1102:2010-2060"},{"id":"all_souls_day_optional","name":"追思節","en":"All Souls' Day","category":"optional","dates":"d|1102:2010-2060"},{"id":"compensatory_rest_day_for_all_souls_day_government","name":"追思節的補假","en":"Compensatory rest day for All Souls' Day","category":"government","dates":"d|1103:2025,2031,2036,2042,2053,2059|1104:2024,2030,2041,2047,2052,2058"},{"id":"immaculate_conception_government","name":"聖母無原罪瞻禮","en":"Immaculate Conception","category":"government","dates":"d|1208:2010-2060"},{"id":"immaculate_conception_optional","name":"聖母無原罪瞻禮","en":"Immaculate Conception","category":"optional","dates":"d|1208:2010-2060"},{"id":"compensatory_rest_day_for_immaculate_conception_government","name":"聖母無原罪瞻禮的補假","en":"Compensatory rest day for Immaculate Conception","category":"government","dates":"d|1209:2024,2030,2041,2047,2052,2058|1210:2029,2035,2040,2046,2057"},{"id":"macao_s_a_r_establishment_day_government","name":"澳門特別行政區成立紀念日","en":"Macao S.A.R. Establishment Day","category":"government","dates":"d|1220:2010-2060"},{"id":"macao_s_a_r_establishment_day_optional","name":"澳門特別行政區成立紀念日","en":"Macao S.A.R. Establishment Day","category":"optional","dates":"d|1220:2010-2060"},{"id":"compensatory_rest_day_for_macao_s_a_r_establishm_government","name":"澳門特別行政區成立紀念日的補假","en":"Compensatory rest day for Macao S.A.R. Establishment Day","category":"government","dates":"d|1221:2026,2043,2054|1222:2020,2025,2036-2037,2048,2053|1223:2031,2042,2059"},{"id":"winter_solstice_government","name":"冬至","en":"Winter Solstice","category":"government","dates":"d|1221:2012,2016,2020-2021,2024-2025,2028-2029,2032-2033,2036-2037,2040-2041,2044-2045,2048-2049,2052-2053,2056-2058,2060|1222:2010-2011,2013-2015,2017-2019,2022-2023,2026-2027,2030-2031,2034-2035,2038-2039,2042-2043,2046-2047,2050-2051,2054-2055,2059"},{"id":"winter_solstice_optional","name":"冬至","en":"Winter Solstice","category":"optional","dates":"d|1221:2012,2016,2020-2021,2024-2025,2028-2029,2032-2033,2036-2037,2040-2041,2044-2045,2048-2049,2052-2053,2056-2058,2060|1222:2010-2011,2013-2015,2017-2019,2022-2023,2026-2027,2030-2031,2034-2035,2038-2039,2042-2043,2046-2047,2050-2051,2054-2055,2059"},{"id":"compensatory_rest_day_for_winter_solstice_government","name":"冬至的補假","en":"Compensatory rest day for Winter Solstice","category":"government","dates":"d|1223:2024-2025,2030,2036,2041,2047,2052-2053,2058|1226:2035,2046"},{"id":"christmas_eve_government","name":"聖誕節前日","en":"Christmas Eve","category":"government","dates":"d|1224:2010-2060"},{"id":"christmas_eve_optional","name":"聖誕節前日","en":"Christmas Eve","category":"optional","dates":"d|1224:2010-2060"},{"id":"christmas_day_government","name":"聖誕節","en":"Christmas Day","category":"government","dates":"d|1225:2010-2060"},{"id":"christmas_day_optional","name":"聖誕節","en":"Christmas Day","category":"optional","dates":"d|1225:2010-2060"},{"id":"compensatory_rest_day_for_christmas_eve_government","name":"聖誕節前日的補假","en":"Compensatory rest day for Christmas Eve","category":"government","dates":"d|1226:2022-2023,2028,2033-2034,2039,2044-2045,2050-2051,2056"},{"id":"compensatory_rest_day_for_christmas_day_government","name":"聖誕節的補假","en":"Compensatory rest day for Christmas Day","category":"government","dates":"d|1227:2021-2022,2027,2032-2033,2038-2039,2044,2049-2050,2055,2060"},{"id":"new_year_s_eve_afternoon_government","name":"除夕(下午)","en":"New Year's Eve (Afternoon)","category":"government","dates":"d|1231:2010,2013-2015,2018-2021,2024-2027,2029-2032,2035-2038,2040-2043,2046-2049,2052-2055,2057-2060"}]} diff --git a/app/Support/Holidays/data/MP.json b/app/Support/Holidays/data/MP.json new file mode 100644 index 000000000..4b2d200ab --- /dev/null +++ b/app/Support/Holidays/data/MP.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"MP","language":"en_US","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051|1231:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"martin_luther_king_jr_day","name":"Martin Luther King Jr. Day","en":"Martin Luther King Jr. Day","category":"public","dates":"d|0115:2018,2024,2029,2035,2046,2052,2057|0116:2012,2017,2023,2034,2040,2045,2051|0117:2011,2022,2028,2033,2039,2050,2056|0118:2010,2016,2021,2027,2038,2044,2049,2055|0119:2015,2026,2032,2037,2043,2054,2060|0120:2014,2020,2025,2031,2042,2048,2053,2059|0121:2013,2019,2030,2036,2041,2047,2058"},{"id":"washington_s_birthday","name":"Washington's Birthday","en":"Washington's Birthday","category":"public","dates":"d|0215:2010,2016,2021,2027,2038,2044,2049,2055|0216:2015,2026,2032,2037,2043,2054,2060|0217:2014,2020,2025,2031,2042,2048,2053,2059|0218:2013,2019,2030,2036,2041,2047,2058|0219:2018,2024,2029,2035,2046,2052,2057|0220:2012,2017,2023,2034,2040,2045,2051|0221:2011,2022,2028,2033,2039,2050,2056"},{"id":"commonwealth_covenant_day","name":"Commonwealth Covenant Day","en":"Commonwealth Covenant Day","category":"public","dates":"d|0323:2012,2018,2029,2035,2040,2046,2057|0324:2010-2060|0325:2013,2019,2024,2030,2041,2047,2052,2058"},{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"memorial_day","name":"Memorial Day","en":"Memorial Day","category":"public","dates":"d|0525:2015,2020,2026,2037,2043,2048,2054|0526:2014,2025,2031,2036,2042,2053,2059|0527:2013,2019,2024,2030,2041,2047,2052,2058|0528:2012,2018,2029,2035,2040,2046,2057|0529:2017,2023,2028,2034,2045,2051,2056|0530:2011,2016,2022,2033,2039,2044,2050|0531:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"juneteenth_national_independence_day","name":"Juneteenth National Independence Day","en":"Juneteenth National Independence Day","category":"public","dates":"d|0618:2021,2027,2032,2038,2049,2055,2060|0619:2021-2060|0620:2022,2033,2039,2044,2050"},{"id":"independence_day","name":"Independence Day","en":"Independence Day","category":"public","dates":"d|0703:2015,2020,2026,2037,2043,2048,2054|0704:2010-2060|0705:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"labor_day","name":"Labor Day","en":"Labor Day","category":"public","dates":"d|0901:2014,2025,2031,2036,2042,2053,2059|0902:2013,2019,2024,2030,2041,2047,2052,2058|0903:2012,2018,2029,2035,2040,2046,2057|0904:2017,2023,2028,2034,2045,2051,2056|0905:2011,2016,2022,2033,2039,2044,2050|0906:2010,2021,2027,2032,2038,2049,2055,2060|0907:2015,2020,2026,2037,2043,2048,2054"},{"id":"commonwealth_cultural_day","name":"Commonwealth Cultural Day","en":"Commonwealth Cultural Day","category":"public","dates":"d|1008:2012,2018,2029,2035,2040,2046,2057|1009:2017,2023,2028,2034,2045,2051,2056|1010:2011,2016,2022,2033,2039,2044,2050|1011:2010,2021,2027,2032,2038,2049,2055,2060|1012:2015,2020,2026,2037,2043,2048,2054|1013:2014,2025,2031,2036,2042,2053,2059|1014:2013,2019,2024,2030,2041,2047,2052,2058"},{"id":"election_day","name":"Election Day","en":"Election Day","category":"public","dates":"d|1102:2010,2032,2038,2060|1103:2020,2026,2048,2054|1104:2014,2036,2042|1105:2024,2030,2052,2058|1106:2012,2018,2040,2046|1107:2028,2034,2056|1108:2016,2022,2044,2050"},{"id":"citizenship_day","name":"Citizenship Day","en":"Citizenship Day","category":"public","dates":"d|1103:2017,2023,2028,2034,2045,2051,2056|1104:2010-2060|1105:2012,2018,2029,2035,2040,2046,2057"},{"id":"veterans_day","name":"Veterans Day","en":"Veterans Day","category":"public","dates":"d|1110:2017,2023,2028,2034,2045,2051,2056|1111:2010-2060|1112:2012,2018,2029,2035,2040,2046,2057"},{"id":"thanksgiving_day","name":"Thanksgiving Day","en":"Thanksgiving Day","category":"public","dates":"d|1122:2012,2018,2029,2035,2040,2046,2057|1123:2017,2023,2028,2034,2045,2051,2056|1124:2011,2016,2022,2033,2039,2044,2050|1125:2010,2021,2027,2032,2038,2049,2055,2060|1126:2015,2020,2026,2037,2043,2048,2054|1127:2014,2025,2031,2036,2042,2053,2059|1128:2013,2019,2024,2030,2041,2047,2052,2058"},{"id":"constitution_day","name":"Constitution Day","en":"Constitution Day","category":"public","dates":"d|1207:2012,2018,2029,2035,2040,2046,2057|1208:2010-2060|1209:2013,2019,2024,2030,2041,2047,2052,2058"},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1224:2010,2021,2027,2032,2038,2049,2055,2060|1225:2010-2060|1226:2011,2016,2022,2033,2039,2044,2050"},{"id":"valentine_s_day_unofficial","name":"Valentine's Day","en":"Valentine's Day","category":"unofficial","dates":"d|0214:2010-2060"},{"id":"saint_patrick_s_day_unofficial","name":"Saint Patrick's Day","en":"Saint Patrick's Day","category":"unofficial","dates":"d|0317:2010-2060"},{"id":"good_friday_unofficial","name":"Good Friday","en":"Good Friday","category":"unofficial","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_sunday_unofficial","name":"Easter Sunday","en":"Easter Sunday","category":"unofficial","dates":"d|0325:2035,2046|0327:2016|0328:2027,2032|0329:2043,2054|0330:2059|0331:2013,2024|0401:2018,2029,2040|0402:2051,2056|0404:2010,2021|0405:2015,2026,2037,2048|0406:2042,2053|0408:2012|0409:2023,2034,2045|0410:2039,2050|0412:2020|0413:2031,2036|0414:2047,2058|0416:2017,2028|0417:2022,2033,2044|0418:2049,2055,2060|0420:2014,2025|0421:2019,2030,2041,2052|0422:2057|0424:2011|0425:2038"},{"id":"mother_s_day_unofficial","name":"Mother's Day","en":"Mother's Day","category":"unofficial","dates":"d|0508:2011,2016,2022,2033,2039,2044,2050|0509:2010,2021,2027,2032,2038,2049,2055,2060|0510:2015,2020,2026,2037,2043,2048,2054|0511:2014,2025,2031,2036,2042,2053,2059|0512:2013,2019,2024,2030,2041,2047,2052,2058|0513:2012,2018,2029,2035,2040,2046,2057|0514:2017,2023,2028,2034,2045,2051,2056"},{"id":"father_s_day_unofficial","name":"Father's Day","en":"Father's Day","category":"unofficial","dates":"d|0615:2014,2025,2031,2036,2042,2053,2059|0616:2013,2019,2024,2030,2041,2047,2052,2058|0617:2012,2018,2029,2035,2040,2046,2057|0618:2017,2023,2028,2034,2045,2051,2056|0619:2011,2016,2022,2033,2039,2044,2050|0620:2010,2021,2027,2032,2038,2049,2055,2060|0621:2015,2020,2026,2037,2043,2048,2054"},{"id":"halloween_unofficial","name":"Halloween","en":"Halloween","category":"unofficial","dates":"d|1031:2010-2060"},{"id":"christmas_eve_unofficial","name":"Christmas Eve","en":"Christmas Eve","category":"unofficial","dates":"d|1224:2010-2060"},{"id":"new_year_s_eve_unofficial","name":"New Year's Eve","en":"New Year's Eve","category":"unofficial","dates":"d|1231:2010-2060"}]} diff --git a/app/Support/Holidays/data/MQ.json b/app/Support/Holidays/data/MQ.json new file mode 100644 index 000000000..7dca34984 --- /dev/null +++ b/app/Support/Holidays/data/MQ.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"MQ","language":"fr","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Jour de l'an","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"good_friday","name":"Vendredi saint","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_monday","name":"Lundi de Pâques","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"labor_day","name":"Fête du Travail","en":"Labor Day","category":"public","dates":"d|0501:2010-2060"},{"id":"ascension_day","name":"Ascension","en":"Ascension Day","category":"public","dates":"d|0503:2035,2046|0505:2016|0506:2027,2032|0507:2043,2054|0508:2059|0509:2013,2024|0510:2018,2029,2040|0511:2051,2056|0513:2010,2021|0514:2015,2026,2037,2048|0515:2042,2053|0517:2012|0518:2023,2034,2045|0519:2039,2050|0521:2020|0522:2031,2036|0523:2047,2058|0525:2017,2028|0526:2022,2033,2044|0527:2049,2055,2060|0529:2014,2025|0530:2019,2030,2041,2052|0531:2057|0602:2011|0603:2038"},{"id":"victory_day","name":"Fête de la Victoire","en":"Victory Day","category":"public","dates":"d|0508:2010-2060"},{"id":"pentecost_monday","name":"Lundi de Pentecôte","en":"Pentecost Monday","category":"public","dates":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038"},{"id":"abolition_of_slavery","name":"Abolition de l'esclavage","en":"Abolition of Slavery","category":"public","dates":"d|0522:2010-2060"},{"id":"national_day","name":"Fête nationale","en":"National Day","category":"public","dates":"d|0714:2010-2060"},{"id":"victor_schoelcher_day","name":"Fête de Victor Schoelcher","en":"Victor Schoelcher Day","category":"public","dates":"d|0721:2010-2060"},{"id":"assumption_day","name":"Assomption","en":"Assumption Day","category":"public","dates":"d|0815:2010-2060"},{"id":"all_saints_day","name":"Toussaint","en":"All Saints' Day","category":"public","dates":"d|1101:2010-2060"},{"id":"armistice_day","name":"Armistice","en":"Armistice Day","category":"public","dates":"d|1111:2010-2060"},{"id":"christmas_day","name":"Noël","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"}]} diff --git a/app/Support/Holidays/data/MR.json b/app/Support/Holidays/data/MR.json new file mode 100644 index 000000000..e561f4ead --- /dev/null +++ b/app/Support/Holidays/data/MR.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"MR","language":"ar","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"رأس السنة الميلادية","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"eid_al_fitr","name":"عيد الفطر","en":"Eid al-Fitr","category":"public","dates":"y|2010:0910-0911|2011:0830-0831|2012:0819-0820|2013:0808-0809|2014:0728-0729|2015:0717-0718|2016:0706-0707|2017:0625-0626|2018:0615-0616|2019:0604-0605|2020:0524-0525|2021:0513-0514|2022:0502-0503|2023:0421-0422|2024:0410-0411|2025:0330-0331|2026:0320-0321|2027:0309-0310|2028:0226-0227|2029:0214-0215|2030:0204-0205|2031:0124-0125|2032:0114-0115|2033:0102-0103,1223-1224|2034:1212-1213|2035:1201-1202|2036:1119-1120|2037:1108-1109|2038:1029-1030|2039:1019-1020|2040:1007-1008|2041:0926-0927|2042:0915-0916|2043:0904-0905|2044:0824-0825|2045:0814-0815|2046:0803-0804|2047:0724-0725|2048:0712-0713|2049:0701-0702|2050:0620-0621|2051:0610-0611|2052:0529-0530|2053:0519-0520|2054:0509-0510|2055:0428-0429|2056:0416-0417|2057:0405-0406|2058:0325-0326|2059:0315-0316|2060:0304-0305","estimated":"y|2010:0910-0911|2011:0830-0831|2012:0819-0820|2013:0808-0809|2014:0728-0729|2015:0717-0718|2016:0706-0707|2017:0625-0626|2018:0615-0616|2019:0604-0605|2020:0524-0525|2021:0513-0514|2022:0502-0503|2023:0421-0422|2024:0410-0411|2025:0330-0331|2026:0320-0321|2027:0309-0310|2028:0226-0227|2029:0214-0215|2030:0204-0205|2031:0124-0125|2032:0114-0115|2033:0102-0103,1223-1224|2034:1212-1213|2035:1201-1202|2036:1119-1120|2037:1108-1109|2038:1029-1030|2039:1019-1020|2040:1007-1008|2041:0926-0927|2042:0915-0916|2043:0904-0905|2044:0824-0825|2045:0814-0815|2046:0803-0804|2047:0724-0725|2048:0712-0713|2049:0701-0702|2050:0620-0621|2051:0610-0611|2052:0529-0530|2053:0519-0520|2054:0509-0510|2055:0428-0429|2056:0416-0417|2057:0405-0406|2058:0325-0326|2059:0315-0316|2060:0304-0305"},{"id":"prophet_s_birthday","name":"المولد النبوي الشريف","en":"Prophet's Birthday","category":"public","dates":"y|2010:0226|2011:0215|2012:0204|2013:0124|2014:0113|2015:0103,1223|2016:1211|2017:1130|2018:1120|2019:1109|2020:1029|2021:1018|2022:1008|2023:0927|2024:0915|2025:0904|2026:0825|2027:0814|2028:0803|2029:0724|2030:0713|2031:0702|2032:0620|2033:0609|2034:0530|2035:0520|2036:0508|2037:0428|2038:0417|2039:0406|2040:0325|2041:0315|2042:0304|2043:0222|2044:0211|2045:0130|2046:0119|2047:0108,1229|2048:1218|2049:1207|2050:1126|2051:1116|2052:1104|2053:1024|2054:1013|2055:1003|2056:0922|2057:0911|2058:0831|2059:0820|2060:0808","estimated":"y|2010:0226|2011:0215|2012:0204|2013:0124|2014:0113|2015:0103,1223|2016:1211|2017:1130|2018:1120|2019:1109|2020:1029|2021:1018|2022:1008|2023:0927|2024:0915|2025:0904|2026:0825|2027:0814|2028:0803|2029:0724|2030:0713|2031:0702|2032:0620|2033:0609|2034:0530|2035:0520|2036:0508|2037:0428|2038:0417|2039:0406|2040:0325|2041:0315|2042:0304|2043:0222|2044:0211|2045:0130|2046:0119|2047:0108,1229|2048:1218|2049:1207|2050:1126|2051:1116|2052:1104|2053:1024|2054:1013|2055:1003|2056:0922|2057:0911|2058:0831|2059:0820|2060:0808"},{"id":"islamic_new_year","name":"رأس السنة الهجرية","en":"Islamic New Year","category":"public","dates":"y|2010:1207|2011:1126|2012:1115|2013:1104|2014:1025|2015:1014|2016:1002|2017:0921|2018:0911|2019:0831|2020:0820|2021:0809|2022:0730|2023:0719|2024:0707|2025:0626|2026:0616|2027:0606|2028:0525|2029:0514|2030:0503|2031:0423|2032:0411|2033:0401|2034:0321|2035:0311|2036:0228|2037:0216|2038:0205|2039:0126|2040:0115|2041:0104,1224|2042:1214|2043:1203|2044:1121|2045:1110|2046:1031|2047:1020|2048:1009|2049:0928|2050:0917|2051:0906|2052:0826|2053:0815|2054:0805|2055:0725|2056:0714|2057:0703|2058:0622|2059:0611|2060:0531","estimated":"y|2010:1207|2011:1126|2012:1115|2013:1104|2014:1025|2015:1014|2016:1002|2017:0921|2018:0911|2019:0831|2020:0820|2021:0809|2022:0730|2023:0719|2024:0707|2025:0626|2026:0616|2027:0606|2028:0525|2029:0514|2030:0503|2031:0423|2032:0411|2033:0401|2034:0321|2035:0311|2036:0228|2037:0216|2038:0205|2039:0126|2040:0115|2041:0104,1224|2042:1214|2043:1203|2044:1121|2045:1110|2046:1031|2047:1020|2048:1009|2049:0928|2050:0917|2051:0906|2052:0826|2053:0815|2054:0805|2055:0725|2056:0714|2057:0703|2058:0622|2059:0611|2060:0531"},{"id":"eid_al_adha","name":"عيد الأضحى","en":"Eid al-Adha","category":"public","dates":"y|2010:1116-1117|2011:1106-1107|2012:1026-1027|2013:1015-1016|2014:1004-1005|2015:0923-0924|2016:0911-0912|2017:0901-0902|2018:0821-0822|2019:0811-0812|2020:0731-0801|2021:0720-0721|2022:0709-0710|2023:0628-0629|2024:0616-0617|2025:0606-0607|2026:0527-0528|2027:0516-0517|2028:0505-0506|2029:0424-0425|2030:0413-0414|2031:0402-0403|2032:0322-0323|2033:0311-0312|2034:0301-0302|2035:0218-0219|2036:0207-0208|2037:0126-0127|2038:0116-0117|2039:0105-0106,1226-1227|2040:1214-1215|2041:1204-1205|2042:1123-1124|2043:1112-1113|2044:1031-1101|2045:1021-1022|2046:1010-1011|2047:0930-1001|2048:0919-0920|2049:0908-0909|2050:0828-0829|2051:0817-0818|2052:0805-0806|2053:0726-0727|2054:0715-0716|2055:0705-0706|2056:0623-0624|2057:0612-0613|2058:0601-0602|2059:0522-0523|2060:0510-0511","estimated":"y|2010:1116-1117|2011:1106-1107|2012:1026-1027|2013:1015-1016|2014:1004-1005|2015:0923-0924|2016:0911-0912|2017:0901-0902|2018:0821-0822|2019:0811-0812|2020:0731-0801|2021:0720-0721|2022:0709-0710|2023:0628-0629|2024:0616-0617|2025:0606-0607|2026:0527-0528|2027:0516-0517|2028:0505-0506|2029:0424-0425|2030:0413-0414|2031:0402-0403|2032:0322-0323|2033:0311-0312|2034:0301-0302|2035:0218-0219|2036:0207-0208|2037:0126-0127|2038:0116-0117|2039:0105-0106,1226-1227|2040:1214-1215|2041:1204-1205|2042:1123-1124|2043:1112-1113|2044:1031-1101|2045:1021-1022|2046:1010-1011|2047:0930-1001|2048:0919-0920|2049:0908-0909|2050:0828-0829|2051:0817-0818|2052:0805-0806|2053:0726-0727|2054:0715-0716|2055:0705-0706|2056:0623-0624|2057:0612-0613|2058:0601-0602|2059:0522-0523|2060:0510-0511"},{"id":"labor_day","name":"عيد العمال","en":"Labor Day","category":"public","dates":"d|0501:2010-2060"},{"id":"africa_day","name":"يوم أفريقيا","en":"Africa Day","category":"public","dates":"d|0525:2010-2060"},{"id":"independence_day","name":"عيد الاستقلال","en":"Independence Day","category":"public","dates":"d|1128:2010-2060"}]} diff --git a/app/Support/Holidays/data/MS.json b/app/Support/Holidays/data/MS.json new file mode 100644 index 000000000..b9ca951eb --- /dev/null +++ b/app/Support/Holidays/data/MS.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"MS","language":"en_MS","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2017-2060|0102:2018,2024,2029,2035,2046,2052,2057|0103:2017,2022-2023,2028,2033-2034,2039-2040,2045,2050-2051,2056"},{"id":"festival_day","name":"Festival Day","en":"Festival Day","category":"public","dates":"d|0102:2017,2023,2034,2040,2045,2051|1231:2017-2060"},{"id":"saint_patrick_s_day","name":"Saint Patrick's Day","en":"Saint Patrick's Day","category":"public","dates":"d|0317:2017-2060|0318:2019,2024,2030,2041,2047,2052,2058|0319:2018,2029,2035,2040,2046,2057"},{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0326:2027,2032|0327:2043,2054|0328:2059|0329:2024|0330:2018,2029,2040|0331:2051,2056|0402:2021|0403:2026,2037,2048|0404:2042,2053|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2025|0419:2019,2030,2041,2052|0420:2057|0423:2038"},{"id":"easter_monday","name":"Easter Monday","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0329:2027,2032|0330:2043,2054|0331:2059|0401:2024|0402:2018,2029,2040|0403:2051,2056|0405:2021|0406:2026,2037,2048|0407:2042,2053|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2025|0422:2019,2030,2041,2052|0423:2057|0426:2038"},{"id":"labor_day","name":"Labour Day","en":"Labor Day","category":"public","dates":"d|0501:2017,2023,2028,2034,2045,2051,2056|0502:2022,2033,2039,2044,2050|0503:2021,2027,2032,2038,2049,2055,2060|0504:2020,2026,2037,2043,2048,2054|0505:2025,2031,2036,2042,2053,2059|0506:2019,2024,2030,2041,2047,2052,2058|0507:2018,2029,2035,2040,2046,2057"},{"id":"pentecost_monday","name":"Whit Monday","en":"Pentecost Monday","category":"public","dates":"d|0514:2035,2046|0517:2027,2032|0518:2043,2054|0519:2059|0520:2024|0521:2018,2029,2040|0522:2051,2056|0524:2021|0525:2026,2037,2048|0526:2042,2053|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2025|0610:2019,2030,2041,2052|0611:2019,2030,2041,2052,2057|0612:2057|0614:2038|0615:2038"},{"id":"king_s_birthday","name":"King's Birthday","en":"King's Birthday","category":"public","dates":"d|0610:2030,2041,2047,2052,2058|0611:2029,2035,2040,2046,2057|0612:2028,2034,2045,2051,2056|0613:2033,2039,2044,2050|0614:2027,2032,2038,2049,2055,2060|0615:2026,2037,2043,2048,2054|0616:2025,2031,2036,2042,2053,2059|0617:2024|0619:2023"},{"id":"day_of_prayer_and_thanksgiving","name":"Day of Prayer and Thanksgiving","en":"Day of Prayer and Thanksgiving","category":"public","dates":"d|0708:2026,2037,2043,2048,2054|0709:2025,2031,2036,2042,2053,2059|0710:2024,2030,2041,2047,2052,2058|0711:2029,2035,2040,2046,2057|0712:2028,2034,2045,2051,2056|0713:2033,2039,2044,2050|0714:2027,2032,2038,2049,2055,2060"},{"id":"emancipation_day","name":"Emancipation Day","en":"Emancipation Day","category":"public","dates":"d|0801:2022,2033,2039,2044,2050|0802:2021,2027,2032,2038,2049,2055,2060|0803:2020,2026,2037,2043,2048,2054|0804:2025,2031,2036,2042,2053,2059|0805:2019,2024,2030,2041,2047,2052,2058|0806:2018,2029,2035,2040,2046,2057|0807:2017,2023,2028,2034,2045,2051,2056"},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1225:2017-2060|1227:2021-2022,2027,2032-2033,2038-2039,2044,2049-2050,2055,2060"},{"id":"boxing_day","name":"Boxing Day","en":"Boxing Day","category":"public","dates":"d|1226:2017-2060|1228:2020-2021,2026-2027,2032,2037-2038,2043,2048-2049,2054-2055,2060"}]} diff --git a/app/Support/Holidays/data/MT.json b/app/Support/Holidays/data/MT.json new file mode 100644 index 000000000..0eb46836a --- /dev/null +++ b/app/Support/Holidays/data/MT.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"MT","language":"mt","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"L-Ewwel tas-Sena","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"feast_of_saint_paul_s_shipwreck","name":"Il-Festa tan-Nawfraġju ta' San Pawl","en":"Feast of Saint Paul's Shipwreck","category":"public","dates":"d|0210:2010-2060"},{"id":"feast_of_saint_joseph","name":"Il-Festa ta' San Ġużepp","en":"Feast of Saint Joseph","category":"public","dates":"d|0319:2010-2060"},{"id":"good_friday","name":"Il-Ġimgħa l-Kbira","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"freedom_day","name":"Jum il-Ħelsien","en":"Freedom Day","category":"public","dates":"d|0331:2010-2060"},{"id":"worker_s_day","name":"Jum il-Ħaddiem","en":"Worker's Day","category":"public","dates":"d|0501:2010-2060"},{"id":"sette_giugno","name":"Sette Giugno","en":"Sette Giugno","category":"public","dates":"d|0607:2010-2060"},{"id":"feast_of_saint_peter_and_saint_paul","name":"Il-Festa ta' San Pietru u San Pawl","en":"Feast of Saint Peter and Saint Paul","category":"public","dates":"d|0629:2010-2060"},{"id":"feast_of_the_assumption","name":"Il-Festa ta' Santa Marija","en":"Feast of the Assumption","category":"public","dates":"d|0815:2010-2060"},{"id":"feast_of_our_lady_of_victories","name":"Jum il-Vitorja","en":"Feast of Our Lady of Victories","category":"public","dates":"d|0908:2010-2060"},{"id":"independence_day","name":"Jum l-Indipendenza","en":"Independence Day","category":"public","dates":"d|0921:2010-2060"},{"id":"feast_of_the_immaculate_conception","name":"Il-Festa tal-Immakulata Kunċizzjoni","en":"Feast of the Immaculate Conception","category":"public","dates":"d|1208:2010-2060"},{"id":"republic_day","name":"Jum ir-Repubblika","en":"Republic Day","category":"public","dates":"d|1213:2010-2060"},{"id":"christmas_day","name":"Il-Milied","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"}]} diff --git a/app/Support/Holidays/data/MU.json b/app/Support/Holidays/data/MU.json new file mode 100644 index 000000000..0f067822b --- /dev/null +++ b/app/Support/Holidays/data/MU.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"MU","language":"en_MU","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"day_after_new_year_s_day","name":"Day after New Year's Day","en":"Day after New Year's Day","category":"public","dates":"d|0102:2010-2060"},{"id":"eid_al_fitr","name":"Eid-ul-Fitr","en":"Eid al-Fitr","category":"public","dates":"y|2010:0911|2011:0831|2012:0820|2013:0809|2014:0729|2015:0718|2016:0706|2017:0626|2018:0616|2019:0605|2020:0524|2021:0514|2022:0503|2023:0422|2024:0411|2025:0401|2026:0321|2027:0310|2028:0227|2029:0215|2030:0205|2031:0125|2032:0115|2033:0103,1224|2034:1213|2035:1202|2036:1120|2037:1109|2038:1030|2039:1020|2040:1008|2041:0927|2042:0916|2043:0905|2044:0825|2045:0815|2046:0804|2047:0725|2048:0713|2049:0702|2050:0621|2051:0611|2052:0530|2053:0520|2054:0510|2055:0429|2056:0417|2057:0406|2058:0326|2059:0316|2060:0305","estimated":"y|2010:0911|2011:0831|2012:0820|2013:0809|2014:0729|2015:0718|2026:0321|2027:0310|2028:0227|2029:0215|2030:0205|2031:0125|2032:0115|2033:0103,1224|2034:1213|2035:1202|2036:1120|2037:1109|2038:1030|2039:1020|2040:1008|2041:0927|2042:0916|2043:0905|2044:0825|2045:0815|2046:0804|2047:0725|2048:0713|2049:0702|2050:0621|2051:0611|2052:0530|2053:0520|2054:0510|2055:0429|2056:0417|2057:0406|2058:0326|2059:0316|2060:0305"},{"id":"thaipusam","name":"Thaipoosam Cavadee","en":"Thaipusam","category":"public","dates":"d|0117:2041|0118:2011,2022,2030,2049,2060|0119:2038,2057|0120:2019|0121:2046|0122:2027|0123:2035,2054|0124:2016|0125:2024,2043|0126:2013|0127:2032,2051|0128:2021,2040,2059|0129:2010,2029,2048|0130:2037|0131:2018,2056|0201:2045|0202:2026|0203:2015,2034,2053|0204:2023|0205:2042|0206:2012,2031,2050|0207:2039,2058|0208:2020|0209:2028,2047|0211:2017,2025,2036,2055|0213:2044|0214:2014,2033|0215:2052"},{"id":"chinese_spring_festival","name":"Chinese Spring Festival","en":"Chinese Spring Festival","category":"public","dates":"d|0122:2023,2042|0123:2012,2031,2050|0124:2039,2058|0125:2020|0126:2028,2047|0128:2017,2036,2055|0129:2025|0130:2044|0131:2014,2033|0201:2022,2041,2052|0202:2049,2060|0203:2011,2030|0204:2038,2057|0205:2019|0206:2027,2046|0208:2016,2035,2054|0210:2013,2024,2043|0211:2032,2051|0212:2021,2040,2059|0213:2029|0214:2010,2048|0215:2037,2056|0216:2018|0217:2026,2045|0219:2015,2034,2053"},{"id":"abolition_of_slavery","name":"Abolition of Slavery","en":"Abolition of Slavery","category":"public","dates":"d|0201:2010-2060"},{"id":"maha_shivaratri","name":"Maha Shivaratree","en":"Maha Shivaratri","category":"public","dates":"d|0211:2029|0212:2010|0213:2018|0215:2026|0217:2015,2034|0218:2023|0220:2012,2031|0221:2020|0223:2028|0224:2017|0226:2025|0227:2014,2033|0301:2022|0302:2011,2030|0304:2019|0306:2027|0307:2016|0308:2024,2035|0310:2013,2032|0311:2021"},{"id":"independence_and_republic_day","name":"Independence and Republic Day","en":"Independence and Republic Day","category":"public","dates":"d|0312:2010-2060"},{"id":"ugadi","name":"Ougadi","en":"Ugadi","category":"public","dates":"d|0316:2010|0318:2018|0319:2026|0321:2015,2034|0322:2023|0323:2012|0324:2031|0325:2020|0327:2028|0328:2017|0330:2025|0331:2014,2033|0402:2022|0403:2030|0404:2011|0406:2019|0407:2027|0408:2016|0409:2024,2035|0411:2013,2032|0413:2021|0414:2029"},{"id":"labor_day","name":"Labour Day","en":"Labor Day","category":"public","dates":"d|0501:2010-2060"},{"id":"assumption_day","name":"Assumption of the Blessed Virgin Mary","en":"Assumption Day","category":"public","dates":"d|0815:2016,2018,2020,2022,2024,2026,2028,2030,2032,2034,2036,2038,2040,2042,2044,2046,2048,2050,2052,2054,2056,2058,2060"},{"id":"ganesh_chaturthi","name":"Ganesh Chaturthi","en":"Ganesh Chaturthi","category":"public","dates":"d|0823:2020,2028|0825:2017|0828:2025,2033|0829:2014|0901:2011,2022,2030|0903:2019|0904:2027|0905:2035|0906:2016|0908:2024,2032|0909:2013|0911:2010,2021,2029|0914:2018,2026|0916:2034|0918:2015|0919:2012|0920:2023,2031"},{"id":"diwali","name":"Divali","en":"Diwali","category":"public","dates":"d|1017:2028|1019:2017|1020:2025|1022:2033|1023:2014|1024:2022|1026:2011,2030|1027:2019|1029:2027|1030:2016,2035|1031:2024|1102:2032|1103:2013|1104:2021|1105:2010,2029|1107:2018|1108:2026|1110:2034|1111:2015|1112:2023|1113:2012|1114:2020,2031"},{"id":"all_saints_day","name":"All Saints' Day","en":"All Saints' Day","category":"public","dates":"d|1101:2010-2015,2017,2019,2021,2023,2025,2027,2029,2031,2033,2035,2037,2039,2041,2043,2045,2047,2049,2051,2053,2055,2057,2059"},{"id":"arrival_of_indentured_laborers","name":"Arrival of Indentured Labourers","en":"Arrival of Indentured Laborers","category":"public","dates":"d|1102:2010-2060"},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"}]} diff --git a/app/Support/Holidays/data/MV.json b/app/Support/Holidays/data/MV.json new file mode 100644 index 000000000..519cb8110 --- /dev/null +++ b/app/Support/Holidays/data/MV.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"MV","language":"dv","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"މީލާދީ އާ އަހަރު ފެށޭ ދުވަސް","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"eid_al_fitr","name":"ފިޠުރުޢީދު ދުވަސް","en":"Eid al-Fitr","category":"public","dates":"y|2010:0910|2011:0830|2012:0819|2013:0808|2014:0728|2015:0717|2016:0706|2017:0625|2018:0615|2019:0604|2020:0524|2021:0513|2022:0502|2023:0421|2024:0410|2025:0330|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304","estimated":"y|2010:0910|2011:0830|2012:0819|2013:0808|2014:0728|2015:0717|2016:0706|2017:0625|2018:0615|2019:0604|2020:0524|2021:0513|2022:0502|2023:0421|2024:0410|2025:0330|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304"},{"id":"national_day","name":"ޤައުމީ ދުވަސް","en":"National Day","category":"public","dates":"y|2010:0215|2011:0204|2012:0124|2013:0113|2014:0102,1223|2015:1212|2016:1130|2017:1119|2018:1109|2019:1029|2020:1018|2021:1007|2022:0927|2023:0916|2024:0904|2025:0824|2026:0814|2027:0803|2028:0723|2029:0713|2030:0702|2031:0621|2032:0609|2033:0529|2034:0519|2035:0509|2036:0427|2037:0417|2038:0406|2039:0326|2040:0314|2041:0304|2042:0221|2043:0211|2044:0131|2045:0119|2046:0108,1228|2047:1218|2048:1207|2049:1126|2050:1115|2051:1105|2052:1024|2053:1013|2054:1002|2055:0922|2056:0911|2057:0831|2058:0820|2059:0809|2060:0728","estimated":"y|2010:0215|2011:0204|2012:0124|2013:0113|2014:0102,1223|2015:1212|2016:1130|2017:1119|2018:1109|2019:1029|2020:1018|2021:1007|2022:0927|2023:0916|2024:0904|2025:0824|2026:0814|2027:0803|2028:0723|2029:0713|2030:0702|2031:0621|2032:0609|2033:0529|2034:0519|2035:0509|2036:0427|2037:0417|2038:0406|2039:0326|2040:0314|2041:0304|2042:0221|2043:0211|2044:0131|2045:0119|2046:0108,1228|2047:1218|2048:1207|2049:1126|2050:1115|2051:1105|2052:1024|2053:1013|2054:1002|2055:0922|2056:0911|2057:0831|2058:0820|2059:0809|2060:0728"},{"id":"eid_al_fitr_holiday","name":"ފިޠުރުޢީދުގެ މުނާސަބަތުގައި","en":"Eid al-Fitr Holiday","category":"public","dates":"y|2010:0911-0912|2011:0831-0901|2012:0820-0821|2013:0809-0810|2014:0729-0730|2015:0718-0719|2016:0707-0708|2017:0626-0627|2018:0616-0617|2019:0605-0606|2020:0525-0526|2021:0514-0515|2022:0503-0504|2023:0422-0423|2024:0411-0412|2025:0331-0401|2026:0321-0322|2027:0310-0311|2028:0227-0228|2029:0215-0216|2030:0205-0206|2031:0125-0126|2032:0115-0116|2033:0103-0104,1224-1225|2034:1213-1214|2035:1202-1203|2036:1120-1121|2037:1109-1110|2038:1030-1031|2039:1020-1021|2040:1008-1009|2041:0927-0928|2042:0916-0917|2043:0905-0906|2044:0825-0826|2045:0815-0816|2046:0804-0805|2047:0725-0726|2048:0713-0714|2049:0702-0703|2050:0621-0622|2051:0611-0612|2052:0530-0531|2053:0520-0521|2054:0510-0511|2055:0429-0430|2056:0417-0418|2057:0406-0407|2058:0326-0327|2059:0316-0317|2060:0305-0306","estimated":"y|2010:0911-0912|2011:0831-0901|2012:0820-0821|2013:0809-0810|2014:0729-0730|2015:0718-0719|2016:0707-0708|2017:0626-0627|2018:0616-0617|2019:0605-0606|2020:0525-0526|2021:0514-0515|2022:0503-0504|2023:0422-0423|2024:0411-0412|2025:0331-0401|2026:0321-0322|2027:0310-0311|2028:0227-0228|2029:0215-0216|2030:0205-0206|2031:0125-0126|2032:0115-0116|2033:0103-0104,1224-1225|2034:1213-1214|2035:1202-1203|2036:1120-1121|2037:1109-1110|2038:1030-1031|2039:1020-1021|2040:1008-1009|2041:0927-0928|2042:0916-0917|2043:0905-0906|2044:0825-0826|2045:0815-0816|2046:0804-0805|2047:0725-0726|2048:0713-0714|2049:0702-0703|2050:0621-0622|2051:0611-0612|2052:0530-0531|2053:0520-0521|2054:0510-0511|2055:0429-0430|2056:0417-0418|2057:0406-0407|2058:0326-0327|2059:0316-0317|2060:0305-0306"},{"id":"prophet_s_birthday","name":"ކީރިތި ރަސޫލާގެ ޢީދުމީލާދު","en":"Prophet's Birthday","category":"public","dates":"y|2010:0226|2011:0215|2012:0204|2013:0124|2014:0113|2015:0103,1223|2016:1211|2017:1130|2018:1120|2019:1109|2020:1029|2021:1018|2022:1008|2023:0927|2024:0915|2025:0904|2026:0825|2027:0814|2028:0803|2029:0724|2030:0713|2031:0702|2032:0620|2033:0609|2034:0530|2035:0520|2036:0508|2037:0428|2038:0417|2039:0406|2040:0325|2041:0315|2042:0304|2043:0222|2044:0211|2045:0130|2046:0119|2047:0108,1229|2048:1218|2049:1207|2050:1126|2051:1116|2052:1104|2053:1024|2054:1013|2055:1003|2056:0922|2057:0911|2058:0831|2059:0820|2060:0808","estimated":"y|2010:0226|2011:0215|2012:0204|2013:0124|2014:0113|2015:0103,1223|2016:1211|2017:1130|2018:1120|2019:1109|2020:1029|2021:1018|2022:1008|2023:0927|2024:0915|2025:0904|2026:0825|2027:0814|2028:0803|2029:0724|2030:0713|2031:0702|2032:0620|2033:0609|2034:0530|2035:0520|2036:0508|2037:0428|2038:0417|2039:0406|2040:0325|2041:0315|2042:0304|2043:0222|2044:0211|2045:0130|2046:0119|2047:0108,1229|2048:1218|2049:1207|2050:1126|2051:1116|2052:1104|2053:1024|2054:1013|2055:1003|2056:0922|2057:0911|2058:0831|2059:0820|2060:0808"},{"id":"day_of_arafah","name":"ޙައްޖުދުވަސް","en":"Day of Arafah","category":"public","dates":"y|2010:1115|2011:1105|2012:1025|2013:1014|2014:1003|2015:0922|2016:0910|2017:0831|2018:0820|2019:0810|2020:0730|2021:0719|2022:0708|2023:0627|2024:0615|2025:0605|2026:0526|2027:0515|2028:0504|2029:0423|2030:0412|2031:0401|2032:0321|2033:0310|2034:0228|2035:0217|2036:0206|2037:0125|2038:0115|2039:0104,1225|2040:1213|2041:1203|2042:1122|2043:1111|2044:1030|2045:1020|2046:1009|2047:0929|2048:0918|2049:0907|2050:0827|2051:0816|2052:0804|2053:0725|2054:0714|2055:0704|2056:0622|2057:0611|2058:0531|2059:0521|2060:0509","estimated":"y|2010:1115|2011:1105|2012:1025|2013:1014|2014:1003|2015:0922|2016:0910|2017:0831|2018:0820|2019:0810|2020:0730|2021:0719|2022:0708|2023:0627|2024:0615|2025:0605|2026:0526|2027:0515|2028:0504|2029:0423|2030:0412|2031:0401|2032:0321|2033:0310|2034:0228|2035:0217|2036:0206|2037:0125|2038:0115|2039:0104,1225|2040:1213|2041:1203|2042:1122|2043:1111|2044:1030|2045:1020|2046:1009|2047:0929|2048:0918|2049:0907|2050:0827|2051:0816|2052:0804|2053:0725|2054:0714|2055:0704|2056:0622|2057:0611|2058:0531|2059:0521|2060:0509"},{"id":"islamic_new_year","name":"ހިޖުރީ އާ އަހަރު ފެށޭ ދުވަސް","en":"Islamic New Year","category":"public","dates":"y|2010:1207|2011:1126|2012:1115|2013:1104|2014:1025|2015:1014|2016:1002|2017:0921|2018:0911|2019:0831|2020:0820|2021:0809|2022:0730|2023:0719|2024:0707|2025:0626|2026:0616|2027:0606|2028:0525|2029:0514|2030:0503|2031:0423|2032:0411|2033:0401|2034:0321|2035:0311|2036:0228|2037:0216|2038:0205|2039:0126|2040:0115|2041:0104,1224|2042:1214|2043:1203|2044:1121|2045:1110|2046:1031|2047:1020|2048:1009|2049:0928|2050:0917|2051:0906|2052:0826|2053:0815|2054:0805|2055:0725|2056:0714|2057:0703|2058:0622|2059:0611|2060:0531","estimated":"y|2010:1207|2011:1126|2012:1115|2013:1104|2014:1025|2015:1014|2016:1002|2017:0921|2018:0911|2019:0831|2020:0820|2021:0809|2022:0730|2023:0719|2024:0707|2025:0626|2026:0616|2027:0606|2028:0525|2029:0514|2030:0503|2031:0423|2032:0411|2033:0401|2034:0321|2035:0311|2036:0228|2037:0216|2038:0205|2039:0126|2040:0115|2041:0104,1224|2042:1214|2043:1203|2044:1121|2045:1110|2046:1031|2047:1020|2048:1009|2049:0928|2050:0917|2051:0906|2052:0826|2053:0815|2054:0805|2055:0725|2056:0714|2057:0703|2058:0622|2059:0611|2060:0531"},{"id":"eid_al_adha","name":"އަޟްޙާޢީދު ދުވަސް","en":"Eid al-Adha","category":"public","dates":"y|2010:1116|2011:1106|2012:1026|2013:1015|2014:1004|2015:0923|2016:0911|2017:0901|2018:0821|2019:0811|2020:0731|2021:0720|2022:0709|2023:0628|2024:0616|2025:0606|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510","estimated":"y|2010:1116|2011:1106|2012:1026|2013:1015|2014:1004|2015:0923|2016:0911|2017:0901|2018:0821|2019:0811|2020:0731|2021:0720|2022:0709|2023:0628|2024:0616|2025:0606|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510"},{"id":"first_day_of_ramadan","name":"ރަމަޟާން މަސް ފެށޭ ދުވަސް","en":"First Day of Ramadan","category":"public","dates":"y|2010:0811|2011:0801|2012:0720|2013:0709|2014:0628|2015:0618|2016:0606|2017:0527|2018:0516|2019:0506|2020:0424|2021:0413|2022:0402|2023:0323|2024:0311|2025:0301|2026:0218|2027:0208|2028:0128|2029:0116|2030:0105,1226|2031:1215|2032:1204|2033:1123|2034:1112|2035:1101|2036:1020|2037:1010|2038:0930|2039:0919|2040:0907|2041:0828|2042:0817|2043:0806|2044:0726|2045:0715|2046:0705|2047:0624|2048:0612|2049:0602|2050:0522|2051:0511|2052:0430|2053:0420|2054:0409|2055:0329|2056:0317|2057:0306|2058:0224|2059:0214|2060:0203","estimated":"y|2010:0811|2011:0801|2012:0720|2013:0709|2014:0628|2015:0618|2016:0606|2017:0527|2018:0516|2019:0506|2020:0424|2021:0413|2022:0402|2023:0323|2024:0311|2025:0301|2026:0218|2027:0208|2028:0128|2029:0116|2030:0105,1226|2031:1215|2032:1204|2033:1123|2034:1112|2035:1101|2036:1020|2037:1010|2038:0930|2039:0919|2040:0907|2041:0828|2042:0817|2043:0806|2044:0726|2045:0715|2046:0705|2047:0624|2048:0612|2049:0602|2050:0522|2051:0511|2052:0430|2053:0420|2054:0409|2055:0329|2056:0317|2057:0306|2058:0224|2059:0214|2060:0203"},{"id":"the_day_maldives_embraced_islam","name":"ރާއްޖެ އިސްލާމްވީ ދުވަސް","en":"The Day Maldives Embraced Islam","category":"public","dates":"y|2010:0317|2011:0306|2012:0223|2013:0211|2014:0201|2015:0121|2016:0111,1230|2017:1219|2018:1208|2019:1128|2020:1116|2021:1106|2022:1026|2023:1016|2024:1004|2025:0923|2026:0912|2027:0902|2028:0822|2029:0811|2030:0801|2031:0721|2032:0709|2033:0628|2034:0617|2035:0607|2036:0527|2037:0516|2038:0505|2039:0424|2040:0413|2041:0402|2042:0323|2043:0312|2044:0301|2045:0218|2046:0207|2047:0127|2048:0116|2049:0105,1226|2050:1215|2051:1204|2052:1122|2053:1111|2054:1101|2055:1021|2056:1010|2057:0930|2058:0919|2059:0908|2060:0827","estimated":"y|2010:0317|2011:0306|2012:0223|2013:0211|2014:0201|2015:0121|2016:0111,1230|2017:1219|2018:1208|2019:1128|2020:1116|2021:1106|2022:1026|2023:1016|2024:1004|2025:0923|2026:0912|2027:0902|2028:0822|2029:0811|2030:0801|2031:0721|2032:0709|2033:0628|2034:0617|2035:0607|2036:0527|2037:0516|2038:0505|2039:0424|2040:0413|2041:0402|2042:0323|2043:0312|2044:0301|2045:0218|2046:0207|2047:0127|2048:0116|2049:0105,1226|2050:1215|2051:1204|2052:1122|2053:1111|2054:1101|2055:1021|2056:1010|2057:0930|2058:0919|2059:0908|2060:0827"},{"id":"eid_al_adha_holiday","name":"އަޟްޙާޢީދުގެ މުނާސަބަތުގައި","en":"Eid al-Adha Holiday","category":"public","dates":"y|2010:1117-1119|2011:1107-1109|2012:1027-1029|2013:1016-1018|2014:1005-1007|2015:0924-0926|2016:0912-0914|2017:0902-0904|2018:0822-0824|2019:0812-0814|2020:0801-0803|2021:0721-0723|2022:0710-0712|2023:0629-0701|2024:0617-0619|2025:0607-0609|2026:0528-0530|2027:0517-0519|2028:0506-0508|2029:0425-0427|2030:0414-0416|2031:0403-0405|2032:0323-0325|2033:0312-0314|2034:0302-0304|2035:0219-0221|2036:0208-0210|2037:0127-0129|2038:0117-0119|2039:0106-0108,1227-1229|2040:1215-1217|2041:1205-1207|2042:1124-1126|2043:1113-1115|2044:1101-1103|2045:1022-1024|2046:1011-1013|2047:1001-1003|2048:0920-0922|2049:0909-0911|2050:0829-0831|2051:0818-0820|2052:0806-0808|2053:0727-0729|2054:0716-0718|2055:0706-0708|2056:0624-0626|2057:0613-0615|2058:0602-0604|2059:0523-0525|2060:0511-0513","estimated":"y|2010:1117-1119|2011:1107-1109|2012:1027-1029|2013:1016-1018|2014:1005-1007|2015:0924-0926|2016:0912-0914|2017:0902-0904|2018:0822-0824|2019:0812-0814|2020:0801-0803|2021:0721-0723|2022:0710-0712|2023:0629-0701|2024:0617-0619|2025:0607-0609|2026:0528-0530|2027:0517-0519|2028:0506-0508|2029:0425-0427|2030:0414-0416|2031:0403-0405|2032:0323-0325|2033:0312-0314|2034:0302-0304|2035:0219-0221|2036:0208-0210|2037:0127-0129|2038:0117-0119|2039:0106-0108,1227-1229|2040:1215-1217|2041:1205-1207|2042:1124-1126|2043:1113-1115|2044:1101-1103|2045:1022-1024|2046:1011-1013|2047:1001-1003|2048:0920-0922|2049:0909-0911|2050:0829-0831|2051:0818-0820|2052:0806-0808|2053:0727-0729|2054:0716-0718|2055:0706-0708|2056:0624-0626|2057:0613-0615|2058:0602-0604|2059:0523-0525|2060:0511-0513"},{"id":"international_worker_s_day","name":"ބައިނަލްއަޤްވާމީ މަސައްކަތްތެރިންގެ ދުވަސް","en":"International Worker's Day","category":"public","dates":"d|0501:2010-2060"},{"id":"independence_day","name":"މިނިވަން ދުވަސް","en":"Independence Day","category":"public","dates":"d|0726:2010-2060"},{"id":"victory_day","name":"ނަޞްރުގެ ދުވަސް","en":"Victory Day","category":"public","dates":"d|1103:2010-2060"},{"id":"republic_day","name":"ޖުމްހޫރީ ދުވަސް","en":"Republic Day","category":"public","dates":"d|1111:2010-2060"}]} diff --git a/app/Support/Holidays/data/MW.json b/app/Support/Holidays/data/MW.json new file mode 100644 index 000000000..c33592c9f --- /dev/null +++ b/app/Support/Holidays/data/MW.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"MW","language":null,"years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051|0103:2011,2022,2028,2033,2039,2050,2056"},{"id":"john_chilembwe_day","name":"John Chilembwe Day","en":"John Chilembwe Day","category":"public","dates":"d|0115:2010-2060|0116:2012,2017,2023,2034,2040,2045,2051|0117:2011,2022,2028,2033,2039,2050,2056"},{"id":"martyrs_day","name":"Martyrs Day","en":"Martyrs Day","category":"public","dates":"d|0303:2010-2060|0304:2013,2019,2024,2030,2041,2047,2052,2058|0305:2012,2018,2029,2035,2040,2046,2057"},{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_monday","name":"Easter Monday","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"labour_day","name":"Labour Day","en":"Labour Day","category":"public","dates":"d|0501:2010-2060|0502:2011,2016,2022,2033,2039,2044,2050|0503:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"kamuzu_day","name":"Kamuzu Day","en":"Kamuzu Day","category":"public","dates":"d|0514:2010-2060|0515:2017,2023,2028,2034,2045,2051,2056|0516:2011,2016,2022,2033,2039,2044,2050"},{"id":"independence_day","name":"Independence Day","en":"Independence Day","category":"public","dates":"d|0706:2010-2060|0707:2014,2025,2031,2036,2042,2053,2059|0708:2013,2019,2024,2030,2041,2047,2052,2058"},{"id":"mother_s_day","name":"Mother's Day","en":"Mother's Day","category":"public","dates":"d|1015:2010-2060|1016:2017,2023,2028,2034,2045,2051,2056|1017:2011,2016,2022,2033,2039,2044,2050"},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060|1227:2010-2011,2016,2021-2022,2027,2032-2033,2038-2039,2044,2049-2050,2055,2060"},{"id":"boxing_day","name":"Boxing Day","en":"Boxing Day","category":"public","dates":"d|1226:2010-2060|1228:2010,2015,2020-2021,2026-2027,2032,2037-2038,2043,2048-2049,2054-2055,2060"}]} diff --git a/app/Support/Holidays/data/MX.json b/app/Support/Holidays/data/MX.json new file mode 100644 index 000000000..e5f3ffd83 --- /dev/null +++ b/app/Support/Holidays/data/MX.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"MX","language":"es","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Año Nuevo","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"constitution_day","name":"Día de la Constitución","en":"Constitution Day","category":"public","dates":"d|0201:2010,2016,2021,2027,2038,2044,2049,2055|0202:2015,2026,2032,2037,2043,2054,2060|0203:2014,2020,2025,2031,2042,2048,2053,2059|0204:2013,2019,2030,2036,2041,2047,2058|0205:2018,2024,2029,2035,2046,2052,2057|0206:2012,2017,2023,2034,2040,2045,2051|0207:2011,2022,2028,2033,2039,2050,2056"},{"id":"benito_juarez_s_birthday","name":"Natalicio de Benito Juárez","en":"Benito Juárez's birthday","category":"public","dates":"d|0315:2010,2021,2027,2032,2038,2049,2055,2060|0316:2015,2020,2026,2037,2043,2048,2054|0317:2014,2025,2031,2036,2042,2053,2059|0318:2013,2019,2024,2030,2041,2047,2052,2058|0319:2012,2018,2029,2035,2040,2046,2057|0320:2017,2023,2028,2034,2045,2051,2056|0321:2011,2016,2022,2033,2039,2044,2050"},{"id":"labor_day","name":"Día del Trabajo","en":"Labor Day","category":"public","dates":"d|0501:2010-2060"},{"id":"independence_day","name":"Día de la Independencia","en":"Independence Day","category":"public","dates":"d|0916:2010-2060"},{"id":"change_of_federal_government","name":"Transmisión del Poder Ejecutivo Federal","en":"Change of Federal Government","category":"public","dates":"d|1001:2024,2030,2036,2042,2048,2054,2060|1201:2012,2018"},{"id":"revolution_day","name":"Día de la Revolución","en":"Revolution Day","category":"public","dates":"d|1115:2010,2021,2027,2032,2038,2049,2055,2060|1116:2015,2020,2026,2037,2043,2048,2054|1117:2014,2025,2031,2036,2042,2053,2059|1118:2013,2019,2024,2030,2041,2047,2052,2058|1119:2012,2018,2029,2035,2040,2046,2057|1120:2017,2023,2028,2034,2045,2051,2056|1121:2011,2016,2022,2033,2039,2044,2050"},{"id":"christmas_day","name":"Navidad","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"}]} diff --git a/app/Support/Holidays/data/MY.json b/app/Support/Holidays/data/MY.json new file mode 100644 index 000000000..471690f86 --- /dev/null +++ b/app/Support/Holidays/data/MY.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"MY","language":"ms_MY","years":[2010,2060],"subdivisions":{"01":"Johor","02":"Kedah","03":"Kelantan","04":"Melaka","05":"Negeri Sembilan","06":"Pahang","07":"Pulau Pinang","08":"Perak","09":"Perlis","10":"Selangor","11":"Terengganu","12":"Sabah","13":"Sarawak","14":"Wilayah Persekutuan Kuala Lumpur","15":"Wilayah Persekutuan Labuan","16":"Wilayah Persekutuan Putrajaya"},"holidays":[{"id":"new_year_s_day","name":"Tahun Baharu","en":"New Year's Day","category":"public","regional":{"04":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051","05":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051","06":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051","07":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051","08":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051","10":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051","12":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051","13":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051","14":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051","15":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051","16":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051"}},{"id":"nuzul_al_quran_day","name":"Hari Nuzul Al-Quran","en":"Nuzul Al-Quran Day","category":"public","regional":{"03":"y|2010:0827|2011:0817|2012:0805|2013:0725|2014:0715|2015:0704-0705|2016:0622|2017:0612|2018:0602-0603|2019:0522|2020:0510|2021:0429|2022:0419|2023:0408-0409|2024:0328|2025:0318|2026:0307-0308|2027:0224|2028:0213|2029:0201|2030:0122|2031:0111-0112|2032:0101,1220|2033:1210-1211|2034:1129|2035:1118|2036:1106|2037:1026|2038:1016-1017|2039:1006|2040:0924|2041:0913|2042:0902|2043:0823|2044:0811|2045:0801|2046:0722|2047:0711|2048:0629|2049:0618|2050:0607|2051:0528|2052:0517|2053:0506|2054:0426|2055:0415|2056:0403|2057:0323|2058:0312|2059:0302|2060:0220","06":"y|2010:0827|2011:0817|2012:0805-0806|2013:0725|2014:0715|2015:0704|2016:0622|2017:0612|2018:0602|2019:0522|2020:0510-0511|2021:0429|2022:0419|2023:0408|2024:0328|2025:0318|2026:0307|2027:0224|2028:0213-0214|2029:0201|2030:0122|2031:0111|2032:0101,1220|2033:1210|2034:1129|2035:1118-1119|2036:1106|2037:1026|2038:1016|2039:1006|2040:0924|2041:0913|2042:0902|2043:0823-0824|2044:0811|2045:0801|2046:0722-0723|2047:0711|2048:0629|2049:0618|2050:0607|2051:0528-0529|2052:0517|2053:0506|2054:0426-0427|2055:0415|2056:0403|2057:0323|2058:0312|2059:0302-0303|2060:0220","07":"y|2010:0827|2011:0817|2012:0805-0806|2013:0725|2014:0715|2015:0704|2016:0622|2017:0612|2018:0602|2019:0522|2020:0510-0511|2021:0429|2022:0419|2023:0408|2024:0328|2025:0318|2026:0307|2027:0224|2028:0213-0214|2029:0201|2030:0122|2031:0111|2032:0101,1220|2033:1210|2034:1129|2035:1118-1119|2036:1106|2037:1026|2038:1016|2039:1006|2040:0924|2041:0913|2042:0902|2043:0823-0824|2044:0811|2045:0801|2046:0722-0723|2047:0711|2048:0629|2049:0618|2050:0607|2051:0528-0529|2052:0517|2053:0506|2054:0426-0427|2055:0415|2056:0403|2057:0323|2058:0312|2059:0302-0303|2060:0220","08":"y|2010:0827|2011:0817|2012:0805-0806|2013:0725|2014:0715|2015:0704|2016:0622|2017:0612|2018:0602|2019:0522|2020:0510-0511|2021:0429|2022:0419|2023:0408|2024:0328|2025:0318|2026:0307|2027:0224|2028:0213-0214|2029:0201|2030:0122|2031:0111|2032:0101,1220|2033:1210|2034:1129|2035:1118-1119|2036:1106|2037:1026|2038:1016|2039:1006|2040:0924|2041:0913|2042:0902|2043:0823-0824|2044:0811|2045:0801|2046:0722-0723|2047:0711|2048:0629|2049:0618|2050:0607|2051:0528-0529|2052:0517|2053:0506|2054:0426-0427|2055:0415|2056:0403|2057:0323|2058:0312|2059:0302-0303|2060:0220","09":"y|2010:0827|2011:0817|2012:0805-0806|2013:0725|2014:0715|2015:0704|2016:0622|2017:0612|2018:0602|2019:0522|2020:0510-0511|2021:0429|2022:0419|2023:0408|2024:0328|2025:0318|2026:0307|2027:0224|2028:0213-0214|2029:0201|2030:0122|2031:0111|2032:0101,1220|2033:1210|2034:1129|2035:1118-1119|2036:1106|2037:1026|2038:1016|2039:1006|2040:0924|2041:0913|2042:0902|2043:0823-0824|2044:0811|2045:0801|2046:0722-0723|2047:0711|2048:0629|2049:0618|2050:0607|2051:0528-0529|2052:0517|2053:0506|2054:0426-0427|2055:0415|2056:0403|2057:0323|2058:0312|2059:0302-0303|2060:0220","10":"y|2010:0827|2011:0817|2012:0805-0806|2013:0725|2014:0715|2015:0704|2016:0622|2017:0612|2018:0602|2019:0522|2020:0510-0511|2021:0429|2022:0419|2023:0408|2024:0328|2025:0318|2026:0307|2027:0224|2028:0213-0214|2029:0201|2030:0122|2031:0111|2032:0101,1220|2033:1210|2034:1129|2035:1118-1119|2036:1106|2037:1026|2038:1016|2039:1006|2040:0924|2041:0913|2042:0902|2043:0823-0824|2044:0811|2045:0801|2046:0722-0723|2047:0711|2048:0629|2049:0618|2050:0607|2051:0528-0529|2052:0517|2053:0506|2054:0426-0427|2055:0415|2056:0403|2057:0323|2058:0312|2059:0302-0303|2060:0220","11":"y|2010:0827|2011:0817|2012:0805|2013:0725|2014:0715|2015:0704-0705|2016:0622|2017:0612|2018:0602-0603|2019:0522|2020:0510|2021:0429|2022:0419|2023:0408-0409|2024:0328|2025:0318|2026:0307-0308|2027:0224|2028:0213|2029:0201|2030:0122|2031:0111-0112|2032:0101,1220|2033:1210-1211|2034:1129|2035:1118|2036:1106|2037:1026|2038:1016-1017|2039:1006|2040:0924|2041:0913|2042:0902|2043:0823|2044:0811|2045:0801|2046:0722|2047:0711|2048:0629|2049:0618|2050:0607|2051:0528|2052:0517|2053:0506|2054:0426|2055:0415|2056:0403|2057:0323|2058:0312|2059:0302|2060:0220","14":"y|2014:0715|2015:0704|2016:0622|2017:0612|2018:0602|2019:0522|2020:0510-0511|2021:0429|2022:0419|2023:0408|2024:0328|2025:0318|2026:0307|2027:0224|2028:0213-0214|2029:0201|2030:0122|2031:0111|2032:0101,1220|2033:1210|2034:1129|2035:1118-1119|2036:1106|2037:1026|2038:1016|2039:1006|2040:0924|2041:0913|2042:0902|2043:0823-0824|2044:0811|2045:0801|2046:0722-0723|2047:0711|2048:0629|2049:0618|2050:0607|2051:0528-0529|2052:0517|2053:0506|2054:0426-0427|2055:0415|2056:0403|2057:0323|2058:0312|2059:0302-0303|2060:0220","15":"y|2014:0715|2015:0704|2016:0622|2017:0612|2018:0602|2019:0522|2020:0510-0511|2021:0429|2022:0419|2023:0408|2024:0328|2025:0318|2026:0307|2027:0224|2028:0213-0214|2029:0201|2030:0122|2031:0111|2032:0101,1220|2033:1210|2034:1129|2035:1118-1119|2036:1106|2037:1026|2038:1016|2039:1006|2040:0924|2041:0913|2042:0902|2043:0823-0824|2044:0811|2045:0801|2046:0722-0723|2047:0711|2048:0629|2049:0618|2050:0607|2051:0528-0529|2052:0517|2053:0506|2054:0426-0427|2055:0415|2056:0403|2057:0323|2058:0312|2059:0302-0303|2060:0220","16":"y|2014:0715|2015:0704|2016:0622|2017:0612|2018:0602|2019:0522|2020:0510-0511|2021:0429|2022:0419|2023:0408|2024:0328|2025:0318|2026:0307|2027:0224|2028:0213-0214|2029:0201|2030:0122|2031:0111|2032:0101,1220|2033:1210|2034:1129|2035:1118-1119|2036:1106|2037:1026|2038:1016|2039:1006|2040:0924|2041:0913|2042:0902|2043:0823-0824|2044:0811|2045:0801|2046:0722-0723|2047:0711|2048:0629|2049:0618|2050:0607|2051:0528-0529|2052:0517|2053:0506|2054:0426-0427|2055:0415|2056:0403|2057:0323|2058:0312|2059:0302-0303|2060:0220"},"estimated":"y|2028:0213|2029:0201|2030:0122|2031:0111|2032:0101,1220|2033:1210|2034:1129|2035:1118|2036:1106|2037:1026|2038:1016|2039:1006|2040:0924|2041:0913|2042:0902|2043:0823|2044:0811|2045:0801|2046:0722|2047:0711|2048:0629|2049:0618|2050:0607|2051:0528|2052:0517|2053:0506|2054:0426|2055:0415|2056:0403|2057:0323|2058:0312|2059:0302|2060:0220"},{"id":"isra_and_mi_raj","name":"Israk dan Mikraj","en":"Isra' and Mi'raj","category":"public","regional":{"02":"y|2010:0709,0711|2011:0629|2012:0617|2013:0606|2014:0527|2015:0516|2016:0505|2017:0424|2018:0414|2019:0403|2020:0322|2021:0311|2022:0301|2023:0218|2024:0208|2025:0127|2026:0117|2027:0106,1226|2028:1214|2029:1204|2030:1123|2031:1113|2032:1101|2033:1021,1023|2034:1010|2035:0930|2036:0918|2037:0908|2038:0829|2039:0818|2040:0806|2041:0726,0728|2042:0715|2043:0705|2044:0624,0626|2045:0613|2046:0603|2047:0523|2048:0511|2049:0430,0502|2050:0420|2051:0409|2052:0329,0331|2053:0318|2054:0307|2055:0224|2056:0213|2057:0202,0206|2058:0123|2059:0112|2060:0102,0104,1221","05":"y|2010:0709|2011:0629|2012:0617-0618|2013:0606|2014:0527|2015:0516|2016:0505|2017:0424|2018:0414|2019:0403|2020:0322-0323|2021:0311|2022:0301|2023:0218|2024:0208|2025:0127|2026:0117|2027:0106,1226-1227|2028:1214|2029:1204|2030:1123|2031:1113|2032:1101|2033:1021|2034:1010|2035:0930-1001|2036:0918|2037:0908|2038:0829-0830|2039:0818|2040:0806|2041:0726|2042:0715|2043:0705-0706|2044:0624|2045:0613|2046:0603,0605|2047:0523|2048:0511|2049:0430|2050:0420|2051:0409-0410|2052:0329|2053:0318|2054:0307|2055:0224|2056:0213-0214|2057:0202|2058:0123|2059:0112-0113|2060:0102,1221","09":"y|2010:0709|2011:0629|2012:0617-0618|2013:0606|2014:0527|2015:0516|2016:0505|2017:0424|2018:0414|2019:0403|2020:0322-0323|2021:0311|2022:0301|2023:0218|2024:0208|2025:0127|2026:0117|2027:0106,1226-1227|2028:1214|2029:1204|2030:1123|2031:1113|2032:1101|2033:1021|2034:1010|2035:0930-1001|2036:0918|2037:0908|2038:0829-0830|2039:0818|2040:0806|2041:0726|2042:0715|2043:0705-0706|2044:0624|2045:0613|2046:0603,0605|2047:0523|2048:0511|2049:0430|2050:0420|2051:0409-0410|2052:0329|2053:0318|2054:0307|2055:0224|2056:0213-0214|2057:0202|2058:0123|2059:0112-0113|2060:0102,1221","11":"y|2020:0322|2021:0311|2022:0301|2023:0218-0219|2024:0208|2025:0127|2026:0117-0118|2027:0106,1226|2028:1214|2029:1204|2030:1123-1124|2031:1113|2032:1101|2033:1021|2034:1010|2035:0930|2036:0918|2037:0908|2038:0829|2039:0818|2040:0806|2041:0726|2042:0715|2043:0705|2044:0624|2045:0613|2046:0603|2047:0523|2048:0511|2049:0430|2050:0420|2051:0409|2052:0329|2053:0318|2054:0307-0308|2055:0224|2056:0213|2057:0202|2058:0123|2059:0112|2060:0102,1221"},"estimated":"y|2028:1214|2029:1204|2030:1123|2031:1113|2032:1101|2033:1021|2034:1010|2035:0930|2036:0918|2037:0908|2038:0829|2039:0818|2040:0806|2041:0726|2042:0715|2043:0705|2044:0624|2045:0613|2046:0603|2047:0523|2048:0511|2049:0430|2050:0420|2051:0409|2052:0329|2053:0318|2054:0307|2055:0224|2056:0213|2057:0202|2058:0123|2059:0112|2060:0102,1221"},{"id":"eid_al_fitr","name":"Hari Raya Puasa","en":"Eid al-Fitr","category":"public","dates":"y|2010:0910|2011:0831|2012:0819,0821|2013:0808|2014:0728|2015:0717|2016:0706|2017:0625,0627|2018:0615|2019:0605|2020:0524,0526|2021:0513|2022:0502|2023:0422|2024:0410|2025:0331|2026:0321|2027:0310|2028:0227,0229|2029:0215|2030:0204|2031:0125|2032:0114|2033:0103,1223|2034:1212|2035:1201|2036:1119|2037:1109|2038:1030|2039:1019|2040:1008|2041:0927|2042:0916|2043:0905|2044:0825|2045:0814|2046:0804|2047:0724|2048:0713|2049:0702|2050:0621|2051:0610|2052:0530|2053:0520|2054:0509|2055:0428|2056:0416,0418|2057:0405|2058:0326|2059:0315|2060:0304","regional":{"01":"d|0617:2018|0719:2015","02":"d|0617:2018|0704:2049|0719:2015|0912:2010|0929:2041|1226:2033","03":"d|0127:2031|0317:2059|0323:2026|0424:2023|0511:2054|0612:2051|0806:2046|0907:2043|1101:2038|1203:2035","04":"d|0301:2028|0419:2056","11":"d|0127:2031|0317:2059|0323:2026|0424:2023|0511:2054|0612:2051|0806:2046|0907:2043|1101:2038|1203:2035"},"estimated":"y|2028:0227|2029:0215|2030:0204|2031:0125|2032:0114|2033:0103,1223|2034:1212|2035:1201|2036:1119|2037:1109|2038:1030|2039:1019|2040:1008|2041:0927|2042:0916|2043:0905|2044:0825|2045:0814|2046:0804|2047:0724|2048:0713|2049:0702|2050:0621|2051:0610|2052:0530|2053:0520|2054:0509|2055:0428|2056:0416|2057:0405|2058:0326|2059:0315|2060:0304"},{"id":"prophet_muhammad_s_birthday","name":"Hari Keputeraan Nabi Muhammad S.A.W.","en":"Prophet Muhammad's Birthday","category":"public","dates":"y|2010:0226|2011:0216|2012:0205-0206|2013:0124|2014:0114|2015:0103,1224|2016:1212|2017:1201|2018:1120|2019:1109|2020:1029|2021:1019|2022:1010|2023:0928|2024:0916|2025:0905|2026:0825|2027:0815-0816|2028:0804|2029:0724|2030:0713|2031:0702|2032:0621|2033:0610|2034:0530|2035:0520-0521|2036:0509|2037:0428|2038:0417|2039:0406|2040:0325-0326|2041:0315|2042:0305|2043:0222-0223|2044:0212|2045:0131|2046:0120|2047:0109,1229-1230|2048:1218|2049:1208|2050:1127-1128|2051:1116|2052:1104|2053:1024|2054:1014|2055:1003-1004|2056:0922|2057:0912|2058:0901-0902|2059:0821|2060:0809","regional":{"01":"d|1203:2017","02":"d|0214:2044|0228:2010|0317:2041|0511:2036|0612:2033|0806:2028|0907:2025|0924:2056|1026:2053|1203:2017|1220:2048","03":"d|0104:2015|0121:2046|0418:2038|0714:2030|1110:2019","11":"d|0104:2015|0121:2046|0418:2038|0714:2030|1110:2019"},"estimated":"y|2028:0804|2029:0724|2030:0713|2031:0702|2032:0621|2033:0610|2034:0530|2035:0520|2036:0509|2037:0428|2038:0417|2039:0406|2040:0325|2041:0315|2042:0305|2043:0222|2044:0212|2045:0131|2046:0120|2047:0109,1229|2048:1218|2049:1208|2050:1127|2051:1116|2052:1104|2053:1024|2054:1014|2055:1003|2056:0922|2057:0912|2058:0901|2059:0821|2060:0809"},{"id":"eid_al_fitr_second_day","name":"Hari Raya Puasa (Hari Kedua)","en":"Eid al-Fitr (Second Day)","category":"public","dates":"y|2010:0911|2011:0901|2012:0820|2013:0809|2014:0729|2015:0718|2016:0707|2017:0626|2018:0616|2019:0606|2020:0525|2021:0514|2022:0503|2023:0423-0424|2024:0411|2025:0401|2026:0322-0323|2027:0311|2028:0228|2029:0216|2030:0205|2031:0126-0127|2032:0115|2033:0104,1224|2034:1213|2035:1202-1203|2036:1120|2037:1110|2038:1031-1101|2039:1020|2040:1009|2041:0928|2042:0917|2043:0906-0907|2044:0826|2045:0815|2046:0805-0806|2047:0725|2048:0714|2049:0703|2050:0622|2051:0611-0612|2052:0531|2053:0521|2054:0510-0511|2055:0429|2056:0417|2057:0406|2058:0327|2059:0316-0317|2060:0305","regional":{"01":"d|0324:2026|0516:2021","02":"d|0218:2029|0307:2060|0408:2057|0516:2021|0602:2052|0811:2013|0828:2044","03":"d|0617:2018|0704:2049|0719:2015|0912:2010|1001:2041|1226:2033","04":"d|0128:2031|0318:2059|0324:2026|0512:2054|0613:2051|0807:2046|0908:2043|1102:2038|1204:2035","11":"d|0617:2018|0704:2049|0719:2015|0912:2010|0929:2041|1226:2033"},"estimated":"y|2028:0228|2029:0216|2030:0205|2031:0126|2032:0115|2033:0104,1224|2034:1213|2035:1202|2036:1120|2037:1110|2038:1031|2039:1020|2040:1009|2041:0928|2042:0917|2043:0906|2044:0826|2045:0815|2046:0805|2047:0725|2048:0714|2049:0703|2050:0622|2051:0611|2052:0531|2053:0521|2054:0510|2055:0429|2056:0417|2057:0406|2058:0327|2059:0316|2060:0305"},{"id":"islamic_new_year","name":"Awal Muharam","en":"Islamic New Year","category":"public","dates":"y|2010:1208|2011:1127|2012:1115|2013:1105|2014:1025|2015:1014|2016:1002|2017:0922|2018:0911|2019:0901|2020:0820|2021:0810|2022:0730|2023:0719|2024:0707|2025:0627|2026:0617|2027:0606|2028:0526|2029:0515|2030:0504|2031:0423|2032:0412|2033:0401|2034:0322|2035:0311|2036:0229|2037:0217|2038:0206|2039:0126|2040:0116|2041:0104,1225|2042:1214|2043:1203|2044:1121|2045:1110|2046:1031|2047:1021|2048:1009|2049:0929|2050:0918|2051:0907|2052:0826|2053:0816|2054:0805|2055:0726|2056:0714|2057:0704|2058:0623|2059:0612|2060:0531","regional":{"07":"d|0708:2024"},"estimated":"y|2028:0526|2029:0515|2030:0504|2031:0423|2032:0412|2033:0401|2034:0322|2035:0311|2036:0229|2037:0217|2038:0206|2039:0126|2040:0116|2041:0104,1225|2042:1214|2043:1203|2044:1121|2045:1110|2046:1031|2047:1021|2048:1009|2049:0929|2050:0918|2051:0907|2052:0826|2053:0816|2054:0805|2055:0726|2056:0714|2057:0704|2058:0623|2059:0612|2060:0531"},{"id":"day_of_arafah","name":"Hari Arafah","en":"Day of Arafah","category":"public","regional":{"03":"y|2023:0628|2024:0616|2025:0606|2026:0526|2027:0516|2028:0504|2029:0424|2030:0413,0416|2031:0402|2032:0321|2033:0311|2034:0228|2035:0218|2036:0207|2037:0126|2038:0115|2039:0105,1225|2040:1214|2041:1203|2042:1122,1125|2043:1111|2044:1031|2045:1020|2046:1010|2047:0929|2048:0918|2049:0907|2050:0827,0830|2051:0816|2052:0805|2053:0725|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601,0604|2059:0521|2060:0510","11":"y|2010:1116|2011:1106|2012:1025|2013:1014|2014:1004,1007|2015:0923|2016:0911|2017:0831|2018:0821|2019:0810,0813|2020:0730|2021:0719|2022:0709,0712|2023:0628|2024:0616|2025:0606|2026:0526|2027:0516|2028:0504|2029:0424|2030:0413,0416|2031:0402|2032:0321|2033:0311|2034:0228|2035:0218|2036:0207|2037:0126|2038:0115|2039:0105,1225|2040:1214|2041:1203|2042:1122,1125|2043:1111|2044:1031|2045:1020|2046:1010|2047:0929|2048:0918|2049:0907|2050:0827,0830|2051:0816|2052:0805|2053:0725|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601,0604|2059:0521|2060:0510"},"estimated":"y|2028:0504|2029:0424|2030:0413|2031:0402|2032:0321|2033:0311|2034:0228|2035:0218|2036:0207|2037:0126|2038:0115|2039:0105,1225|2040:1214|2041:1203|2042:1122|2043:1111|2044:1031|2045:1020|2046:1010|2047:0929|2048:0918|2049:0907|2050:0827|2051:0816|2052:0805|2053:0725|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0521|2060:0510"},{"id":"eid_al_fitr_third_day","name":"Hari Raya Puasa (Hari Ketiga)","en":"Eid al-Fitr (Third Day)","category":"public","regional":{"04":"y|2025:0402|2026:0323|2027:0312|2028:0229|2029:0217|2030:0206|2031:0127|2032:0116|2033:0105,1225-1226|2034:1214|2035:1203|2036:1121|2037:1111|2038:1101|2039:1021|2040:1010|2041:0929-0930|2042:0918|2043:0907|2044:0827|2045:0816|2046:0806|2047:0726|2048:0715|2049:0704-0705|2050:0623|2051:0612|2052:0601|2053:0522|2054:0511|2055:0430|2056:0418|2057:0407|2058:0328|2059:0317|2060:0306"},"estimated":"y|2028:0229|2029:0217|2030:0206|2031:0127|2032:0116|2033:0105,1225|2034:1214|2035:1203|2036:1121|2037:1111|2038:1101|2039:1021|2040:1010|2041:0929|2042:0918|2043:0907|2044:0827|2045:0816|2046:0806|2047:0726|2048:0715|2049:0704|2050:0623|2051:0612|2052:0601|2053:0522|2054:0511|2055:0430|2056:0418|2057:0407|2058:0328|2059:0317|2060:0306"},{"id":"beginning_of_ramadan","name":"Awal Ramadan","en":"Beginning of Ramadan","category":"public","regional":{"01":"y|2010:0811|2011:0801|2012:0720|2013:0709|2014:0629|2015:0618|2016:0607|2017:0527|2018:0517|2019:0506|2020:0424,0426|2021:0413|2022:0403|2023:0323|2024:0312|2025:0302-0303|2026:0219|2027:0208|2028:0128|2029:0116|2030:0106-0107,1226|2031:1216|2032:1204|2033:1124|2034:1113|2035:1102|2036:1021|2037:1010|2038:0930|2039:0920|2040:0908|2041:0828|2042:0817-0818|2043:0807|2044:0726|2045:0716-0717|2046:0706|2047:0625|2048:0613|2049:0602|2050:0522-0523|2051:0512|2052:0501|2053:0420-0421|2054:0410|2055:0330|2056:0318|2057:0307|2058:0224-0225|2059:0214|2060:0204","02":"y|2010:0811|2011:0801|2012:0720,0722|2013:0709|2014:0629|2015:0618|2016:0607|2017:0527|2018:0517|2019:0506|2020:0424,0426|2021:0413|2022:0403|2023:0323|2024:0312|2025:0302|2026:0219|2027:0208|2028:0128,0130|2029:0116|2030:0106,1226|2031:1216|2032:1204|2033:1124|2034:1113|2035:1102,1104|2036:1021|2037:1010|2038:0930|2039:0920|2040:0908|2041:0828|2042:0817|2043:0807,0809|2044:0726|2045:0716|2046:0706,0708|2047:0625|2048:0613|2049:0602|2050:0522|2051:0512,0514|2052:0501|2053:0420|2054:0410,0412|2055:0330|2056:0318|2057:0307|2058:0224|2059:0214,0216|2060:0204","04":"y|2010:0811|2011:0801|2012:0720|2013:0709|2014:0629-0630|2015:0618|2016:0607|2017:0527|2018:0517|2019:0506|2020:0424|2021:0413|2022:0403-0404|2023:0323|2024:0312"},"estimated":"y|2028:0128|2029:0116|2030:0106,1226|2031:1216|2032:1204|2033:1124|2034:1113|2035:1102|2036:1021|2037:1010|2038:0930|2039:0920|2040:0908|2041:0828|2042:0817|2043:0807|2044:0726|2045:0716|2046:0706|2047:0625|2048:0613|2049:0602|2050:0522|2051:0512|2052:0501|2053:0420|2054:0410|2055:0330|2056:0318|2057:0307|2058:0224|2059:0214|2060:0204"},{"id":"eid_al_adha","name":"Hari Raya Qurban","en":"Eid al-Adha","category":"public","dates":"y|2010:1117|2011:1107|2012:1026|2013:1015|2014:1005-1006|2015:0924|2016:0912|2017:0901|2018:0822|2019:0811-0812|2020:0731|2021:0720|2022:0710-0711|2023:0629|2024:0617|2025:0607|2026:0527|2027:0517|2028:0505|2029:0425|2030:0414-0415|2031:0403|2032:0322|2033:0312|2034:0301|2035:0219|2036:0208|2037:0127|2038:0116|2039:0106,1226|2040:1215|2041:1204|2042:1123-1124|2043:1112|2044:1101|2045:1021|2046:1011|2047:0930|2048:0919|2049:0908|2050:0828-0829|2051:0817|2052:0806|2053:0726|2054:0716|2055:0706|2056:0624|2057:0613|2058:0602,0604|2059:0522|2060:0511","regional":{"01":"d|0802:2020|0903:2017","02":"d|0210:2036|0507:2028|0802:2020|0903:2017|1028:2012","03":"d|0118:2038|0314:2033|0609:2025|0626:2056|0728:2053|0921:2048|1023:2045|1217:2040","09":"d|0416:2030|0712:2022|0813:2019|0830:2050|1007:2014|1125:2042","11":"d|0118:2038|0314:2033|0609:2025|0626:2056|0728:2053|0921:2048|1023:2045|1217:2040"},"estimated":"y|2028:0505|2029:0425|2030:0414|2031:0403|2032:0322|2033:0312|2034:0301|2035:0219|2036:0208|2037:0127|2038:0116|2039:0106,1226|2040:1215|2041:1204|2042:1123|2043:1112|2044:1101|2045:1021|2046:1011|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0806|2053:0726|2054:0716|2055:0706|2056:0624|2057:0613|2058:0602|2059:0522|2060:0511"},{"id":"eid_al_adha_second_day","name":"Hari Raya Qurban (Hari Kedua)","en":"Eid al-Adha (Second Day)","category":"public","regional":{"02":"y|2010:1118|2011:1108|2012:1027|2013:1016|2014:1006|2015:0925,0927|2016:0913|2017:0902|2018:0823|2019:0812|2020:0801|2021:0721|2022:0711|2023:0630,0702|2024:0618|2025:0608|2026:0528|2027:0518|2028:0506|2029:0426|2030:0415|2031:0404,0406|2032:0323|2033:0313|2034:0302|2035:0220|2036:0209|2037:0128|2038:0117|2039:0107,0109,1227|2040:1216|2041:1205|2042:1124|2043:1113,1115|2044:1102|2045:1022|2046:1012,1014|2047:1001|2048:0920|2049:0909|2050:0829|2051:0818,0820|2052:0807|2053:0727|2054:0717,0719|2055:0707|2056:0625|2057:0614|2058:0603|2059:0523,0525|2060:0512","03":"y|2010:1118|2011:1108|2012:1027-1028|2013:1016|2014:1006|2015:0925|2016:0913|2017:0902-0903|2018:0823|2019:0812|2020:0801-0802|2021:0721|2022:0711|2023:0630|2024:0618|2025:0608|2026:0528|2027:0518|2028:0506-0507|2029:0426|2030:0415|2031:0404|2032:0323|2033:0313|2034:0302|2035:0220|2036:0209-0210|2037:0128|2038:0117|2039:0107,1227|2040:1216|2041:1205|2042:1124|2043:1113|2044:1102|2045:1022|2046:1012|2047:1001|2048:0920|2049:0909|2050:0829|2051:0818|2052:0807|2053:0727|2054:0717|2055:0707|2056:0625|2057:0614|2058:0603|2059:0523|2060:0512","09":"y|2010:1118|2011:1108|2012:1027|2013:1016|2014:1006|2015:0925|2016:0913|2017:0902|2018:0823|2019:0812|2020:0801|2021:0721|2022:0711|2023:0630|2024:0618|2025:0608-0609|2026:0528|2027:0518|2028:0506|2029:0426|2030:0415|2031:0404|2032:0323|2033:0313-0314|2034:0302|2035:0220|2036:0209|2037:0128|2038:0117-0118|2039:0107,1227|2040:1216-1217|2041:1205|2042:1124|2043:1113|2044:1102|2045:1022-1023|2046:1012|2047:1001|2048:0920-0921|2049:0909|2050:0829|2051:0818|2052:0807|2053:0727-0728|2054:0717|2055:0707|2056:0625-0626|2057:0614|2058:0603|2059:0523|2060:0512","11":"y|2010:1118|2011:1108|2012:1027-1028|2013:1016|2014:1006|2015:0925|2016:0913|2017:0902-0903|2018:0823|2019:0812|2020:0801-0802|2021:0721|2022:0711|2023:0630|2024:0618|2025:0608|2026:0528|2027:0518|2028:0506-0507|2029:0426|2030:0415|2031:0404|2032:0323|2033:0313|2034:0302|2035:0220|2036:0209-0210|2037:0128|2038:0117|2039:0107,1227|2040:1216|2041:1205|2042:1124|2043:1113|2044:1102|2045:1022|2046:1012|2047:1001|2048:0920|2049:0909|2050:0829|2051:0818|2052:0807|2053:0727|2054:0717|2055:0707|2056:0625|2057:0614|2058:0603|2059:0523|2060:0512"},"estimated":"y|2028:0506|2029:0426|2030:0415|2031:0404|2032:0323|2033:0313|2034:0302|2035:0220|2036:0209|2037:0128|2038:0117|2039:0107,1227|2040:1216|2041:1205|2042:1124|2043:1113|2044:1102|2045:1022|2046:1012|2047:1001|2048:0920|2049:0909|2050:0829|2051:0818|2052:0807|2053:0727|2054:0717|2055:0707|2056:0625|2057:0614|2058:0603|2059:0523|2060:0512"},{"id":"the_sultan_of_johor_hol","name":"Hari Hol Almarhum Sultan Iskandar","en":"The Sultan of Johor Hol","category":"public","regional":{"01":"y|2011:0112|2012:1220|2013:1210|2014:1129|2015:1119|2016:1107|2017:1027|2018:1015|2019:1005|2020:0924|2021:0913|2022:0903|2023:0823|2024:0811|2025:0731|2026:0721|2027:0711|2028:0629|2029:0618|2030:0607|2031:0527|2032:0515|2033:0505|2034:0425|2035:0414|2036:0403|2037:0323|2038:0312|2039:0301|2040:0219|2041:0207|2042:0128|2043:0117|2044:0107,1226|2045:1215|2046:1204|2047:1124|2048:1112|2049:1102|2050:1022|2051:1011|2052:0929|2053:0918|2054:0908|2055:0829|2056:0817|2057:0806|2058:0726|2059:0716|2060:0704"},"estimated":"y|2028:0629|2029:0618|2030:0607|2031:0527|2032:0515|2033:0505|2034:0425|2035:0414|2036:0403|2037:0323|2038:0312|2039:0301|2040:0219|2041:0207|2042:0128|2043:0117|2044:0107,1226|2045:1215|2046:1204|2047:1124|2048:1112|2049:1102|2050:1022|2051:1011|2052:0929|2053:0918|2054:0908|2055:0829|2056:0817|2057:0806|2058:0726|2059:0716|2060:0704"},{"id":"birthday_of_the_sultan_of_negeri_sembilan","name":"Hari Keputeraan Yang di-Pertuan Besar Negeri Sembilan","en":"Birthday of the Sultan of Negeri Sembilan","category":"public","regional":{"05":"d|0114:2010-2060|0115:2018,2024,2029,2035,2046,2052,2057"}},{"id":"birthday_of_the_sultan_of_kedah","name":"Hari Keputeraan Sultan Kedah","en":"Birthday of The Sultan of Kedah","category":"public","regional":{"02":"d|0115:2012,2017|0116:2011|0117:2010,2016|0118:2015|0119:2014|0120:2013,2019|0121:2018|0615:2025,2031,2036,2042,2053,2059|0616:2030,2041,2047,2052,2058|0617:2029,2035,2040,2046,2057|0618:2023,2028,2034,2045,2051,2056|0619:2022,2033,2039,2044,2050|0620:2021,2027,2032,2038,2049,2055,2060|0621:2020,2037,2043,2048,2054|0630:2024|0705:2026"}},{"id":"thaipusam","name":"Hari Thaipusam","en":"Thaipusam","category":"public","regional":{"01":"d|0117:2014,2041|0118:2022,2030,2049,2060|0119:2014,2038,2057,2060|0120:2011|0121:2019,2046|0122:2027,2046|0123:2035,2054|0124:2016|0125:2024,2043|0126:2043|0127:2013,2032,2051|0128:2013,2021,2040,2059|0129:2010,2029,2048|0130:2037|0131:2018,2056|0201:2026,2045|0202:2026|0203:2015,2034,2053|0205:2023,2042|0206:2031,2050|0207:2012,2039,2050,2058|0208:2020|0209:2017,2028,2047|0211:2025,2036,2055|0213:2044|0214:2033|0215:2052","02":"d|0118:2022","05":"d|0117:2014,2041|0118:2022,2030,2049,2060|0119:2038,2057,2060|0120:2011|0121:2019,2046|0122:2027,2046|0123:2035,2054|0124:2016|0125:2016,2024,2043|0126:2043|0127:2013,2032,2051|0128:2013,2021,2040,2059|0129:2010,2029,2048|0130:2037|0131:2018,2056|0201:2026,2045|0202:2026|0203:2015,2034,2053|0205:2023,2042|0206:2023,2031,2050|0207:2012,2039,2050,2058|0208:2020|0209:2017,2028,2047|0211:2025,2036,2055|0213:2044|0214:2033|0215:2052","07":"d|0117:2014,2041|0118:2022,2030,2049,2060|0119:2038,2057,2060|0120:2011|0121:2019,2046|0122:2027,2046|0123:2035,2054|0124:2016|0125:2016,2024,2043|0126:2043|0127:2013,2032,2051|0128:2013,2021,2040,2059|0129:2010,2029,2048|0130:2037|0131:2018,2056|0201:2026,2045|0202:2026|0203:2015,2034,2053|0205:2023,2042|0206:2023,2031,2050|0207:2012,2039,2050,2058|0208:2020|0209:2017,2028,2047|0211:2025,2036,2055|0213:2044|0214:2033|0215:2052","08":"d|0117:2014,2041|0118:2022,2030,2049,2060|0119:2038,2057,2060|0120:2011|0121:2019,2046|0122:2027,2046|0123:2035,2054|0124:2016|0125:2016,2024,2043|0126:2043|0127:2013,2032,2051|0128:2013,2021,2040,2059|0129:2010,2029,2048|0130:2037|0131:2018,2056|0201:2026,2045|0202:2026|0203:2015,2034,2053|0205:2023,2042|0206:2023,2031,2050|0207:2012,2039,2050,2058|0208:2020|0209:2017,2028,2047|0211:2025,2036,2055|0213:2044|0214:2033|0215:2052","10":"d|0117:2014,2041|0118:2022,2030,2049,2060|0119:2038,2057,2060|0120:2011|0121:2019,2046|0122:2027,2046|0123:2035,2054|0124:2016|0125:2016,2024,2043|0126:2043|0127:2013,2032,2051|0128:2013,2021,2040,2059|0129:2010,2029,2048|0130:2037|0131:2018,2056|0201:2026,2045|0202:2026|0203:2015,2034,2053|0205:2023,2042|0206:2023,2031,2050|0207:2012,2039,2050,2058|0208:2020|0209:2017,2028,2047|0211:2025,2036,2055|0213:2044|0214:2033|0215:2052","14":"d|0117:2014,2041|0118:2022,2030,2049,2060|0119:2038,2057,2060|0120:2011|0121:2019,2046|0122:2027,2046|0123:2035,2054|0124:2016|0125:2016,2024,2043|0126:2043|0127:2013,2032,2051|0128:2013,2021,2040,2059|0129:2010,2029,2048|0130:2037|0131:2018,2056|0201:2026,2045|0202:2026|0203:2015,2034,2053|0205:2023,2042|0206:2023,2031,2050|0207:2012,2039,2050,2058|0208:2020|0209:2017,2028,2047|0211:2025,2036,2055|0213:2044|0214:2033|0215:2052","16":"d|0117:2014,2041|0118:2022,2030,2049,2060|0119:2038,2057,2060|0120:2011|0121:2019,2046|0122:2027,2046|0123:2035,2054|0124:2016|0125:2016,2024,2043|0126:2043|0127:2013,2032,2051|0128:2013,2021,2040,2059|0129:2010,2029,2048|0130:2037|0131:2018,2056|0201:2026,2045|0202:2026|0203:2015,2034,2053|0205:2023,2042|0206:2023,2031,2050|0207:2012,2039,2050,2058|0208:2020|0209:2017,2028,2047|0211:2025,2036,2055|0213:2044|0214:2033|0215:2052"}},{"id":"chinese_new_year","name":"Tahun Baharu Cina","en":"Chinese New Year","category":"public","dates":"d|0122:2023,2042|0123:2012,2031,2050|0124:2023,2039,2058|0125:2020,2050|0126:2028,2047|0128:2017,2036,2055|0129:2025|0130:2044|0131:2014,2033|0201:2022,2041,2052|0202:2049,2060|0203:2011,2030|0204:2038,2057|0205:2019|0206:2027,2030,2046,2057|0208:2016,2035,2054|0210:2013,2024,2043,2054|0211:2032,2051|0212:2013,2021,2040,2059|0213:2029|0214:2010,2040,2048|0215:2037,2056|0216:2010,2018|0217:2026,2045|0218:2037|0219:2015,2034,2053|0221:2034","regional":{"01":"d|0202:2014|0214:2021|0218:2018","02":"d|0202:2014|0203:2041|0214:2021|0216:2048|0218:2018|0219:2045","03":"d|0127:2020|0128:2047|0130:2017|0201:2044|0208:2027|0212:2024|0213:2051","04":"d|0207:2030","11":"d|0127:2020|0128:2047|0130:2017|0201:2044|0208:2027|0212:2024|0213:2051"},"estimated":"d|0122:2042|0123:2031,2050|0124:2039,2058|0126:2028,2047|0128:2036,2055|0130:2044|0131:2033|0201:2041,2052|0202:2049,2060|0203:2030|0204:2038,2057|0206:2046|0208:2035,2054|0210:2043|0211:2032,2051|0212:2040,2059|0213:2029|0214:2048|0215:2037,2056|0217:2045|0219:2034,2053"},{"id":"chinese_new_year_second_day","name":"Tahun Baharu Cina (Hari Kedua)","en":"Chinese New Year (Second Day)","category":"public","dates":"d|0123:2023,2042|0124:2012,2031,2050|0125:2039,2058|0126:2020|0127:2020,2028,2047|0128:2047|0129:2017,2036,2055|0130:2017,2025|0131:2044|0201:2014,2033,2044|0202:2022,2041,2052|0203:2049,2060|0204:2011,2030|0205:2038,2057|0206:2019|0207:2027,2046|0208:2027|0209:2016,2035,2054|0211:2013,2024,2043|0212:2024,2032,2051|0213:2021,2040,2051,2059|0214:2029|0215:2010,2048|0216:2037,2056|0217:2018|0218:2026,2045|0220:2015,2034,2053","regional":{"01":"d|0209:2027|0222:2015","02":"d|0127:2031,2058|0131:2055|0204:2052|0206:2011|0207:2038|0211:2035|0222:2015","03":"d|0202:2014|0203:2041|0214:2021|0216:2048|0218:2018|0219:2045","11":"d|0202:2014|0203:2041|0214:2021|0216:2048|0218:2018|0219:2045","14":"d|0202:2044","15":"d|0202:2044","16":"d|0202:2044"},"estimated":"d|0123:2042|0124:2031,2050|0125:2039,2058|0127:2028,2047|0129:2036,2055|0131:2044|0201:2033|0202:2041,2052|0203:2049,2060|0204:2030|0205:2038,2057|0207:2046|0209:2035,2054|0211:2043|0212:2032,2051|0213:2040,2059|0214:2029|0215:2048|0216:2037,2056|0218:2045|0220:2034,2053"},{"id":"federal_territory_day","name":"Hari Wilayah Persekutuan","en":"Federal Territory Day","category":"public","regional":{"14":"d|0201:2010-2013,2015-2060|0202:2015,2026,2032,2037,2043,2054|0203:2014|0204:2060","15":"d|0201:2010-2013,2015-2060|0202:2015,2026,2032,2037,2043,2054|0203:2014|0204:2060","16":"d|0201:2010-2013,2015-2060|0202:2015,2026,2032,2037,2043,2054|0203:2014|0204:2060"}},{"id":"declaration_of_independence_day","name":"Hari Pengisytiharan Tarikh Kemerdekaan","en":"Declaration of Independence Day","category":"public","regional":{"04":"d|0220:2024-2060|0221:2028,2033,2039,2050,2056"}},{"id":"anniversary_of_the_installation_of_the_sultan_of","name":"Hari Ulang Tahun Pertabalan Sultan Terengganu","en":"Anniversary of the Installation of the Sultan of Terengganu","category":"public","regional":{"11":"d|0304:2010-2060|0305:2017,2023,2028,2034,2045,2051,2056"}},{"id":"eid_al_fitr_additional_holiday","name":"Hari Raya Puasa (pergantian hari)","en":"Eid al-Fitr (additional holiday)","category":"public","dates":"d|0320:2026|0421:2023"},{"id":"birthday_of_the_sultan_of_johor","name":"Hari Keputeraan Sultan Johor","en":"Birthday of the Sultan of Johor","category":"public","regional":{"01":"d|0323:2015-2060"}},{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","regional":{"12":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038","13":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"}},{"id":"birthday_of_the_governor_of_sabah","name":"Hari Jadi Yang di-Pertua Negeri Sabah","en":"Birthday of the Governor of Sabah","category":"public","regional":{"12":"d|0330:2025-2060|1001:2011,2016,2022|1002:2010,2021|1003:2015,2020|1004:2014|1005:2013,2019,2024|1006:2012,2018|1007:2017,2023"}},{"id":"birthday_of_the_sultan_of_kelantan","name":"Hari Keputeraan Sultan Kelantan","en":"Birthday of the Sultan of Kelantan","category":"public","regional":{"03":"d|0330:2010-2011|0331:2010-2011|0929:2023-2060|0930:2023-2060|1111:2012-2022|1112:2012-2022"}},{"id":"birthday_of_the_sultan_of_terengganu","name":"Hari Keputeraan Sultan Terengganu","en":"Birthday of the Sultan of Terengganu","category":"public","regional":{"11":"d|0426:2010-2060|0427:2014,2025,2031,2036,2042,2053,2059"}},{"id":"labor_day","name":"Hari Pekerja","en":"Labor Day","category":"public","dates":"d|0501:2010-2060|0502:2011,2016,2033,2039,2044,2050|0504:2022","regional":{"01":"d|0503:2020|0504:2015","02":"d|0503:2020,2026,2037,2043,2048,2054|0504:2015","03":"d|0502:2010,2021,2027,2032,2038,2049,2055,2060","11":"d|0502:2010,2021,2027,2032,2038,2049,2055,2060"}},{"id":"vesak_day","name":"Hari Wesak","en":"Vesak Day","category":"public","dates":"d|0501:2045|0503:2015,2034,2053|0504:2015,2023,2042|0505:2012,2042,2050|0506:2031|0507:2020,2039,2058|0509:2028,2047|0510:2017,2036|0511:2055|0512:2025,2044|0513:2014,2033,2052|0514:2041,2060|0515:2022|0516:2022,2030,2049|0517:2011,2049|0518:2038,2057|0519:2019|0520:2019,2027,2046|0521:2016,2046|0522:2024,2035,2054|0523:2032,2043|0524:2013,2032,2051|0525:2040|0526:2021,2059|0527:2029,2048|0528:2010,2029|0529:2018,2037,2056|0531:2026|0602:2026","regional":{"02":"d|0515:2033|0516:2060|0520:2057|0524:2054|0526:2013|0527:2040|0530:2010|0531:2037","03":"d|0504:2053|0506:2012|0508:2039|0511:2036|0522:2016|0524:2043","09":"d|0518:2049","11":"d|0504:2053|0506:2012|0508:2039|0511:2036|0522:2016|0524:2043","13":"d|0603:2026"},"estimated":"d|0501:2045|0503:2034,2053|0504:2042|0505:2050|0506:2031|0507:2039,2058|0509:2028,2047|0510:2036|0511:2055|0512:2044|0513:2033,2052|0514:2041,2060|0516:2030,2049|0518:2038,2057|0520:2046|0522:2035,2054|0523:2032,2043|0524:2051|0525:2040|0526:2059|0527:2029,2048|0529:2037,2056"},{"id":"the_sultan_of_pahang_hol","name":"Hari Hol Sultan Pahang","en":"The Sultan of Pahang Hol","category":"public","regional":{"06":"d|0507:2010-2019|0508:2017|0522:2020-2060|0523:2022,2033,2039,2044,2050"}},{"id":"birthday_of_the_raja_of_perlis","name":"Hari Ulang Tahun Keputeraan Raja Perlis","en":"Birthday of the Raja of Perlis","category":"public","regional":{"09":"d|0517:2010-2017,2022-2060|0518:2015,2026,2037,2043,2048,2054|0717:2018-2021"}},{"id":"pesta_kaamatan","name":"Pesta Kaamatan","en":"Pesta Kaamatan","category":"public","regional":{"12":"d|0530:2010-2060|0531:2010-2060|0602:2026","15":"d|0530:2010-2060|0531:2010-2060|0602:2026"}},{"id":"birthday_of_hm_yang_di_pertuan_agong","name":"Hari Keputeraan Rasmi Seri Paduka Baginda Yang di-Pertuan Agong","en":"Birthday of HM Yang di-Pertuan Agong","category":"public","dates":"d|0601:2013,2026,2037,2043,2048,2054|0602:2012,2025,2031,2036,2042,2053,2059|0603:2024,2030,2041,2047,2052,2058|0604:2011,2016,2029,2035,2040,2046,2057|0605:2010,2023,2028,2034,2045,2051,2056|0606:2015,2022,2033,2039,2044,2050|0607:2014,2021,2027,2032,2038,2049,2055,2060|0608:2020|0909:2017-2019|0910:2018","regional":{"03":"d|0602:2013|0603:2012|0605:2011,2016|0606:2010|0607:2015|0608:2014|0910:2017","11":"d|0602:2013|0603:2012|0605:2011,2016|0606:2010|0607:2015|0608:2014|0910:2017"}},{"id":"dayak_festival_day","name":"Perayaan Hari Gawai Dayak","en":"Dayak Festival Day","category":"public","regional":{"13":"d|0601:2010-2060|0602:2010-2060|0603:2013-2014,2019,2025,2031,2036,2042,2053,2059|0604:2024,2030,2041,2047,2052,2058"}},{"id":"george_town_heritage_day","name":"Hari Ulang Tahun Perisytiharan Tapak Warisan Dunia","en":"George Town Heritage Day","category":"public","regional":{"07":"d|0707:2010-2060|0708:2013,2019,2024,2030,2041,2047,2052,2058"}},{"id":"birthday_of_the_governor_of_penang","name":"Hari Jadi Yang di-Pertua Negeri Pulau Pinang","en":"Birthday of the Governor of Penang","category":"public","regional":{"07":"d|0708:2017,2023,2028,2034,2045,2051,2056|0709:2011,2016,2022,2033,2039,2044,2050|0710:2010,2021,2027,2032,2038,2049,2055,2060|0711:2015,2020,2026,2037,2043,2048,2054|0712:2014,2025,2031,2036,2042,2053,2059|0713:2013,2019,2024,2030,2041,2047,2052,2058|0714:2012,2018,2029,2035,2040,2046,2057"}},{"id":"sarawak_independence_day","name":"Hari Kemerdekaan Sarawak","en":"Sarawak Independence Day","category":"public","regional":{"13":"d|0722:2017-2060|0723:2018,2029,2035,2040,2046,2057"}},{"id":"birthday_of_the_sultan_of_pahang","name":"Hari Keputeraan Sultan Pahang","en":"Birthday of the Sultan of Pahang","category":"public","regional":{"06":"d|0730:2019-2021,2023-2025,2027-2060|0731:2023,2026,2028,2034,2045,2051,2056|0801:2022|1024:2010-2018|1025:2010"}},{"id":"birthday_of_the_governor_of_malacca","name":"Hari Jadi Yang di-Pertua Negeri Melaka","en":"Birthday of the Governor of Malacca","category":"public","regional":{"04":"d|0824:2020-2060|0825:2025,2031,2036,2042,2053,2059|0826:2024|1008:2010|1009:2015|1010:2014|1011:2013,2019|1012:2012,2018|1013:2017|1014:2011,2016"}},{"id":"national_day","name":"Hari Kebangsaan","en":"National Day","category":"public","dates":"d|0831:2010-2060|0901:2014,2025,2031,2036,2042,2053,2059","regional":{"01":"d|0902:2018","02":"d|0902:2012,2018,2029,2035,2040,2046,2057","03":"d|0901:2013,2024,2030,2041,2047,2052|0902:2019,2058","11":"d|0901:2013,2024,2030,2041,2047,2052|0902:2019,2058"}},{"id":"malaysia_day","name":"Hari Malaysia","en":"Malaysia Day","category":"public","dates":"d|0916:2010-2060|0917:2012,2018,2029,2035,2040,2046,2057","regional":{"01":"d|0918:2016,2022","02":"d|0918:2011,2016,2022,2033,2039,2044|0919:2050","03":"d|0917:2017,2023,2028,2034,2045,2051,2056","11":"d|0917:2017,2023,2028,2034,2045,2051,2056"}},{"id":"birthday_of_the_governor_of_sarawak","name":"Hari Jadi Yang di-Pertua Negeri Sarawak","en":"Birthday of the Governor of Sarawak","category":"public","regional":{"13":"d|1008:2011,2016,2022,2033,2039,2044,2050|1009:2010,2021,2027,2032,2038,2049,2055,2060|1010:2015,2020,2026,2037,2043,2048,2054|1011:2014,2025,2031,2036,2042,2053,2059|1012:2013,2019,2024,2030,2041,2047,2052,2058|1013:2012,2018,2029,2035,2040,2046,2057|1014:2017,2023,2028,2034,2045,2051,2056"}},{"id":"diwali","name":"Hari Deepavali","en":"Diwali","category":"public","regional":{"01":"d|1018:2017|1020:2025|1022:2014,2033|1023:2060|1024:2022,2041|1026:2011,2030,2049|1027:2019,2038,2057|1028:2027,2046|1029:2016,2046|1030:2035,2054|1031:2024|1101:2043|1102:2013,2032,2043,2051|1104:2021,2040,2059|1105:2010,2029,2040,2048|1106:2018,2037,2056|1108:2026,2045|1109:2026,2053|1110:2015,2034,2053|1112:2023,2042|1113:2012,2050|1114:2020,2031,2050|1115:2028,2039,2058|1116:2047|1117:2036|1118:2044,2055|1120:2052","02":"d|1018:2017|1020:2025|1022:2014,2033|1023:2060|1024:2022,2041|1026:2011,2030,2049|1027:2019,2038,2057|1028:2027,2046|1029:2016|1030:2035,2054|1031:2024|1101:2043,2054|1102:2013,2032,2051|1104:2021,2040,2059|1105:2010,2029,2048|1106:2018,2037,2056|1107:2010|1108:2026,2037,2045|1109:2053|1110:2015,2034|1112:2023,2034,2042|1113:2012,2050|1114:2020,2031|1115:2028,2039,2058|1116:2031,2047|1117:2036,2058|1118:2044,2055|1120:2044,2052","03":"d|1018:2017|1020:2025|1022:2014,2033|1023:2033,2060|1024:2022,2041,2060|1026:2011,2030,2049|1027:2019,2030,2038,2057|1028:2027,2046,2057|1029:2016|1030:2016,2035,2054|1031:2024|1101:2043|1102:2013,2032,2051|1103:2013|1104:2021,2040,2059|1105:2010,2029,2048|1106:2018,2037,2056|1108:2026,2045|1109:2053|1110:2015,2034|1112:2023,2042|1113:2012,2050|1114:2020,2031|1115:2020,2028,2039,2058|1116:2047|1117:2036,2047|1118:2044,2055|1120:2052","04":"d|1018:2017|1020:2025|1022:2014,2033|1023:2060|1024:2022,2041|1026:2011,2030,2049|1027:2019,2038,2057|1028:2019,2027,2046|1029:2016,2046|1030:2035,2054|1031:2024|1101:2043|1102:2013,2032,2043,2051|1104:2021,2040,2059|1105:2010,2029,2040,2048|1106:2018,2037,2056|1108:2026,2045|1109:2026,2053|1110:2015,2034,2053|1112:2023,2042|1113:2012,2023,2050|1114:2020,2031,2050|1115:2028,2039,2058|1116:2047|1117:2036|1118:2044,2055|1120:2052","05":"d|1018:2017|1020:2025|1022:2014,2033|1023:2060|1024:2022,2041|1026:2011,2030,2049|1027:2019,2038,2057|1028:2019,2027,2046|1029:2016,2046|1030:2035,2054|1031:2024|1101:2043|1102:2013,2032,2043,2051|1104:2021,2040,2059|1105:2010,2029,2040,2048|1106:2018,2037,2056|1108:2026,2045|1109:2026,2053|1110:2015,2034,2053|1112:2023,2042|1113:2012,2023,2050|1114:2020,2031,2050|1115:2028,2039,2058|1116:2047|1117:2036|1118:2044,2055|1120:2052","06":"d|1018:2017|1020:2025|1022:2014,2033|1023:2060|1024:2022,2041|1026:2011,2030,2049|1027:2019,2038,2057|1028:2019,2027,2046|1029:2016,2046|1030:2035,2054|1031:2024|1101:2043|1102:2013,2032,2043,2051|1104:2021,2040,2059|1105:2010,2029,2040,2048|1106:2018,2037,2056|1108:2026,2045|1109:2026,2053|1110:2015,2034,2053|1112:2023,2042|1113:2012,2023,2050|1114:2020,2031,2050|1115:2028,2039,2058|1116:2047|1117:2036|1118:2044,2055|1120:2052","07":"d|1018:2017|1020:2025|1022:2014,2033|1023:2060|1024:2022,2041|1026:2011,2030,2049|1027:2019,2038,2057|1028:2019,2027,2046|1029:2016,2046|1030:2035,2054|1031:2024|1101:2043|1102:2013,2032,2043,2051|1104:2021,2040,2059|1105:2010,2029,2040,2048|1106:2018,2037,2056|1108:2026,2045|1109:2026,2053|1110:2015,2034,2053|1112:2023,2042|1113:2012,2023,2050|1114:2020,2031,2050|1115:2028,2039,2058|1116:2047|1117:2036|1118:2044,2055|1120:2052","08":"d|1018:2017|1020:2025|1022:2014,2033|1023:2060|1024:2022,2041|1026:2011,2030,2049|1027:2019,2038,2057|1028:2019,2027,2046|1029:2016,2046|1030:2035,2054|1031:2024|1101:2043|1102:2013,2032,2043,2051|1104:2021,2040,2059|1105:2010,2029,2040,2048|1106:2018,2037,2056|1108:2026,2045|1109:2026,2053|1110:2015,2034,2053|1112:2023,2042|1113:2012,2023,2050|1114:2020,2031,2050|1115:2028,2039,2058|1116:2047|1117:2036|1118:2044,2055|1120:2052","09":"d|1018:2017|1020:2025|1022:2014,2033|1023:2060|1024:2022,2041|1026:2011,2030,2049|1027:2019,2038,2057|1028:2019,2027,2046|1029:2016,2046|1030:2035,2054|1031:2024|1101:2043|1102:2013,2032,2043,2051|1104:2021,2040,2059|1105:2010,2029,2040,2048|1106:2018,2037,2056|1108:2026,2045|1109:2026,2053|1110:2015,2034,2053|1112:2023,2042|1113:2012,2023,2050|1114:2020,2031,2050|1115:2028,2039,2058|1116:2047|1117:2036|1118:2044,2055|1120:2052","10":"d|1018:2017|1020:2025|1022:2014,2033|1023:2060|1024:2022,2041|1026:2011,2030,2049|1027:2019,2038,2057|1028:2019,2027,2046|1029:2016,2046|1030:2035,2054|1031:2024|1101:2043|1102:2013,2032,2043,2051|1104:2021,2040,2059|1105:2010,2029,2040,2048|1106:2018,2037,2056|1108:2026,2045|1109:2026,2053|1110:2015,2034,2053|1112:2023,2042|1113:2012,2023,2050|1114:2020,2031,2050|1115:2028,2039,2058|1116:2047|1117:2036|1118:2044,2055|1120:2052","11":"d|1018:2017|1020:2025|1022:2014,2033|1023:2033,2060|1024:2022,2041,2060|1026:2011,2030,2049|1027:2019,2030,2038,2057|1028:2027,2046,2057|1029:2016|1030:2016,2035,2054|1031:2024|1101:2043|1102:2013,2032,2051|1103:2013|1104:2021,2040,2059|1105:2010,2029,2048|1106:2018,2037,2056|1108:2026,2045|1109:2053|1110:2015,2034|1112:2023,2042|1113:2012,2050|1114:2020,2031|1115:2020,2028,2039,2058|1116:2047|1117:2036,2047|1118:2044,2055|1120:2052","12":"d|1018:2017|1020:2025|1022:2014,2033|1023:2060|1024:2022,2041|1026:2011,2030,2049|1027:2019,2038,2057|1028:2019,2027,2046|1029:2016,2046|1030:2035,2054|1031:2024|1101:2043|1102:2013,2032,2043,2051|1104:2021,2040,2059|1105:2010,2029,2040,2048|1106:2018,2037,2056|1108:2026,2045|1109:2026,2053|1110:2015,2034,2053|1112:2023,2042|1113:2012,2023,2050|1114:2020,2031,2050|1115:2028,2039,2058|1116:2047|1117:2036|1118:2044,2055|1120:2052","14":"d|1018:2017|1020:2025|1022:2014,2033|1023:2060|1024:2022,2041|1026:2011,2030,2049|1027:2019,2038,2057|1028:2019,2027,2046|1029:2016,2046|1030:2035,2054|1031:2024|1101:2043|1102:2013,2032,2043,2051|1104:2021,2040,2059|1105:2010,2029,2040,2048|1106:2018,2037,2056|1108:2026,2045|1109:2026,2053|1110:2015,2034,2053|1112:2023,2042|1113:2012,2023,2050|1114:2020,2031,2050|1115:2028,2039,2058|1116:2047|1117:2036|1118:2044,2055|1120:2052","15":"d|1018:2017|1020:2025|1022:2014,2033|1023:2060|1024:2022,2041|1026:2030,2049|1027:2019,2038,2057|1028:2019,2027,2046|1029:2016,2046|1030:2035,2054|1031:2024|1101:2043|1102:2013,2032,2043,2051|1104:2021,2040,2059|1105:2029,2040,2048|1106:2018,2037,2056|1108:2026,2045|1109:2026,2053|1110:2015,2034,2053|1112:2023,2042|1113:2023,2050|1114:2020,2031,2050|1115:2028,2039,2058|1116:2047|1117:2036|1118:2044,2055|1120:2052","16":"d|1018:2017|1020:2025|1022:2014,2033|1023:2060|1024:2022,2041|1026:2011,2030,2049|1027:2019,2038,2057|1028:2019,2027,2046|1029:2016,2046|1030:2035,2054|1031:2024|1101:2043|1102:2013,2032,2043,2051|1104:2021,2040,2059|1105:2010,2029,2040,2048|1106:2018,2037,2056|1108:2026,2045|1109:2026,2053|1110:2015,2034,2053|1112:2023,2042|1113:2012,2023,2050|1114:2020,2031,2050|1115:2028,2039,2058|1116:2047|1117:2036|1118:2044,2055|1120:2052"}},{"id":"birthday_of_the_sultan_of_perak","name":"Hari Keputeraan Sultan Perak","en":"Birthday of the Sultan of Perak","category":"public","regional":{"08":"d|1101:2019,2024,2030,2041,2047,2052,2058|1102:2018,2029,2035,2040,2046,2057|1103:2023,2028,2034,2045,2051,2056|1104:2022,2033,2039,2044,2050|1105:2021,2027,2032,2038,2049,2055,2060|1106:2020,2026,2037,2043,2048,2054|1107:2025,2031,2036,2042,2053,2059|1127:2010-2017"}},{"id":"birthday_of_the_sultan_of_selangor","name":"Hari Keputeraan Sultan Selangor","en":"Birthday of The Sultan of Selangor","category":"public","regional":{"10":"d|1211:2010-2060|1212:2011,2022,2033,2039,2044,2050|1213:2016"}},{"id":"christmas_eve","name":"Krismas (Eve)","en":"Christmas Eve","category":"public","regional":{"12":"d|1224:2019-2060"}},{"id":"christmas_day","name":"Hari Krismas","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060|1226:2011,2016,2022,2033,2044,2050|1227:2039","regional":{"01":"d|1227:2015,2020,2044","02":"d|1227:2015,2020,2026,2037,2043,2048,2054","03":"d|1226:2010,2021,2027,2032,2038,2049,2055,2060","09":"d|1228:2039","11":"d|1226:2010,2021,2032,2038,2049,2055,2060|1227:2027"}}]} diff --git a/app/Support/Holidays/data/MZ.json b/app/Support/Holidays/data/MZ.json new file mode 100644 index 000000000..36b3a2e02 --- /dev/null +++ b/app/Support/Holidays/data/MZ.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"MZ","language":"pt_MZ","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"international_fraternalism_day","name":"Dia da Fraternidade universal","en":"International Fraternalism Day","category":"public","dates":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051"},{"id":"heroes_day","name":"Dia dos Heróis Moçambicanos","en":"Heroes' Day","category":"public","dates":"d|0203:2010-2060|0204:2013,2019,2030,2036,2041,2047,2058"},{"id":"women_s_day","name":"Dia da Mulher Moçambicana","en":"Women's Day","category":"public","dates":"d|0407:2010-2060|0408:2013,2019,2024,2030,2041,2047,2052,2058"},{"id":"international_workers_day","name":"Dia Internacional dos Trabalhadores","en":"International Workers' Day","category":"public","dates":"d|0501:2010-2060|0502:2011,2016,2022,2033,2039,2044,2050"},{"id":"national_independence_day","name":"Dia da Independência Nacional","en":"National Independence Day","category":"public","dates":"d|0625:2010-2060|0626:2017,2023,2028,2034,2045,2051,2056"},{"id":"victory_day","name":"Dia da Vitória","en":"Victory Day","category":"public","dates":"d|0907:2010-2060|0908:2014,2025,2031,2036,2042,2053,2059"},{"id":"armed_forces_day","name":"Dia das Forças Armadas de Libertação Nacional","en":"Armed Forces Day","category":"public","dates":"d|0925:2010-2060|0926:2011,2016,2022,2033,2039,2044,2050"},{"id":"peace_and_reconciliation_day","name":"Dia da Paz e Reconciliação","en":"Peace and Reconciliation Day","category":"public","dates":"d|1004:2010-2060|1005:2015,2020,2026,2037,2043,2048,2054"},{"id":"family_day","name":"Dia da Família","en":"Family Day","category":"public","dates":"d|1225:2010-2060|1226:2011,2016,2022,2033,2039,2044,2050"}]} diff --git a/app/Support/Holidays/data/NA.json b/app/Support/Holidays/data/NA.json new file mode 100644 index 000000000..d43c274a0 --- /dev/null +++ b/app/Support/Holidays/data/NA.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"NA","language":"en_NA","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051"},{"id":"independence_day","name":"Independence Day","en":"Independence Day","category":"public","dates":"d|0321:2010-2060|0322:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_monday","name":"Easter Monday","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"workers_day","name":"Workers' Day","en":"Workers' Day","category":"public","dates":"d|0501:2010-2060|0502:2011,2016,2022,2033,2039,2044,2050"},{"id":"ascension_day","name":"Ascension Day","en":"Ascension Day","category":"public","dates":"d|0503:2035,2046|0505:2016|0506:2027,2032|0507:2043,2054|0508:2059|0509:2013,2024|0510:2018,2029,2040|0511:2051,2056|0513:2010,2021|0514:2015,2026,2037,2048|0515:2042,2053|0517:2012|0518:2023,2034,2045|0519:2039,2050|0521:2020|0522:2031,2036|0523:2047,2058|0525:2017,2028|0526:2022,2033,2044|0527:2049,2055,2060|0529:2014,2025|0530:2019,2030,2041,2052|0531:2057|0602:2011|0603:2038"},{"id":"cassinga_day","name":"Cassinga Day","en":"Cassinga Day","category":"public","dates":"d|0504:2010-2060|0505:2014,2025,2031,2036,2042,2053,2059"},{"id":"africa_day","name":"Africa Day","en":"Africa Day","category":"public","dates":"d|0525:2010-2060|0526:2014,2025,2031,2036,2042,2053,2059"},{"id":"genocide_remembrance_day","name":"Genocide Remembrance Day","en":"Genocide Remembrance Day","category":"public","dates":"d|0528:2025-2060"},{"id":"heroes_day","name":"Heroes' Day","en":"Heroes' Day","category":"public","dates":"d|0826:2010-2060|0827:2012,2018,2029,2035,2040,2046,2057"},{"id":"day_of_the_namibian_women_and_international_huma","name":"Day of the Namibian Women and International Human Rights Day","en":"Day of the Namibian Women and International Human Rights Day","category":"public","dates":"d|0910:2010-2060|0911:2017,2023,2028,2034,2045,2051,2056"},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"},{"id":"family_day","name":"Family Day","en":"Family Day","category":"public","dates":"d|1226:2010-2060|1227:2010,2021,2027,2032,2038,2049,2055,2060"}]} diff --git a/app/Support/Holidays/data/NC.json b/app/Support/Holidays/data/NC.json new file mode 100644 index 000000000..d8c5f4e4d --- /dev/null +++ b/app/Support/Holidays/data/NC.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"NC","language":"fr","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Jour de l'an","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"easter_monday","name":"Lundi de Pâques","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"labor_day","name":"Fête du Travail","en":"Labor Day","category":"public","dates":"d|0501:2010-2060"},{"id":"ascension_day","name":"Ascension","en":"Ascension Day","category":"public","dates":"d|0503:2035,2046|0505:2016|0506:2027,2032|0507:2043,2054|0508:2059|0509:2013,2024|0510:2018,2029,2040|0511:2051,2056|0513:2010,2021|0514:2015,2026,2037,2048|0515:2042,2053|0517:2012|0518:2023,2034,2045|0519:2039,2050|0521:2020|0522:2031,2036|0523:2047,2058|0525:2017,2028|0526:2022,2033,2044|0527:2049,2055,2060|0529:2014,2025|0530:2019,2030,2041,2052|0531:2057|0602:2011|0603:2038"},{"id":"victory_day","name":"Fête de la Victoire","en":"Victory Day","category":"public","dates":"d|0508:2010-2060"},{"id":"pentecost_monday","name":"Lundi de Pentecôte","en":"Pentecost Monday","category":"public","dates":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038"},{"id":"national_day","name":"Fête nationale","en":"National Day","category":"public","dates":"d|0714:2010-2060"},{"id":"assumption_day","name":"Assomption","en":"Assumption Day","category":"public","dates":"d|0815:2010-2060"},{"id":"citizenship_day","name":"Fête de la Citoyenneté","en":"Citizenship Day","category":"public","dates":"d|0924:2010-2060"},{"id":"all_saints_day","name":"Toussaint","en":"All Saints' Day","category":"public","dates":"d|1101:2010-2060"},{"id":"armistice_day","name":"Armistice","en":"Armistice Day","category":"public","dates":"d|1111:2010-2060"},{"id":"christmas_day","name":"Noël","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"}]} diff --git a/app/Support/Holidays/data/NE.json b/app/Support/Holidays/data/NE.json new file mode 100644 index 000000000..faae1e08e --- /dev/null +++ b/app/Support/Holidays/data/NE.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"NE","language":"fr_NE","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Jour de l'An","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051"},{"id":"eid_al_fitr","name":"Korité","en":"Eid al-Fitr","category":"public","dates":"y|2010:0910|2011:0831|2012:0819-0820|2013:0808|2014:0729|2015:0718|2016:0707|2017:0626|2018:0615|2019:0604|2020:0523|2021:0512|2022:0501-0502|2023:0421|2024:0409|2025:0330-0331|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102-0103,1223|2034:1212|2035:1201|2036:1119|2037:1108-1109|2038:1029|2039:1019|2040:1007-1008|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712-0713|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416-0417|2057:0405|2058:0325|2059:0315|2060:0304","estimated":"y|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304"},{"id":"prophet_s_birthday","name":"Mouloud","en":"Prophet's Birthday","category":"public","dates":"y|2010:0226|2011:0216|2012:0205-0206|2013:0124|2014:0114|2015:0103,1224|2016:1212|2017:1201|2018:1121|2019:1110-1111|2020:1029|2021:1018|2022:1008|2023:0927|2024:0916|2025:0905|2026:0825|2027:0814|2028:0803|2029:0724|2030:0713|2031:0702|2032:0620-0621|2033:0609|2034:0530|2035:0520-0521|2036:0508|2037:0428|2038:0417|2039:0406|2040:0325-0326|2041:0315|2042:0304|2043:0222-0223|2044:0211|2045:0130|2046:0119|2047:0108,1229-1230|2048:1218|2049:1207|2050:1126|2051:1116|2052:1104|2053:1024|2054:1013|2055:1003-1004|2056:0922|2057:0911|2058:0831|2059:0820|2060:0808-0809","estimated":"y|2026:0825|2027:0814|2028:0803|2029:0724|2030:0713|2031:0702|2032:0620|2033:0609|2034:0530|2035:0520|2036:0508|2037:0428|2038:0417|2039:0406|2040:0325|2041:0315|2042:0304|2043:0222|2044:0211|2045:0130|2046:0119|2047:0108,1229|2048:1218|2049:1207|2050:1126|2051:1116|2052:1104|2053:1024|2054:1013|2055:1003|2056:0922|2057:0911|2058:0831|2059:0820|2060:0808"},{"id":"islamic_new_year","name":"Jour de l'An musulman","en":"Islamic New Year","category":"public","dates":"y|2010:1208|2011:1127-1128|2012:1115|2013:1105|2014:1025|2015:1015|2016:1003|2017:0922|2018:0912|2019:0831|2020:0821|2021:0810|2022:0730|2023:0719|2024:0706|2025:0627|2026:0616|2027:0606-0607|2028:0525|2029:0514|2030:0503|2031:0423|2032:0411-0412|2033:0401|2034:0321|2035:0311-0312|2036:0228|2037:0216|2038:0205|2039:0126|2040:0115-0116|2041:0104,1224|2042:1214-1215|2043:1203|2044:1121|2045:1110|2046:1031|2047:1020-1021|2048:1009|2049:0928|2050:0917|2051:0906|2052:0826|2053:0815|2054:0805|2055:0725-0726|2056:0714|2057:0703|2058:0622|2059:0611|2060:0531","estimated":"y|2026:0616|2027:0606|2028:0525|2029:0514|2030:0503|2031:0423|2032:0411|2033:0401|2034:0321|2035:0311|2036:0228|2037:0216|2038:0205|2039:0126|2040:0115|2041:0104,1224|2042:1214|2043:1203|2044:1121|2045:1110|2046:1031|2047:1020|2048:1009|2049:0928|2050:0917|2051:0906|2052:0826|2053:0815|2054:0805|2055:0725|2056:0714|2057:0703|2058:0622|2059:0611|2060:0531"},{"id":"eid_al_adha","name":"Tabaski","en":"Eid al-Adha","category":"public","dates":"y|2010:1117|2011:1107|2012:1026|2013:1015|2014:1005|2015:0924|2016:0913|2017:0902|2018:0822|2019:0811|2020:0731|2021:0720|2022:0710|2023:0628|2024:0616|2025:0607|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510","estimated":"y|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510"},{"id":"day_after_eid_al_adha","name":"Lendemain de la Tabaski","en":"Day after Eid al-Adha","category":"public","dates":"y|2010:1118|2011:1108|2012:1027|2013:1016|2014:1006|2015:0925|2016:0914|2017:0903-0904|2018:0823|2019:0812|2020:0801|2021:0721|2022:0711|2023:0629|2024:0617|2025:0608-0609|2026:0528|2027:0517|2028:0506|2029:0425|2030:0414-0415|2031:0403|2032:0323|2033:0312|2034:0302|2035:0219|2036:0208|2037:0127|2038:0117-0118|2039:0106,1227|2040:1215|2041:1205|2042:1124|2043:1113|2044:1101|2045:1022-1023|2046:1011|2047:1001|2048:0920-0921|2049:0909|2050:0829|2051:0818|2052:0806|2053:0727-0728|2054:0716|2055:0706|2056:0624|2057:0613|2058:0602-0603|2059:0523|2060:0511","estimated":"y|2026:0528|2027:0517|2028:0506|2029:0425|2030:0414|2031:0403|2032:0323|2033:0312|2034:0302|2035:0219|2036:0208|2037:0127|2038:0117|2039:0106,1227|2040:1215|2041:1205|2042:1124|2043:1113|2044:1101|2045:1022|2046:1011|2047:1001|2048:0920|2049:0909|2050:0829|2051:0818|2052:0806|2053:0727|2054:0716|2055:0706|2056:0624|2057:0613|2058:0602|2059:0523|2060:0511"},{"id":"laylat_al_qadr","name":"Laylat al-Qadr","en":"Laylat al-Qadr","category":"public","dates":"y|2010:0906|2011:0827|2012:0815|2013:0804-0805|2014:0725|2015:0714|2016:0702|2017:0622|2018:0611|2019:0601|2020:0520|2021:0509-0510|2022:0428|2023:0418|2024:0406|2025:0327|2026:0316|2027:0306|2028:0223|2029:0211-0212|2030:0131|2031:0121|2032:0110,1230|2033:1219|2034:1208|2035:1127|2036:1115|2037:1105|2038:1026|2039:1015|2040:1003|2041:0923|2042:0912|2043:0901|2044:0821-0822|2045:0810|2046:0731|2047:0720|2048:0708|2049:0628|2050:0617|2051:0606|2052:0526-0527|2053:0516|2054:0505|2055:0424|2056:0412|2057:0401-0402|2058:0322|2059:0312|2060:0229-0301","estimated":"y|2026:0316|2027:0306|2028:0223|2029:0211|2030:0131|2031:0121|2032:0110,1230|2033:1219|2034:1208|2035:1127|2036:1115|2037:1105|2038:1026|2039:1015|2040:1003|2041:0923|2042:0912|2043:0901|2044:0821|2045:0810|2046:0731|2047:0720|2048:0708|2049:0628|2050:0617|2051:0606|2052:0526|2053:0516|2054:0505|2055:0424|2056:0412|2057:0401|2058:0322|2059:0312|2060:0229"},{"id":"easter_monday","name":"Lundi de Pâques","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"national_concord_day","name":"Fête nationale de la Concorde","en":"National Concord Day","category":"public","dates":"d|0424:2010-2060|0425:2011,2016,2022,2033,2039,2044,2050"},{"id":"international_labor_day","name":"Journée internationale du travail","en":"International Labor Day","category":"public","dates":"d|0501:2010-2060|0502:2011,2016,2022,2033,2039,2044,2050"},{"id":"anniversary_of_the_cnsp_coup","name":"Anniversaire du coup d'État du CNSP","en":"Anniversary of the CNSP Coup","category":"public","dates":"d|0726:2024-2060|0727:2026,2037,2043,2048,2054"},{"id":"anniversary_of_the_proclamation_of_independence","name":"L'anniversaire de la proclamation de l'indépendance","en":"Anniversary of the Proclamation of Independence","category":"public","dates":"d|0803:2010-2060|0804:2014,2025,2031,2036,2042,2053,2059"},{"id":"national_day","name":"Fête nationale","en":"National Day","category":"public","dates":"d|1218:2010-2060|1219:2011,2016,2022,2033,2039,2044,2050"},{"id":"christmas_day","name":"Noël","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060|1226:2011,2016,2022,2033,2039,2044,2050"},{"id":"ascension_day_optional","name":"Ascension","en":"Ascension Day","category":"optional","dates":"d|0503:2035,2046|0505:2016|0506:2027,2032|0507:2043,2054|0508:2059|0509:2013,2024|0510:2018,2029,2040|0511:2051,2056|0513:2010,2021|0514:2015,2026,2037,2048|0515:2042,2053|0517:2012|0518:2023,2034,2045|0519:2039,2050|0521:2020|0522:2031,2036|0523:2047,2058|0525:2017,2028|0526:2022,2033,2044|0527:2049,2055,2060|0529:2014,2025|0530:2019,2030,2041,2052|0531:2057|0602:2011|0603:2038"},{"id":"pentecost_monday_optional","name":"Lundi de Pentecôte","en":"Pentecost Monday","category":"optional","dates":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038"},{"id":"assumption_day_optional","name":"Assomption","en":"Assumption Day","category":"optional","dates":"d|0815:2010-2060|0816:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"all_saints_day_optional","name":"Toussaint","en":"All Saints' Day","category":"optional","dates":"d|1101:2010-2060|1102:2015,2020,2026,2037,2043,2048,2054"}]} diff --git a/app/Support/Holidays/data/NF.json b/app/Support/Holidays/data/NF.json new file mode 100644 index 000000000..b563d7b1e --- /dev/null +++ b/app/Support/Holidays/data/NF.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"NF","language":"en_NF","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2016-2060|0102:2017,2023,2034,2040,2045,2051|0103:2022,2028,2033,2039,2050,2056"},{"id":"australia_day","name":"Australia Day","en":"Australia Day","category":"public","dates":"d|0126:2016-2060|0127:2020,2025,2031,2042,2048,2053,2059|0128:2019,2030,2036,2041,2047,2058"},{"id":"foundation_day","name":"Foundation Day","en":"Foundation Day","category":"public","dates":"d|0306:2016-2060|0307:2016,2022,2033,2039,2044,2050"},{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2024|0330:2018,2029,2040|0331:2051,2056|0402:2021|0403:2026,2037,2048|0404:2042,2053|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2025|0419:2019,2030,2041,2052|0420:2057|0423:2038"},{"id":"easter_monday","name":"Easter Monday","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2024|0402:2018,2029,2040|0403:2051,2056|0405:2021|0406:2026,2037,2048|0407:2042,2053|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2025|0422:2019,2030,2041,2052|0423:2057|0426:2038"},{"id":"anzac_day","name":"ANZAC Day","en":"ANZAC Day","category":"public","dates":"d|0425:2016-2060"},{"id":"bounty_day","name":"Bounty Day","en":"Bounty Day","category":"public","dates":"d|0608:2016-2060|0609:2025,2031,2036,2042,2053,2059|0610:2019,2024,2030,2041,2047,2052,2058"},{"id":"king_s_birthday","name":"King's Birthday","en":"King's Birthday","category":"public","dates":"d|0611:2029,2035,2040,2046,2057|0612:2023,2028,2034,2045,2051,2056|0613:2033,2039,2044,2050|0614:2027,2032,2038,2049,2055,2060|0615:2026,2037,2043,2048,2054|0616:2025,2031,2036,2042,2053,2059|0617:2024,2030,2041,2047,2052,2058"},{"id":"show_day","name":"Show Day","en":"Show Day","category":"public","dates":"d|1008:2018,2029,2035,2040,2046,2057|1009:2017,2023,2028,2034,2045,2051,2056|1010:2016,2022,2033,2039,2044,2050|1011:2021,2027,2032,2038,2049,2055,2060|1012:2020,2026,2037,2043,2048,2054|1013:2025,2031,2036,2042,2053,2059|1014:2019,2024,2030,2041,2047,2052,2058"},{"id":"thanksgiving_day","name":"Thanksgiving Day","en":"Thanksgiving Day","category":"public","dates":"d|1124:2021,2027,2032,2038,2049,2055,2060|1125:2020,2026,2037,2043,2048,2054|1126:2025,2031,2036,2042,2053,2059|1127:2019,2024,2030,2041,2047,2052,2058|1128:2018,2029,2035,2040,2046,2057|1129:2017,2023,2028,2034,2045,2051,2056|1130:2016,2022,2033,2039,2044,2050"},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1225:2016-2060|1227:2016,2021-2022,2027,2032-2033,2038-2039,2044,2049-2050,2055,2060"},{"id":"boxing_day","name":"Boxing Day","en":"Boxing Day","category":"public","dates":"d|1226:2016-2060|1228:2020-2021,2026-2027,2032,2037-2038,2043,2048-2049,2054-2055,2060"}]} diff --git a/app/Support/Holidays/data/NG.json b/app/Support/Holidays/data/NG.json new file mode 100644 index 000000000..6c70b335e --- /dev/null +++ b/app/Support/Holidays/data/NG.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"NG","language":"en_NG","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051|0103:2011,2022,2028,2039,2050,2056|0105:2033"},{"id":"prophet_s_birthday","name":"Id el Maulud","en":"Prophet's Birthday","category":"public","dates":"y|2010:0226|2011:0216|2012:0206|2013:0124|2014:0113|2015:0102,1224|2016:1212|2017:1201|2018:1120|2019:1109,1111|2020:1029|2021:1019|2022:1008,1010|2023:0927|2024:0916|2025:0905|2026:0826|2027:0815-0816|2028:0804|2029:0725|2030:0714-0715|2031:0703|2032:0621|2033:0610|2034:0531|2035:0521|2036:0509|2037:0429|2038:0418-0419|2039:0407|2040:0326|2041:0316,0318|2042:0305|2043:0223|2044:0212|2045:0131|2046:0120,0122|2047:0109,1230|2048:1219,1221|2049:1208|2050:1127-1128|2051:1117|2052:1105|2053:1025,1027|2054:1014|2055:1004|2056:0923,0925|2057:0912|2058:0901-0902|2059:0821|2060:0809","estimated":"y|2026:0826|2027:0815|2028:0804|2029:0725|2030:0714|2031:0703|2032:0621|2033:0610|2034:0531|2035:0521|2036:0509|2037:0429|2038:0418|2039:0407|2040:0326|2041:0316|2042:0305|2043:0223|2044:0212|2045:0131|2046:0120|2047:0109,1230|2048:1219|2049:1208|2050:1127|2051:1117|2052:1105|2053:1025|2054:1014|2055:1004|2056:0923|2057:0912|2058:0901|2059:0821|2060:0809"},{"id":"eid_al_fitr","name":"Id el Fitr","en":"Eid al-Fitr","category":"public","dates":"y|2010:0909|2011:0830|2012:0820|2013:0808|2014:0728|2015:0717|2016:0706|2017:0626|2018:0615|2019:0604|2020:0524,0526|2021:0513|2022:0502|2023:0421|2024:0410|2025:0330,0401|2026:0319|2027:0310|2028:0227,0229|2029:0215|2030:0205|2031:0125,0127|2032:0115|2033:0103,1224,1227|2034:1213|2035:1202,1204|2036:1120|2037:1109|2038:1030,1101|2039:1020|2040:1008|2041:0927|2042:0916|2043:0905,0907|2044:0825|2045:0815|2046:0804,0806|2047:0725|2048:0713|2049:0702|2050:0621|2051:0611,0613|2052:0530|2053:0520|2054:0510,0512|2055:0429|2056:0417|2057:0406|2058:0326|2059:0316,0318|2060:0305","estimated":"y|2027:0310|2028:0227|2029:0215|2030:0205|2031:0125|2032:0115|2033:0103,1224|2034:1213|2035:1202|2036:1120|2037:1109|2038:1030|2039:1020|2040:1008|2041:0927|2042:0916|2043:0905|2044:0825|2045:0815|2046:0804|2047:0725|2048:0713|2049:0702|2050:0621|2051:0611|2052:0530|2053:0520|2054:0510|2055:0429|2056:0417|2057:0406|2058:0326|2059:0316|2060:0305"},{"id":"eid_al_fitr_holiday","name":"Id el Fitr Holiday","en":"Eid al-Fitr Holiday","category":"public","dates":"y|2010:0910|2011:0831|2012:0821|2013:0809|2014:0729|2015:0718,0720|2016:0707|2017:0627|2018:0616,0618|2019:0605|2020:0525|2021:0514|2022:0503|2023:0422,0424|2024:0411|2025:0331|2026:0320|2027:0311|2028:0228|2029:0216|2030:0206|2031:0126,0128|2032:0116|2033:0104,1225,1228|2034:1214|2035:1203|2036:1121|2037:1110|2038:1031,1102|2039:1021|2040:1009|2041:0928,0930|2042:0917|2043:0906,0908|2044:0826|2045:0816|2046:0805,0807|2047:0726|2048:0714|2049:0703,0705|2050:0622|2051:0612|2052:0531|2053:0521|2054:0511|2055:0430|2056:0418|2057:0407,0409|2058:0327|2059:0317|2060:0306,0308","estimated":"y|2027:0311|2028:0228|2029:0216|2030:0206|2031:0126|2032:0116|2033:0104,1225|2034:1214|2035:1203|2036:1121|2037:1110|2038:1031|2039:1021|2040:1009|2041:0928|2042:0917|2043:0906|2044:0826|2045:0816|2046:0805|2047:0726|2048:0714|2049:0703|2050:0622|2051:0612|2052:0531|2053:0521|2054:0511|2055:0430|2056:0418|2057:0407|2058:0327|2059:0317|2060:0306"},{"id":"eid_al_adha","name":"Id el Kabir","en":"Eid al-Adha","category":"public","dates":"y|2010:1117|2011:1107|2012:1026|2013:1015|2014:1006|2015:0924|2016:0912|2017:0902,0904|2018:0822|2019:0812|2020:0731|2021:0720|2022:0709,0711|2023:0628|2024:0616,0618|2025:0606|2026:0528|2027:0517|2028:0506,0508|2029:0425|2030:0414,0416|2031:0403|2032:0323|2033:0312,0314|2034:0302|2035:0219|2036:0208|2037:0127|2038:0117,0119|2039:0106,1227|2040:1215,1217|2041:1205|2042:1124|2043:1113|2044:1101|2045:1022,1024|2046:1011|2047:1001|2048:0920,0922|2049:0909|2050:0829|2051:0818|2052:0806|2053:0727,0729|2054:0716|2055:0706|2056:0624,0626|2057:0613|2058:0602,0604|2059:0523|2060:0511","estimated":"y|2026:0528|2027:0517|2028:0506|2029:0425|2030:0414|2031:0403|2032:0323|2033:0312|2034:0302|2035:0219|2036:0208|2037:0127|2038:0117|2039:0106,1227|2040:1215|2041:1205|2042:1124|2043:1113|2044:1101|2045:1022|2046:1011|2047:1001|2048:0920|2049:0909|2050:0829|2051:0818|2052:0806|2053:0727|2054:0716|2055:0706|2056:0624|2057:0613|2058:0602|2059:0523|2060:0511"},{"id":"eid_al_adha_holiday","name":"Id el Kabir Holiday","en":"Eid al-Adha Holiday","category":"public","dates":"y|2010:1118|2011:1108|2012:1027,1029|2013:1016|2014:1007|2015:0925|2016:0913|2017:0903,0905|2018:0823|2019:0813|2020:0801,0803|2021:0721|2022:0710,0712|2023:0629|2024:0617|2025:0607,0609|2026:0529|2027:0518|2028:0507,0509|2029:0426|2030:0415|2031:0404|2032:0324|2033:0313,0315|2034:0303|2035:0220|2036:0209,0211|2037:0128|2038:0118|2039:0107,1228|2040:1216,1218|2041:1206|2042:1125|2043:1114,1116|2044:1102|2045:1023|2046:1012|2047:1002|2048:0921|2049:0910|2050:0830|2051:0819,0821|2052:0807|2053:0728|2054:0717|2055:0707|2056:0625,0627|2057:0614|2058:0603|2059:0524,0526|2060:0512","estimated":"y|2026:0529|2027:0518|2028:0507|2029:0426|2030:0415|2031:0404|2032:0324|2033:0313|2034:0303|2035:0220|2036:0209|2037:0128|2038:0118|2039:0107,1228|2040:1216|2041:1206|2042:1125|2043:1114|2044:1102|2045:1023|2046:1012|2047:1002|2048:0921|2049:0910|2050:0830|2051:0819|2052:0807|2053:0728|2054:0717|2055:0707|2056:0625|2057:0614|2058:0603|2059:0524|2060:0512"},{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_monday","name":"Easter Monday","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"workers_day","name":"Workers' Day","en":"Workers' Day","category":"public","dates":"d|0501:2010-2060|0502:2011,2016,2033,2039,2044,2050|0503:2010,2021,2027,2032,2038,2049,2055,2060|0504:2022"},{"id":"democracy_day","name":"Democracy Day","en":"Democracy Day","category":"public","dates":"d|0529:2010-2018|0530:2011,2016|0531:2010|0612:2019-2060|0613:2022,2033,2039,2044,2050|0614:2021,2027,2032,2038,2049,2055,2060"},{"id":"presidential_inauguration_day","name":"Presidential Inauguration Day","en":"Presidential Inauguration Day","category":"public","dates":"d|0529:2011,2015,2019,2023,2027,2031,2035,2039,2043,2047,2051,2055,2059|0530:2011"},{"id":"national_day","name":"National Day","en":"National Day","category":"public","dates":"d|1001:2010-2060|1002:2017,2023,2028,2034,2045,2051,2056|1003:2011,2016,2022,2033,2039,2044,2050"},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060|1227:2010-2011,2016,2021-2022,2027,2032,2038,2044,2049-2050,2055,2060|1228:2033|1229:2039"},{"id":"boxing_day","name":"Boxing Day","en":"Boxing Day","category":"public","dates":"d|1226:2010-2060|1228:2010,2015,2020-2021,2026-2027,2032,2037-2038,2043,2048-2049,2054-2055,2060"}]} diff --git a/app/Support/Holidays/data/NI.json b/app/Support/Holidays/data/NI.json new file mode 100644 index 000000000..2b71590a6 --- /dev/null +++ b/app/Support/Holidays/data/NI.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"NI","language":"es","years":[2010,2060],"subdivisions":{"AN":"Costa Caribe Norte","AS":"Costa Caribe Sur","BO":"Boaco","CA":"Carazo","CI":"Chinandega","CO":"Chontales","ES":"Estelí","GR":"Granada","JI":"Jinotega","LE":"León","MD":"Madriz","MN":"Managua","MS":"Masaya","MT":"Matagalpa","NS":"Nueva Segovia","RI":"Rivas","SJ":"Río San Juan"},"holidays":[{"id":"new_year_s_day","name":"Año Nuevo","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"maundy_thursday","name":"Jueves Santo","en":"Maundy Thursday","category":"public","dates":"d|0322:2035,2046|0324:2016|0325:2027,2032|0326:2043,2054|0327:2059|0328:2013,2024|0329:2018,2029,2040|0330:2051,2056|0401:2010,2021|0402:2015,2026,2037,2048|0403:2042,2053|0405:2012|0406:2023,2034,2045|0407:2039,2050|0409:2020|0410:2031,2036|0411:2047,2058|0413:2017,2028|0414:2022,2033,2044|0415:2049,2055,2060|0417:2014,2025|0418:2019,2030,2041,2052|0419:2057|0421:2011|0422:2038"},{"id":"good_friday","name":"Viernes Santo","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"labor_day","name":"Día del Trabajo","en":"Labor Day","category":"public","dates":"d|0501:2010-2060"},{"id":"mother_s_day","name":"Día de la Madre","en":"Mother's Day","category":"public","dates":"d|0530:2022-2060"},{"id":"revolution_day","name":"Día de la Revolución","en":"Revolution Day","category":"public","dates":"d|0719:2010-2060"},{"id":"descent_of_saint_dominic","name":"Bajada de Santo Domingo","en":"Descent of Saint Dominic","category":"public","regional":{"MN":"d|0801:2010-2060"}},{"id":"ascent_of_saint_dominic","name":"Subida de Santo Domingo","en":"Ascent of Saint Dominic","category":"public","regional":{"MN":"d|0810:2010-2060"}},{"id":"battle_of_san_jacinto_day","name":"Batalla de San Jacinto","en":"Battle of San Jacinto Day","category":"public","dates":"d|0914:2010-2060"},{"id":"independence_day","name":"Día de la Independencia","en":"Independence Day","category":"public","dates":"d|0915:2010-2060"},{"id":"immaculate_conception","name":"Concepción de María","en":"Immaculate Conception","category":"public","dates":"d|1208:2010-2060"},{"id":"christmas_day","name":"Navidad","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"}]} diff --git a/app/Support/Holidays/data/NL.json b/app/Support/Holidays/data/NL.json new file mode 100644 index 000000000..846ba4e5e --- /dev/null +++ b/app/Support/Holidays/data/NL.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"NL","language":"nl","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Nieuwjaarsdag","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"good_friday","name":"Goede vrijdag","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_sunday","name":"Eerste paasdag","en":"Easter Sunday","category":"public","dates":"d|0325:2035,2046|0327:2016|0328:2027,2032|0329:2043,2054|0330:2059|0331:2013,2024|0401:2018,2029,2040|0402:2051,2056|0404:2010,2021|0405:2015,2026,2037,2048|0406:2042,2053|0408:2012|0409:2023,2034,2045|0410:2039,2050|0412:2020|0413:2031,2036|0414:2047,2058|0416:2017,2028|0417:2022,2033,2044|0418:2049,2055,2060|0420:2014,2025|0421:2019,2030,2041,2052|0422:2057|0424:2011|0425:2038"},{"id":"easter_monday","name":"Tweede paasdag","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"king_s_day","name":"Koningsdag","en":"King's Day","category":"public","dates":"d|0426:2014,2025,2031,2036,2042,2053,2059|0427:2015-2024,2026-2030,2032-2035,2037-2041,2043-2052,2054-2058,2060"},{"id":"ascension_day","name":"Hemelvaartsdag","en":"Ascension Day","category":"public","dates":"d|0503:2035,2046|0505:2016|0506:2027,2032|0507:2043,2054|0508:2059|0509:2013,2024|0510:2018,2029,2040|0511:2051,2056|0513:2010,2021|0514:2015,2026,2037,2048|0515:2042,2053|0517:2012|0518:2023,2034,2045|0519:2039,2050|0521:2020|0522:2031,2036|0523:2047,2058|0525:2017,2028|0526:2022,2033,2044|0527:2049,2055,2060|0529:2014,2025|0530:2019,2030,2041,2052|0531:2057|0602:2011|0603:2038"},{"id":"liberation_day","name":"Bevrijdingsdag","en":"Liberation Day","category":"public","dates":"d|0505:2010,2015,2020,2025,2030,2035,2040,2045,2050,2055,2060"},{"id":"pentecost","name":"Eerste pinksterdag","en":"Pentecost","category":"public","dates":"d|0513:2035,2046|0515:2016|0516:2027,2032|0517:2043,2054|0518:2059|0519:2013,2024|0520:2018,2029,2040|0521:2051,2056|0523:2010,2021|0524:2015,2026,2037,2048|0525:2042,2053|0527:2012|0528:2023,2034,2045|0529:2039,2050|0531:2020|0601:2031,2036|0602:2047,2058|0604:2017,2028|0605:2022,2033,2044|0606:2049,2055,2060|0608:2014,2025|0609:2019,2030,2041,2052|0610:2057|0612:2011|0613:2038"},{"id":"pentecost_monday","name":"Tweede pinksterdag","en":"Pentecost Monday","category":"public","dates":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038"},{"id":"christmas_day","name":"Eerste kerstdag","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"},{"id":"second_day_of_christmas","name":"Tweede kerstdag","en":"Second Day of Christmas","category":"public","dates":"d|1226:2010-2060"},{"id":"liberation_day_optional","name":"Bevrijdingsdag","en":"Liberation Day","category":"optional","dates":"d|0505:2010-2060"}]} diff --git a/app/Support/Holidays/data/NO.json b/app/Support/Holidays/data/NO.json new file mode 100644 index 000000000..abfeefa8e --- /dev/null +++ b/app/Support/Holidays/data/NO.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"NO","language":"no","years":[2010,2060],"subdivisions":{"03":"Oslo","11":"Rogaland","15":"Møre og Romsdal","18":"Nordland","21":"Svalbard","22":"Jan Mayen","31":"Østfold","32":"Akershus","33":"Buskerud","34":"Innlandet","39":"Vestfold","40":"Telemark","42":"Agder","46":"Vestland","50":"Trööndelage","55":"Romssa","56":"Finmarkun"},"holidays":[{"id":"new_year_s_day","name":"Første nyttårsdag","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"maundy_thursday","name":"Skjærtorsdag","en":"Maundy Thursday","category":"public","dates":"d|0322:2035,2046|0324:2016|0325:2027,2032|0326:2043,2054|0327:2059|0328:2013,2024|0329:2018,2029,2040|0330:2051,2056|0401:2010,2021|0402:2015,2026,2037,2048|0403:2042,2053|0405:2012|0406:2023,2034,2045|0407:2039,2050|0409:2020|0410:2031,2036|0411:2047,2058|0413:2017,2028|0414:2022,2033,2044|0415:2049,2055,2060|0417:2014,2025|0418:2019,2030,2041,2052|0419:2057|0421:2011|0422:2038"},{"id":"good_friday","name":"Langfredag","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_sunday","name":"Første påskedag","en":"Easter Sunday","category":"public","dates":"d|0325:2035,2046|0327:2016|0328:2027,2032|0329:2043,2054|0330:2059|0331:2013,2024|0401:2018,2029,2040|0402:2051,2056|0404:2010,2021|0405:2015,2026,2037,2048|0406:2042,2053|0408:2012|0409:2023,2034,2045|0410:2039,2050|0412:2020|0413:2031,2036|0414:2047,2058|0416:2017,2028|0417:2022,2033,2044|0418:2049,2055,2060|0420:2014,2025|0421:2019,2030,2041,2052|0422:2057|0424:2011|0425:2038"},{"id":"easter_monday","name":"Andre påskedag","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"labor_day","name":"Arbeidernes dag","en":"Labor Day","category":"public","dates":"d|0501:2010-2060"},{"id":"ascension_day","name":"Kristi himmelfartsdag","en":"Ascension Day","category":"public","dates":"d|0503:2035,2046|0505:2016|0506:2027,2032|0507:2043,2054|0508:2059|0509:2013,2024|0510:2018,2029,2040|0511:2051,2056|0513:2010,2021|0514:2015,2026,2037,2048|0515:2042,2053|0517:2012|0518:2023,2034,2045|0519:2039,2050|0521:2020|0522:2031,2036|0523:2047,2058|0525:2017,2028|0526:2022,2033,2044|0527:2049,2055,2060|0529:2014,2025|0530:2019,2030,2041,2052|0531:2057|0602:2011|0603:2038"},{"id":"pentecost","name":"Første pinsedag","en":"Pentecost","category":"public","dates":"d|0513:2035,2046|0515:2016|0516:2027,2032|0517:2043,2054|0518:2059|0519:2013,2024|0520:2018,2029,2040|0521:2051,2056|0523:2010,2021|0524:2015,2026,2037,2048|0525:2042,2053|0527:2012|0528:2023,2034,2045|0529:2039,2050|0531:2020|0601:2031,2036|0602:2047,2058|0604:2017,2028|0605:2022,2033,2044|0606:2049,2055,2060|0608:2014,2025|0609:2019,2030,2041,2052|0610:2057|0612:2011|0613:2038"},{"id":"pentecost_monday","name":"Andre pinsedag","en":"Pentecost Monday","category":"public","dates":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038"},{"id":"constitution_day","name":"Grunnlovsdag","en":"Constitution Day","category":"public","dates":"d|0517:2010-2060"},{"id":"christmas_day","name":"Første juledag","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"},{"id":"second_day_of_christmas","name":"Andre juledag","en":"Second Day of Christmas","category":"public","dates":"d|1226:2010-2060"}]} diff --git a/app/Support/Holidays/data/NP.json b/app/Support/Holidays/data/NP.json new file mode 100644 index 000000000..6f0c02c8c --- /dev/null +++ b/app/Support/Holidays/data/NP.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"NP","language":"ne","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"eid_al_fitr","name":"ईद (ईद उल फित्र)","en":"Eid al-Fitr","category":"public","dates":"y|2010:0910|2011:0831|2012:0820|2013:0808|2014:0729|2015:0718|2016:0707|2017:0626|2018:0616|2019:0605|2020:0525|2021:0514|2022:0503|2023:0422|2024:0411|2025:0331|2026:0321|2027:0310|2028:0227|2029:0215|2030:0205|2031:0125|2032:0115|2033:0103,1224|2034:1213|2035:1202|2036:1120|2037:1109|2038:1030|2039:1020|2040:1008|2041:0927|2042:0916|2043:0905|2044:0825|2045:0815|2046:0804|2047:0725|2048:0713|2049:0702|2050:0621|2051:0611|2052:0530|2053:0520|2054:0510|2055:0429|2056:0417|2057:0406|2058:0326|2059:0316|2060:0305","estimated":"y|2026:0321|2027:0310|2028:0227|2029:0215|2030:0205|2031:0125|2032:0115|2033:0103,1224|2034:1213|2035:1202|2036:1120|2037:1109|2038:1030|2039:1020|2040:1008|2041:0927|2042:0916|2043:0905|2044:0825|2045:0815|2046:0804|2047:0725|2048:0713|2049:0702|2050:0621|2051:0611|2052:0530|2053:0520|2054:0510|2055:0429|2056:0417|2057:0406|2058:0326|2059:0316|2060:0305"},{"id":"eid_al_adha","name":"बकर ईद (ईद उल अजहा)","en":"Eid al-Adha","category":"public","dates":"y|2010:1117|2011:1107|2012:1027|2013:1016|2014:1006|2015:0925|2016:0913|2017:0902|2018:0822|2019:0812|2020:0801|2021:0721|2022:0710|2023:0629|2024:0617|2025:0607|2026:0528|2027:0517|2028:0506|2029:0425|2030:0414|2031:0403|2032:0323|2033:0312|2034:0302|2035:0219|2036:0208|2037:0127|2038:0117|2039:0106,1227|2040:1215|2041:1205|2042:1124|2043:1113|2044:1101|2045:1022|2046:1011|2047:1001|2048:0920|2049:0909|2050:0829|2051:0818|2052:0806|2053:0727|2054:0716|2055:0706|2056:0624|2057:0613|2058:0602|2059:0523|2060:0511","estimated":"y|2026:0528|2027:0517|2028:0506|2029:0425|2030:0414|2031:0403|2032:0323|2033:0312|2034:0302|2035:0219|2036:0208|2037:0127|2038:0117|2039:0106,1227|2040:1215|2041:1205|2042:1124|2043:1113|2044:1101|2045:1022|2046:1011|2047:1001|2048:0920|2049:0909|2050:0829|2051:0818|2052:0806|2053:0727|2054:0716|2055:0706|2056:0624|2057:0613|2058:0602|2059:0523|2060:0511"},{"id":"prithvi_jayanti","name":"पृथ्वी जयन्ती","en":"Prithvi Jayanti","category":"public","dates":"d|0111:2023-2060"},{"id":"maghe_sankranti","name":"माघे संक्रान्ति","en":"Maghe Sankranti","category":"public","dates":"d|0114:2010,2013-2014,2017-2018,2021,2025,2029-2030,2033-2034|0115:2011-2012,2015-2016,2019-2020,2022-2024,2026-2028,2031-2032,2035"},{"id":"sonam_lhochhar","name":"सोनम ल्होछार","en":"Sonam Lhochhar","category":"public","dates":"d|0115:2029|0116:2010|0118:2018|0119:2026|0121:2015,2034|0122:2023|0124:2012,2031|0125:2020|0128:2017|0130:2025|0131:2014,2033|0202:2022|0203:2030|0204:2011|0205:2019|0207:2027|0209:2016,2035|0210:2024|0211:2013|0212:2021,2032|0226:2028"},{"id":"martyr_s_day","name":"शहीद दिवस","en":"Martyr's Day","category":"public","dates":"d|0129:2013,2017,2021,2025-2030,2033|0130:2010-2012,2014-2016,2018,2022-2024,2031-2032"},{"id":"gyalpo_lhosar","name":"ग्याल्पो ल्होसार","en":"Gyalpo Lhosar","category":"public","dates":"d|0207:2027|0209:2035|0212:2032|0213:2010|0214:2029|0216:2018|0218:2026|0219:2015,2034|0221:2023|0222:2012,2031|0224:2020|0226:2028|0227:2017|0228:2025|0302:2014,2033|0303:2022|0305:2011,2030|0307:2019|0309:2016|0311:2024|0312:2013|0314:2021"},{"id":"maha_shivaratri","name":"महाशिवरात्रि","en":"Maha Shivaratri","category":"public","dates":"d|0211:2029|0212:2010|0213:2018|0215:2026|0217:2015,2034|0218:2023|0220:2012,2031|0221:2020|0223:2028|0224:2017|0226:2025|0227:2014,2033|0301:2022|0302:2011,2030|0304:2019|0306:2027|0307:2016|0308:2024,2035|0310:2013,2032|0311:2021"},{"id":"national_democracy_day","name":"राष्ट्रिय प्रजातन्त्र दिवस","en":"National Democracy Day","category":"public","dates":"d|0218:2013,2017|0219:2010-2012,2014-2016,2018,2021-2030,2033|0220:2031-2032"},{"id":"fagu_poornima","name":"फागुपूर्णिमा","en":"Fagu Poornima","category":"public","dates":"d|0228:2010,2029|0301:2018|0302:2026|0304:2034|0305:2015|0306:2023|0307:2012|0308:2031|0309:2020|0310:2028|0312:2017|0313:2025|0315:2033|0316:2014|0317:2022|0319:2011,2030|0320:2019|0321:2027|0323:2016,2035|0324:2024|0326:2013,2032|0328:2021"},{"id":"fagu_poornima_terai","name":"फागुपूर्णिमा (तराई)","en":"Fagu Poornima (Terai)","category":"public","dates":"d|0301:2010,2029|0302:2018|0303:2026|0305:2034|0306:2015|0307:2023|0308:2012|0309:2031|0310:2020|0311:2028|0313:2017|0314:2025|0316:2033|0317:2014|0318:2022|0320:2011,2030|0321:2019|0322:2027|0324:2016,2035|0325:2024|0327:2013,2032|0329:2021"},{"id":"international_women_s_day","name":"अन्तर्राष्ट्रिय महिला दिवस","en":"International Women's Day","category":"public","dates":"d|0308:2010-2060"},{"id":"ram_navami","name":"राम नवमी","en":"Ram Navami","category":"public","dates":"d|0324:2010|0325:2018|0327:2026|0328:2015,2034|0330:2023|0401:2012,2031|0403:2028|0404:2017|0406:2025|0407:2033|0408:2014|0410:2022|0412:2011,2030|0415:2016,2027|0416:2035|0417:2024|0419:2013,2032|0421:2021|0422:2029"},{"id":"nepali_new_year","name":"नेपाली नयाँ वर्ष","en":"Nepali New Year","category":"public","dates":"d|0413:2012-2013,2016,2020,2024,2028,2032|0414:2010-2011,2014-2015,2017-2019,2021-2023,2025-2027,2029-2031,2033-2035"},{"id":"buddha_jayanti","name":"बुद्ध जयन्ती","en":"Buddha Jayanti","category":"public","dates":"d|0430:2018|0501:2026|0503:2034|0504:2015|0505:2023|0506:2012|0507:2020,2031|0508:2028|0510:2017|0512:2025|0514:2014,2033|0516:2022|0517:2011,2030|0518:2019|0520:2027|0521:2016|0522:2035|0523:2024|0525:2013,2032|0526:2021|0527:2010,2029"},{"id":"international_labor_day","name":"अन्तर्राष्ट्रिय श्रम दिवस","en":"International Labor Day","category":"public","dates":"d|0501:2010-2060"},{"id":"republic_day","name":"गणतन्त्र दिवस","en":"Republic Day","category":"public","dates":"d|0528:2012,2016,2024-2025,2028-2029,2032|0529:2010-2011,2013-2015,2017-2018,2021-2023,2026-2027,2030-2031"},{"id":"janai_poornima","name":"जनै पूर्णिमा","en":"Janai Poornima","category":"public","dates":"d|0802:2012,2031|0805:2028|0807:2017|0809:2025|0810:2014,2033|0812:2022|0813:2011,2030|0817:2027|0818:2016,2035|0819:2024|0820:2013,2032|0822:2021|0823:2029|0824:2010|0826:2018|0828:2026|0829:2015,2034|0831:2023"},{"id":"shree_krishna_janmashtami","name":"श्रीकृष्ण जन्माष्टमी","en":"Shree Krishna Janmashtami","category":"public","dates":"d|0810:2012,2031|0813:2028|0815:2017|0816:2025|0817:2033|0818:2014|0819:2022|0821:2030|0822:2011|0825:2016,2027|0826:2024,2035|0828:2013,2032|0830:2021|0901:2029|0902:2010|0903:2018|0904:2026|0905:2015|0906:2023,2034"},{"id":"constitution_day","name":"संविधान दिवस","en":"Constitution Day","category":"public","dates":"d|0918:2028|0919:2016-2018,2020-2022,2024-2027,2029-2032|0920:2019,2023"},{"id":"ghatasthapana","name":"घटस्थापना","en":"Ghatasthapana","category":"public","dates":"d|0919:2028|0921:2017|0922:2025|0924:2033|0925:2014|0926:2022|0928:2011,2030|0930:2027|1001:2016|1002:2035|1003:2024|1005:2013,2032|1007:2021|1008:2010,2029|1010:2018|1011:2026|1013:2015,2034|1015:2023|1016:2012|1017:2031"},{"id":"fulpati","name":"फुलपाती","en":"Fulpati","category":"public","dates":"d|0925:2028|0927:2017|0929:2025|1001:2014,2033|1002:2022|1003:2011,2030|1005:2019|1006:2027|1008:2016,2035|1010:2024|1011:2013,2032|1012:2021|1013:2029|1014:2010|1016:2018|1018:2026|1019:2034|1020:2015|1021:2012,2023|1022:2031|1023:2020"},{"id":"maha_ashtami","name":"महा अष्टमी","en":"Maha Ashtami","category":"public","dates":"d|0926:2028|0928:2017|0930:2025|1002:2014,2033|1003:2022|1004:2011,2030|1006:2019|1007:2027|1009:2016,2035|1011:2024|1012:2013,2032|1013:2021|1014:2029|1015:2010|1017:2018|1019:2026|1020:2034|1021:2015|1022:2012,2023|1023:2031|1024:2020"},{"id":"maha_navami","name":"महा नवमी","en":"Maha Navami","category":"public","dates":"d|0926:2028|0929:2017|1001:2025|1002:2014,2033|1004:2022|1005:2011,2030|1006:2019|1008:2027|1010:2016,2035|1011:2024|1012:2013|1013:2032|1014:2021|1015:2029|1016:2010|1017:2018|1019:2026|1021:2015,2034|1023:2012,2023|1024:2031|1025:2020"},{"id":"bijaya_dashami","name":"विजया दशमी","en":"Bijaya Dashami","category":"public","dates":"d|0927:2028|0930:2017|1002:2025|1003:2014,2033|1005:2022|1006:2011,2030|1008:2019|1009:2027|1011:2016,2035|1012:2024|1013:2013|1014:2032|1015:2021|1016:2029|1017:2010|1019:2018|1020:2026|1022:2015,2034|1024:2012,2023|1025:2031|1026:2020"},{"id":"ekadashi_dashain","name":"एकादशी (दशैं)","en":"Ekadashi (Dashain)","category":"public","dates":"d|0928:2028|1001:2017|1003:2025|1004:2014,2033|1006:2022|1007:2011,2030|1009:2019|1010:2027|1012:2016,2035|1013:2024|1014:2013|1015:2032|1016:2021|1017:2029|1018:2010|1020:2018|1021:2026|1023:2015,2034|1025:2012,2023|1026:2031|1027:2020"},{"id":"duwadashi_dashain","name":"दुवादशी (दशैं)","en":"Duwadashi (Dashain)","category":"public","dates":"d|0929:2028|1002:2017|1004:2025|1005:2014,2033|1007:2022|1008:2011,2030|1011:2027|1013:2016,2035|1014:2024|1015:2013|1016:2032|1017:2021|1018:2029|1019:2010|1021:2018|1022:2026|1024:2015,2034|1026:2012,2023|1027:2031"},{"id":"gai_tihar","name":"गाई तिहार","en":"Gai Tihar","category":"public","dates":"d|1017:2028|1019:2017|1021:2025|1022:2033|1023:2014|1025:2022|1026:2011,2030|1027:2019|1029:2027|1030:2016,2035|1101:2024|1102:2032|1103:2013|1104:2021|1105:2010,2029|1107:2018|1109:2026|1110:2034|1111:2015|1113:2012,2023|1114:2031|1115:2020"},{"id":"laxmi_pooja","name":"लक्ष्मीपूजा","en":"Laxmi Pooja","category":"public","dates":"d|1017:2028|1019:2017|1020:2025|1022:2033|1023:2014|1024:2022|1026:2011,2030|1027:2019|1029:2027|1030:2016,2035|1031:2024|1102:2032|1103:2013|1104:2021|1105:2010,2029|1107:2018|1108:2026|1110:2034|1111:2015|1112:2023|1113:2012|1114:2020,2031"},{"id":"gobardhan_pooja","name":"गोवर्धन पूजा","en":"Gobardhan Pooja","category":"public","dates":"d|1018:2028|1020:2017|1022:2025|1023:2033|1024:2014|1026:2022|1027:2011,2030|1028:2019|1030:2027|1031:2016,2035|1102:2024|1103:2032|1104:2013|1105:2021|1106:2010,2029|1108:2018|1110:2026|1111:2034|1112:2015|1114:2012,2023|1115:2031|1116:2020"},{"id":"mha_pooja","name":"म्ह पूजा","en":"Mha Pooja","category":"public","dates":"d|1018:2028|1020:2017|1022:2025|1023:2033|1024:2014|1026:2022|1027:2011,2030|1028:2019|1030:2027|1031:2016,2035|1102:2024|1103:2032|1104:2013|1105:2021|1106:2010,2029|1108:2018|1110:2026|1111:2034|1112:2015|1114:2012,2023|1115:2031|1116:2020"},{"id":"bhai_tika","name":"भाइटीका","en":"Bhai Tika","category":"public","dates":"d|1019:2028|1021:2017|1023:2025|1024:2033|1025:2014|1027:2022|1028:2011,2030|1029:2019|1031:2027|1101:2016,2035|1103:2024|1104:2032|1105:2013|1106:2021|1107:2010,2029|1109:2018|1111:2026|1112:2034|1113:2015|1115:2012,2023|1116:2031|1117:2020"},{"id":"chhath_parva","name":"छठ पर्व","en":"Chhath Parva","category":"public","dates":"d|1023:2028|1026:2017|1027:2025|1029:2014,2033|1030:2022|1101:2011,2030|1102:2019|1104:2027|1106:2016,2035|1107:2024|1108:2013|1109:2032|1110:2021|1111:2010,2029|1113:2018|1115:2026|1117:2015,2034|1119:2012,2023|1120:2020,2031"},{"id":"christmas_day","name":"क्रिसमसको दिन","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"},{"id":"tamu_lhochhar","name":"तमु ल्हाेसार","en":"Tamu Lhochhar","category":"public","dates":"d|1230:2010-2018,2020-2022,2024-2029,2032|1231:2019,2023,2030-2031"}]} diff --git a/app/Support/Holidays/data/NR.json b/app/Support/Holidays/data/NR.json new file mode 100644 index 000000000..7b91f3b4c --- /dev/null +++ b/app/Support/Holidays/data/NR.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"NR","language":"en_NR","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051|0103:2011,2022,2028,2033,2039,2050,2056"},{"id":"independence_day","name":"Independence Day","en":"Independence Day","category":"public","dates":"d|0131:2010-2060|0202:2010,2015-2016,2021,2026-2027,2032,2037-2038,2043-2044,2049,2054-2055,2060"},{"id":"day_following_independence_day","name":"Day following Independence Day","en":"Day following Independence Day","category":"public","dates":"d|0201:2010-2060|0203:2014-2015,2020,2025-2026,2031-2032,2037,2042-2043,2048,2053-2054,2059-2060"},{"id":"international_women_s_day","name":"International Women's Day","en":"International Women's Day","category":"public","dates":"d|0308:2019-2060|0309:2020,2026,2037,2043,2048,2054|0310:2025,2031,2036,2042,2053,2059"},{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_monday","name":"Easter Monday","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"easter_tuesday","name":"Easter Tuesday","en":"Easter Tuesday","category":"public","dates":"d|0327:2035,2046|0329:2016|0330:2027,2032|0331:2043,2054|0401:2059|0402:2013,2024|0403:2018,2029,2040|0404:2051,2056|0406:2010,2021|0407:2015,2026,2037,2048|0408:2042,2053|0410:2012|0411:2023,2034,2045|0412:2039,2050|0414:2020|0415:2031,2036|0416:2047,2058|0418:2017,2028|0419:2022,2033,2044|0420:2049,2055,2060|0422:2014,2025|0423:2019,2030,2041,2052|0424:2057|0426:2011|0427:2038"},{"id":"constitution_day","name":"Constitution Day","en":"Constitution Day","category":"public","dates":"d|0517:2010-2060|0518:2015,2020,2026,2037,2043,2048,2054|0519:2014,2025,2031,2036,2042,2053,2059"},{"id":"ronphos_handover","name":"RONPHOS Handover","en":"RONPHOS Handover","category":"public","dates":"d|0701:2018-2060|0702:2018,2029,2035,2040,2046,2057|0703:2023,2028,2034,2045,2051,2056"},{"id":"ibumin_earoeni_day","name":"Ibumin Earoeni Day","en":"Ibumin Earoeni Day","category":"public","dates":"d|0819:2019-2060|0820:2029,2035,2040,2046,2057|0821:2023,2028,2034,2045,2051,2056"},{"id":"sir_hammer_deroburt_day","name":"Sir Hammer DeRoburt Day","en":"Sir Hammer DeRoburt Day","category":"public","dates":"d|0925:2020-2060|0926:2022,2033,2039,2044,2050|0927:2021,2027,2032,2038,2049,2055,2060"},{"id":"angam_day","name":"Angam Day","en":"Angam Day","category":"public","dates":"d|1026:2010-2060|1027:2014,2025,2031,2036,2042,2053,2059|1028:2013,2019,2024,2030,2041,2047,2052,2058"},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060|1227:2010-2011,2016,2021-2022,2027,2032-2033,2038-2039,2044,2049-2050,2055,2060"},{"id":"day_following_christmas","name":"Day following Christmas","en":"Day following Christmas","category":"public","dates":"d|1226:2010-2060|1228:2010,2015,2020-2021,2026-2027,2032,2037-2038,2043,2048-2049,2054-2055,2060"}]} diff --git a/app/Support/Holidays/data/NU.json b/app/Support/Holidays/data/NU.json new file mode 100644 index 000000000..38c3e7a01 --- /dev/null +++ b/app/Support/Holidays/data/NU.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"NU","language":"en_NU","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0103:2011-2012,2017,2022-2023,2028,2033-2034,2039-2040,2045,2050-2051,2056"},{"id":"takai_commission_holiday","name":"Takai Commission Holiday","en":"Takai Commission Holiday","category":"public","dates":"d|0102:2010-2060|0104:2010-2011,2016,2021-2022,2027-2028,2033,2038-2039,2044,2049-2050,2055-2056"},{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_monday","name":"Easter Monday","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"anzac_day","name":"ANZAC Day","en":"ANZAC Day","category":"public","dates":"d|0425:2010-2060|0426:2010,2021,2027,2032,2038,2049,2055,2060|0427:2015,2020,2026,2037,2043,2048,2054"},{"id":"king_s_birthday","name":"King's Birthday","en":"King's Birthday","category":"public","dates":"d|0601:2026,2037,2043,2048,2054|0602:2025,2031,2036,2042,2053,2059|0603:2024,2030,2041,2047,2052,2058|0604:2029,2035,2040,2046,2057|0605:2023,2028,2034,2045,2051,2056|0606:2033,2039,2044,2050|0607:2027,2032,2038,2049,2055,2060"},{"id":"constitution_day","name":"Constitution Day","en":"Constitution Day","category":"public","dates":"d|1019:2010-2060|1021:2013-2014,2019,2024-2025,2030-2031,2036,2041-2042,2047,2052-2053,2058-2059"},{"id":"constitution_day_holiday","name":"Constitution Day Holiday","en":"Constitution Day Holiday","category":"public","dates":"d|1020:2010-2060|1022:2012-2013,2018-2019,2024,2029-2030,2035,2040-2041,2046-2047,2052,2057-2058"},{"id":"peniamina_gospel_day","name":"Peniamina Gospel Day","en":"Peniamina Gospel Day","category":"public","dates":"d|1022:2012,2018,2029,2035,2040,2046,2057|1023:2017,2023,2028,2034,2045,2051,2056|1024:2011,2016,2022,2033,2039,2044,2050|1025:2010,2021,2027,2032,2038,2049,2055,2060|1026:2015,2020,2026,2037,2043,2048,2054|1027:2014,2025,2031,2036,2042,2053,2059|1028:2013,2019,2024,2030,2041,2047,2052,2058"},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060|1227:2010-2011,2016,2021-2022,2027,2032-2033,2038-2039,2044,2049-2050,2055,2060"},{"id":"boxing_day","name":"Boxing Day","en":"Boxing Day","category":"public","dates":"d|1226:2010-2060|1228:2010,2015,2020-2021,2026-2027,2032,2037-2038,2043,2048-2049,2054-2055,2060"}]} diff --git a/app/Support/Holidays/data/NZ.json b/app/Support/Holidays/data/NZ.json new file mode 100644 index 000000000..03517ac1e --- /dev/null +++ b/app/Support/Holidays/data/NZ.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"NZ","language":null,"years":[2010,2060],"subdivisions":{"AUK":"Auckland","BOP":"Bay of Plenty","CAN":"Canterbury","CIT":"Chatham Islands Territory","GIS":"Gisborne","HKB":"Hawke's Bay","MBH":"Marlborough","MWT":"Manawatū Whanganui","NSN":"Nelson","NTL":"Northland","OTA":"Otago","STL":"Southland","TAS":"Tasman","TKI":"Taranaki","WGN":"Greater Wellington","WKO":"Waikato","WTC":"West Coast","South Canterbury":"South Canterbury"},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0103:2011-2012,2017,2022-2023,2028,2033-2034,2039-2040,2045,2050-2051,2056"},{"id":"day_after_new_year_s_day","name":"Day after New Year's Day","en":"Day after New Year's Day","category":"public","dates":"d|0102:2010-2060|0104:2010-2011,2016,2021-2022,2027-2028,2033,2038-2039,2044,2049-2050,2055-2056"},{"id":"southland_anniversary_day","name":"Southland Anniversary Day","en":"Southland Anniversary Day","category":"public","regional":{"STL":"d|0117:2011|0118:2010|0327:2035,2046|0329:2016|0330:2027,2032|0331:2043,2054|0401:2059|0402:2013,2024|0403:2018,2029,2040|0404:2051,2056|0406:2021|0407:2015,2026,2037,2048|0408:2042,2053|0410:2012|0411:2023,2034,2045|0412:2039,2050|0414:2020|0415:2031,2036|0416:2047,2058|0418:2017,2028|0419:2022,2033,2044|0420:2049,2055,2060|0422:2014,2025|0423:2019,2030,2041,2052|0424:2057|0427:2038"}},{"id":"wellington_anniversary_day","name":"Wellington Anniversary Day","en":"Wellington Anniversary Day","category":"public","regional":{"WGN":"d|0119:2015,2026,2032,2037,2043,2054,2060|0120:2014,2020,2025,2031,2042,2048,2053,2059|0121:2013,2019,2030,2036,2041,2047,2058|0122:2018,2024,2029,2035,2046,2052,2057|0123:2012,2017,2023,2034,2040,2045,2051|0124:2011,2022,2028,2033,2039,2050,2056|0125:2010,2016,2021,2027,2038,2044,2049,2055"}},{"id":"auckland_anniversary_day","name":"Auckland Anniversary Day","en":"Auckland Anniversary Day","category":"public","regional":{"AUK":"d|0126:2015,2026,2032,2037,2043,2054,2060|0127:2014,2020,2025,2031,2042,2048,2053,2059|0128:2013,2019,2030,2036,2041,2047,2058|0129:2018,2024,2029,2035,2046,2052,2057|0130:2012,2017,2023,2034,2040,2045,2051|0131:2011,2022,2028,2033,2039,2050,2056|0201:2010,2016,2021,2027,2038,2044,2049,2055","NTL":"d|0126:2015,2026,2032,2037,2043,2054,2060|0127:2014,2020,2025,2031,2042,2048,2053,2059|0128:2013,2019,2030,2036,2041,2047,2058|0129:2018,2024,2029,2035,2046,2052,2057|0130:2012,2017,2023,2034,2040,2045,2051|0131:2011,2022,2028,2033,2039,2050,2056|0201:2010,2016,2021,2027,2038,2044,2049,2055"}},{"id":"nelson_anniversary_day","name":"Nelson Anniversary Day","en":"Nelson Anniversary Day","category":"public","regional":{"NSN":"d|0129:2018,2024,2029,2035,2046,2052,2057|0130:2012,2017,2023,2034,2040,2045,2051|0131:2011,2022,2028,2033,2039,2050,2056|0201:2010,2016,2021,2027,2038,2044,2049,2055|0202:2015,2026,2032,2037,2043,2054,2060|0203:2014,2020,2025,2031,2042,2048,2053,2059|0204:2013,2019,2030,2036,2041,2047,2058"}},{"id":"waitangi_day","name":"Waitangi Day","en":"Waitangi Day","category":"public","dates":"d|0206:2010-2060|0207:2022,2028,2033,2039,2050,2056|0208:2016,2021,2027,2038,2044,2049,2055"},{"id":"taranaki_anniversary_day","name":"Taranaki Anniversary Day","en":"Taranaki Anniversary Day","category":"public","regional":{"TKI":"d|0308:2010,2021,2027,2032,2038,2049,2055,2060|0309:2015,2020,2026,2037,2043,2048,2054|0310:2014,2025,2031,2036,2042,2053,2059|0311:2013,2019,2024,2030,2041,2047,2052,2058|0312:2012,2018,2029,2035,2040,2046,2057|0313:2017,2023,2028,2034,2045,2051,2056|0314:2011,2016,2022,2033,2039,2044,2050"}},{"id":"otago_anniversary_day","name":"Otago Anniversary Day","en":"Otago Anniversary Day","category":"public","regional":{"OTA":"d|0320:2017,2023,2028,2034,2045,2051,2056|0321:2011,2016,2022,2033,2039,2044,2050|0322:2010,2021,2027,2032,2038,2049,2055,2060|0323:2015,2020,2026,2037,2043,2048,2054|0324:2014,2025,2031,2036,2042,2053,2059|0325:2013,2019,2024,2030,2041,2047,2052,2058|0326:2012,2018,2029,2040,2057|0327:2035,2046"}},{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_monday","name":"Easter Monday","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"anzac_day","name":"Anzac Day","en":"Anzac Day","category":"public","dates":"d|0425:2010-2060|0426:2021,2027,2032,2038,2049,2055,2060|0427:2015,2020,2026,2037,2043,2048,2054"},{"id":"king_s_birthday","name":"King's Birthday","en":"King's Birthday","category":"public","dates":"d|0601:2026,2037,2043,2048,2054|0602:2025,2031,2036,2042,2053,2059|0603:2024,2030,2041,2047,2052,2058|0604:2029,2035,2040,2046,2057|0605:2023,2028,2034,2045,2051,2056|0606:2033,2039,2044,2050|0607:2027,2032,2038,2049,2055,2060"},{"id":"matariki","name":"Matariki","en":"Matariki","category":"public","dates":"d|0620:2025|0621:2030,2052|0624:2022,2033,2044|0625:2027,2038,2049|0628:2024|0629:2035,2046|0630:2051|0702:2032|0703:2043,2048|0706:2029,2040|0707:2034,2045|0710:2026,2037|0711:2031,2042|0714:2023,2028|0715:2039,2050|0718:2036|0719:2041,2047"},{"id":"south_canterbury_anniversary_day","name":"South Canterbury Anniversary Day","en":"South Canterbury Anniversary Day","category":"public","regional":{"South Canterbury":"d|0922:2014,2025,2031,2036,2042,2053,2059|0923:2013,2019,2024,2030,2041,2047,2052,2058|0924:2012,2018,2029,2035,2040,2046,2057|0925:2017,2023,2028,2034,2045,2051,2056|0926:2011,2016,2022,2033,2039,2044,2050|0927:2010,2021,2027,2032,2038,2049,2055,2060|0928:2015,2020,2026,2037,2043,2048,2054"}},{"id":"hawke_s_bay_anniversary_day","name":"Hawke's Bay Anniversary Day","en":"Hawke's Bay Anniversary Day","category":"public","regional":{"HKB":"d|1019:2012,2018,2029,2035,2040,2046,2057|1020:2017,2023,2028,2034,2045,2051,2056|1021:2011,2016,2022,2033,2039,2044,2050|1022:2010,2021,2027,2032,2038,2049,2055,2060|1023:2015,2020,2026,2037,2043,2048,2054|1024:2014,2025,2031,2036,2042,2053,2059|1025:2013,2019,2024,2030,2041,2047,2052,2058"}},{"id":"labour_day","name":"Labour Day","en":"Labour Day","category":"public","dates":"d|1022:2012,2018,2029,2035,2040,2046,2057|1023:2017,2023,2028,2034,2045,2051,2056|1024:2011,2016,2022,2033,2039,2044,2050|1025:2010,2021,2027,2032,2038,2049,2055,2060|1026:2015,2020,2026,2037,2043,2048,2054|1027:2014,2025,2031,2036,2042,2053,2059|1028:2013,2019,2024,2030,2041,2047,2052,2058"},{"id":"marlborough_anniversary_day","name":"Marlborough Anniversary Day","en":"Marlborough Anniversary Day","category":"public","regional":{"MBH":"d|1029:2012,2018,2029,2035,2040,2046,2057|1030:2017,2023,2028,2034,2045,2051,2056|1031:2011,2016,2022,2033,2039,2044,2050|1101:2010,2021,2027,2032,2038,2049,2055,2060|1102:2015,2020,2026,2037,2043,2048,2054|1103:2014,2025,2031,2036,2042,2053,2059|1104:2013,2019,2024,2030,2041,2047,2052,2058"}},{"id":"canterbury_anniversary_day","name":"Canterbury Anniversary Day","en":"Canterbury Anniversary Day","category":"public","regional":{"CAN":"d|1111:2011,2016,2022,2033,2039,2044,2050|1112:2010,2021,2027,2032,2038,2049,2055,2060|1113:2015,2020,2026,2037,2043,2048,2054|1114:2014,2025,2031,2036,2042,2053,2059|1115:2013,2019,2024,2030,2041,2047,2052,2058|1116:2012,2018,2029,2035,2040,2046,2057|1117:2017,2023,2028,2034,2045,2051,2056"}},{"id":"chatham_islands_anniversary_day","name":"Chatham Islands Anniversary Day","en":"Chatham Islands Anniversary Day","category":"public","regional":{"CIT":"d|1127:2017,2023,2028,2034,2045,2051,2056|1128:2011,2016,2022,2033,2039,2044,2050|1129:2010,2021,2027,2032,2038,2049,2055,2060|1130:2015,2020,2026,2037,2043,2048,2054|1201:2014,2025,2031,2036,2042,2053,2059|1202:2013,2019,2024,2030,2041,2047,2052,2058|1203:2012,2018,2029,2035,2040,2046,2057"}},{"id":"west_coast_anniversary_day","name":"West Coast Anniversary Day","en":"West Coast Anniversary Day","category":"public","regional":{"WTC":"d|1128:2011,2016,2022,2033,2039,2044,2050|1129:2010,2021,2027,2032,2038,2049,2055,2060|1130:2015,2020,2026,2037,2043,2048,2054|1201:2014,2025,2031,2036,2042,2053,2059|1202:2013,2019,2024,2030,2041,2047,2052,2058|1203:2012,2018,2029,2035,2040,2046,2057|1204:2017,2023,2028,2034,2045,2051,2056"}},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060|1227:2010-2011,2016,2021-2022,2027,2032-2033,2038-2039,2044,2049-2050,2055,2060"},{"id":"boxing_day","name":"Boxing Day","en":"Boxing Day","category":"public","dates":"d|1226:2010-2060|1228:2010,2015,2020-2021,2026-2027,2032,2037-2038,2043,2048-2049,2054-2055,2060"}]} diff --git a/app/Support/Holidays/data/OM.json b/app/Support/Holidays/data/OM.json new file mode 100644 index 000000000..198c8bfbb --- /dev/null +++ b/app/Support/Holidays/data/OM.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"OM","language":"ar","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"eid_al_fitr","name":"عيد الفطر","en":"Eid al-Fitr","category":"public","dates":"y|2010:0908-0912|2011:0829-0901|2012:0817-0821|2013:0806-0810|2014:0726-0730|2015:0716-0719|2016:0704-0708|2017:0624-0627|2018:0613-0617|2019:0603-0606|2020:0522-0526|2021:0511-0515|2022:0430-0504|2023:0420-0424|2024:0409-0412|2025:0329-0402|2026:0318-0322|2027:0308-0311|2028:0225-0228|2029:0213-0216|2030:0202-0206|2031:0123-0126|2032:0112-0116|2033:0101-0104,1221-1225|2034:1210-1214|2035:1129-1203|2036:1117-1121|2037:1107-1110|2038:1028-1031|2039:1017-1021|2040:1005-1009|2041:0925-0928|2042:0914-0917|2043:0903-0906|2044:0823-0826|2045:0812-0816|2046:0802-0805|2047:0722-0726|2048:0710-0714|2049:0630-0703|2050:0619-0622|2051:0608-0612|2052:0528-0531|2053:0518-0521|2054:0507-0511|2055:0426-0430|2056:0414-0418|2057:0403-0407|2058:0324-0327|2059:0314-0317|2060:0302-0306","estimated":"y|2010:0908-0912|2011:0829-0901|2012:0817-0821|2013:0806-0810|2014:0726-0730|2015:0716-0719|2016:0704-0708|2017:0624-0627|2018:0613-0617|2019:0603|2020:0522|2021:0511|2022:0430|2026:0318-0322|2027:0308-0311|2028:0225-0228|2029:0213-0216|2030:0202-0206|2031:0123-0126|2032:0112-0116|2033:0101-0104,1221-1225|2034:1210-1214|2035:1129-1203|2036:1117-1121|2037:1107-1110|2038:1028-1031|2039:1017-1021|2040:1005-1009|2041:0925-0928|2042:0914-0917|2043:0903-0906|2044:0823-0826|2045:0812-0816|2046:0802-0805|2047:0722-0726|2048:0710-0714|2049:0630-0703|2050:0619-0622|2051:0608-0612|2052:0528-0531|2053:0518-0521|2054:0507-0511|2055:0426-0430|2056:0414-0418|2057:0403-0407|2058:0324-0327|2059:0314-0317|2060:0302-0306"},{"id":"isra_and_mi_raj","name":"الإسراء والمعراج","en":"Isra' and Mi'raj","category":"public","dates":"y|2010:0709|2011:0629|2012:0617|2013:0606|2014:0526|2015:0516|2016:0504|2017:0424|2018:0413|2019:0403|2020:0322|2021:0311|2022:0301|2023:0219|2024:0208|2025:0127|2026:0116|2027:0105,1225|2028:1214|2029:1203|2030:1123|2031:1112|2032:1101|2033:1021|2034:1010|2035:0929|2036:0918|2037:0907|2038:0828|2039:0817|2040:0805|2041:0725|2042:0715|2043:0704|2044:0623|2045:0613|2046:0602|2047:0522|2048:0510|2049:0429|2050:0419|2051:0409|2052:0328|2053:0318|2054:0307|2055:0224|2056:0213|2057:0201|2058:0122|2059:0112|2060:0101,1220","estimated":"y|2010:0709|2011:0629|2012:0617|2013:0606|2014:0526|2015:0516|2016:0504|2017:0424|2026:0116|2027:0105,1225|2028:1214|2029:1203|2030:1123|2031:1112|2032:1101|2033:1021|2034:1010|2035:0929|2036:0918|2037:0907|2038:0828|2039:0817|2040:0805|2041:0725|2042:0715|2043:0704|2044:0623|2045:0613|2046:0602|2047:0522|2048:0510|2049:0429|2050:0419|2051:0409|2052:0328|2053:0318|2054:0307|2055:0224|2056:0213|2057:0201|2058:0122|2059:0112|2060:0101,1220"},{"id":"prophet_s_birthday","name":"مولد النبي","en":"Prophet's Birthday","category":"public","dates":"y|2010:0226|2011:0215|2012:0204|2013:0124|2014:0113|2015:0103,1223|2016:1211|2017:1130|2018:1120|2019:1109|2020:1029|2021:1019|2022:1009|2023:0928|2024:0915|2025:0904|2026:0825|2027:0814|2028:0803|2029:0724|2030:0713|2031:0702|2032:0620|2033:0609|2034:0530|2035:0520|2036:0508|2037:0428|2038:0417|2039:0406|2040:0325|2041:0315|2042:0304|2043:0222|2044:0211|2045:0130|2046:0119|2047:0108,1229|2048:1218|2049:1207|2050:1126|2051:1116|2052:1104|2053:1024|2054:1013|2055:1003|2056:0922|2057:0911|2058:0831|2059:0820|2060:0808","estimated":"y|2010:0226|2011:0215|2012:0204|2013:0124|2014:0113|2015:0103,1223|2016:1211|2017:1130|2025:0904|2026:0825|2027:0814|2028:0803|2029:0724|2030:0713|2031:0702|2032:0620|2033:0609|2034:0530|2035:0520|2036:0508|2037:0428|2038:0417|2039:0406|2040:0325|2041:0315|2042:0304|2043:0222|2044:0211|2045:0130|2046:0119|2047:0108,1229|2048:1218|2049:1207|2050:1126|2051:1116|2052:1104|2053:1024|2054:1013|2055:1003|2056:0922|2057:0911|2058:0831|2059:0820|2060:0808"},{"id":"eid_al_adha","name":"عيد الأضحى","en":"Eid al-Adha","category":"public","dates":"y|2010:1115-1118|2011:1105-1108|2012:1025-1028|2013:1014-1017|2014:1003-1006|2015:0922-0925|2016:0910-0913|2017:0831-0903|2018:0820-0823|2019:0810-0813|2020:0730-0802|2021:0719-0722|2022:0708-0711|2023:0627-0630|2024:0616-0619|2025:0605-0608|2026:0526-0529|2027:0515-0518|2028:0504-0507|2029:0423-0426|2030:0412-0415|2031:0401-0404|2032:0321-0324|2033:0310-0313|2034:0228-0303|2035:0217-0220|2036:0206-0209|2037:0125-0128|2038:0115-0118|2039:0104-0107,1225-1228|2040:1213-1216|2041:1203-1206|2042:1122-1125|2043:1111-1114|2044:1030-1102|2045:1020-1023|2046:1009-1012|2047:0929-1002|2048:0918-0921|2049:0907-0910|2050:0827-0830|2051:0816-0819|2052:0804-0807|2053:0725-0728|2054:0714-0717|2055:0704-0707|2056:0622-0625|2057:0611-0614|2058:0531-0603|2059:0521-0524|2060:0509-0512","estimated":"y|2010:1115-1118|2011:1105-1108|2012:1025-1028|2013:1014-1017|2014:1003-1006|2015:0922-0925|2016:0910-0913|2017:0831-0903|2018:0820-0823|2025:0605-0608|2026:0526-0529|2027:0515-0518|2028:0504-0507|2029:0423-0426|2030:0412-0415|2031:0401-0404|2032:0321-0324|2033:0310-0313|2034:0228-0303|2035:0217-0220|2036:0206-0209|2037:0125-0128|2038:0115-0118|2039:0104-0107,1225-1228|2040:1213-1216|2041:1203-1206|2042:1122-1125|2043:1111-1114|2044:1030-1102|2045:1020-1023|2046:1009-1012|2047:0929-1002|2048:0918-0921|2049:0907-0910|2050:0827-0830|2051:0816-0819|2052:0804-0807|2053:0725-0728|2054:0714-0717|2055:0704-0707|2056:0622-0625|2057:0611-0614|2058:0531-0603|2059:0521-0524|2060:0509-0512"},{"id":"islamic_new_year","name":"رأس السنة الهجرية","en":"Islamic New Year","category":"public","dates":"y|2010:1207|2011:1126|2012:1115|2013:1104|2014:1025|2015:1014|2016:1002|2017:0921|2018:0911|2019:0901|2020:0821|2021:0810|2022:0730|2023:0720|2024:0707|2025:0626|2026:0616|2027:0606|2028:0525|2029:0514|2030:0503|2031:0423|2032:0411|2033:0401|2034:0321|2035:0311|2036:0228|2037:0216|2038:0205|2039:0126|2040:0115|2041:0104,1224|2042:1214|2043:1203|2044:1121|2045:1110|2046:1031|2047:1020|2048:1009|2049:0928|2050:0917|2051:0906|2052:0826|2053:0815|2054:0805|2055:0725|2056:0714|2057:0703|2058:0622|2059:0611|2060:0531","estimated":"y|2010:1207|2011:1126|2012:1115|2013:1104|2014:1025|2015:1014|2016:1002|2017:0921|2025:0626|2026:0616|2027:0606|2028:0525|2029:0514|2030:0503|2031:0423|2032:0411|2033:0401|2034:0321|2035:0311|2036:0228|2037:0216|2038:0205|2039:0126|2040:0115|2041:0104,1224|2042:1214|2043:1203|2044:1121|2045:1110|2046:1031|2047:1020|2048:1009|2049:0928|2050:0917|2051:0906|2052:0826|2053:0815|2054:0805|2055:0725|2056:0714|2057:0703|2058:0622|2059:0611|2060:0531"},{"id":"sultan_s_accession_day","name":"اليوم الوطني لتولي السلطان","en":"Sultan's Accession Day","category":"public","dates":"d|0111:2020-2060"},{"id":"national_day","name":"يوم وطني","en":"National Day","category":"public","dates":"d|1118:2020-2024|1119:2020-2024|1120:2025-2060|1121:2025-2060"}]} diff --git a/app/Support/Holidays/data/PA.json b/app/Support/Holidays/data/PA.json new file mode 100644 index 000000000..54164b108 --- /dev/null +++ b/app/Support/Holidays/data/PA.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"PA","language":"es","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Año Nuevo","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051"},{"id":"martyrs_day","name":"Día de los Mártires","en":"Martyrs' Day","category":"public","dates":"d|0109:2010-2060|0110:2011,2022,2028,2033,2039,2050,2056"},{"id":"carnival_tuesday","name":"Martes de Carnaval","en":"Carnival Tuesday","category":"public","dates":"d|0206:2035,2046|0209:2016,2027|0210:2032,2043,2054|0211:2059|0212:2013|0213:2018,2024,2029|0214:2040,2051|0215:2056|0216:2010,2021|0217:2015,2026,2037|0218:2042,2048,2053|0221:2012,2023,2034,2045|0222:2039,2050|0225:2020,2031|0226:2036,2047,2058|0228:2017|0229:2028|0301:2022,2033,2044|0302:2049,2055,2060|0304:2014,2025|0305:2019,2030,2041,2052|0306:2057|0308:2011|0309:2038"},{"id":"good_friday","name":"Viernes Santo","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"labor_day","name":"Día del Trabajo","en":"Labor Day","category":"public","dates":"d|0501:2010-2060|0502:2011,2016,2022,2033,2039,2044,2050"},{"id":"separation_day","name":"Separación de Panamá de Colombia","en":"Separation Day","category":"public","dates":"d|1103:2010-2060|1104:2013,2019,2024,2030,2041,2047,2052,2058"},{"id":"colon_day","name":"Día de Colón","en":"Colon Day","category":"public","dates":"d|1105:2010-2060|1106:2017,2023,2028,2034,2045,2051,2056"},{"id":"los_santos_uprising_day","name":"Primer Grito de Independencia","en":"Los Santos Uprising Day","category":"public","dates":"d|1110:2010-2060|1111:2013,2019,2024,2030,2041,2047,2052,2058"},{"id":"independence_day","name":"Independencia de Panamá de España","en":"Independence Day","category":"public","dates":"d|1128:2010-2060|1129:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"mother_s_day","name":"Día de la Madre","en":"Mother's Day","category":"public","dates":"d|1208:2010-2060|1209:2013,2019,2024,2030,2041,2047,2052,2058"},{"id":"national_mourning_day","name":"Día de Duelo Nacional","en":"National Mourning Day","category":"public","dates":"d|1220:2022-2060|1221:2026,2037,2043,2048,2054"},{"id":"christmas_day","name":"Navidad","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060|1226:2011,2016,2022,2033,2039,2044,2050"},{"id":"carnival_monday_bank","name":"Lunes de Carnaval","en":"Carnival Monday","category":"bank","dates":"d|0205:2035,2046|0208:2016,2027|0209:2032,2043,2054|0210:2059|0211:2013|0212:2018,2024,2029|0213:2040,2051|0214:2056|0215:2010,2021|0216:2015,2026,2037|0217:2042,2048,2053|0220:2012,2023,2034,2045|0221:2039,2050|0224:2020,2031|0225:2036,2047,2058|0227:2017|0228:2022,2028,2033|0229:2044|0301:2049,2055,2060|0303:2014,2025|0304:2019,2030,2041,2052|0305:2057|0307:2011|0308:2038"},{"id":"national_symbols_day_bank","name":"Día de los Símbolos Patrios","en":"National Symbols Day","category":"bank","dates":"d|1104:2010-2060"}]} diff --git a/app/Support/Holidays/data/PE.json b/app/Support/Holidays/data/PE.json new file mode 100644 index 000000000..02b3105e5 --- /dev/null +++ b/app/Support/Holidays/data/PE.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"PE","language":"es","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Año Nuevo","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"maundy_thursday","name":"Jueves Santo","en":"Maundy Thursday","category":"public","dates":"d|0322:2035,2046|0324:2016|0325:2027,2032|0326:2043,2054|0327:2059|0328:2013,2024|0329:2018,2029,2040|0330:2051,2056|0401:2010,2021|0402:2015,2026,2037,2048|0403:2042,2053|0405:2012|0406:2023,2034,2045|0407:2039,2050|0409:2020|0410:2031,2036|0411:2047,2058|0413:2017,2028|0414:2022,2033,2044|0415:2049,2055,2060|0417:2014,2025|0418:2019,2030,2041,2052|0419:2057|0421:2011|0422:2038"},{"id":"good_friday","name":"Viernes Santo","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_sunday","name":"Domingo de Resurrección","en":"Easter Sunday","category":"public","dates":"d|0325:2035,2046|0327:2016|0328:2027,2032|0329:2043,2054|0330:2059|0331:2013,2024|0401:2018,2029,2040|0402:2051,2056|0404:2010,2021|0405:2015,2026,2037,2048|0406:2042,2053|0408:2012|0409:2023,2034,2045|0410:2039,2050|0412:2020|0413:2031,2036|0414:2047,2058|0416:2017,2028|0417:2022,2033,2044|0418:2049,2055,2060|0420:2014,2025|0421:2019,2030,2041,2052|0422:2057|0424:2011|0425:2038"},{"id":"labor_day","name":"Día del Trabajo","en":"Labor Day","category":"public","dates":"d|0501:2010-2060"},{"id":"battle_of_arica_and_flag_day","name":"Batalla de Arica y Día de la Bandera","en":"Battle of Arica and Flag Day","category":"public","dates":"d|0607:2024-2060"},{"id":"saint_peter_and_saint_paul_s_day","name":"San Pedro y San Pablo","en":"Saint Peter and Saint Paul's Day","category":"public","dates":"d|0629:2010-2060"},{"id":"peruvian_air_force_day","name":"Día de la Fuerza Aérea del Perú","en":"Peruvian Air Force Day","category":"public","dates":"d|0723:2023-2060"},{"id":"independence_day","name":"Día de la Independencia","en":"Independence Day","category":"public","dates":"d|0728:2010-2060"},{"id":"great_military_parade_day","name":"Día de la Gran Parada Militar","en":"Great Military Parade Day","category":"public","dates":"d|0729:2010-2060"},{"id":"battle_of_junin_day","name":"Batalla de Junín","en":"Battle of Junín Day","category":"public","dates":"d|0806:2022-2060"},{"id":"rose_of_lima_day","name":"Santa Rosa de Lima","en":"Rose of Lima Day","category":"public","dates":"d|0830:2010-2060"},{"id":"battle_of_angamos_day","name":"Combate de Angamos","en":"Battle of Angamos Day","category":"public","dates":"d|1008:2010-2060"},{"id":"all_saints_day","name":"Todos Los Santos","en":"All Saints' Day","category":"public","dates":"d|1101:2010-2060"},{"id":"immaculate_conception","name":"Inmaculada Concepción","en":"Immaculate Conception","category":"public","dates":"d|1208:2010-2060"},{"id":"battle_of_ayacucho_day","name":"Batalla de Ayacucho","en":"Battle of Ayacucho Day","category":"public","dates":"d|1209:2022-2060"},{"id":"christmas_day","name":"Navidad del Señor","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"}]} diff --git a/app/Support/Holidays/data/PF.json b/app/Support/Holidays/data/PF.json new file mode 100644 index 000000000..a539e8aa3 --- /dev/null +++ b/app/Support/Holidays/data/PF.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"PF","language":"fr","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Jour de l'an","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"missionary_day","name":"Arrivée de l'Évangile","en":"Missionary Day","category":"public","dates":"d|0305:2010-2060"},{"id":"good_friday","name":"Vendredi saint","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_monday","name":"Lundi de Pâques","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"labor_day","name":"Fête du Travail","en":"Labor Day","category":"public","dates":"d|0501:2010-2060"},{"id":"ascension_day","name":"Ascension","en":"Ascension Day","category":"public","dates":"d|0503:2035,2046|0505:2016|0506:2027,2032|0507:2043,2054|0508:2059|0509:2013,2024|0510:2018,2029,2040|0511:2051,2056|0513:2010,2021|0514:2015,2026,2037,2048|0515:2042,2053|0517:2012|0518:2023,2034,2045|0519:2039,2050|0521:2020|0522:2031,2036|0523:2047,2058|0525:2017,2028|0526:2022,2033,2044|0527:2049,2055,2060|0529:2014,2025|0530:2019,2030,2041,2052|0531:2057|0602:2011|0603:2038"},{"id":"victory_day","name":"Fête de la Victoire","en":"Victory Day","category":"public","dates":"d|0508:2010-2060"},{"id":"pentecost_monday","name":"Lundi de Pentecôte","en":"Pentecost Monday","category":"public","dates":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038"},{"id":"national_day","name":"Fête nationale","en":"National Day","category":"public","dates":"d|0714:2010-2060"},{"id":"assumption_day","name":"Assomption","en":"Assumption Day","category":"public","dates":"d|0815:2010-2060"},{"id":"all_saints_day","name":"Toussaint","en":"All Saints' Day","category":"public","dates":"d|1101:2010-2060"},{"id":"armistice_day","name":"Armistice","en":"Armistice Day","category":"public","dates":"d|1111:2010-2060"},{"id":"matari_i","name":"Matāri'i","en":"Matāri'i","category":"public","dates":"d|1120:2025-2060"},{"id":"christmas_day","name":"Noël","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"}]} diff --git a/app/Support/Holidays/data/PG.json b/app/Support/Holidays/data/PG.json new file mode 100644 index 000000000..e4987f3de --- /dev/null +++ b/app/Support/Holidays/data/PG.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"PG","language":null,"years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051"},{"id":"grand_chief_sir_michael_somare_remembrance_day","name":"Grand Chief Sir Michael Somare Remembrance Day","en":"Grand Chief Sir Michael Somare Remembrance Day","category":"public","dates":"d|0224:2023|0226:2022-2060|0228:2022"},{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_sunday","name":"Easter Sunday","en":"Easter Sunday","category":"public","dates":"d|0325:2035,2046|0327:2016|0328:2027,2032|0329:2043,2054|0330:2059|0331:2013,2024|0401:2018,2029,2040|0402:2051,2056|0404:2010,2021|0405:2015,2026,2037,2048|0406:2042,2053|0408:2012|0409:2023,2034,2045|0410:2039,2050|0412:2020|0413:2031,2036|0414:2047,2058|0416:2017,2028|0417:2022,2033,2044|0418:2049,2055,2060|0420:2014,2025|0421:2019,2030,2041,2052|0422:2057|0424:2011|0425:2038"},{"id":"easter_monday","name":"Easter Monday","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"king_s_birthday","name":"King's Birthday","en":"King's Birthday","category":"public","dates":"d|0616:2023|0617:2023-2060"},{"id":"papua_new_guinea_remembrance_day","name":"Papua New Guinea Remembrance Day","en":"Papua New Guinea Remembrance Day","category":"public","dates":"d|0723:2010-2060|0724:2017,2023,2028,2034,2045,2051,2056"},{"id":"national_repentance_day","name":"National Repentance Day","en":"National Repentance Day","category":"public","dates":"d|0826:2011-2060|0827:2012,2018,2029,2035,2040,2046,2057"},{"id":"independence_day","name":"Independence Day","en":"Independence Day","category":"public","dates":"d|0915:2023|0916:2010-2060|0917:2012,2018,2029,2035,2040,2046,2057"},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060|1227:2011,2016,2022,2033,2039,2044,2050"},{"id":"boxing_day","name":"Boxing Day","en":"Boxing Day","category":"public","dates":"d|1226:2010-2060|1227:2010,2021,2027,2032,2038,2049,2055,2060"}]} diff --git a/app/Support/Holidays/data/PH.json b/app/Support/Holidays/data/PH.json new file mode 100644 index 000000000..36754e58d --- /dev/null +++ b/app/Support/Holidays/data/PH.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"PH","language":"en_PH","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"eid_al_fitr","name":"Eid'l Fitr","en":"Eid al-Fitr","category":"public","dates":"y|2010:0910|2011:0830|2012:0820|2013:0809|2014:0729|2015:0717|2016:0707|2017:0626|2018:0615|2019:0605|2020:0525|2021:0513|2022:0503|2023:0421|2024:0410|2025:0401|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304","estimated":"y|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304"},{"id":"eid_al_adha","name":"Eid'l Adha","en":"Eid al-Adha","category":"public","dates":"y|2010:1117|2011:1107|2012:1026|2013:1015|2014:1006|2015:0925|2016:0910|2017:0902|2018:0821|2019:0812|2020:0731|2021:0720|2022:0709|2023:0628|2024:0617|2025:0606|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510","estimated":"y|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510"},{"id":"chinese_new_year","name":"Chinese New Year","en":"Chinese New Year","category":"public","dates":"d|0122:2042|0123:2012,2031,2050|0124:2039,2058|0125:2020|0126:2028,2047|0128:2017,2036,2055|0129:2025|0130:2044|0131:2014,2033|0201:2022,2041,2052|0202:2049,2060|0203:2030|0204:2038,2057|0205:2019|0206:2027,2046|0208:2016,2035,2054|0210:2013,2024,2043|0211:2032,2051|0212:2021,2040,2059|0213:2029|0214:2048|0215:2037,2056|0216:2018|0217:2026,2045|0219:2015,2034,2053"},{"id":"maundy_thursday","name":"Maundy Thursday","en":"Maundy Thursday","category":"public","dates":"d|0322:2035,2046|0324:2016|0325:2027,2032|0326:2043,2054|0327:2059|0328:2013,2024|0329:2018,2029,2040|0330:2051,2056|0401:2010,2021|0402:2015,2026,2037,2048|0403:2042,2053|0405:2012|0406:2023,2034,2045|0407:2039,2050|0409:2020|0410:2031,2036|0411:2047,2058|0413:2017,2028|0414:2022,2033,2044|0415:2049,2055,2060|0417:2014,2025|0418:2019,2030,2041,2052|0419:2057|0421:2011|0422:2038"},{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"black_saturday","name":"Black Saturday","en":"Black Saturday","category":"public","dates":"d|0324:2035,2046|0326:2016|0327:2027,2032|0328:2043,2054|0329:2059|0330:2013,2024|0331:2018,2029,2040|0401:2051,2056|0403:2021|0404:2015,2026,2037,2048|0405:2042,2053|0408:2023,2034,2045|0409:2039,2050|0411:2020|0412:2031,2036|0413:2047,2058|0415:2017,2028|0416:2022,2033,2044|0417:2049,2055,2060|0419:2014,2025|0420:2019,2030,2041,2052|0421:2057|0424:2038"},{"id":"day_of_valor","name":"Araw ng Kagitingan","en":"Day of Valor","category":"public","dates":"d|0409:2010-2022,2024-2060|0410:2023"},{"id":"labor_day","name":"Labor Day","en":"Labor Day","category":"public","dates":"d|0501:2010-2060"},{"id":"independence_day","name":"Independence Day","en":"Independence Day","category":"public","dates":"d|0612:2011-2060|0614:2010"},{"id":"ninoy_aquino_day","name":"Ninoy Aquino Day","en":"Ninoy Aquino Day","category":"public","dates":"d|0821:2011-2023,2025-2060|0823:2010,2024"},{"id":"national_heroes_day","name":"National Heroes Day","en":"National Heroes Day","category":"public","dates":"d|0825:2014,2025,2031,2036,2042,2053,2059|0826:2013,2019,2024,2030,2041,2047,2052,2058|0827:2012,2018,2029,2035,2040,2046,2057|0828:2017,2023,2028,2034,2045,2051,2056|0829:2011,2016,2022,2033,2039,2044,2050|0830:2010,2021,2027,2032,2038,2049,2055,2060|0831:2015,2020,2026,2037,2043,2048,2054"},{"id":"all_saints_day","name":"All Saints' Day","en":"All Saints' Day","category":"public","dates":"d|1101:2010-2060"},{"id":"all_souls_day","name":"All Souls' Day","en":"All Souls' Day","category":"public","dates":"d|1102:2026-2027"},{"id":"bonifacio_day","name":"Bonifacio Day","en":"Bonifacio Day","category":"public","dates":"d|1127:2023|1129:2010|1130:2011-2022,2024-2060"},{"id":"immaculate_conception","name":"Feast of the Immaculate Conception of Mary","en":"Immaculate Conception","category":"public","dates":"d|1208:2019-2060"},{"id":"christmas_eve","name":"Christmas Eve","en":"Christmas Eve","category":"public","dates":"d|1224:2025-2027"},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"},{"id":"rizal_day","name":"Rizal Day","en":"Rizal Day","category":"public","dates":"d|1227:2010|1230:2011-2060"},{"id":"new_year_s_eve","name":"Last Day of the Year","en":"New Year's Eve","category":"public","dates":"d|1231:2010-2020,2023-2060"}]} diff --git a/app/Support/Holidays/data/PK.json b/app/Support/Holidays/data/PK.json new file mode 100644 index 000000000..72fade71e --- /dev/null +++ b/app/Support/Holidays/data/PK.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"PK","language":"en_PK","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"ashura","name":"Ashura","en":"Ashura","category":"public","dates":"y|2010:1216-1217|2011:1205-1206|2012:1124-1125|2013:1114-1115|2014:1103-1104|2015:1023-1024|2016:1011-1012|2017:0930-1001|2018:0920-0921|2019:0909-0910|2020:0829-0830|2021:0818-0819|2022:0808-0809|2023:0728-0729|2024:0716-0717|2025:0705-0706|2026:0625-0626|2027:0615-0616|2028:0603-0604|2029:0523-0524|2030:0512-0513|2031:0502-0503|2032:0420-0421|2033:0410-0411|2034:0330-0331|2035:0320-0321|2036:0308-0309|2037:0225-0226|2038:0214-0215|2039:0204-0205|2040:0124-0125|2041:0113-0114|2042:0102-0103,1223-1224|2043:1212-1213|2044:1130-1201|2045:1119-1120|2046:1109-1110|2047:1029-1030|2048:1018-1019|2049:1007-1008|2050:0926-0927|2051:0915-0916|2052:0904-0905|2053:0824-0825|2054:0814-0815|2055:0803-0804|2056:0723-0724|2057:0712-0713|2058:0701-0702|2059:0620-0621|2060:0609-0610","estimated":"y|2027:0615-0616|2028:0603-0604|2029:0523-0524|2030:0512-0513|2031:0502-0503|2032:0420-0421|2033:0410-0411|2034:0330-0331|2035:0320-0321|2036:0308-0309|2037:0225-0226|2038:0214-0215|2039:0204-0205|2040:0124-0125|2041:0113-0114|2042:0102-0103,1223-1224|2043:1212-1213|2044:1130-1201|2045:1119-1120|2046:1109-1110|2047:1029-1030|2048:1018-1019|2049:1007-1008|2050:0926-0927|2051:0915-0916|2052:0904-0905|2053:0824-0825|2054:0814-0815|2055:0803-0804|2056:0723-0724|2057:0712-0713|2058:0701-0702|2059:0620-0621|2060:0609-0610"},{"id":"eid_al_fitr","name":"Eid-ul-Fitr","en":"Eid al-Fitr","category":"public","dates":"y|2010:0910-0913|2011:0831-0903|2012:0820-0822|2013:0808-0810|2014:0729-0801|2015:0717-0721|2016:0705-0708|2017:0626-0628|2018:0615-0618|2019:0604-0607|2020:0522-0527|2021:0510-0515|2022:0502-0505|2023:0421-0425|2024:0410-0412|2025:0331-0402|2026:0320-0321|2027:0310-0312|2028:0227-0229|2029:0215-0217|2030:0205-0207|2031:0125-0127|2032:0115-0117|2033:0103-0105,1224-1226|2034:1213-1215|2035:1202-1204|2036:1120-1122|2037:1109-1111|2038:1030-1101|2039:1020-1022|2040:1008-1010|2041:0927-0929|2042:0916-0918|2043:0905-0907|2044:0825-0827|2045:0815-0817|2046:0804-0806|2047:0725-0727|2048:0713-0715|2049:0702-0704|2050:0621-0623|2051:0611-0613|2052:0530-0601|2053:0520-0522|2054:0510-0512|2055:0429-0501|2056:0417-0419|2057:0406-0408|2058:0326-0328|2059:0316-0318|2060:0305-0307","estimated":"y|2027:0310-0312|2028:0227-0229|2029:0215-0217|2030:0205-0207|2031:0125-0127|2032:0115-0117|2033:0103-0105,1224-1226|2034:1213-1215|2035:1202-1204|2036:1120-1122|2037:1109-1111|2038:1030-1101|2039:1020-1022|2040:1008-1010|2041:0927-0929|2042:0916-0918|2043:0905-0907|2044:0825-0827|2045:0815-0817|2046:0804-0806|2047:0725-0727|2048:0713-0715|2049:0702-0704|2050:0621-0623|2051:0611-0613|2052:0530-0601|2053:0520-0522|2054:0510-0512|2055:0429-0501|2056:0417-0419|2057:0406-0408|2058:0326-0328|2059:0316-0318|2060:0305-0307"},{"id":"prophet_s_birthday","name":"Eid Milad-un-Nabi","en":"Prophet's Birthday","category":"public","dates":"y|2010:0227|2011:0216|2012:0205|2013:0125|2014:0114|2015:0104,1224|2016:1212|2017:1201|2018:1121|2019:1110|2020:1030|2021:1019|2022:1009|2023:0929|2024:0917|2025:0906|2026:0826|2027:0815|2028:0804|2029:0725|2030:0714|2031:0703|2032:0621|2033:0610|2034:0531|2035:0521|2036:0509|2037:0429|2038:0418|2039:0407|2040:0326|2041:0316|2042:0305|2043:0223|2044:0212|2045:0131|2046:0120|2047:0109,1230|2048:1219|2049:1208|2050:1127|2051:1117|2052:1105|2053:1025|2054:1014|2055:1004|2056:0923|2057:0912|2058:0901|2059:0821|2060:0809","estimated":"y|2027:0815|2028:0804|2029:0725|2030:0714|2031:0703|2032:0621|2033:0610|2034:0531|2035:0521|2036:0509|2037:0429|2038:0418|2039:0407|2040:0326|2041:0316|2042:0305|2043:0223|2044:0212|2045:0131|2046:0120|2047:0109,1230|2048:1219|2049:1208|2050:1127|2051:1117|2052:1105|2053:1025|2054:1014|2055:1004|2056:0923|2057:0912|2058:0901|2059:0821|2060:0809"},{"id":"eid_al_adha","name":"Eid-ul-Adha","en":"Eid al-Adha","category":"public","dates":"y|2010:1117-1119|2011:1107-1108|2012:1026-1029|2013:1015-1018|2014:1006-1008|2015:0924-0926|2016:0912-0914|2017:0901-0904|2018:0821-0823|2019:0812-0815|2020:0731-0802|2021:0720-0722|2022:0708-0712|2023:0628-0701|2024:0617-0619|2025:0606-0609|2026:0526-0528|2027:0517-0519|2028:0506-0508|2029:0425-0427|2030:0414-0416|2031:0403-0405|2032:0323-0325|2033:0312-0314|2034:0302-0304|2035:0219-0221|2036:0208-0210|2037:0127-0129|2038:0117-0119|2039:0106-0108,1227-1229|2040:1215-1217|2041:1205-1207|2042:1124-1126|2043:1113-1115|2044:1101-1103|2045:1022-1024|2046:1011-1013|2047:1001-1003|2048:0920-0922|2049:0909-0911|2050:0829-0831|2051:0818-0820|2052:0806-0808|2053:0727-0729|2054:0716-0718|2055:0706-0708|2056:0624-0626|2057:0613-0615|2058:0602-0604|2059:0523-0525|2060:0511-0513","estimated":"y|2027:0517-0519|2028:0506-0508|2029:0425-0427|2030:0414-0416|2031:0403-0405|2032:0323-0325|2033:0312-0314|2034:0302-0304|2035:0219-0221|2036:0208-0210|2037:0127-0129|2038:0117-0119|2039:0106-0108,1227-1229|2040:1215-1217|2041:1205-1207|2042:1124-1126|2043:1113-1115|2044:1101-1103|2045:1022-1024|2046:1011-1013|2047:1001-1003|2048:0920-0922|2049:0909-0911|2050:0829-0831|2051:0818-0820|2052:0806-0808|2053:0727-0729|2054:0716-0718|2055:0706-0708|2056:0624-0626|2057:0613-0615|2058:0602-0604|2059:0523-0525|2060:0511-0513"},{"id":"kashmir_solidarity_day","name":"Kashmir Solidarity Day","en":"Kashmir Solidarity Day","category":"public","dates":"d|0205:2010-2060"},{"id":"pakistan_day","name":"Pakistan Day","en":"Pakistan Day","category":"public","dates":"d|0323:2010-2060"},{"id":"labor_day","name":"Labour Day","en":"Labor Day","category":"public","dates":"d|0501:2010-2060"},{"id":"youm_e_takbeer","name":"Youm-e-Takbeer","en":"Youm-e-Takbeer","category":"public","dates":"d|0528:2024-2060"},{"id":"independence_day","name":"Independence Day","en":"Independence Day","category":"public","dates":"d|0814:2010-2060"},{"id":"iqbal_day","name":"Iqbal Day","en":"Iqbal Day","category":"public","dates":"d|1109:2010-2014,2022-2060"},{"id":"quaid_e_azam_day","name":"Quaid-e-Azam Day","en":"Quaid-e-Azam Day","category":"public","dates":"d|1225:2010-2060"}]} diff --git a/app/Support/Holidays/data/PL.json b/app/Support/Holidays/data/PL.json new file mode 100644 index 000000000..522f1335d --- /dev/null +++ b/app/Support/Holidays/data/PL.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"PL","language":"pl","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Nowy Rok","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"epiphany","name":"Święto Trzech Króli","en":"Epiphany","category":"public","dates":"d|0106:2011-2060"},{"id":"easter_sunday","name":"Niedziela Wielkanocna","en":"Easter Sunday","category":"public","dates":"d|0325:2035,2046|0327:2016|0328:2027,2032|0329:2043,2054|0330:2059|0331:2013,2024|0401:2018,2029,2040|0402:2051,2056|0404:2010,2021|0405:2015,2026,2037,2048|0406:2042,2053|0408:2012|0409:2023,2034,2045|0410:2039,2050|0412:2020|0413:2031,2036|0414:2047,2058|0416:2017,2028|0417:2022,2033,2044|0418:2049,2055,2060|0420:2014,2025|0421:2019,2030,2041,2052|0422:2057|0424:2011|0425:2038"},{"id":"easter_monday","name":"Poniedziałek Wielkanocny","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"national_day","name":"Święto Państwowe","en":"National Day","category":"public","dates":"d|0501:2010-2060"},{"id":"national_day_of_the_third_of_may","name":"Święto Narodowe Trzeciego Maja","en":"National Day of the Third of May","category":"public","dates":"d|0503:2010-2060"},{"id":"pentecost","name":"Zielone Świątki","en":"Pentecost","category":"public","dates":"d|0513:2035,2046|0515:2016|0516:2027,2032|0517:2043,2054|0518:2059|0519:2013,2024|0520:2018,2029,2040|0521:2051,2056|0523:2010,2021|0524:2015,2026,2037,2048|0525:2042,2053|0527:2012|0528:2023,2034,2045|0529:2039,2050|0531:2020|0601:2031,2036|0602:2047,2058|0604:2017,2028|0605:2022,2033,2044|0606:2049,2055,2060|0608:2014,2025|0609:2019,2030,2041,2052|0610:2057|0612:2011|0613:2038"},{"id":"corpus_christi","name":"Dzień Bożego Ciała","en":"Corpus Christi","category":"public","dates":"d|0524:2035,2046|0526:2016|0527:2027,2032|0528:2043,2054|0529:2059|0530:2013,2024|0531:2018,2029,2040|0601:2051,2056|0603:2010,2021|0604:2015,2026,2037,2048|0605:2042,2053|0607:2012|0608:2023,2034,2045|0609:2039,2050|0611:2020|0612:2031,2036|0613:2047,2058|0615:2017,2028|0616:2022,2033,2044|0617:2049,2055,2060|0619:2014,2025|0620:2019,2030,2041,2052|0621:2057|0623:2011|0624:2038"},{"id":"assumption_day","name":"Wniebowzięcie Najświętszej Marii Panny","en":"Assumption Day","category":"public","dates":"d|0815:2010-2060"},{"id":"all_saints_day","name":"Uroczystość Wszystkich Świętych","en":"All Saints' Day","category":"public","dates":"d|1101:2010-2060"},{"id":"national_independence_day","name":"Narodowe Święto Niepodległości","en":"National Independence Day","category":"public","dates":"d|1111:2010-2060"},{"id":"christmas_eve","name":"Wigilia Bożego Narodzenia","en":"Christmas Eve","category":"public","dates":"d|1224:2025-2060"},{"id":"christmas_day","name":"Boże Narodzenie (pierwszy dzień)","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"},{"id":"second_day_of_christmas","name":"Boże Narodzenie (drugi dzień)","en":"Second Day of Christmas","category":"public","dates":"d|1226:2010-2060"}]} diff --git a/app/Support/Holidays/data/PM.json b/app/Support/Holidays/data/PM.json new file mode 100644 index 000000000..4769d0eec --- /dev/null +++ b/app/Support/Holidays/data/PM.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"PM","language":"fr","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Jour de l'an","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"easter_monday","name":"Lundi de Pâques","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"labor_day","name":"Fête du Travail","en":"Labor Day","category":"public","dates":"d|0501:2010-2060"},{"id":"ascension_day","name":"Ascension","en":"Ascension Day","category":"public","dates":"d|0503:2035,2046|0505:2016|0506:2027,2032|0507:2043,2054|0508:2059|0509:2013,2024|0510:2018,2029,2040|0511:2051,2056|0513:2010,2021|0514:2015,2026,2037,2048|0515:2042,2053|0517:2012|0518:2023,2034,2045|0519:2039,2050|0521:2020|0522:2031,2036|0523:2047,2058|0525:2017,2028|0526:2022,2033,2044|0527:2049,2055,2060|0529:2014,2025|0530:2019,2030,2041,2052|0531:2057|0602:2011|0603:2038"},{"id":"victory_day","name":"Fête de la Victoire","en":"Victory Day","category":"public","dates":"d|0508:2010-2060"},{"id":"pentecost_monday","name":"Lundi de Pentecôte","en":"Pentecost Monday","category":"public","dates":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038"},{"id":"national_day","name":"Fête nationale","en":"National Day","category":"public","dates":"d|0714:2010-2060"},{"id":"assumption_day","name":"Assomption","en":"Assumption Day","category":"public","dates":"d|0815:2010-2060"},{"id":"all_saints_day","name":"Toussaint","en":"All Saints' Day","category":"public","dates":"d|1101:2010-2060"},{"id":"armistice_day","name":"Armistice","en":"Armistice Day","category":"public","dates":"d|1111:2010-2060"},{"id":"christmas_day","name":"Noël","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"}]} diff --git a/app/Support/Holidays/data/PN.json b/app/Support/Holidays/data/PN.json new file mode 100644 index 000000000..97982ef4c --- /dev/null +++ b/app/Support/Holidays/data/PN.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"PN","language":null,"years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2016-2060"},{"id":"bounty_day","name":"Bounty Day","en":"Bounty Day","category":"public","dates":"d|0123:2016-2060"},{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2024|0330:2018,2029,2040|0331:2051,2056|0402:2021|0403:2026,2037,2048|0404:2042,2053|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2025|0419:2019,2030,2041,2052|0420:2057|0423:2038"},{"id":"easter_monday","name":"Easter Monday","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2024|0402:2018,2029,2040|0403:2051,2056|0405:2021|0406:2026,2037,2048|0407:2042,2053|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2025|0422:2019,2030,2041,2052|0423:2057|0426:2038"},{"id":"king_s_birthday","name":"King's Birthday","en":"King's Birthday","category":"public","dates":"d|0601:2026,2037,2043,2048,2054|0602:2025,2031,2036,2042,2053,2059|0603:2024,2030,2041,2047,2052,2058|0604:2029,2035,2040,2046,2057|0605:2023,2028,2034,2045,2051,2056|0606:2033,2039,2044,2050|0607:2027,2032,2038,2049,2055,2060"},{"id":"pitcairn_day","name":"Pitcairn Day","en":"Pitcairn Day","category":"public","dates":"d|0702:2016-2060"},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1225:2016-2060"},{"id":"boxing_day","name":"Boxing Day","en":"Boxing Day","category":"public","dates":"d|1226:2016-2060"},{"id":"new_year_s_day_government","name":"New Year's Day","en":"New Year's Day","category":"government","dates":"d|0102:2016-2060"}]} diff --git a/app/Support/Holidays/data/PR.json b/app/Support/Holidays/data/PR.json new file mode 100644 index 000000000..bf7d4094e --- /dev/null +++ b/app/Support/Holidays/data/PR.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"PR","language":"en_US","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051|1231:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"epiphany","name":"Epiphany","en":"Epiphany","category":"public","dates":"d|0106:2010-2060"},{"id":"martin_luther_king_jr_day","name":"Martin Luther King Jr. Day","en":"Martin Luther King Jr. Day","category":"public","dates":"d|0115:2018,2024,2029,2035,2046,2052,2057|0116:2012,2017,2023,2034,2040,2045,2051|0117:2011,2022,2028,2033,2039,2050,2056|0118:2010,2016,2021,2027,2038,2044,2049,2055|0119:2015,2026,2032,2037,2043,2054,2060|0120:2014,2020,2025,2031,2042,2048,2053,2059|0121:2013,2019,2030,2036,2041,2047,2058"},{"id":"george_washington_day_presidents_day_and_the_day","name":"George Washington Day, Presidents' Day, and the Day of the Hero and the Illustrious Woman of Puerto Rico","en":"George Washington Day, Presidents' Day, and the Day of the Hero and the Illustrious Woman of Puerto Rico","category":"public","dates":"d|0215:2016,2021,2027,2038,2044,2049,2055|0216:2026,2032,2037,2043,2054,2060|0217:2020,2025,2031,2042,2048,2053,2059|0218:2019,2030,2036,2041,2047,2058|0219:2018,2024,2029,2035,2046,2052,2057|0220:2017,2023,2034,2040,2045,2051|0221:2022,2028,2033,2039,2050,2056"},{"id":"emancipation_day","name":"Emancipation Day","en":"Emancipation Day","category":"public","dates":"d|0322:2010-2060|0323:2015,2020,2026,2037,2043,2048,2054"},{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"memorial_day","name":"Memorial Day","en":"Memorial Day","category":"public","dates":"d|0525:2015,2020,2026,2037,2043,2048,2054|0526:2014,2025,2031,2036,2042,2053,2059|0527:2013,2019,2024,2030,2041,2047,2052,2058|0528:2012,2018,2029,2035,2040,2046,2057|0529:2017,2023,2028,2034,2045,2051,2056|0530:2011,2016,2022,2033,2039,2044,2050|0531:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"juneteenth_national_independence_day","name":"Juneteenth National Independence Day","en":"Juneteenth National Independence Day","category":"public","dates":"d|0618:2021,2027,2032,2038,2049,2055,2060|0619:2021-2060|0620:2022,2033,2039,2044,2050"},{"id":"independence_day","name":"Independence Day","en":"Independence Day","category":"public","dates":"d|0703:2015,2020,2026,2037,2043,2048,2054|0704:2010-2060|0705:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"puerto_rico_constitution_day","name":"Puerto Rico Constitution Day","en":"Puerto Rico Constitution Day","category":"public","dates":"d|0725:2010-2060|0726:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"labor_day","name":"Labor Day","en":"Labor Day","category":"public","dates":"d|0901:2014,2025,2031,2036,2042,2053,2059|0902:2013,2019,2024,2030,2041,2047,2052,2058|0903:2012,2018,2029,2035,2040,2046,2057|0904:2017,2023,2028,2034,2045,2051,2056|0905:2011,2016,2022,2033,2039,2044,2050|0906:2010,2021,2027,2032,2038,2049,2055,2060|0907:2015,2020,2026,2037,2043,2048,2054"},{"id":"columbus_day","name":"Columbus Day","en":"Columbus Day","category":"public","dates":"d|1008:2018,2029,2035,2040,2046,2057|1009:2017,2023,2028,2034,2045,2051,2056|1010:2016,2022,2033,2039,2044,2050|1011:2021,2027,2032,2038,2049,2055,2060|1012:2010-2013,2015,2020,2026,2037,2043,2048,2054|1013:2014,2025,2031,2036,2042,2053,2059|1014:2019,2024,2030,2041,2047,2052,2058"},{"id":"veterans_day","name":"Veterans Day","en":"Veterans Day","category":"public","dates":"d|1111:2010-2060|1112:2012,2018,2029,2035,2040,2046,2057"},{"id":"puerto_rican_identity_day","name":"Puerto Rican Identity Day","en":"Puerto Rican Identity Day","category":"public","dates":"d|1119:2023-2060|1120:2023,2028,2034,2045,2051,2056"},{"id":"thanksgiving_day","name":"Thanksgiving Day","en":"Thanksgiving Day","category":"public","dates":"d|1122:2012,2018,2029,2035,2040,2046,2057|1123:2017,2023,2028,2034,2045,2051,2056|1124:2011,2016,2022,2033,2039,2044,2050|1125:2010,2021,2027,2032,2038,2049,2055,2060|1126:2015,2020,2026,2037,2043,2048,2054|1127:2014,2025,2031,2036,2042,2053,2059|1128:2013,2019,2024,2030,2041,2047,2052,2058"},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1224:2010,2021,2027,2032,2038,2049,2055,2060|1225:2010-2060|1226:2011,2016,2022,2033,2039,2044,2050"},{"id":"valentine_s_day_unofficial","name":"Valentine's Day","en":"Valentine's Day","category":"unofficial","dates":"d|0214:2010-2060"},{"id":"american_citizenship_day_government","name":"American Citizenship Day","en":"American Citizenship Day","category":"government","dates":"d|0302:2010-2060"},{"id":"saint_patrick_s_day_unofficial","name":"Saint Patrick's Day","en":"Saint Patrick's Day","category":"unofficial","dates":"d|0317:2010-2060"},{"id":"good_friday_unofficial","name":"Good Friday","en":"Good Friday","category":"unofficial","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_sunday_unofficial","name":"Easter Sunday","en":"Easter Sunday","category":"unofficial","dates":"d|0325:2035,2046|0327:2016|0328:2027,2032|0329:2043,2054|0330:2059|0331:2013,2024|0401:2018,2029,2040|0402:2051,2056|0404:2010,2021|0405:2015,2026,2037,2048|0406:2042,2053|0408:2012|0409:2023,2034,2045|0410:2039,2050|0412:2020|0413:2031,2036|0414:2047,2058|0416:2017,2028|0417:2022,2033,2044|0418:2049,2055,2060|0420:2014,2025|0421:2019,2030,2041,2052|0422:2057|0424:2011|0425:2038"},{"id":"mother_s_day_unofficial","name":"Mother's Day","en":"Mother's Day","category":"unofficial","dates":"d|0508:2011,2016,2022,2033,2039,2044,2050|0509:2010,2021,2027,2032,2038,2049,2055,2060|0510:2015,2020,2026,2037,2043,2048,2054|0511:2014,2025,2031,2036,2042,2053,2059|0512:2013,2019,2024,2030,2041,2047,2052,2058|0513:2012,2018,2029,2035,2040,2046,2057|0514:2017,2023,2028,2034,2045,2051,2056"},{"id":"father_s_day_unofficial","name":"Father's Day","en":"Father's Day","category":"unofficial","dates":"d|0615:2014,2025,2031,2036,2042,2053,2059|0616:2013,2019,2024,2030,2041,2047,2052,2058|0617:2012,2018,2029,2035,2040,2046,2057|0618:2017,2023,2028,2034,2045,2051,2056|0619:2011,2016,2022,2033,2039,2044,2050|0620:2010,2021,2027,2032,2038,2049,2055,2060|0621:2015,2020,2026,2037,2043,2048,2054"},{"id":"halloween_unofficial","name":"Halloween","en":"Halloween","category":"unofficial","dates":"d|1031:2010-2060"},{"id":"christmas_eve_half_day","name":"Christmas Eve","en":"Christmas Eve","category":"half_day","dates":"d|1224:2010-2060"},{"id":"christmas_eve_unofficial","name":"Christmas Eve","en":"Christmas Eve","category":"unofficial","dates":"d|1224:2010-2060"},{"id":"new_year_s_eve_unofficial","name":"New Year's Eve","en":"New Year's Eve","category":"unofficial","dates":"d|1231:2010-2060"}]} diff --git a/app/Support/Holidays/data/PS.json b/app/Support/Holidays/data/PS.json new file mode 100644 index 000000000..ec9659648 --- /dev/null +++ b/app/Support/Holidays/data/PS.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"PS","language":"ar","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"isra_and_mi_raj","name":"ذكرى الإسراء والمعراج","en":"Isra' and Mi'raj","category":"public","dates":"y|2010:0709|2011:0629|2012:0617|2013:0606|2014:0526|2015:0516|2016:0504|2017:0424|2018:0413|2019:0403|2020:0322|2021:0311|2022:0228|2023:0218|2024:0208|2025:0127|2026:0116|2027:0105,1225|2028:1214|2029:1203|2030:1123|2031:1112|2032:1101|2033:1021|2034:1010|2035:0929|2036:0918|2037:0907|2038:0828|2039:0817|2040:0805|2041:0725|2042:0715|2043:0704|2044:0623|2045:0613|2046:0602|2047:0522|2048:0510|2049:0429|2050:0419|2051:0409|2052:0328|2053:0318|2054:0307|2055:0224|2056:0213|2057:0201|2058:0122|2059:0112|2060:0101,1220"},{"id":"new_year_s_day","name":"رأس السنة الميلادي","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"eid_al_fitr","name":"عيد الفطر السعيد","en":"Eid al-Fitr","category":"public","dates":"y|2010:0910-0912|2011:0830-0901|2012:0819-0821|2013:0808-0810|2014:0728-0730|2015:0717-0719|2016:0706-0708|2017:0625-0627|2018:0615-0617|2019:0604-0606|2020:0524-0526|2021:0513-0515|2022:0502-0504|2023:0421-0423|2024:0410-0412|2025:0330-0401|2026:0320-0322|2027:0309-0311|2028:0226-0228|2029:0214-0216|2030:0204-0206|2031:0124-0126|2032:0114-0116|2033:0102-0104,1223-1225|2034:1212-1214|2035:1201-1203|2036:1119-1121|2037:1108-1110|2038:1029-1031|2039:1019-1021|2040:1007-1009|2041:0926-0928|2042:0915-0917|2043:0904-0906|2044:0824-0826|2045:0814-0816|2046:0803-0805|2047:0724-0726|2048:0712-0714|2049:0701-0703|2050:0620-0622|2051:0610-0612|2052:0529-0531|2053:0519-0521|2054:0509-0511|2055:0428-0430|2056:0416-0418|2057:0405-0407|2058:0325-0327|2059:0315-0317|2060:0304-0306"},{"id":"prophet_s_birthday","name":"ذكرى المولد النبوي الشريف","en":"Prophet's Birthday","category":"public","dates":"y|2010:0226|2011:0215|2012:0204|2013:0124|2014:0113|2015:0103,1223|2016:1211|2017:1130|2018:1120|2019:1109|2020:1029|2021:1018|2022:1008|2023:0927|2024:0915|2025:0904|2026:0825|2027:0814|2028:0803|2029:0724|2030:0713|2031:0702|2032:0620|2033:0609|2034:0530|2035:0520|2036:0508|2037:0428|2038:0417|2039:0406|2040:0325|2041:0315|2042:0304|2043:0222|2044:0211|2045:0130|2046:0119|2047:0108,1229|2048:1218|2049:1207|2050:1126|2051:1116|2052:1104|2053:1024|2054:1013|2055:1003|2056:0922|2057:0911|2058:0831|2059:0820|2060:0808"},{"id":"islamic_new_year","name":"رأس السنة الهجرية","en":"Islamic New Year","category":"public","dates":"y|2010:1207|2011:1126|2012:1115|2013:1104|2014:1025|2015:1014|2016:1002|2017:0921|2018:0911|2019:0831|2020:0820|2021:0809|2022:0730|2023:0719|2024:0707|2025:0626|2026:0616|2027:0606|2028:0525|2029:0514|2030:0503|2031:0423|2032:0411|2033:0401|2034:0321|2035:0311|2036:0228|2037:0216|2038:0205|2039:0126|2040:0115|2041:0104,1224|2042:1214|2043:1203|2044:1121|2045:1110|2046:1031|2047:1020|2048:1009|2049:0928|2050:0917|2051:0906|2052:0826|2053:0815|2054:0805|2055:0725|2056:0714|2057:0703|2058:0622|2059:0611|2060:0531"},{"id":"eid_al_adha","name":"عيد الأضحى المبارك","en":"Eid al-Adha","category":"public","dates":"y|2010:1116-1119|2011:1106-1109|2012:1026-1029|2013:1015-1018|2014:1004-1007|2015:0923-0926|2016:0911-0914|2017:0901-0904|2018:0821-0824|2019:0811-0814|2020:0731-0803|2021:0720-0723|2022:0709-0712|2023:0628-0701|2024:0616-0619|2025:0606-0609|2026:0527-0530|2027:0516-0519|2028:0505-0508|2029:0424-0427|2030:0413-0416|2031:0402-0405|2032:0322-0325|2033:0311-0314|2034:0301-0304|2035:0218-0221|2036:0207-0210|2037:0126-0129|2038:0116-0119|2039:0105-0108,1226-1229|2040:1214-1217|2041:1204-1207|2042:1123-1126|2043:1112-1115|2044:1031-1103|2045:1021-1024|2046:1010-1013|2047:0930-1003|2048:0919-0922|2049:0908-0911|2050:0828-0831|2051:0817-0820|2052:0805-0808|2053:0726-0729|2054:0715-0718|2055:0705-0708|2056:0623-0626|2057:0612-0615|2058:0601-0604|2059:0522-0525|2060:0510-0513"},{"id":"orthodox_christmas_day","name":"عيد الميلاد المجيد الشرقي","en":"Orthodox Christmas Day","category":"public","dates":"d|0107:2010-2060"},{"id":"international_women_s_day","name":"يوم المراة العالمي","en":"International Women's Day","category":"public","dates":"d|0308:2010-2060"},{"id":"easter","name":"عيد الفصح المجيد","en":"Easter","category":"public","dates":"d|0325:2035,2046|0327:2016|0328:2027,2032|0329:2043,2054|0330:2059|0331:2013,2024|0401:2018,2029,2040|0402:2051,2056|0404:2010,2021|0405:2015,2026,2037,2048|0406:2042,2053|0408:2012,2018,2029|0409:2023,2034,2045,2056|0410:2039,2050|0412:2015,2020,2026|0413:2031,2036,2042,2053|0414:2047,2058|0415:2012|0416:2017,2023,2028|0417:2022,2033,2039,2044,2050|0418:2049,2055,2060|0419:2020|0420:2014,2025,2036|0421:2019,2030,2041,2047,2052|0422:2057|0424:2011,2022,2033,2044|0425:2038,2049,2060|0428:2019,2030|0429:2035,2046,2057|0501:2016|0502:2021,2027,2032|0503:2043,2054|0504:2059|0505:2013,2024|0506:2040|0507:2051"},{"id":"labor_day","name":"عيد العمال","en":"Labor Day","category":"public","dates":"d|0501:2010-2060"},{"id":"independence_day","name":"عيد الاستقلال","en":"Independence Day","category":"public","dates":"d|1115:2010-2060"},{"id":"catholic_christmas_day","name":"عيد الميلاد المجيد الغربي","en":"Catholic Christmas Day","category":"public","dates":"d|1225:2010-2060"},{"id":"new_year_s_day_catholic","name":"رأس السنة الميلادي","en":"New Year's Day","category":"catholic","dates":"d|0101:2010-2060"},{"id":"epiphany_catholic","name":"عيد الغطاس","en":"Epiphany","category":"catholic","dates":"d|0106:2010-2060"},{"id":"orthodox_christmas_day_orthodox","name":"عيد الميلاد المجيد الشرقي","en":"Orthodox Christmas Day","category":"orthodox","dates":"d|0108:2010-2060"},{"id":"orthodox_new_year_s_day_orthodox","name":"عيد رأس السنة الشرقي","en":"Orthodox New Year's Day","category":"orthodox","dates":"d|0114:2010-2060"},{"id":"epiphany_orthodox","name":"عيد الغطاس","en":"Epiphany","category":"orthodox","dates":"d|0119:2010-2060"},{"id":"palm_sunday_catholic","name":"أحد الشعانين","en":"Palm Sunday","category":"catholic","dates":"d|0318:2035,2046|0320:2016|0321:2027,2032|0322:2043,2054|0323:2059|0324:2013,2024|0325:2018,2029,2040|0326:2051,2056|0328:2010,2021|0329:2015,2026,2037,2048|0330:2042,2053|0401:2012|0402:2023,2034,2045|0403:2039,2050|0405:2020|0406:2031,2036|0407:2047,2058|0409:2017,2028|0410:2022,2033,2044|0411:2049,2055,2060|0413:2014,2025|0414:2019,2030,2041,2052|0415:2057|0417:2011|0418:2038"},{"id":"holy_thursday_catholic","name":"خميس الغسل","en":"Holy Thursday","category":"catholic","dates":"d|0322:2035,2046|0324:2016|0325:2027,2032|0326:2043,2054|0327:2059|0328:2013,2024|0329:2018,2029,2040|0330:2051,2056|0401:2010,2021|0402:2015,2026,2037,2048|0403:2042,2053|0405:2012|0406:2023,2034,2045|0407:2039,2050|0409:2020|0410:2031,2036|0411:2047,2058|0413:2017,2028|0414:2022,2033,2044|0415:2049,2055,2060|0417:2014,2025|0418:2019,2030,2041,2052|0419:2057|0421:2011|0422:2038"},{"id":"good_friday_catholic","name":"الجمعة العظيمة","en":"Good Friday","category":"catholic","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"holy_saturday_catholic","name":"سبت النور","en":"Holy Saturday","category":"catholic","dates":"d|0324:2035,2046|0326:2016|0327:2027,2032|0328:2043,2054|0329:2059|0330:2013,2024|0331:2018,2029,2040|0401:2051,2056|0403:2010,2021|0404:2015,2026,2037,2048|0405:2042,2053|0407:2012|0408:2023,2034,2045|0409:2039,2050|0411:2020|0412:2031,2036|0413:2047,2058|0415:2017,2028|0416:2022,2033,2044|0417:2049,2055,2060|0419:2014,2025|0420:2019,2030,2041,2052|0421:2057|0423:2011|0424:2038"},{"id":"easter_catholic","name":"عيد الفصح المجيد","en":"Easter","category":"catholic","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"palm_sunday_orthodox","name":"أحد الشعانين","en":"Palm Sunday","category":"orthodox","dates":"d|0328:2010|0329:2037,2048|0401:2018,2029|0402:2034,2045,2056|0405:2015,2026|0406:2031,2042,2053|0407:2058|0408:2012|0409:2017,2023,2028|0410:2039,2050|0411:2055|0412:2020|0413:2014,2025,2036|0414:2041,2047,2052|0417:2011,2022,2033,2044|0418:2038,2049,2060|0421:2019,2030|0422:2035,2046,2057|0424:2016|0425:2021,2027,2032|0426:2043,2054|0427:2059|0428:2013,2024|0429:2040|0430:2051"},{"id":"holy_thursday_orthodox","name":"خميس الغسل","en":"Holy Thursday","category":"orthodox","dates":"d|0401:2010|0402:2037,2048|0405:2018,2029|0406:2034,2045,2056|0409:2015,2026|0410:2031,2042,2053|0411:2058|0412:2012|0413:2017,2023,2028|0414:2039,2050|0415:2055|0416:2020|0417:2014,2025,2036|0418:2041,2047,2052|0421:2011,2022,2033,2044|0422:2038,2049,2060|0425:2019,2030|0426:2035,2046,2057|0428:2016|0429:2021,2027,2032|0430:2043,2054|0501:2059|0502:2013,2024|0503:2040|0504:2051"},{"id":"good_friday_orthodox","name":"الجمعة العظيمة","en":"Good Friday","category":"orthodox","dates":"d|0402:2010|0403:2037,2048|0406:2018,2029|0407:2034,2045,2056|0410:2015,2026|0411:2031,2042,2053|0412:2058|0413:2012|0414:2017,2023,2028|0415:2039,2050|0416:2055|0417:2020|0418:2014,2025,2036|0419:2041,2047,2052|0422:2011,2022,2033,2044|0423:2038,2049,2060|0426:2019,2030|0427:2035,2046,2057|0429:2016|0430:2021,2027,2032|0501:2043,2054|0502:2059|0503:2013,2024|0504:2040|0505:2051"},{"id":"holy_saturday_orthodox","name":"سبت النور","en":"Holy Saturday","category":"orthodox","dates":"d|0403:2010|0404:2037,2048|0407:2018,2029|0408:2034,2045,2056|0411:2015,2026|0412:2031,2042,2053|0413:2058|0414:2012|0415:2017,2023,2028|0416:2039,2050|0417:2055|0418:2020|0419:2014,2025,2036|0420:2041,2047,2052|0423:2011,2022,2033,2044|0424:2038,2049,2060|0427:2019,2030|0428:2035,2046,2057|0430:2016|0501:2021,2027,2032|0502:2043,2054|0503:2059|0504:2013,2024|0505:2040|0506:2051"},{"id":"easter_orthodox","name":"عيد الفصح المجيد","en":"Easter","category":"orthodox","dates":"d|0405:2010|0406:2037,2048|0409:2018,2029|0410:2034,2045,2056|0413:2015,2026|0414:2031,2042,2053|0415:2058|0416:2012|0417:2017,2023,2028|0418:2039,2050|0419:2055|0420:2020|0421:2014,2025,2036|0422:2041,2047,2052|0425:2011,2022,2033,2044|0426:2038,2049,2060|0429:2019,2030|0430:2035,2046,2057|0502:2016|0503:2021,2027,2032|0504:2043,2054|0505:2059|0506:2013,2024|0507:2040|0508:2051"},{"id":"ascension_day_catholic","name":"خميس الصعود","en":"Ascension Day","category":"catholic","dates":"d|0503:2035,2046|0505:2016|0506:2027,2032|0507:2043,2054|0508:2059|0509:2013,2024|0510:2018,2029,2040|0511:2051,2056|0513:2010,2021|0514:2015,2026,2037,2048|0515:2042,2053|0517:2012|0518:2023,2034,2045|0519:2039,2050|0521:2020|0522:2031,2036|0523:2047,2058|0525:2017,2028|0526:2022,2033,2044|0527:2049,2055,2060|0529:2014,2025|0530:2019,2030,2041,2052|0531:2057|0602:2011|0603:2038"},{"id":"ascension_day_orthodox","name":"خميس الصعود","en":"Ascension Day","category":"orthodox","dates":"d|0513:2010|0514:2037,2048|0517:2018,2029|0518:2034,2045,2056|0521:2015,2026|0522:2031,2042,2053|0523:2058|0524:2012|0525:2017,2023,2028|0526:2039,2050|0527:2055|0528:2020|0529:2014,2025,2036|0530:2041,2047,2052|0602:2011,2022,2033,2044|0603:2038,2049,2060|0606:2019,2030|0607:2035,2046,2057|0609:2016|0610:2021,2027,2032|0611:2043,2054|0612:2059|0613:2013,2024|0614:2040|0615:2051"},{"id":"pentecost_catholic","name":"أحد العنصرة","en":"Pentecost","category":"catholic","dates":"d|0513:2035,2046|0515:2016|0516:2027,2032|0517:2043,2054|0518:2059|0519:2013,2024|0520:2018,2029,2040|0521:2051,2056|0523:2010,2021|0524:2015,2026,2037,2048|0525:2042,2053|0527:2012|0528:2023,2034,2045|0529:2039,2050|0531:2020|0601:2031,2036|0602:2047,2058|0604:2017,2028|0605:2022,2033,2044|0606:2049,2055,2060|0608:2014,2025|0609:2019,2030,2041,2052|0610:2057|0612:2011|0613:2038"},{"id":"pentecost_orthodox","name":"أحد العنصرة","en":"Pentecost","category":"orthodox","dates":"d|0523:2010|0524:2037,2048|0527:2018,2029|0528:2034,2045,2056|0531:2015,2026|0601:2031,2042,2053|0602:2058|0603:2012|0604:2017,2023,2028|0605:2039,2050|0606:2055|0607:2020|0608:2014,2025,2036|0609:2041,2047,2052|0612:2011,2022,2033,2044|0613:2038,2049,2060|0616:2019,2030|0617:2035,2046,2057|0619:2016|0620:2021,2027,2032|0621:2043,2054|0622:2059|0623:2013,2024|0624:2040|0625:2051"},{"id":"catholic_christmas_day_catholic","name":"عيد الميلاد المجيد الغربي","en":"Catholic Christmas Day","category":"catholic","dates":"d|1226:2010-2060"}]} diff --git a/app/Support/Holidays/data/PT.json b/app/Support/Holidays/data/PT.json new file mode 100644 index 000000000..699bc4bb4 --- /dev/null +++ b/app/Support/Holidays/data/PT.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"PT","language":"pt_PT","years":[2010,2060],"subdivisions":{"01":"Aveiro","02":"Beja","03":"Braga","04":"Bragança","05":"Castelo Branco","06":"Coimbra","07":"Évora","08":"Faro","09":"Guarda","10":"Leiria","11":"Lisboa","12":"Portalegre","13":"Porto","14":"Santarém","15":"Setúbal","16":"Viana do Castelo","17":"Vila Real","18":"Viseu","20":"Região Autónoma dos Açores","30":"Região Autónoma da Madeira"},"holidays":[{"id":"new_year_s_day","name":"Ano Novo","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"saint_joseph_s_day","name":"Dia de São José","en":"Saint Joseph's Day","category":"public","regional":{"14":"d|0319:2010-2060"}},{"id":"good_friday","name":"Sexta-feira Santa","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_sunday","name":"Páscoa","en":"Easter Sunday","category":"public","dates":"d|0325:2035,2046|0327:2016|0328:2027,2032|0329:2043,2054|0330:2059|0331:2013,2024|0401:2018,2029,2040|0402:2051,2056|0404:2010,2021|0405:2015,2026,2037,2048|0406:2042,2053|0408:2012|0409:2023,2034,2045|0410:2039,2050|0412:2020|0413:2031,2036|0414:2047,2058|0416:2017,2028|0417:2022,2033,2044|0418:2049,2055,2060|0420:2014,2025|0421:2019,2030,2041,2052|0422:2057|0424:2011|0425:2038"},{"id":"feast_of_our_lady_of_mercoles","name":"Dia de Nossa Senhora de Mércoles","en":"Feast of Our Lady of Mércoles","category":"public","regional":{"05":"d|0410:2035,2046|0412:2016|0413:2027,2032|0414:2043,2054|0415:2059|0416:2013,2024|0417:2018,2029,2040|0418:2051,2056|0420:2010,2021|0421:2015,2026,2037,2048|0422:2042,2053|0424:2012|0425:2023,2034,2045|0426:2039,2050|0428:2020|0429:2031,2036|0430:2047,2058|0502:2017,2028|0503:2022,2033,2044|0504:2049,2055,2060|0506:2014,2025|0507:2019,2030,2041,2052|0508:2057|0510:2011|0511:2038"}},{"id":"freedom_day","name":"Dia da Liberdade","en":"Freedom Day","category":"public","dates":"d|0425:2010-2060"},{"id":"labor_day","name":"Dia do Trabalhador","en":"Labor Day","category":"public","dates":"d|0501:2010-2060"},{"id":"ascension_day","name":"Quinta-feira da Ascensão","en":"Ascension Day","category":"public","regional":{"02":"d|0503:2035,2046|0505:2016|0506:2027,2032|0507:2043,2054|0508:2059|0509:2013,2024|0510:2018,2029,2040|0511:2051,2056|0513:2010,2021|0514:2015,2026,2037,2048|0515:2042,2053|0517:2012|0518:2023,2034,2045|0519:2039,2050|0521:2020|0522:2031,2036|0523:2047,2058|0525:2017,2028|0526:2022,2033,2044|0527:2049,2055,2060|0529:2014,2025|0530:2019,2030,2041,2052|0531:2057|0602:2011|0603:2038"}},{"id":"saint_joanna_s_day","name":"Dia de Santa Joana","en":"Saint Joanna's Day","category":"public","regional":{"01":"d|0512:2010-2060"}},{"id":"day_of_the_autonomous_region_of_the_azores","name":"Dia da Região Autónoma dos Açores","en":"Day of the Autonomous Region of the Azores","category":"public","regional":{"20":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038"}},{"id":"municipal_holiday_of_leiria","name":"Dia do Município de Leiria","en":"Municipal Holiday of Leiria","category":"public","regional":{"10":"d|0522:2010-2060"}},{"id":"municipal_holiday_of_portalegre","name":"Dia do Município de Portalegre","en":"Municipal Holiday of Portalegre","category":"public","regional":{"12":"d|0523:2010-2060"}},{"id":"corpus_christi","name":"Corpo de Deus","en":"Corpus Christi","category":"public","dates":"d|0524:2035,2046|0526:2016|0527:2027,2032|0528:2043,2054|0529:2059|0530:2024|0531:2018,2029,2040|0601:2051,2056|0603:2010,2021|0604:2026,2037,2048|0605:2042,2053|0607:2012|0608:2023,2034,2045|0609:2039,2050|0611:2020|0612:2031,2036|0613:2047,2058|0615:2017,2028|0616:2022,2033,2044|0617:2049,2055,2060|0619:2025|0620:2019,2030,2041,2052|0621:2057|0623:2011|0624:2038"},{"id":"day_of_portugal_camoes_and_the_portuguese_commun","name":"Dia de Portugal, de Camões e das Comunidades Portuguesas","en":"Day of Portugal, Camões, and the Portuguese Communities","category":"public","dates":"d|0610:2010-2060"},{"id":"saint_anthony_s_day","name":"Dia de Santo António","en":"Saint Anthony's Day","category":"public","regional":{"11":"d|0613:2010-2060","17":"d|0613:2010-2060"}},{"id":"saint_john_s_day","name":"Dia de São João","en":"Saint John's Day","category":"public","regional":{"03":"d|0624:2010-2060","13":"d|0624:2010-2060"}},{"id":"saint_peter_s_day","name":"Dia de São Pedro","en":"Saint Peter's Day","category":"public","regional":{"07":"d|0629:2010-2060"}},{"id":"day_of_the_autonomous_region_of_madeira_and_the_","name":"Dia da Região Autónoma da Madeira e das Comunidades Madeirenses","en":"Day of the Autonomous Region of Madeira and the Madeiran Communities","category":"public","regional":{"30":"d|0701:2010-2060"}},{"id":"saint_elizabeth_s_day","name":"Dia de Santa Isabel","en":"Saint Elizabeth's Day","category":"public","regional":{"06":"d|0704:2010-2060"}},{"id":"assumption_day","name":"Assunção de Nossa Senhora","en":"Assumption Day","category":"public","dates":"d|0815:2010-2060"},{"id":"feast_of_our_lady_of_sorrows","name":"Dia de Nossa Senhora da Agonia","en":"Feast of Our Lady of Sorrows","category":"public","regional":{"16":"d|0820:2010-2060"}},{"id":"feast_of_our_lady_of_graces","name":"Dia de Nossa Senhora das Graças","en":"Feast of Our Lady of Graces","category":"public","regional":{"04":"d|0822:2010-2060"}},{"id":"municipal_holiday_of_faro","name":"Dia do Município de Faro","en":"Municipal Holiday of Faro","category":"public","regional":{"08":"d|0907:2010-2060"}},{"id":"bocage_day","name":"Dia de Bocage","en":"Bocage Day","category":"public","regional":{"15":"d|0915:2010-2060"}},{"id":"saint_matthew_s_day","name":"Dia de São Mateus","en":"Saint Matthew's Day","category":"public","regional":{"18":"d|0921:2010-2060"}},{"id":"republic_day","name":"Implantação da República","en":"Republic Day","category":"public","dates":"d|1005:2010-2012,2016-2060"},{"id":"all_saints_day","name":"Dia de Todos os Santos","en":"All Saints' Day","category":"public","dates":"d|1101:2010-2012,2016-2060"},{"id":"municipal_holiday_of_guarda","name":"Dia do Município da Guarda","en":"Municipal Holiday of Guarda","category":"public","regional":{"09":"d|1127:2010-2060"}},{"id":"restoration_of_independence_day","name":"Restauração da Independência","en":"Restoration of Independence Day","category":"public","dates":"d|1201:2010-2012,2016-2060"},{"id":"immaculate_conception","name":"Imaculada Conceição","en":"Immaculate Conception","category":"public","dates":"d|1208:2010-2060"},{"id":"christmas_day","name":"Dia de Natal","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"},{"id":"1st_octave","name":"Primeira Oitava","en":"1st Octave","category":"public","regional":{"30":"d|1226:2010-2060"}},{"id":"carnival_optional","name":"Carnaval","en":"Carnival","category":"optional","dates":"d|0206:2035,2046|0209:2016,2027|0210:2032,2043,2054|0211:2059|0212:2013|0213:2018,2024,2029|0214:2040,2051|0215:2056|0216:2010,2021|0217:2015,2026,2037|0218:2042,2048,2053|0221:2012,2023,2034,2045|0222:2039,2050|0225:2020,2031|0226:2036,2047,2058|0228:2017|0229:2028|0301:2022,2033,2044|0302:2049,2055,2060|0304:2014,2025|0305:2019,2030,2041,2052|0306:2057|0308:2011|0309:2038"},{"id":"saint_anthony_s_day_optional","name":"Dia de Santo António","en":"Saint Anthony's Day","category":"optional","dates":"d|0613:2010-2060"},{"id":"christmas_eve_optional","name":"Véspera de Natal","en":"Christmas Eve","category":"optional","dates":"d|1224:2010-2060"},{"id":"second_day_of_christmas_optional","name":"26 de Dezembro","en":"Second Day of Christmas","category":"optional","dates":"d|1226:2010-2060"},{"id":"new_year_s_eve_optional","name":"Véspera de Ano Novo","en":"New Year's Eve","category":"optional","dates":"d|1231:2010-2060"}]} diff --git a/app/Support/Holidays/data/PW.json b/app/Support/Holidays/data/PW.json new file mode 100644 index 000000000..197e3facf --- /dev/null +++ b/app/Support/Holidays/data/PW.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"PW","language":null,"years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051|1231:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"youth_day","name":"Youth Day","en":"Youth Day","category":"public","dates":"d|0314:2014,2025,2031,2036,2042,2053,2059|0315:2010-2060|0316:2015,2020,2026,2037,2043,2048,2054"},{"id":"senior_citizens_day","name":"Senior Citizens Day","en":"Senior Citizens Day","category":"public","dates":"d|0504:2012,2018,2029,2035,2040,2046,2057|0505:2010-2060|0506:2013,2019,2024,2030,2041,2047,2052,2058"},{"id":"president_s_day","name":"President's Day","en":"President's Day","category":"public","dates":"d|0531:2019,2024,2030,2041,2047,2052,2058|0601:2018-2060|0602:2025,2031,2036,2042,2053,2059"},{"id":"constitution_day","name":"Constitution Day","en":"Constitution Day","category":"public","dates":"d|0708:2011,2016,2022,2033,2039,2044,2050|0709:2010-2060|0710:2017,2023,2028,2034,2045,2051,2056"},{"id":"labor_day","name":"Labor Day","en":"Labor Day","category":"public","dates":"d|0901:2014,2025,2031,2036,2042,2053,2059|0902:2013,2019,2024,2030,2041,2047,2052,2058|0903:2012,2018,2029,2035,2040,2046,2057|0904:2017,2023,2028,2034,2045,2051,2056|0905:2011,2016,2022,2033,2039,2044,2050|0906:2010,2021,2027,2032,2038,2049,2055,2060|0907:2015,2020,2026,2037,2043,2048,2054"},{"id":"independence_day","name":"Independence Day","en":"Independence Day","category":"public","dates":"d|0930:2022,2033,2039,2044,2050|1001:2018-2060|1002:2023,2028,2034,2045,2051,2056"},{"id":"united_nations_day","name":"United Nations Day","en":"United Nations Day","category":"public","dates":"d|1023:2015,2020,2026,2037,2043,2048,2054|1024:2010-2060|1025:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"family_day","name":"Family Day","en":"Family Day","category":"public","dates":"d|1122:2019,2024,2030,2041,2047,2052,2058|1123:2018,2029,2035,2040,2046,2057|1124:2017,2023,2028,2034,2045,2051,2056|1125:2022,2033,2039,2044,2050|1126:2021,2027,2032,2038,2049,2055,2060|1127:2020,2026,2037,2043,2048,2054|1128:2025,2031,2036,2042,2053,2059"},{"id":"thanksgiving_day","name":"Thanksgiving Day","en":"Thanksgiving Day","category":"public","dates":"d|1122:2012,2018,2029,2035,2040,2046,2057|1123:2017,2023,2028,2034,2045,2051,2056|1124:2011,2016,2022,2033,2039,2044,2050|1125:2010,2021,2027,2032,2038,2049,2055,2060|1126:2015,2020,2026,2037,2043,2048,2054|1127:2014,2025,2031,2036,2042,2053,2059|1128:2013,2019,2024,2030,2041,2047,2052,2058"},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1224:2010,2021,2027,2032,2038,2049,2055,2060|1225:2010-2060|1226:2011,2016,2022,2033,2039,2044,2050"}]} diff --git a/app/Support/Holidays/data/PY.json b/app/Support/Holidays/data/PY.json new file mode 100644 index 000000000..c0e34b495 --- /dev/null +++ b/app/Support/Holidays/data/PY.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"PY","language":"es","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Año Nuevo","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"patriots_day","name":"Día de los Héroes de la Patria","en":"Patriots Day","category":"public","dates":"d|0226:2018|0227:2017,2023|0228:2022|0229:2016|0301:2010-2012,2014-2015,2020-2021,2024,2026-2060|0303:2025|0304:2013,2019"},{"id":"maundy_thursday","name":"Jueves Santo","en":"Maundy Thursday","category":"public","dates":"d|0322:2035,2046|0324:2016|0325:2027,2032|0326:2043,2054|0327:2059|0328:2013,2024|0329:2018,2029,2040|0330:2051,2056|0401:2010,2021|0402:2015,2026,2037,2048|0403:2042,2053|0405:2012|0406:2023,2034,2045|0407:2039,2050|0409:2020|0410:2031,2036|0411:2047,2058|0413:2017,2028|0414:2022,2033,2044|0415:2049,2055,2060|0417:2014,2025|0418:2019,2030,2041,2052|0419:2057|0421:2011|0422:2038"},{"id":"good_friday","name":"Viernes Santo","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_sunday","name":"Domingo de Resurrección","en":"Easter Sunday","category":"public","dates":"d|0325:2035,2046|0327:2016|0328:2027,2032|0329:2043,2054|0330:2059|0331:2013,2024|0401:2018,2029,2040|0402:2051,2056|0404:2010,2021|0405:2015,2026,2037,2048|0406:2042,2053|0408:2012|0409:2023,2034,2045|0410:2039,2050|0412:2020|0413:2031,2036|0414:2047,2058|0416:2017,2028|0417:2022,2033,2044|0418:2049,2055,2060|0420:2014,2025|0421:2019,2030,2041,2052|0422:2057|0424:2011|0425:2038"},{"id":"workers_day","name":"Día de los Trabajadores","en":"Workers' Day","category":"public","dates":"d|0501:2010-2060"},{"id":"national_holiday","name":"Feriado Nacional","en":"National Holiday","category":"public","dates":"d|0514:2012-2060"},{"id":"national_independence_day","name":"Día de la Independencia Nacional","en":"National Independence Day","category":"public","dates":"d|0515:2010-2060"},{"id":"chaco_armistice_day","name":"Día de la Paz del Chaco","en":"Chaco Armistice Day","category":"public","dates":"d|0610:2013,2024|0611:2018|0612:2010-2012,2015-2017,2020-2023,2026-2060|0616:2014,2025|0617:2019"},{"id":"constitution_oath_day","name":"Día de la Jura de la Constitución Nacional","en":"Constitution Oath Day","category":"public","dates":"d|0620:2027-2060|0622:2026"},{"id":"asuncion_foundation_s_day","name":"Día de la Fundación de Asunción","en":"Asuncion Foundation's Day","category":"public","dates":"d|0814:2017|0815:2010-2016,2018-2060"},{"id":"boqueron_battle_day","name":"Día de la Batalla de Boquerón","en":"Boqueron Battle Day","category":"public","dates":"d|0927:2021|0928:2015|0929:2010-2014,2018-2020,2023,2025-2060|0930:2024|1002:2017|1003:2016,2022"},{"id":"caacupe_virgin_day","name":"Día de la Virgen de Caacupé","en":"Caacupe Virgin Day","category":"public","dates":"d|1208:2010-2060"},{"id":"christmas_day","name":"Día de la Navidad","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"}]} diff --git a/app/Support/Holidays/data/QA.json b/app/Support/Holidays/data/QA.json new file mode 100644 index 000000000..a6666bc2a --- /dev/null +++ b/app/Support/Holidays/data/QA.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"QA","language":"ar_QA","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"eid_al_fitr","name":"عيد الفطر","en":"Eid al-Fitr","category":"public","dates":"y|2010:0910-0912|2011:0831-0902|2012:0819-0821|2013:0808-0810|2014:0728-0730|2015:0718-0720|2016:0706-0708|2017:0625-0627|2018:0615-0617|2019:0604-0606|2020:0524-0526|2021:0513-0515|2022:0502-0504|2023:0421-0423|2024:0410-0412|2025:0330-0401|2026:0320-0322|2027:0309-0311|2028:0226-0228|2029:0214-0216|2030:0204-0206|2031:0124-0126|2032:0114-0116|2033:0102-0104,1223-1225|2034:1212-1214|2035:1201-1203|2036:1119-1121|2037:1108-1110|2038:1029-1031|2039:1019-1021|2040:1007-1009|2041:0926-0928|2042:0915-0917|2043:0904-0906|2044:0824-0826|2045:0814-0816|2046:0803-0805|2047:0724-0726|2048:0712-0714|2049:0701-0703|2050:0620-0622|2051:0610-0612|2052:0529-0531|2053:0519-0521|2054:0509-0511|2055:0428-0430|2056:0416-0418|2057:0405-0407|2058:0325-0327|2059:0315-0317|2060:0304-0306","estimated":"y|2026:0320-0322|2027:0309-0311|2028:0226-0228|2029:0214-0216|2030:0204-0206|2031:0124-0126|2032:0114-0116|2033:0102-0104,1223-1225|2034:1212-1214|2035:1201-1203|2036:1119-1121|2037:1108-1110|2038:1029-1031|2039:1019-1021|2040:1007-1009|2041:0926-0928|2042:0915-0917|2043:0904-0906|2044:0824-0826|2045:0814-0816|2046:0803-0805|2047:0724-0726|2048:0712-0714|2049:0701-0703|2050:0620-0622|2051:0610-0612|2052:0529-0531|2053:0519-0521|2054:0509-0511|2055:0428-0430|2056:0416-0418|2057:0405-0407|2058:0325-0327|2059:0315-0317|2060:0304-0306"},{"id":"eid_al_adha","name":"عيد الأضحى","en":"Eid al-Adha","category":"public","dates":"y|2010:1115-1117|2011:1106-1108|2012:1026-1028|2013:1015-1017|2014:1004-1006|2015:0923-0925|2016:0910-0912|2017:0831-0902|2018:0822-0824|2019:0811-0813|2020:0731-0802|2021:0720-0722|2022:0709-0711|2023:0628-0630|2024:0616-0618|2025:0606-0608|2026:0527-0529|2027:0516-0518|2028:0505-0507|2029:0424-0426|2030:0413-0415|2031:0402-0404|2032:0322-0324|2033:0311-0313|2034:0301-0303|2035:0218-0220|2036:0207-0209|2037:0126-0128|2038:0116-0118|2039:0105-0107,1226-1228|2040:1214-1216|2041:1204-1206|2042:1123-1125|2043:1112-1114|2044:1031-1102|2045:1021-1023|2046:1010-1012|2047:0930-1002|2048:0919-0921|2049:0908-0910|2050:0828-0830|2051:0817-0819|2052:0805-0807|2053:0726-0728|2054:0715-0717|2055:0705-0707|2056:0623-0625|2057:0612-0614|2058:0601-0603|2059:0522-0524|2060:0510-0512","estimated":"y|2026:0527-0529|2027:0516-0518|2028:0505-0507|2029:0424-0426|2030:0413-0415|2031:0402-0404|2032:0322-0324|2033:0311-0313|2034:0301-0303|2035:0218-0220|2036:0207-0209|2037:0126-0128|2038:0116-0118|2039:0105-0107,1226-1228|2040:1214-1216|2041:1204-1206|2042:1123-1125|2043:1112-1114|2044:1031-1102|2045:1021-1023|2046:1010-1012|2047:0930-1002|2048:0919-0921|2049:0908-0910|2050:0828-0830|2051:0817-0819|2052:0805-0807|2053:0726-0728|2054:0715-0717|2055:0705-0707|2056:0623-0625|2057:0612-0614|2058:0601-0603|2059:0522-0524|2060:0510-0512"},{"id":"national_sports_day","name":"اليوم الوطني للرياضة","en":"National Sports Day","category":"public","dates":"d|0208:2022,2028,2033,2039,2050,2056|0209:2016,2021,2027,2038,2044,2049,2055|0210:2015,2026,2032,2037,2043,2054,2060|0211:2014,2020,2025,2031,2042,2048,2053,2059|0212:2013,2019,2030,2036,2041,2047,2058|0213:2018,2024,2029,2035,2046,2052,2057|0214:2012,2017,2023,2034,2040,2045,2051"},{"id":"qatar_national_day","name":"اليوم الوطني لقطر","en":"Qatar National Day","category":"public","dates":"d|1218:2010-2060"},{"id":"new_year_s_day_bank","name":"رأس السنة الميلادية","en":"New Year's Day","category":"bank","dates":"d|0101:2010-2060"},{"id":"march_bank_holiday_bank","name":"عطلة البنك","en":"March Bank Holiday","category":"bank","dates":"d|0301:2015,2020,2026,2037,2043,2048,2054|0302:2014,2025,2031,2036,2042,2053,2059|0303:2013,2019,2024,2030,2041,2047,2052,2058|0304:2012,2018,2029,2035,2040,2046,2057|0305:2017,2023,2028,2034,2045,2051,2056|0306:2011,2016,2022,2033,2039,2044,2050|0307:2010,2021,2027,2032,2038,2049,2055,2060"}]} diff --git a/app/Support/Holidays/data/RE.json b/app/Support/Holidays/data/RE.json new file mode 100644 index 000000000..b493098bd --- /dev/null +++ b/app/Support/Holidays/data/RE.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"RE","language":"fr","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Jour de l'an","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"easter_monday","name":"Lundi de Pâques","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"labor_day","name":"Fête du Travail","en":"Labor Day","category":"public","dates":"d|0501:2010-2060"},{"id":"ascension_day","name":"Ascension","en":"Ascension Day","category":"public","dates":"d|0503:2035,2046|0505:2016|0506:2027,2032|0507:2043,2054|0508:2059|0509:2013,2024|0510:2018,2029,2040|0511:2051,2056|0513:2010,2021|0514:2015,2026,2037,2048|0515:2042,2053|0517:2012|0518:2023,2034,2045|0519:2039,2050|0521:2020|0522:2031,2036|0523:2047,2058|0525:2017,2028|0526:2022,2033,2044|0527:2049,2055,2060|0529:2014,2025|0530:2019,2030,2041,2052|0531:2057|0602:2011|0603:2038"},{"id":"victory_day","name":"Fête de la Victoire","en":"Victory Day","category":"public","dates":"d|0508:2010-2060"},{"id":"pentecost_monday","name":"Lundi de Pentecôte","en":"Pentecost Monday","category":"public","dates":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038"},{"id":"national_day","name":"Fête nationale","en":"National Day","category":"public","dates":"d|0714:2010-2060"},{"id":"assumption_day","name":"Assomption","en":"Assumption Day","category":"public","dates":"d|0815:2010-2060"},{"id":"all_saints_day","name":"Toussaint","en":"All Saints' Day","category":"public","dates":"d|1101:2010-2060"},{"id":"armistice_day","name":"Armistice","en":"Armistice Day","category":"public","dates":"d|1111:2010-2060"},{"id":"abolition_of_slavery","name":"Abolition de l'esclavage","en":"Abolition of Slavery","category":"public","dates":"d|1220:2010-2060"},{"id":"christmas_day","name":"Noël","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"}]} diff --git a/app/Support/Holidays/data/RO.json b/app/Support/Holidays/data/RO.json new file mode 100644 index 000000000..f8ad961ab --- /dev/null +++ b/app/Support/Holidays/data/RO.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"RO","language":"ro","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Anul Nou","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2010-2060"},{"id":"epiphany","name":"Botezul Domnului - Boboteaza","en":"Epiphany","category":"public","dates":"d|0106:2024-2060"},{"id":"saint_john_the_baptist","name":"Soborul Sfântului Proroc Ioan Botezătorul","en":"Saint John the Baptist","category":"public","dates":"d|0107:2024-2060"},{"id":"unification_of_the_romanian_principalities_day","name":"Ziua Unirii Principatelor Române","en":"Unification of the Romanian Principalities Day","category":"public","dates":"d|0124:2017-2060"},{"id":"good_friday","name":"Vinerea Mare","en":"Good Friday","category":"public","dates":"d|0403:2037,2048|0406:2018,2029|0407:2034,2045,2056|0410:2026|0411:2031,2042,2053|0412:2058|0414:2023,2028|0415:2039,2050|0416:2055|0417:2020|0418:2025,2036|0419:2041,2047,2052|0422:2022,2033,2044|0423:2038,2049,2060|0426:2019,2030|0427:2035,2046,2057|0430:2021,2027,2032|0501:2043,2054|0502:2059|0503:2024|0504:2040|0505:2051"},{"id":"easter","name":"Paștele","en":"Easter","category":"public","dates":"d|0404:2010|0405:2010,2037,2048|0406:2037,2048|0408:2018,2029|0409:2018,2029,2034,2045,2056|0410:2034,2045,2056|0412:2015,2026|0413:2015,2026,2031,2042,2053|0414:2031,2042,2053,2058|0415:2012,2058|0416:2012,2017,2023,2028|0417:2017,2023,2028,2039,2050|0418:2039,2050,2055|0419:2020,2055|0420:2014,2020,2025,2036|0421:2014,2025,2036,2041,2047,2052|0422:2041,2047,2052|0424:2011,2022,2033,2044|0425:2011,2022,2033,2038,2044,2049,2060|0426:2038,2049,2060|0428:2019,2030|0429:2019,2030,2035,2046,2057|0430:2035,2046,2057|0501:2016|0502:2016,2021,2027,2032|0503:2021,2027,2032,2043,2054|0504:2043,2054,2059|0505:2013,2024,2059|0506:2013,2024,2040|0507:2040,2051|0508:2051"},{"id":"labor_day","name":"Ziua Muncii","en":"Labor Day","category":"public","dates":"d|0501:2010-2060"},{"id":"pentecost","name":"Rusaliile","en":"Pentecost","category":"public","dates":"d|0523:2010|0524:2010,2037,2048|0525:2037,2048|0527:2018,2029|0528:2018,2029,2034,2045,2056|0529:2034,2045,2056|0531:2015,2026|0601:2015,2026,2031,2042,2053|0602:2031,2042,2053,2058|0603:2012,2058|0604:2012,2017,2023,2028|0605:2017,2023,2028,2039,2050|0606:2039,2050,2055|0607:2020,2055|0608:2014,2020,2025,2036|0609:2014,2025,2036,2041,2047,2052|0610:2041,2047,2052|0612:2011,2022,2033,2044|0613:2011,2022,2033,2038,2044,2049,2060|0614:2038,2049,2060|0616:2019,2030|0617:2019,2030,2035,2046,2057|0618:2035,2046,2057|0619:2016|0620:2016,2021,2027,2032|0621:2021,2027,2032,2043,2054|0622:2043,2054,2059|0623:2013,2024,2059|0624:2013,2024,2040|0625:2040,2051|0626:2051"},{"id":"children_s_day","name":"Ziua Copilului","en":"Children's Day","category":"public","dates":"d|0601:2017-2060"},{"id":"dormition_of_the_mother_of_god","name":"Adormirea Maicii Domnului","en":"Dormition of the Mother of God","category":"public","dates":"d|0815:2010-2060"},{"id":"saint_andrew_s_day","name":"Sfântul Apostol Andrei, cel Întâi chemat, Ocrotitorul României","en":"Saint Andrew's Day","category":"public","dates":"d|1130:2012-2060"},{"id":"national_day","name":"Ziua Națională a României","en":"National Day","category":"public","dates":"d|1201:2010-2060"},{"id":"christmas_day","name":"Crăciunul","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060|1226:2010-2060"}]} diff --git a/app/Support/Holidays/data/RS.json b/app/Support/Holidays/data/RS.json new file mode 100644 index 000000000..18db368ee --- /dev/null +++ b/app/Support/Holidays/data/RS.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"RS","language":"sr","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Нова година","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2010-2060|0103:2011-2012,2017,2022-2023,2028,2033-2034,2039-2040,2045,2050-2051,2056"},{"id":"orthodox_christmas_day","name":"Божић","en":"Orthodox Christmas Day","category":"public","dates":"d|0107:2010-2060"},{"id":"statehood_day","name":"Дан државности Србије","en":"Statehood Day","category":"public","dates":"d|0215:2010-2060|0216:2010-2060|0217:2014-2015,2020,2025-2026,2031-2032,2037,2042-2043,2048,2053-2054,2059-2060"},{"id":"good_friday","name":"Велики петак","en":"Good Friday","category":"public","dates":"d|0402:2010|0403:2037,2048|0406:2018,2029|0407:2034,2045,2056|0410:2015,2026|0411:2031,2042,2053|0412:2058|0413:2012|0414:2017,2023,2028|0415:2039,2050|0416:2055|0417:2020|0418:2014,2025,2036|0419:2041,2047,2052|0422:2011,2022,2033,2044|0423:2038,2049,2060|0426:2019,2030|0427:2035,2046,2057|0429:2016|0430:2021,2027,2032|0501:2043,2054|0502:2059|0503:2013,2024|0504:2040|0505:2051"},{"id":"holy_saturday","name":"Велика субота","en":"Holy Saturday","category":"public","dates":"d|0403:2010|0404:2037,2048|0407:2018,2029|0408:2034,2045,2056|0411:2015,2026|0412:2031,2042,2053|0413:2058|0414:2012|0415:2017,2023,2028|0416:2039,2050|0417:2055|0418:2020|0419:2014,2025,2036|0420:2041,2047,2052|0423:2011,2022,2033,2044|0424:2038,2049,2060|0427:2019,2030|0428:2035,2046,2057|0430:2016|0501:2021,2027,2032|0502:2043,2054|0503:2059|0504:2013,2024|0505:2040|0506:2051"},{"id":"easter_sunday","name":"Васкрс","en":"Easter Sunday","category":"public","dates":"d|0404:2010|0405:2037,2048|0408:2018,2029|0409:2034,2045,2056|0412:2015,2026|0413:2031,2042,2053|0414:2058|0415:2012|0416:2017,2023,2028|0417:2039,2050|0418:2055|0419:2020|0420:2014,2025,2036|0421:2041,2047,2052|0424:2011,2022,2033,2044|0425:2038,2049,2060|0428:2019,2030|0429:2035,2046,2057|0501:2016|0502:2021,2027,2032|0503:2043,2054|0504:2059|0505:2013,2024|0506:2040|0507:2051"},{"id":"easter_monday","name":"Други дан Васкрса","en":"Easter Monday","category":"public","dates":"d|0405:2010|0406:2037,2048|0409:2018,2029|0410:2034,2045,2056|0413:2015,2026|0414:2031,2042,2053|0415:2058|0416:2012|0417:2017,2023,2028|0418:2039,2050|0419:2055|0420:2020|0421:2014,2025,2036|0422:2041,2047,2052|0425:2011,2022,2033,2044|0426:2038,2049,2060|0429:2019,2030|0430:2035,2046,2057|0502:2016|0503:2021,2027,2032|0504:2043,2054|0505:2059|0506:2013,2024|0507:2040|0508:2051"},{"id":"labor_day","name":"Празник рада","en":"Labor Day","category":"public","dates":"d|0501:2010-2060|0502:2010-2060|0503:2010-2011,2016,2022,2033,2038-2039,2044,2049-2050,2055,2060|0504:2021,2027,2032"},{"id":"armistice_day","name":"Дан примирја у Првом светском рату","en":"Armistice Day","category":"public","dates":"d|1111:2010-2060|1112:2012,2018,2029,2035,2040,2046,2057"}]} diff --git a/app/Support/Holidays/data/RU.json b/app/Support/Holidays/data/RU.json new file mode 100644 index 000000000..86ed8fbf3 --- /dev/null +++ b/app/Support/Holidays/data/RU.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"RU","language":"ru","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_holidays","name":"Новогодние каникулы","en":"New Year Holidays","category":"public","dates":"d|0101:2010-2060|0102:2010-2060|0103:2010-2060|0104:2010-2060|0105:2010-2060|0106:2010-2060|0108:2010,2013-2060|0110:2011"},{"id":"christmas_day","name":"Рождество Христово","en":"Christmas Day","category":"public","dates":"d|0107:2010-2060|0109:2012"},{"id":"day_off","name":"Выходной","en":"Day off","category":"public","dates":"d|0109:2015,2026|0222:2010,2016,2021,2027|0224:2017,2023|0307:2011,2016,2022|0309:2012,2018|0429:2024|0430:2012,2018,2024|0502:2013-2014,2018-2019,2025|0503:2013,2016,2019,2022|0504:2015,2020|0505:2020|0507:2012|0508:2012,2017,2023,2025|0510:2013,2019,2022,2024|0611:2012,2018|0613:2014,2025|1103:2014,2025|1105:2010,2021,2027|1230:2024|1231:2012,2018,2021,2024-2027"},{"id":"defender_of_the_fatherland_day","name":"День защитника Отечества","en":"Defender of the Fatherland Day","category":"public","dates":"d|0223:2010-2060|0224:2020"},{"id":"international_women_s_day","name":"Международный женский день","en":"International Women's Day","category":"public","dates":"d|0308:2010-2060|0309:2015,2020,2026"},{"id":"holiday_of_spring_and_labor","name":"Праздник Весны и Труда","en":"Holiday of Spring and Labor","category":"public","dates":"d|0501:2010-2060|0502:2011,2016,2022|0503:2010,2021,2027"},{"id":"victory_day","name":"День Победы","en":"Victory Day","category":"public","dates":"d|0509:2010-2060|0510:2010,2021,2027|0511:2015,2020,2026"},{"id":"russia_day","name":"День России","en":"Russia Day","category":"public","dates":"d|0612:2010-2060|0613:2011,2016,2022|0614:2010,2021,2027"},{"id":"unity_day","name":"День народного единства","en":"Unity Day","category":"public","dates":"d|1104:2010-2060|1105:2012,2018|1106:2017,2023"}]} diff --git a/app/Support/Holidays/data/RW.json b/app/Support/Holidays/data/RW.json new file mode 100644 index 000000000..fc07e4d74 --- /dev/null +++ b/app/Support/Holidays/data/RW.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"RW","language":"rw","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Ubunani","en":"New Year's Day","category":"public","dates":"d|0101:2012-2060|0103:2022-2023,2028,2033-2034,2039-2040,2045,2050-2051,2056"},{"id":"day_after_new_year_s_day","name":"Umunsi ukurikira Ubunani","en":"Day after New Year's Day","category":"public","dates":"d|0102:2016-2060|0104:2021,2027,2038,2044,2049,2055"},{"id":"eid_al_fitr","name":"Eid El Fitr","en":"Eid al-Fitr","category":"public","dates":"y|2012:0819|2013:0808|2014:0729|2015:0718|2016:0707|2017:0626|2018:0615|2019:0604|2020:0524-0525|2021:0513|2022:0502|2023:0421|2024:0410|2025:0331|2026:0320|2027:0309|2028:0226,0228|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,0104,1223|2034:1212|2035:1201,1203|2036:1119|2037:1108-1109|2038:1029|2039:1019|2040:1007-1008|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712-0713|2049:0701|2050:0620|2051:0610,0612|2052:0529|2053:0519|2054:0509,0511|2055:0428|2056:0416-0417|2057:0405|2058:0325|2059:0315,0317|2060:0304","estimated":"y|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304"},{"id":"eid_al_adha","name":"Eid al-Adha","en":"Eid al-Adha","category":"public","dates":"y|2015:0923|2016:0911|2017:0901|2018:0822|2019:0812|2020:0731|2021:0720|2022:0711|2023:0628|2024:0616-0617|2025:0606|2026:0527|2027:0516-0517|2028:0505|2029:0424|2030:0413,0415|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218-0219|2036:0207|2037:0126|2038:0116,0118|2039:0105,1226|2040:1214|2041:1204|2042:1123-1124|2043:1112|2044:1031|2045:1021,1023|2046:1010|2047:0930|2048:0919,0921|2049:0908|2050:0828-0829|2051:0817|2052:0805|2053:0726,0728|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601,0603|2059:0522|2060:0510","estimated":"y|2015:0923|2016:0911|2017:0901|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510"},{"id":"national_heroes_day","name":"Umunsi w'Intwari","en":"National Heroes' Day","category":"public","dates":"d|0201:2012-2060|0202:2026,2032,2037,2043,2054,2060|0203:2020,2025,2031,2042,2048,2053,2059"},{"id":"good_friday","name":"Umunsi wa Gatanu Mutagatifu","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0423:2038"},{"id":"easter_monday","name":"Ku wa mbere wa Pasika","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0329:2027,2032|0330:2043,2054|0331:2059|0401:2024|0402:2018,2029,2040|0403:2051,2056|0405:2021|0406:2026,2037,2048|0407:2042,2053|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2025|0422:2019,2030,2041,2052|0423:2057|0426:2038"},{"id":"memorial_day_of_genocide_perpetrated_against_the","name":"Umunsi wo Kwibuka Jenoside yakorewe Abatutsi mu 1994","en":"Memorial Day of Genocide perpetrated against the Tutsi in 1994","category":"public","dates":"d|0407:2012-2060"},{"id":"labor_day","name":"Umunsi Mukuru w'Umurimo","en":"Labor Day","category":"public","dates":"d|0501:2012-2060|0502:2033,2039,2044,2050|0503:2021-2022,2027,2032,2038,2049,2055,2060"},{"id":"independence_day","name":"Umunsi w'Ubwigenge","en":"Independence Day","category":"public","dates":"d|0701:2012-2060|0702:2018,2029,2035,2040,2046,2057|0703:2017,2023,2028,2034,2045,2051,2056"},{"id":"liberation_day","name":"Umunsi wo Kwibohora","en":"Liberation Day","category":"public","dates":"d|0704:2012-2060|0705:2021,2027,2032,2038,2049,2060|0706:2020,2026,2037,2043,2048,2054-2055"},{"id":"umuganura_day","name":"Umunsi w'Umuganura","en":"Umuganura Day","category":"public","dates":"d|0801:2025,2031,2036,2042,2053,2059|0802:2019,2024,2030,2041,2047,2052,2058|0803:2018,2029,2035,2040,2046,2057|0804:2017,2023,2028,2034,2045,2051,2056|0805:2016,2022,2033,2039,2044,2050|0806:2021,2027,2032,2038,2049,2055,2060|0807:2015,2020,2026,2037,2043,2048,2054"},{"id":"assumption_day","name":"Ijyanwa mu Ijuru rya Bikiramariya","en":"Assumption Day","category":"public","dates":"d|0815:2012-2060|0816:2021,2027,2032,2038,2049,2055,2060|0817:2020,2026,2037,2043,2048,2054"},{"id":"christmas_day","name":"Noheli","en":"Christmas Day","category":"public","dates":"d|1225:2012-2060|1227:2021-2022,2027,2032-2033,2038-2039,2044,2049-2050,2055,2060"},{"id":"boxing_day","name":"Umunsi ukurikira Noheli","en":"Boxing Day","category":"public","dates":"d|1226:2012-2060|1228:2020,2026,2037,2043,2048,2054"}]} diff --git a/app/Support/Holidays/data/SA.json b/app/Support/Holidays/data/SA.json new file mode 100644 index 000000000..dfafde562 --- /dev/null +++ b/app/Support/Holidays/data/SA.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"SA","language":"ar","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"eid_al_fitr_holiday","name":"عطلة عيد الفطر","en":"Eid al-Fitr Holiday","category":"public","dates":"y|2010:0909-0914|2011:0830-0904|2012:0818-0821|2013:0807-0812|2014:0727-0730|2015:0717-0722|2016:0705-0708,0710|2017:0625-0628|2018:0614-0619|2019:0604-0607,0609|2020:0523-0527|2021:0512-0517|2022:0501-0504|2023:0421-0426|2024:0409-0412,0414|2025:0330-0402|2026:0319-0324|2027:0309-0312,0314|2028:0226-0301|2029:0214-0219|2030:0203-0206|2031:0124-0129|2032:0113-0116,0118|2033:0102-0105,1222-1227|2034:1211-1214|2035:1130-1205|2036:1118-1121,1123|2037:1108-1111|2038:1029-1103|2039:1018-1021,1023|2040:1006-1010|2041:0926-1001|2042:0915-0918|2043:0904-0909|2044:0824-0829|2045:0813-0816|2046:0803-0808|2047:0723-0726,0728|2048:0711-0715|2049:0701-0706|2050:0620-0623|2051:0609-0614|2052:0529-0603|2053:0519-0522|2054:0508-0513|2055:0427-0430,0502|2056:0415-0419|2057:0404-0409|2058:0325-0328|2059:0315-0319|2060:0303-0308"},{"id":"day_of_arafah","name":"يوم عرفة","en":"Day of Arafah","category":"public","dates":"y|2010:1115|2011:1105|2012:1025|2013:1014|2014:1003|2015:0923|2016:0910|2017:0831|2018:0820|2019:0810|2020:0730|2021:0719|2022:0708|2023:0627|2024:0615|2025:0605|2026:0526|2027:0515|2028:0504|2029:0423|2030:0412|2031:0401|2032:0321|2033:0310|2034:0228|2035:0217|2036:0206|2037:0125|2038:0115|2039:0104,1225|2040:1213|2041:1203|2042:1122|2043:1111|2044:1030|2045:1020|2046:1009|2047:0929|2048:0918|2049:0907|2050:0827|2051:0816|2052:0804|2053:0725|2054:0714|2055:0704|2056:0622|2057:0611|2058:0531|2059:0521|2060:0509"},{"id":"eid_al_adha_holiday","name":"عطلة عيد الأضحى","en":"Eid al-Adha Holiday","category":"public","dates":"y|2010:1116-1118,1120|2011:1106-1108|2012:1026-1030|2013:1015-1017|2014:1004-1008|2015:0924-0928|2016:0911-0914|2017:0901-0905|2018:0821-0823|2019:0811-0814|2020:0731-0804|2021:0720-0722|2022:0709-0713|2023:0628-0630,0702|2024:0616-0619|2025:0606-0610|2026:0527-0529,0531|2027:0516-0519|2028:0505-0509|2029:0424-0426|2030:0413-0417|2031:0402-0404,0406|2032:0322-0324|2033:0311-0315|2034:0301-0303,0305|2035:0218-0221|2036:0207-0211|2037:0126-0128|2038:0116-0120|2039:0105-0107,0109,1226-1228|2040:1214-1218|2041:1204-1206,1208|2042:1123-1126|2043:1112-1116|2044:1031-1102|2045:1021-1025|2046:1010-1012,1014|2047:0930-1002|2048:0919-0923|2049:0908-0910,0912|2050:0828-0831|2051:0817-0821|2052:0805-0807|2053:0726-0730|2054:0715-0717,0719|2055:0705-0707|2056:0623-0627|2057:0612-0614|2058:0601-0605|2059:0522-0526|2060:0510-0512"},{"id":"founding_day_holiday","name":"يوم التأسيس","en":"Founding Day Holiday","category":"public","dates":"d|0221:2030,2036,2041,2047,2058|0222:2022-2060|0223:2025,2031,2042,2048,2053,2059"},{"id":"national_day_holiday","name":"اليوم الوطني","en":"National Day Holiday","category":"public","dates":"d|0922:2010,2016,2022,2033,2039,2044,2050|0923:2010-2060|0924:2011,2017,2023,2028,2034,2045,2051,2056"}]} diff --git a/app/Support/Holidays/data/SB.json b/app/Support/Holidays/data/SB.json new file mode 100644 index 000000000..ffa50b75f --- /dev/null +++ b/app/Support/Holidays/data/SB.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"SB","language":null,"years":[2010,2060],"subdivisions":{"CE":"Central","CH":"Choiseul","CT":"Capital Territory","GU":"Guadalcanal","IS":"Isabel","MK":"Makira-Ulawa","ML":"Malaita","RB":"Rennell and Bellona","TE":"Temotu","WE":"Western"},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051|1231:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"choiseul_province_day","name":"Choiseul Province Day","en":"Choiseul Province Day","category":"public","regional":{"CH":"d|0224:2012,2017,2023,2034,2040,2045,2051|0225:2010-2060|0226:2018,2024,2029,2035,2046,2052,2057"}},{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"holy_saturday","name":"Holy Saturday","en":"Holy Saturday","category":"public","dates":"d|0324:2035,2046|0326:2016|0327:2027,2032|0328:2043,2054|0329:2059|0330:2013,2024|0331:2018,2029,2040|0401:2051,2056|0403:2010,2021|0404:2015,2026,2037,2048|0405:2042,2053|0407:2012|0408:2023,2034,2045|0409:2039,2050|0411:2020|0412:2031,2036|0413:2047,2058|0415:2017,2028|0416:2022,2033,2044|0417:2049,2055,2060|0419:2014,2025|0420:2019,2030,2041,2052|0421:2057|0423:2011|0424:2038"},{"id":"easter_monday","name":"Easter Monday","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"whit_monday","name":"Whit Monday","en":"Whit Monday","category":"public","dates":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038"},{"id":"isabel_province_day","name":"Isabel Province Day","en":"Isabel Province Day","category":"public","regional":{"IS":"d|0601:2012,2018,2029,2035,2040,2046,2057|0602:2010-2060|0603:2013,2019,2024,2030,2041,2047,2052,2058"}},{"id":"temotu_province_day","name":"Temotu Province Day","en":"Temotu Province Day","category":"public","regional":{"TE":"d|0607:2013,2019,2024,2030,2041,2047,2052,2058|0608:2010-2060|0609:2014,2025,2031,2036,2042,2053,2059"}},{"id":"king_s_birthday","name":"King's Birthday","en":"King's Birthday","category":"public","dates":"d|0613:2025|0614:2024,2030,2041,2047,2052,2058|0615:2024,2029,2035,2040,2046,2057|0616:2023,2028,2034,2045,2051,2056|0617:2033,2039,2044,2050|0618:2027,2032,2038,2049,2055,2060|0619:2026,2037,2043,2048,2054|0620:2031,2036,2042,2053,2059"},{"id":"central_province_day","name":"Central Province Day","en":"Central Province Day","category":"public","regional":{"CE":"d|0628:2013,2019,2024,2030,2041,2047,2052,2058|0629:2010-2060|0630:2014,2025,2031,2036,2042,2053,2059"}},{"id":"independence_day","name":"Independence Day","en":"Independence Day","category":"public","dates":"d|0706:2012,2018,2029,2035,2040,2046,2057|0707:2010-2060|0708:2013,2019,2024,2030,2041,2047,2052,2058"},{"id":"rennell_and_bellona_province_day","name":"Rennell and Bellona Province Day","en":"Rennell and Bellona Province Day","category":"public","regional":{"RB":"d|0719:2013,2019,2024,2030,2041,2047,2052,2058|0720:2010-2060|0721:2014,2025,2031,2036,2042,2053,2059"}},{"id":"guadalcanal_province_day","name":"Guadalcanal Province Day","en":"Guadalcanal Province Day","category":"public","regional":{"GU":"d|0731:2015,2020,2026,2037,2043,2048,2054|0801:2010-2060|0802:2010,2021,2027,2032,2038,2049,2055,2060"}},{"id":"makira_ulawa_province_day","name":"Makira-Ulawa Province Day","en":"Makira-Ulawa Province Day","category":"public","regional":{"MK":"d|0802:2013,2019,2024,2030,2041,2047,2052,2058|0803:2010-2060|0804:2014,2025,2031,2036,2042,2053,2059"}},{"id":"malaita_province_day","name":"Malaita Province Day","en":"Malaita Province Day","category":"public","regional":{"ML":"d|0814:2015,2020,2026,2037,2043,2048,2054|0815:2010-2060|0816:2010,2021,2027,2032,2038,2049,2055,2060"}},{"id":"western_province_day","name":"Western Province Day","en":"Western Province Day","category":"public","regional":{"WE":"d|1206:2013,2019,2024,2030,2041,2047,2052,2058|1207:2010-2060|1208:2014,2025,2031,2036,2042,2053,2059"}},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060|1227:2010-2011,2016,2021-2022,2027,2032-2033,2038-2039,2044,2049-2050,2055,2060"},{"id":"national_day_of_thanksgiving","name":"National Day of Thanksgiving","en":"National Day of Thanksgiving","category":"public","dates":"d|1226:2010-2060|1228:2010,2015,2020-2021,2026-2027,2032,2037-2038,2043,2048-2049,2054-2055,2060"}]} diff --git a/app/Support/Holidays/data/SC.json b/app/Support/Holidays/data/SC.json new file mode 100644 index 000000000..34647b83c --- /dev/null +++ b/app/Support/Holidays/data/SC.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"SC","language":"en_SC","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"new_year_holiday","name":"New Year Holiday","en":"New Year Holiday","category":"public","dates":"d|0102:2010-2060|0103:2011,2022,2028,2033,2039,2050,2056"},{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_saturday","name":"Easter Saturday","en":"Easter Saturday","category":"public","dates":"d|0324:2035,2046|0326:2016|0327:2027,2032|0328:2043,2054|0329:2059|0330:2013,2024|0331:2018,2029,2040|0401:2051,2056|0403:2010,2021|0404:2015,2026,2037,2048|0405:2042,2053|0407:2012|0408:2023,2034,2045|0409:2039,2050|0411:2020|0412:2031,2036|0413:2047,2058|0415:2017,2028|0416:2022,2033,2044|0417:2049,2055,2060|0419:2014,2025|0420:2019,2030,2041,2052|0421:2057|0423:2011|0424:2038"},{"id":"easter_monday","name":"Easter Monday","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0329:2027,2032|0330:2043,2054|0331:2059|0401:2024|0402:2018,2029,2040|0403:2051,2056|0405:2021|0406:2026,2037,2048|0407:2042,2053|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2025|0422:2019,2030,2041,2052|0423:2057|0426:2038"},{"id":"labor_day","name":"Labour Day","en":"Labor Day","category":"public","dates":"d|0501:2010-2060|0502:2011,2016,2022,2033,2039,2044,2050"},{"id":"corpus_christi","name":"The Fete Dieu","en":"Corpus Christi","category":"public","dates":"d|0524:2035,2046|0526:2016|0527:2027,2032|0528:2043,2054|0529:2059|0530:2013,2024|0531:2018,2029,2040|0601:2051,2056|0603:2010,2021|0604:2015,2026,2037,2048|0605:2042,2053|0607:2012|0608:2023,2034,2045|0609:2039,2050|0611:2020|0612:2031,2036|0613:2047,2058|0615:2017,2028|0616:2022,2033,2044|0617:2049,2055,2060|0619:2014,2025|0620:2019,2030,2041,2052|0621:2057|0623:2011|0624:2038"},{"id":"constitution_day","name":"Constitution Day","en":"Constitution Day","category":"public","dates":"d|0618:2015-2060|0619:2017,2023,2028,2034,2045,2051,2056"},{"id":"independence_national_day","name":"Independence (National) Day","en":"Independence (National) Day","category":"public","dates":"d|0629:2015-2060|0630:2025,2031,2036,2042,2053,2059"},{"id":"assumption_day","name":"Assumption Day","en":"Assumption Day","category":"public","dates":"d|0815:2010-2060|0816:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"all_saints_day","name":"All Saints Day","en":"All Saints' Day","category":"public","dates":"d|1101:2010-2060|1102:2015,2020,2026,2037,2043,2048,2054"},{"id":"immaculate_conception","name":"The Feast of the Immaculate Conception","en":"Immaculate Conception","category":"public","dates":"d|1208:2010-2060|1209:2013,2019,2024,2030,2041,2047,2052,2058"},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060|1226:2011,2016,2022,2033,2039,2044,2050"}]} diff --git a/app/Support/Holidays/data/SD.json b/app/Support/Holidays/data/SD.json new file mode 100644 index 000000000..3d43e5340 --- /dev/null +++ b/app/Support/Holidays/data/SD.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"SD","language":"ar_SD","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"independence_day","name":"عيد الاستقلال","en":"Independence Day","category":"public","dates":"d|0101:2010-2060"},{"id":"eid_al_fitr","name":"عيد الفطر المبارك","en":"Eid al-Fitr","category":"public","dates":"y|2010:0910-0912|2011:0830-0901|2012:0819-0821|2013:0808-0810|2014:0728-0730|2015:0717-0719|2016:0706-0708|2017:0625-0627|2018:0615-0617|2019:0604-0606|2020:0524-0527|2021:0513-0516|2022:0501-0504|2023:0421-0424|2024:0410-0413|2025:0330-0402|2026:0320-0323|2027:0309-0312|2028:0226-0229|2029:0214-0217|2030:0204-0207|2031:0124-0127|2032:0114-0117|2033:0102-0105,1223-1226|2034:1212-1215|2035:1201-1204|2036:1119-1122|2037:1108-1111|2038:1029-1101|2039:1019-1022|2040:1007-1010|2041:0926-0929|2042:0915-0918|2043:0904-0907|2044:0824-0827|2045:0814-0817|2046:0803-0806|2047:0724-0727|2048:0712-0715|2049:0701-0704|2050:0620-0623|2051:0610-0613|2052:0529-0601|2053:0519-0522|2054:0509-0512|2055:0428-0501|2056:0416-0419|2057:0405-0408|2058:0325-0328|2059:0315-0318|2060:0304-0307","estimated":"y|2010:0910-0912|2011:0830-0901|2012:0819-0821|2013:0808-0810|2014:0728-0730|2015:0717-0719|2016:0706-0708|2017:0625-0627|2018:0615-0617|2019:0604-0606|2026:0320-0323|2027:0309-0312|2028:0226-0229|2029:0214-0217|2030:0204-0207|2031:0124-0127|2032:0114-0117|2033:0102-0105,1223-1226|2034:1212-1215|2035:1201-1204|2036:1119-1122|2037:1108-1111|2038:1029-1101|2039:1019-1022|2040:1007-1010|2041:0926-0929|2042:0915-0918|2043:0904-0907|2044:0824-0827|2045:0814-0817|2046:0803-0806|2047:0724-0727|2048:0712-0715|2049:0701-0704|2050:0620-0623|2051:0610-0613|2052:0529-0601|2053:0519-0522|2054:0509-0512|2055:0428-0501|2056:0416-0419|2057:0405-0408|2058:0325-0328|2059:0315-0318|2060:0304-0307"},{"id":"prophet_s_birthday","name":"المولد النبوي الشريف","en":"Prophet's Birthday","category":"public","dates":"y|2010:0226|2011:0215|2012:0204|2013:0124|2014:0113|2015:0103,1223|2016:1211|2017:1130|2018:1120|2019:1109|2020:1029|2021:1018|2022:1008|2023:0928|2024:0915|2025:0904|2026:0825|2027:0814|2028:0803|2029:0724|2030:0713|2031:0702|2032:0620|2033:0609|2034:0530|2035:0520|2036:0508|2037:0428|2038:0417|2039:0406|2040:0325|2041:0315|2042:0304|2043:0222|2044:0211|2045:0130|2046:0119|2047:0108,1229|2048:1218|2049:1207|2050:1126|2051:1116|2052:1104|2053:1024|2054:1013|2055:1003|2056:0922|2057:0911|2058:0831|2059:0820|2060:0808","estimated":"y|2010:0226|2011:0215|2012:0204|2013:0124|2014:0113|2015:0103,1223|2016:1211|2017:1130|2018:1120|2019:1109|2026:0825|2027:0814|2028:0803|2029:0724|2030:0713|2031:0702|2032:0620|2033:0609|2034:0530|2035:0520|2036:0508|2037:0428|2038:0417|2039:0406|2040:0325|2041:0315|2042:0304|2043:0222|2044:0211|2045:0130|2046:0119|2047:0108,1229|2048:1218|2049:1207|2050:1126|2051:1116|2052:1104|2053:1024|2054:1013|2055:1003|2056:0922|2057:0911|2058:0831|2059:0820|2060:0808"},{"id":"eid_al_adha","name":"عيد الأضحى المبارك","en":"Eid al-Adha","category":"public","dates":"y|2010:1115-1119|2011:1105-1109|2012:1025-1029|2013:1014-1018|2014:1003-1007|2015:0922-0926|2016:0910-0914|2017:0831-0904|2018:0820-0824|2019:0810-0814|2020:0730-0803|2021:0719-0723|2022:0709-0713|2023:0627-0701|2024:0615-0619|2025:0605-0609|2026:0526-0530|2027:0515-0519|2028:0504-0508|2029:0423-0427|2030:0412-0416|2031:0401-0405|2032:0321-0325|2033:0310-0314|2034:0228-0304|2035:0217-0221|2036:0206-0210|2037:0125-0129|2038:0115-0119|2039:0104-0108,1225-1229|2040:1213-1217|2041:1203-1207|2042:1122-1126|2043:1111-1115|2044:1030-1103|2045:1020-1024|2046:1009-1013|2047:0929-1003|2048:0918-0922|2049:0907-0911|2050:0827-0831|2051:0816-0820|2052:0804-0808|2053:0725-0729|2054:0714-0718|2055:0704-0708|2056:0622-0626|2057:0611-0615|2058:0531-0604|2059:0521-0525|2060:0509-0513","estimated":"y|2010:1115-1119|2011:1105-1109|2012:1025-1029|2013:1014-1018|2014:1003-1007|2015:0922-0926|2016:0910-0914|2017:0831-0904|2018:0820-0824|2019:0810-0814|2026:0526-0530|2027:0515-0519|2028:0504-0508|2029:0423-0427|2030:0412-0416|2031:0401-0405|2032:0321-0325|2033:0310-0314|2034:0228-0304|2035:0217-0221|2036:0206-0210|2037:0125-0129|2038:0115-0119|2039:0104-0108,1225-1229|2040:1213-1217|2041:1203-1207|2042:1122-1126|2043:1111-1115|2044:1030-1103|2045:1020-1024|2046:1009-1013|2047:0929-1003|2048:0918-0922|2049:0907-0911|2050:0827-0831|2051:0816-0820|2052:0804-0808|2053:0725-0729|2054:0714-0718|2055:0704-0708|2056:0622-0626|2057:0611-0615|2058:0531-0604|2059:0521-0525|2060:0509-0513"},{"id":"islamic_new_year","name":"رأس السنة الهجرية","en":"Islamic New Year","category":"public","dates":"y|2010:1207|2011:1126|2012:1115|2013:1104|2014:1025|2015:1014|2016:1002|2017:0921|2018:0911|2019:0831|2020:0820|2021:0811|2022:0730|2023:0719|2024:0707|2025:0626|2026:0616|2027:0606|2028:0525|2029:0514|2030:0503|2031:0423|2032:0411|2033:0401|2034:0321|2035:0311|2036:0228|2037:0216|2038:0205|2039:0126|2040:0115|2041:0104,1224|2042:1214|2043:1203|2044:1121|2045:1110|2046:1031|2047:1020|2048:1009|2049:0928|2050:0917|2051:0906|2052:0826|2053:0815|2054:0805|2055:0725|2056:0714|2057:0703|2058:0622|2059:0611|2060:0531","estimated":"y|2010:1207|2011:1126|2012:1115|2013:1104|2014:1025|2015:1014|2016:1002|2017:0921|2018:0911|2019:0831|2026:0616|2027:0606|2028:0525|2029:0514|2030:0503|2031:0423|2032:0411|2033:0401|2034:0321|2035:0311|2036:0228|2037:0216|2038:0205|2039:0126|2040:0115|2041:0104,1224|2042:1214|2043:1203|2044:1121|2045:1110|2046:1031|2047:1020|2048:1009|2049:0928|2050:0917|2051:0906|2052:0826|2053:0815|2054:0805|2055:0725|2056:0714|2057:0703|2058:0622|2059:0611|2060:0531"},{"id":"coptic_christmas_day","name":"عيد الميلاد المجيد","en":"Coptic Christmas Day","category":"public","dates":"d|0107:2010,2019-2060"},{"id":"coptic_easter","name":"عيد الفصح القبطي","en":"Coptic Easter","category":"public","dates":"d|0404:2010|0405:2037,2048|0408:2029|0409:2034,2045,2056|0412:2026|0413:2031,2042,2053|0414:2058|0416:2023,2028|0417:2039,2050|0418:2055|0419:2020|0420:2025,2036|0421:2041,2047,2052|0424:2022,2033,2044|0425:2038,2049,2060|0428:2019,2030|0429:2035,2046,2057|0502:2021,2027,2032|0503:2043,2054|0504:2059|0505:2024|0506:2040|0507:2051"},{"id":"christmas_day","name":"عيد الميلاد","en":"Christmas Day","category":"public","dates":"d|1225:2010,2019-2060"}]} diff --git a/app/Support/Holidays/data/SE.json b/app/Support/Holidays/data/SE.json new file mode 100644 index 000000000..48f1a496e --- /dev/null +++ b/app/Support/Holidays/data/SE.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"SE","language":"sv","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Nyårsdagen","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"epiphany","name":"Trettondedag jul","en":"Epiphany","category":"public","dates":"d|0106:2010-2060"},{"id":"good_friday","name":"Långfredagen","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_sunday","name":"Påskdagen","en":"Easter Sunday","category":"public","dates":"d|0325:2035,2046|0327:2016|0328:2027,2032|0329:2043,2054|0330:2059|0331:2013,2024|0401:2018,2029,2040|0402:2051,2056|0404:2010,2021|0405:2015,2026,2037,2048|0406:2042,2053|0408:2012|0409:2023,2034,2045|0410:2039,2050|0412:2020|0413:2031,2036|0414:2047,2058|0416:2017,2028|0417:2022,2033,2044|0418:2049,2055,2060|0420:2014,2025|0421:2019,2030,2041,2052|0422:2057|0424:2011|0425:2038"},{"id":"easter_monday","name":"Annandag påsk","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"may_day","name":"Första maj","en":"May Day","category":"public","dates":"d|0501:2010-2060"},{"id":"ascension_day","name":"Kristi himmelsfärdsdag","en":"Ascension Day","category":"public","dates":"d|0503:2035,2046|0505:2016|0506:2027,2032|0507:2043,2054|0508:2059|0509:2013,2024|0510:2018,2029,2040|0511:2051,2056|0513:2010,2021|0514:2015,2026,2037,2048|0515:2042,2053|0517:2012|0518:2023,2034,2045|0519:2039,2050|0521:2020|0522:2031,2036|0523:2047,2058|0525:2017,2028|0526:2022,2033,2044|0527:2049,2055,2060|0529:2014,2025|0530:2019,2030,2041,2052|0531:2057|0602:2011|0603:2038"},{"id":"pentecost","name":"Pingstdagen","en":"Pentecost","category":"public","dates":"d|0513:2035,2046|0515:2016|0516:2027,2032|0517:2043,2054|0518:2059|0519:2013,2024|0520:2018,2029,2040|0521:2051,2056|0523:2010,2021|0524:2015,2026,2037,2048|0525:2042,2053|0527:2012|0528:2023,2034,2045|0529:2039,2050|0531:2020|0601:2031,2036|0602:2047,2058|0604:2017,2028|0605:2022,2033,2044|0606:2049,2055,2060|0608:2014,2025|0609:2019,2030,2041,2052|0610:2057|0612:2011|0613:2038"},{"id":"national_day","name":"Nationaldagen","en":"National Day","category":"public","dates":"d|0606:2010-2060"},{"id":"midsummer_day","name":"Midsommardagen","en":"Midsummer Day","category":"public","dates":"d|0620:2015,2020,2026,2037,2043,2048,2054|0621:2014,2025,2031,2036,2042,2053,2059|0622:2013,2019,2024,2030,2041,2047,2052,2058|0623:2012,2018,2029,2035,2040,2046,2057|0624:2017,2023,2028,2034,2045,2051,2056|0625:2011,2016,2022,2033,2039,2044,2050|0626:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"all_saints_day","name":"Alla helgons dag","en":"All Saints' Day","category":"public","dates":"d|1031:2015,2020,2026,2037,2043,2048,2054|1101:2014,2025,2031,2036,2042,2053,2059|1102:2013,2019,2024,2030,2041,2047,2052,2058|1103:2012,2018,2029,2035,2040,2046,2057|1104:2017,2023,2028,2034,2045,2051,2056|1105:2011,2016,2022,2033,2039,2044,2050|1106:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"christmas_day","name":"Juldagen","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"},{"id":"second_day_of_christmas","name":"Annandag jul","en":"Second Day of Christmas","category":"public","dates":"d|1226:2010-2060"},{"id":"twelfth_night_bank","name":"Trettondagsafton","en":"Twelfth Night","category":"bank","dates":"d|0105:2010-2060"},{"id":"twelfth_night_optional","name":"Trettondagsafton","en":"Twelfth Night","category":"optional","dates":"d|0105:2010-2060"},{"id":"maundy_thursday_bank","name":"Skärtorsdagen","en":"Maundy Thursday","category":"bank","dates":"d|0322:2035,2046|0324:2016|0325:2027,2032|0326:2043,2054|0327:2059|0328:2013,2024|0329:2018,2029,2040|0330:2051,2056|0401:2010,2021|0402:2015,2026,2037,2048|0403:2042,2053|0405:2012|0406:2023,2034,2045|0407:2039,2050|0409:2020|0410:2031,2036|0411:2047,2058|0413:2017,2028|0414:2022,2033,2044|0415:2049,2055,2060|0417:2014,2025|0418:2019,2030,2041,2052|0419:2057|0421:2011|0422:2038"},{"id":"maundy_thursday_optional","name":"Skärtorsdagen","en":"Maundy Thursday","category":"optional","dates":"d|0322:2035,2046|0324:2016|0325:2027,2032|0326:2043,2054|0327:2059|0328:2013,2024|0329:2018,2029,2040|0330:2051,2056|0401:2010,2021|0402:2015,2026,2037,2048|0403:2042,2053|0405:2012|0406:2023,2034,2045|0407:2039,2050|0409:2020|0410:2031,2036|0411:2047,2058|0413:2017,2028|0414:2022,2033,2044|0415:2049,2055,2060|0417:2014,2025|0418:2019,2030,2041,2052|0419:2057|0421:2011|0422:2038"},{"id":"holy_saturday_optional","name":"Påskafton","en":"Holy Saturday","category":"optional","dates":"d|0324:2035,2046|0326:2016|0327:2027,2032|0328:2043,2054|0329:2059|0330:2013,2024|0331:2018,2029,2040|0401:2051,2056|0403:2010,2021|0404:2015,2026,2037,2048|0405:2042,2053|0407:2012|0408:2023,2034,2045|0409:2039,2050|0411:2020|0412:2031,2036|0413:2047,2058|0415:2017,2028|0416:2022,2033,2044|0417:2049,2055,2060|0419:2014,2025|0420:2019,2030,2041,2052|0421:2057|0423:2011|0424:2038"},{"id":"walpurgis_night_bank","name":"Valborgsmässoafton","en":"Walpurgis Night","category":"bank","dates":"d|0430:2010-2060"},{"id":"walpurgis_night_optional","name":"Valborgsmässoafton","en":"Walpurgis Night","category":"optional","dates":"d|0430:2010-2060"},{"id":"day_before_ascension_day_bank","name":"Dag före Kristi himmelsfärdsdag","en":"Day before Ascension Day","category":"bank","dates":"d|0502:2035,2046|0504:2016|0505:2027,2032|0506:2043,2054|0507:2059|0508:2013,2024|0509:2018,2029,2040|0510:2051,2056|0512:2010,2021|0513:2015,2026,2037,2048|0514:2042,2053|0516:2012|0517:2023,2034,2045|0518:2039,2050|0520:2020|0521:2031,2036|0522:2047,2058|0524:2017,2028|0525:2022,2033,2044|0526:2049,2055,2060|0528:2014,2025|0529:2019,2030,2041,2052|0530:2057|0601:2011|0602:2038"},{"id":"squeeze_day_optional","name":"Klämdag","en":"Squeeze day","category":"optional","dates":"d|0504:2035,2046|0506:2016|0507:2027,2032|0508:2043,2054|0509:2059|0510:2013,2024|0511:2018,2029,2040|0512:2051,2056|0514:2010,2021|0515:2015,2026,2037,2048|0516:2042,2053|0518:2012|0519:2023,2034,2045|0520:2039,2050|0522:2020|0523:2031,2036|0524:2047,2058|0526:2017,2028|0527:2022,2033,2044|0528:2049,2055,2060|0530:2014,2025|0531:2019,2030,2041,2052|0601:2057|0603:2011|0604:2038"},{"id":"day_before_pentecost_eve_bank","name":"Dag före Pingstafton","en":"Day before Pentecost Eve","category":"bank","dates":"d|0511:2035,2046|0513:2016|0514:2027,2032|0515:2043,2054|0516:2059|0517:2013,2024|0518:2018,2029,2040|0519:2051,2056|0521:2010,2021|0522:2015,2026,2037,2048|0523:2042,2053|0525:2012|0526:2023,2034,2045|0527:2039,2050|0529:2020|0530:2031,2036|0531:2047,2058|0602:2017,2028|0603:2022,2033,2044|0604:2049,2055,2060|0606:2014,2025|0607:2019,2030,2041,2052|0608:2057|0610:2011|0611:2038"},{"id":"pentecost_eve_optional","name":"Pingstafton","en":"Pentecost Eve","category":"optional","dates":"d|0512:2035,2046|0514:2016|0515:2027,2032|0516:2043,2054|0517:2059|0518:2013,2024|0519:2018,2029,2040|0520:2051,2056|0522:2010,2021|0523:2015,2026,2037,2048|0524:2042,2053|0526:2012|0527:2023,2034,2045|0528:2039,2050|0530:2020|0531:2031,2036|0601:2047,2058|0603:2017,2028|0604:2022,2033,2044|0605:2049,2055,2060|0607:2014,2025|0608:2019,2030,2041,2052|0609:2057|0611:2011|0612:2038"},{"id":"day_before_midsummer_eve_bank","name":"Dag före Midsommarafton","en":"Day before Midsummer Eve","category":"bank","dates":"d|0618:2015,2020,2026,2037,2043,2048,2054|0619:2014,2025,2031,2036,2042,2053,2059|0620:2013,2019,2024,2030,2041,2047,2052,2058|0621:2012,2018,2029,2035,2040,2046,2057|0622:2017,2023,2028,2034,2045,2051,2056|0623:2011,2016,2022,2033,2039,2044,2050|0624:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"midsummer_eve_bank","name":"Midsommarafton","en":"Midsummer Eve","category":"bank","dates":"d|0619:2015,2020,2026,2037,2043,2048,2054|0620:2014,2025,2031,2036,2042,2053,2059|0621:2013,2019,2024,2030,2041,2047,2052,2058|0622:2012,2018,2029,2035,2040,2046,2057|0623:2017,2023,2028,2034,2045,2051,2056|0624:2011,2016,2022,2033,2039,2044,2050|0625:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"midsummer_eve_de_facto","name":"Midsommarafton","en":"Midsummer Eve","category":"de_facto","dates":"d|0619:2015,2020,2026,2037,2043,2048,2054|0620:2014,2025,2031,2036,2042,2053,2059|0621:2013,2019,2024,2030,2041,2047,2052,2058|0622:2012,2018,2029,2035,2040,2046,2057|0623:2017,2023,2028,2034,2045,2051,2056|0624:2011,2016,2022,2033,2039,2044,2050|0625:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"all_saints_eve_bank","name":"Allahelgonsafton","en":"All Saints' Eve","category":"bank","dates":"d|1030:2015,2020,2026,2037,2043,2048,2054|1031:2014,2025,2031,2036,2042,2053,2059|1101:2013,2019,2024,2030,2041,2047,2052,2058|1102:2012,2018,2029,2035,2040,2046,2057|1103:2017,2023,2028,2034,2045,2051,2056|1104:2011,2016,2022,2033,2039,2044,2050|1105:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"all_saints_eve_optional","name":"Allahelgonsafton","en":"All Saints' Eve","category":"optional","dates":"d|1030:2015,2020,2026,2037,2043,2048,2054|1031:2014,2025,2031,2036,2042,2053,2059|1101:2013,2019,2024,2030,2041,2047,2052,2058|1102:2012,2018,2029,2035,2040,2046,2057|1103:2017,2023,2028,2034,2045,2051,2056|1104:2011,2016,2022,2033,2039,2044,2050|1105:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"day_before_christmas_eve_bank","name":"Dag före Julafton","en":"Day before Christmas Eve","category":"bank","dates":"d|1223:2010-2060"},{"id":"christmas_eve_bank","name":"Julafton","en":"Christmas Eve","category":"bank","dates":"d|1224:2010-2060"},{"id":"christmas_eve_de_facto","name":"Julafton","en":"Christmas Eve","category":"de_facto","dates":"d|1224:2010-2060"},{"id":"day_before_new_year_s_eve_bank","name":"Dag före Nyårsafton","en":"Day before New Year's Eve","category":"bank","dates":"d|1230:2010-2060"},{"id":"new_year_s_eve_bank","name":"Nyårsafton","en":"New Year's Eve","category":"bank","dates":"d|1231:2010-2060"},{"id":"new_year_s_eve_de_facto","name":"Nyårsafton","en":"New Year's Eve","category":"de_facto","dates":"d|1231:2010-2060"}]} diff --git a/app/Support/Holidays/data/SG.json b/app/Support/Holidays/data/SG.json new file mode 100644 index 000000000..201d4bcf1 --- /dev/null +++ b/app/Support/Holidays/data/SG.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"SG","language":"en_SG","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051"},{"id":"eid_al_fitr","name":"Hari Raya Puasa","en":"Eid al-Fitr","category":"public","dates":"y|2010:0910|2011:0830|2012:0819-0820|2013:0808|2014:0728|2015:0717|2016:0706|2017:0625-0626|2018:0615|2019:0605|2020:0524-0525|2021:0513|2022:0503|2023:0422|2024:0410|2025:0331|2026:0321|2027:0310|2028:0227-0228|2029:0215|2030:0204|2031:0125|2032:0114|2033:0103,1223|2034:1212|2035:1201|2036:1119|2037:1109|2038:1030|2039:1019|2040:1008|2041:0927|2042:0916|2043:0905|2044:0825|2045:0814|2046:0804|2047:0724|2048:0713|2049:0702|2050:0621|2051:0610|2052:0530|2053:0520|2054:0509|2055:0428|2056:0416-0417|2057:0405|2058:0326|2059:0315|2060:0304","estimated":"y|2028:0227|2029:0215|2030:0204|2031:0125|2032:0114|2033:0103,1223|2034:1212|2035:1201|2036:1119|2037:1109|2038:1030|2039:1019|2040:1008|2041:0927|2042:0916|2043:0905|2044:0825|2045:0814|2046:0804|2047:0724|2048:0713|2049:0702|2050:0621|2051:0610|2052:0530|2053:0520|2054:0509|2055:0428|2056:0416|2057:0405|2058:0326|2059:0315|2060:0304"},{"id":"eid_al_adha","name":"Hari Raya Haji","en":"Eid al-Adha","category":"public","dates":"y|2010:1117|2011:1106-1107|2012:1026|2013:1015|2014:1005-1006|2015:0924|2016:0912|2017:0901|2018:0822|2019:0811-0812|2020:0731|2021:0720|2022:0710-0711|2023:0629|2024:0617|2025:0607|2026:0527|2027:0517|2028:0505|2029:0425|2030:0414-0415|2031:0403|2032:0322|2033:0312|2034:0301|2035:0219|2036:0208|2037:0127|2038:0116|2039:0106,1226|2040:1215|2041:1204|2042:1123-1124|2043:1112|2044:1101|2045:1021|2046:1011|2047:0930|2048:0919|2049:0908|2050:0828-0829|2051:0817|2052:0806|2053:0726|2054:0716|2055:0706|2056:0624|2057:0613|2058:0602-0603|2059:0522|2060:0511","estimated":"y|2028:0505|2029:0425|2030:0414|2031:0403|2032:0322|2033:0312|2034:0301|2035:0219|2036:0208|2037:0127|2038:0116|2039:0106,1226|2040:1215|2041:1204|2042:1123|2043:1112|2044:1101|2045:1021|2046:1011|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0806|2053:0726|2054:0716|2055:0706|2056:0624|2057:0613|2058:0602|2059:0522|2060:0511"},{"id":"chinese_new_year","name":"Chinese New Year","en":"Chinese New Year","category":"public","dates":"d|0122:2023,2042|0123:2012,2023,2031,2042,2050|0124:2012,2023,2031,2039,2050,2058|0125:2020,2039,2050,2058|0126:2020,2028,2047|0127:2020,2028,2047|0128:2017,2036,2047,2055|0129:2017,2025,2036,2055|0130:2017,2025,2044|0131:2014,2033,2044|0201:2014,2022,2033,2041,2044,2052|0202:2022,2041,2049,2052,2060|0203:2011,2030,2049,2060|0204:2011,2030,2038,2057|0205:2019,2030,2038,2057|0206:2019,2027,2046,2057|0207:2027,2046|0208:2016,2027,2035,2054|0209:2016,2035,2054|0210:2013,2024,2043,2054|0211:2013,2024,2032,2043,2051|0212:2013,2021,2024,2032,2040,2051,2059|0213:2021,2029,2040,2051,2059|0214:2010,2029,2040,2048|0215:2010,2037,2048,2056|0216:2010,2018,2037,2056|0217:2018,2026,2037,2045|0218:2026,2045|0219:2015,2034,2053|0220:2015,2034,2053|0221:2034","estimated":"d|0122:2042|0123:2031,2042,2050|0124:2031,2039,2050,2058|0125:2039,2058|0126:2028,2047|0127:2028,2047|0128:2036,2055|0129:2036,2055|0130:2044|0131:2033,2044|0201:2033,2041,2052|0202:2041,2049,2052,2060|0203:2030,2049,2060|0204:2030,2038,2057|0205:2038,2057|0206:2046|0207:2046|0208:2035,2054|0209:2035,2054|0210:2043|0211:2032,2043,2051|0212:2032,2040,2051,2059|0213:2029,2040,2059|0214:2029,2048|0215:2037,2048,2056|0216:2037,2056|0217:2045|0218:2045|0219:2034,2053|0220:2034,2053"},{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"labor_day","name":"Labour Day","en":"Labor Day","category":"public","dates":"d|0501:2010-2060|0502:2011,2016,2022,2033,2039,2044,2050"},{"id":"vesak_day","name":"Vesak Day","en":"Vesak Day","category":"public","dates":"d|0505:2012|0507:2020,2039,2058|0509:2028,2047|0510:2017,2036|0511:2055|0512:2025,2044|0513:2014,2033,2052|0514:2041,2060|0515:2022|0516:2022,2030,2049|0517:2011,2049|0518:2038,2057|0519:2019|0520:2019,2027,2046|0521:2016,2046|0522:2024,2035,2054|0523:2032,2043|0524:2013,2032,2051|0525:2040|0526:2021,2059|0527:2029,2048|0528:2010,2029|0529:2018,2037,2056|0531:2026,2045|0601:2015,2026,2034,2053|0602:2023,2042,2053|0604:2031,2050","estimated":"d|0507:2039,2058|0509:2028,2047|0510:2036|0511:2055|0512:2044|0513:2033,2052|0514:2041,2060|0516:2030,2049|0518:2038,2057|0520:2046|0522:2035,2054|0523:2032,2043|0524:2051|0525:2040|0526:2059|0527:2029,2048|0529:2037,2056|0531:2045|0601:2034,2053|0602:2042|0604:2031,2050"},{"id":"national_day","name":"National Day","en":"National Day","category":"public","dates":"d|0809:2010-2060|0810:2015,2020,2026,2037,2043,2048,2054"},{"id":"diwali","name":"Deepavali","en":"Diwali","category":"public","dates":"d|1018:2017|1020:2025|1022:2014,2033|1023:2060|1024:2022,2041|1026:2011,2030,2049|1027:2019,2038,2057|1028:2019,2027,2046|1029:2016,2046|1030:2035,2054|1031:2024|1101:2043|1102:2013,2032,2043,2051|1104:2021,2040,2059|1105:2010,2029,2040,2048|1106:2018,2037,2056|1108:2026,2045|1109:2026,2053|1110:2015,2034,2053|1112:2023,2042|1113:2012,2023,2050|1114:2020,2031,2050|1115:2028,2039,2058|1116:2047|1117:2036|1118:2044,2055|1120:2052"},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060|1226:2011,2016,2022,2033,2044,2050|1227:2039"}]} diff --git a/app/Support/Holidays/data/SH.json b/app/Support/Holidays/data/SH.json new file mode 100644 index 000000000..5d5d3cc30 --- /dev/null +++ b/app/Support/Holidays/data/SH.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"SH","language":"en_GB","years":[2010,2060],"subdivisions":{"AC":"Ascension","HL":"Saint Helena","TA":"Tristan da Cunha"},"holidays":[{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2024|0330:2018,2029,2040|0331:2051,2056|0402:2021|0403:2015,2026,2037,2048|0404:2042,2053|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2025|0419:2019,2030,2041,2052|0420:2057|0423:2038"},{"id":"easter_monday","name":"Easter Monday","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2024|0402:2018,2029,2040|0403:2051,2056|0405:2021|0406:2015,2026,2037,2048|0407:2042,2053|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2025|0422:2019,2030,2041,2052|0423:2057|0426:2038"},{"id":"ascension_day","name":"Ascension Day","en":"Ascension Day","category":"public","regional":{"AC":"d|0503:2035,2046|0505:2016|0506:2027,2032|0507:2043,2054|0508:2059|0509:2024|0510:2018,2029,2040|0511:2051,2056|0513:2021|0514:2015,2026,2037,2048|0515:2042,2053|0518:2023,2034,2045|0519:2039,2050|0521:2020|0522:2031,2036|0523:2047,2058|0525:2017,2028|0526:2022,2033,2044|0527:2049,2055,2060|0529:2025|0530:2019,2030,2041,2052|0531:2057|0603:2038","TA":"d|0503:2035,2046|0505:2016|0506:2027,2032|0507:2043,2054|0508:2059|0509:2024|0510:2018,2029,2040|0511:2051,2056|0513:2021|0514:2015,2026,2037,2048|0515:2042,2053|0518:2023,2034,2045|0519:2039,2050|0521:2020|0522:2031,2036|0523:2047,2058|0525:2017,2028|0526:2022,2033,2044|0527:2049,2055,2060|0529:2025|0530:2019,2030,2041,2052|0531:2057|0603:2038"}},{"id":"saint_helena_day","name":"Saint Helena Day","en":"Saint Helena Day","category":"public","regional":{"HL":"d|0520:2016,2022,2033,2039,2044,2050|0521:2015-2060|0522:2017,2023,2028,2034,2045,2051,2056"}},{"id":"king_s_birthday","name":"King's Birthday","en":"King's Birthday","category":"public","dates":"d|0619:2023|1111:2033,2039,2044,2050|1112:2027,2032,2038,2049,2055,2060|1113:2026,2037,2043,2048,2054|1114:2025,2031,2036,2042,2053,2059|1115:2024,2030,2041,2047,2052,2058|1116:2029,2035,2040,2046,2057|1117:2028,2034,2045,2051,2056"},{"id":"anniversary_day","name":"Anniversary Day","en":"Anniversary Day","category":"public","regional":{"TA":"d|0814:2015-2060"}},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1225:2015-2060|1227:2016,2021-2022,2027,2032-2033,2038-2039,2044,2049-2050,2055,2060"},{"id":"boxing_day","name":"Boxing Day","en":"Boxing Day","category":"public","dates":"d|1226:2015-2060|1228:2015,2020-2021,2026-2027,2032,2037-2038,2043,2048-2049,2054-2055,2060"},{"id":"new_year_s_day_government","name":"New Year's Day","en":"New Year's Day","category":"government","dates":"d|0101:2015-2060|0102:2017,2023,2034,2040,2045,2051|0103:2022,2028,2033,2039,2050,2056"},{"id":"pentecost_monday_government","name":"Whit Monday","en":"Pentecost Monday","category":"government","dates":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2024|0521:2018,2029,2040|0522:2051,2056|0524:2021|0525:2015,2026,2037,2048|0526:2042,2053|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2025|0610:2019,2030,2041,2052|0611:2057|0614:2038"},{"id":"august_bank_holiday_government","name":"August Bank Holiday","en":"August Bank Holiday","category":"government","regional":{"AC":"d|0825:2025,2031,2036,2042,2053,2059|0826:2019,2024,2030,2041,2047,2052,2058|0827:2018,2029,2035,2040,2046,2057|0828:2017,2023,2028,2034,2045,2051,2056|0829:2016,2022,2033,2039,2044,2050|0830:2021,2027,2032,2038,2049,2055,2060|0831:2015,2020,2026,2037,2043,2048,2054","HL":"d|0825:2025,2031,2036,2042,2053,2059|0826:2019,2024,2030,2041,2047,2052,2058|0827:2018,2029,2035,2040,2046,2057|0828:2017,2023,2028,2034,2045,2051,2056|0829:2016,2022,2033,2039,2044,2050|0830:2021,2027,2032,2038,2049,2055,2060|0831:2015,2020,2026,2037,2043,2048,2054"}}]} diff --git a/app/Support/Holidays/data/SI.json b/app/Support/Holidays/data/SI.json new file mode 100644 index 000000000..16a58e0ce --- /dev/null +++ b/app/Support/Holidays/data/SI.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"SI","language":"sl","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"novo leto","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2010-2012,2017-2060"},{"id":"preseren_s_day_the_slovenian_cultural_holiday","name":"Prešernov dan, slovenski kulturni praznik","en":"Prešeren's Day, the Slovenian Cultural Holiday","category":"public","dates":"d|0208:2010-2060"},{"id":"easter_sunday","name":"velikonočna nedelja","en":"Easter Sunday","category":"public","dates":"d|0325:2035,2046|0327:2016|0328:2027,2032|0329:2043,2054|0330:2059|0331:2013,2024|0401:2018,2029,2040|0402:2051,2056|0404:2010,2021|0405:2015,2026,2037,2048|0406:2042,2053|0408:2012|0409:2023,2034,2045|0410:2039,2050|0412:2020|0413:2031,2036|0414:2047,2058|0416:2017,2028|0417:2022,2033,2044|0418:2049,2055,2060|0420:2014,2025|0421:2019,2030,2041,2052|0422:2057|0424:2011|0425:2038"},{"id":"easter_monday","name":"velikonočni ponedeljek","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"day_of_uprising_against_occupation","name":"dan upora proti okupatorju","en":"Day of Uprising Against Occupation","category":"public","dates":"d|0427:2010-2060"},{"id":"labor_day","name":"praznik dela","en":"Labor Day","category":"public","dates":"d|0501:2010-2060|0502:2010-2060"},{"id":"pentecost","name":"binkoštna nedelja","en":"Pentecost","category":"public","dates":"d|0513:2035,2046|0515:2016|0516:2027,2032|0517:2043,2054|0518:2059|0519:2013,2024|0520:2018,2029,2040|0521:2051,2056|0523:2010,2021|0524:2015,2026,2037,2048|0525:2042,2053|0527:2012|0528:2023,2034,2045|0529:2039,2050|0531:2020|0601:2031,2036|0602:2047,2058|0604:2017,2028|0605:2022,2033,2044|0606:2049,2055,2060|0608:2014,2025|0609:2019,2030,2041,2052|0610:2057|0612:2011|0613:2038"},{"id":"statehood_day","name":"dan državnosti","en":"Statehood Day","category":"public","dates":"d|0625:2010-2060"},{"id":"assumption_day","name":"Marijino vnebovzetje","en":"Assumption Day","category":"public","dates":"d|0815:2010-2060"},{"id":"reformation_day","name":"dan reformacije","en":"Reformation Day","category":"public","dates":"d|1031:2010-2060"},{"id":"day_of_remembrance_for_the_dead","name":"dan spomina na mrtve","en":"Day of Remembrance for the Dead","category":"public","dates":"d|1101:2010-2060"},{"id":"christmas_day","name":"božič","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"},{"id":"independence_and_unity_day","name":"dan samostojnosti in enotnosti","en":"Independence and Unity Day","category":"public","dates":"d|1226:2010-2060"}]} diff --git a/app/Support/Holidays/data/SJ.json b/app/Support/Holidays/data/SJ.json new file mode 100644 index 000000000..b71cd435c --- /dev/null +++ b/app/Support/Holidays/data/SJ.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"SJ","language":"no","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Første nyttårsdag","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"maundy_thursday","name":"Skjærtorsdag","en":"Maundy Thursday","category":"public","dates":"d|0322:2035,2046|0324:2016|0325:2027,2032|0326:2043,2054|0327:2059|0328:2013,2024|0329:2018,2029,2040|0330:2051,2056|0401:2010,2021|0402:2015,2026,2037,2048|0403:2042,2053|0405:2012|0406:2023,2034,2045|0407:2039,2050|0409:2020|0410:2031,2036|0411:2047,2058|0413:2017,2028|0414:2022,2033,2044|0415:2049,2055,2060|0417:2014,2025|0418:2019,2030,2041,2052|0419:2057|0421:2011|0422:2038"},{"id":"good_friday","name":"Langfredag","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_sunday","name":"Første påskedag","en":"Easter Sunday","category":"public","dates":"d|0325:2035,2046|0327:2016|0328:2027,2032|0329:2043,2054|0330:2059|0331:2013,2024|0401:2018,2029,2040|0402:2051,2056|0404:2010,2021|0405:2015,2026,2037,2048|0406:2042,2053|0408:2012|0409:2023,2034,2045|0410:2039,2050|0412:2020|0413:2031,2036|0414:2047,2058|0416:2017,2028|0417:2022,2033,2044|0418:2049,2055,2060|0420:2014,2025|0421:2019,2030,2041,2052|0422:2057|0424:2011|0425:2038"},{"id":"easter_monday","name":"Andre påskedag","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"labor_day","name":"Arbeidernes dag","en":"Labor Day","category":"public","dates":"d|0501:2010-2060"},{"id":"ascension_day","name":"Kristi himmelfartsdag","en":"Ascension Day","category":"public","dates":"d|0503:2035,2046|0505:2016|0506:2027,2032|0507:2043,2054|0508:2059|0509:2013,2024|0510:2018,2029,2040|0511:2051,2056|0513:2010,2021|0514:2015,2026,2037,2048|0515:2042,2053|0517:2012|0518:2023,2034,2045|0519:2039,2050|0521:2020|0522:2031,2036|0523:2047,2058|0525:2017,2028|0526:2022,2033,2044|0527:2049,2055,2060|0529:2014,2025|0530:2019,2030,2041,2052|0531:2057|0602:2011|0603:2038"},{"id":"pentecost","name":"Første pinsedag","en":"Pentecost","category":"public","dates":"d|0513:2035,2046|0515:2016|0516:2027,2032|0517:2043,2054|0518:2059|0519:2013,2024|0520:2018,2029,2040|0521:2051,2056|0523:2010,2021|0524:2015,2026,2037,2048|0525:2042,2053|0527:2012|0528:2023,2034,2045|0529:2039,2050|0531:2020|0601:2031,2036|0602:2047,2058|0604:2017,2028|0605:2022,2033,2044|0606:2049,2055,2060|0608:2014,2025|0609:2019,2030,2041,2052|0610:2057|0612:2011|0613:2038"},{"id":"pentecost_monday","name":"Andre pinsedag","en":"Pentecost Monday","category":"public","dates":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038"},{"id":"constitution_day","name":"Grunnlovsdag","en":"Constitution Day","category":"public","dates":"d|0517:2010-2060"},{"id":"christmas_day","name":"Første juledag","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"},{"id":"second_day_of_christmas","name":"Andre juledag","en":"Second Day of Christmas","category":"public","dates":"d|1226:2010-2060"}]} diff --git a/app/Support/Holidays/data/SK.json b/app/Support/Holidays/data/SK.json new file mode 100644 index 000000000..5ac8d5143 --- /dev/null +++ b/app/Support/Holidays/data/SK.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"SK","language":"sk","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"day_of_the_establishment_of_the_slovak_republic","name":"Deň vzniku Slovenskej republiky","en":"Day of the Establishment of the Slovak Republic","category":"public","dates":"d|0101:2010-2060"},{"id":"epiphany_three_kings_day_and_orthodox_christmas","name":"Zjavenie Pána (Traja králi a vianočný sviatok pravoslávnych kresťanov)","en":"Epiphany (Three Kings' Day and Orthodox Christmas)","category":"public","dates":"d|0106:2010-2060"},{"id":"good_friday","name":"Veľký piatok","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_monday","name":"Veľkonočný pondelok","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"labor_day","name":"Sviatok práce","en":"Labor Day","category":"public","dates":"d|0501:2010-2060"},{"id":"day_of_victory_over_fascism","name":"Deň víťazstva nad fašizmom","en":"Day of Victory over Fascism","category":"public","dates":"d|0508:2010-2025,2027-2060"},{"id":"saints_cyril_and_methodius_day","name":"Sviatok svätého Cyrila a svätého Metoda","en":"Saints Cyril and Methodius Day","category":"public","dates":"d|0705:2010-2060"},{"id":"slovak_national_uprising_anniversary","name":"Výročie Slovenského národného povstania","en":"Slovak National Uprising Anniversary","category":"public","dates":"d|0829:2010-2060"},{"id":"day_of_our_lady_of_the_seven_sorrows","name":"Sedembolestná Panna Mária","en":"Day of Our Lady of the Seven Sorrows","category":"public","dates":"d|0915:2010-2025,2027-2060"},{"id":"all_saints_day","name":"Sviatok Všetkých svätých","en":"All Saints' Day","category":"public","dates":"d|1101:2010-2060"},{"id":"christmas_eve","name":"Štedrý deň","en":"Christmas Eve","category":"public","dates":"d|1224:2010-2060"},{"id":"christmas_day","name":"Prvý sviatok vianočný","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"},{"id":"second_day_of_christmas","name":"Druhý sviatok vianočný","en":"Second Day of Christmas","category":"public","dates":"d|1226:2010-2060"}]} diff --git a/app/Support/Holidays/data/SL.json b/app/Support/Holidays/data/SL.json new file mode 100644 index 000000000..30894a97e --- /dev/null +++ b/app/Support/Holidays/data/SL.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"SL","language":"en_SL","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051|0103:2011,2022,2028,2033,2039,2050,2056"},{"id":"eid_al_fitr","name":"Eid al-Fitr","en":"Eid al-Fitr","category":"public","dates":"y|2010:0910|2011:0830|2012:0819-0820|2013:0808|2014:0728|2015:0717|2016:0706|2017:0625-0626|2018:0615|2019:0605|2020:0524-0525|2021:0513|2022:0502|2023:0421|2024:0410|2025:0330-0331|2026:0320|2027:0309|2028:0226,0228|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,0104,1223|2034:1212|2035:1201,1203|2036:1119|2037:1108-1109|2038:1029|2039:1019|2040:1007-1008|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712-0713|2049:0701|2050:0620|2051:0610,0612|2052:0529|2053:0519|2054:0509,0511|2055:0428|2056:0416-0417|2057:0405|2058:0325|2059:0315,0317|2060:0304","estimated":"y|2010:0910|2011:0830|2012:0819|2013:0808|2014:0728|2015:0717|2016:0706|2017:0625|2025:0330|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304"},{"id":"prophet_s_birthday","name":"Prophet's Birthday","en":"Prophet's Birthday","category":"public","dates":"y|2010:0226|2011:0215|2012:0204,0206|2013:0124|2014:0113|2015:0103,0105,1223|2016:1211-1212|2017:1130|2018:1121|2019:1110-1111|2020:1029|2021:1018|2022:1008,1010|2023:0927|2024:0915-0916|2025:0904|2026:0825|2027:0814,0816|2028:0803|2029:0724|2030:0713,0715|2031:0702|2032:0620-0621|2033:0609|2034:0530|2035:0520-0521|2036:0508|2037:0428|2038:0417,0419|2039:0406|2040:0325-0326|2041:0315|2042:0304|2043:0222-0223|2044:0211|2045:0130|2046:0119|2047:0108,1229-1230|2048:1218|2049:1207|2050:1126,1128|2051:1116|2052:1104|2053:1024|2054:1013|2055:1003-1004|2056:0922|2057:0911|2058:0831,0902|2059:0820|2060:0808-0809","estimated":"y|2010:0226|2011:0215|2012:0204|2013:0124|2014:0113|2015:0103,1223|2016:1211|2017:1130|2025:0904|2026:0825|2027:0814|2028:0803|2029:0724|2030:0713|2031:0702|2032:0620|2033:0609|2034:0530|2035:0520|2036:0508|2037:0428|2038:0417|2039:0406|2040:0325|2041:0315|2042:0304|2043:0222|2044:0211|2045:0130|2046:0119|2047:0108,1229|2048:1218|2049:1207|2050:1126|2051:1116|2052:1104|2053:1024|2054:1013|2055:1003|2056:0922|2057:0911|2058:0831|2059:0820|2060:0808"},{"id":"eid_al_adha","name":"Eid al-Adha","en":"Eid al-Adha","category":"public","dates":"y|2010:1116|2011:1106-1107|2012:1026|2013:1015|2014:1004,1006|2015:0923|2016:0911-0912|2017:0901|2018:0822|2019:0812|2020:0731|2021:0720|2022:0709,0711|2023:0628|2024:0616-0617|2025:0606|2026:0527|2027:0516-0517|2028:0505|2029:0424|2030:0413,0415|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218-0219|2036:0207|2037:0126|2038:0116,0118|2039:0105,1226|2040:1214|2041:1204|2042:1123-1124|2043:1112|2044:1031|2045:1021,1023|2046:1010|2047:0930|2048:0919,0921|2049:0908|2050:0828-0829|2051:0817|2052:0805|2053:0726,0728|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601,0603|2059:0522|2060:0510","estimated":"y|2010:1116|2011:1106|2012:1026|2013:1015|2014:1004|2015:0923|2016:0911|2017:0901|2025:0606|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510"},{"id":"armed_forces_day","name":"Armed Forces Day","en":"Armed Forces Day","category":"public","dates":"d|0218:2010-2060|0219:2018,2024,2029,2035,2046,2052,2057|0220:2012,2017,2023,2034,2040,2045,2051"},{"id":"international_women_s_day","name":"International Women's Day","en":"International Women's Day","category":"public","dates":"d|0308:2018-2060|0309:2020,2026,2037,2043,2048,2054|0310:2025,2031,2036,2042,2053,2059"},{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_monday","name":"Easter Monday","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"independence_day","name":"Independence Day","en":"Independence Day","category":"public","dates":"d|0427:2010-2060|0428:2014,2025,2031,2036,2042,2053,2059|0429:2013,2019,2024,2030,2041,2047,2052,2058"},{"id":"international_worker_s_day","name":"International Worker's Day","en":"International Worker's Day","category":"public","dates":"d|0501:2010-2060|0502:2011,2016,2033,2039,2044,2050|0503:2010,2021-2022,2027,2032,2038,2049,2055,2060"},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060|1227:2010-2011,2016,2021-2022,2027,2032-2033,2038-2039,2044,2049-2050,2055,2060"},{"id":"boxing_day","name":"Boxing Day","en":"Boxing Day","category":"public","dates":"d|1226:2010-2060|1228:2010,2015,2020-2021,2026-2027,2032,2037-2038,2043,2048-2049,2054-2055,2060"}]} diff --git a/app/Support/Holidays/data/SM.json b/app/Support/Holidays/data/SM.json new file mode 100644 index 000000000..a28e942c1 --- /dev/null +++ b/app/Support/Holidays/data/SM.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"SM","language":"it","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Capodanno","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"epiphany","name":"Epifania","en":"Epiphany","category":"public","dates":"d|0106:2010-2060"},{"id":"anniversary_of_the_liberation_of_the_republic_an","name":"Anniversario della Liberazione della Repubblica e Festa di Sant'Agata","en":"Anniversary of the Liberation of the Republic and Feast of Saint Agatha","category":"public","dates":"d|0205:2010-2060"},{"id":"anniversary_of_the_arengo","name":"Anniversario dell'Arengo","en":"Anniversary of the Arengo","category":"public","dates":"d|0325:2010-2060"},{"id":"easter_sunday","name":"Pasqua","en":"Easter Sunday","category":"public","dates":"d|0325:2035,2046|0327:2016|0328:2027,2032|0329:2043,2054|0330:2059|0331:2013,2024|0401:2018,2029,2040|0402:2051,2056|0404:2010,2021|0405:2015,2026,2037,2048|0406:2042,2053|0408:2012|0409:2023,2034,2045|0410:2039,2050|0412:2020|0413:2031,2036|0414:2047,2058|0416:2017,2028|0417:2022,2033,2044|0418:2049,2055,2060|0420:2014,2025|0421:2019,2030,2041,2052|0422:2057|0424:2011|0425:2038"},{"id":"easter_monday","name":"Lunedì dell'angelo","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"investiture_of_captains_regent","name":"Investitura Capitani Reggenti","en":"Investiture of Captains Regent","category":"public","dates":"d|0401:2010-2060|1001:2010-2060"},{"id":"workers_day","name":"Festa dei lavoratori","en":"Workers' Day","category":"public","dates":"d|0501:2010-2060"},{"id":"corpus_christi","name":"Corpus Domini","en":"Corpus Christi","category":"public","dates":"d|0524:2035,2046|0526:2016|0527:2027,2032|0528:2043,2054|0529:2059|0530:2013,2024|0531:2018,2029,2040|0601:2051,2056|0603:2010,2021|0604:2015,2026,2037,2048|0605:2042,2053|0607:2012|0608:2023,2034,2045|0609:2039,2050|0611:2020|0612:2031,2036|0613:2047,2058|0615:2017,2028|0616:2022,2033,2044|0617:2049,2055,2060|0619:2014,2025|0620:2019,2030,2041,2052|0621:2057|0623:2011|0624:2038"},{"id":"anniversary_of_the_fall_of_fascism_and_freedom_d","name":"Anniversario della Caduta del Fascismo e Festa della Libertà","en":"Anniversary of the Fall of Fascism and Freedom Day","category":"public","dates":"d|0728:2010-2060"},{"id":"assumption_day","name":"Assunzione della B.V. Maria","en":"Assumption Day","category":"public","dates":"d|0815:2010-2060"},{"id":"saint_marinus_day_anniversary_of_the_founding_of","name":"San Marino, Anniversario di Fondazione della Repubblica","en":"Saint Marinus' Day, Anniversary of the Founding of the Republic","category":"public","dates":"d|0903:2010-2060"},{"id":"all_saints_day","name":"Tutti i Santi","en":"All Saints' Day","category":"public","dates":"d|1101:2010-2060"},{"id":"commemoration_of_the_dead","name":"Commemorazione dei defunti","en":"Commemoration of the Dead","category":"public","dates":"d|1102:2010-2060"},{"id":"immaculate_conception","name":"Immacolata Concezione","en":"Immaculate Conception","category":"public","dates":"d|1208:2010-2060"},{"id":"christmas_day","name":"Natale","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"},{"id":"saint_stephen_s_day","name":"Santo Stefano","en":"Saint Stephen's Day","category":"public","dates":"d|1226:2010-2060"},{"id":"christmas_eve_bank","name":"Vigilia di Natale","en":"Christmas Eve","category":"bank","dates":"d|1224:2010-2060"},{"id":"new_year_s_eve_bank","name":"Ultimo dell'anno","en":"New Year's Eve","category":"bank","dates":"d|1231:2010-2060"}]} diff --git a/app/Support/Holidays/data/SN.json b/app/Support/Holidays/data/SN.json new file mode 100644 index 000000000..f8fc7229c --- /dev/null +++ b/app/Support/Holidays/data/SN.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"SN","language":"fr_SN","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Jour de l'an","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"ashura","name":"Tamxarit","en":"Ashura","category":"public","dates":"y|2010:1216|2011:1205|2012:1124|2013:1113|2014:1103|2015:1023|2016:1011|2017:0930|2018:0920|2019:0909|2020:0829|2021:0818|2022:0808|2023:0728|2024:0717|2025:0705|2026:0625|2027:0615|2028:0603|2029:0523|2030:0512|2031:0502|2032:0420|2033:0410|2034:0330|2035:0320|2036:0308|2037:0225|2038:0214|2039:0204|2040:0124|2041:0113|2042:0102,1223|2043:1212|2044:1130|2045:1119|2046:1109|2047:1029|2048:1018|2049:1007|2050:0926|2051:0915|2052:0904|2053:0824|2054:0814|2055:0803|2056:0723|2057:0712|2058:0701|2059:0620|2060:0609","estimated":"y|2010:1216|2011:1205|2012:1124|2013:1113|2014:1103|2015:1023|2016:1011|2017:0930|2018:0920|2019:0909|2025:0705|2026:0625|2027:0615|2028:0603|2029:0523|2030:0512|2031:0502|2032:0420|2033:0410|2034:0330|2035:0320|2036:0308|2037:0225|2038:0214|2039:0204|2040:0124|2041:0113|2042:0102,1223|2043:1212|2044:1130|2045:1119|2046:1109|2047:1029|2048:1018|2049:1007|2050:0926|2051:0915|2052:0904|2053:0824|2054:0814|2055:0803|2056:0723|2057:0712|2058:0701|2059:0620|2060:0609"},{"id":"eid_al_fitr","name":"Journée de la Korité","en":"Eid al-Fitr","category":"public","dates":"y|2010:0910|2011:0830|2012:0819-0820|2013:0808|2014:0728|2015:0717|2016:0706|2017:0625-0626|2018:0615|2019:0604|2020:0524-0525|2021:0512|2022:0501-0502|2023:0422|2024:0410|2025:0330-0331|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102-0103,1223|2034:1212|2035:1201|2036:1119|2037:1108-1109|2038:1029|2039:1019|2040:1007-1008|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712-0713|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416-0417|2057:0405|2058:0325|2059:0315|2060:0304","estimated":"y|2010:0910|2011:0830|2012:0819|2013:0808|2014:0728|2015:0717|2016:0706|2017:0625|2018:0615|2019:0604|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304"},{"id":"prophet_s_birthday","name":"Journée du Maouloud","en":"Prophet's Birthday","category":"public","dates":"y|2010:0226|2011:0215|2012:0204|2013:0124|2014:0113|2015:0103,1223|2016:1211|2017:1130|2018:1120|2019:1109|2020:1029|2021:1018|2022:1008|2023:0927|2024:0915|2025:0904|2026:0825|2027:0814|2028:0803|2029:0724|2030:0713|2031:0702|2032:0620|2033:0609|2034:0530|2035:0520|2036:0508|2037:0428|2038:0417|2039:0406|2040:0325|2041:0315|2042:0304|2043:0222|2044:0211|2045:0130|2046:0119|2047:0108,1229|2048:1218|2049:1207|2050:1126|2051:1116|2052:1104|2053:1024|2054:1013|2055:1003|2056:0922|2057:0911|2058:0831|2059:0820|2060:0808","estimated":"y|2010:0226|2011:0215|2012:0204|2013:0124|2014:0113|2015:0103,1223|2016:1211|2017:1130|2018:1120|2019:1109|2025:0904|2026:0825|2027:0814|2028:0803|2029:0724|2030:0713|2031:0702|2032:0620|2033:0609|2034:0530|2035:0520|2036:0508|2037:0428|2038:0417|2039:0406|2040:0325|2041:0315|2042:0304|2043:0222|2044:0211|2045:0130|2046:0119|2047:0108,1229|2048:1218|2049:1207|2050:1126|2051:1116|2052:1104|2053:1024|2054:1013|2055:1003|2056:0922|2057:0911|2058:0831|2059:0820|2060:0808"},{"id":"eid_al_adha","name":"Journée de la Tabaski","en":"Eid al-Adha","category":"public","dates":"y|2010:1116|2011:1106-1107|2012:1026|2013:1015|2014:1004|2015:0923|2016:0911-0912|2017:0901|2018:0821|2019:0811-0812|2020:0731|2021:0721|2022:0710-0711|2023:0629|2024:0617|2025:0606|2026:0527|2027:0516-0517|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218-0219|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123-1124|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828-0829|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510","estimated":"y|2010:1116|2011:1106|2012:1026|2013:1015|2014:1004|2015:0923|2016:0911|2017:0901|2018:0821|2019:0811|2025:0606|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510"},{"id":"grand_magal_of_touba","name":"Grand Magal de Touba","en":"Grand Magal of Touba","category":"public","dates":"y|2014:1210|2015:1130|2016:1118|2017:1107|2018:1027|2019:1017|2020:1006|2021:0926|2022:0915|2023:0904|2024:0823|2025:0812|2026:0801|2027:0722|2028:0711|2029:0630|2030:0619|2031:0608|2032:0527|2033:0517|2034:0507|2035:0426|2036:0415|2037:0404|2038:0324|2039:0313|2040:0302|2041:0219|2042:0209|2043:0129|2044:0119|2045:0107,1227|2046:1216|2047:1206|2048:1124|2049:1114|2050:1103|2051:1023|2052:1011|2053:0930|2054:0920|2055:0910|2056:0829|2057:0818|2058:0807|2059:0728|2060:0716","estimated":"y|2014:1210|2015:1130|2016:1118|2017:1107|2018:1027|2019:1017|2025:0812|2026:0801|2027:0722|2028:0711|2029:0630|2030:0619|2031:0608|2032:0527|2033:0517|2034:0507|2035:0426|2036:0415|2037:0404|2038:0324|2039:0313|2040:0302|2041:0219|2042:0209|2043:0129|2044:0119|2045:0107,1227|2046:1216|2047:1206|2048:1124|2049:1114|2050:1103|2051:1023|2052:1011|2053:0930|2054:0920|2055:0910|2056:0829|2057:0818|2058:0807|2059:0728|2060:0716"},{"id":"easter_monday","name":"Lundi de Pâques","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"independence_day","name":"Fête de l'Indépendance","en":"Independence Day","category":"public","dates":"d|0404:2010-2060"},{"id":"labor_day","name":"Fête du Travail","en":"Labor Day","category":"public","dates":"d|0501:2010-2060"},{"id":"ascension_day","name":"Jeudi de l'Ascension","en":"Ascension Day","category":"public","dates":"d|0503:2035,2046|0505:2016|0506:2027,2032|0507:2043,2054|0508:2059|0509:2013,2024|0510:2018,2029,2040|0511:2051,2056|0513:2010,2021|0514:2015,2026,2037,2048|0515:2042,2053|0517:2012|0518:2023,2034,2045|0519:2039,2050|0521:2020|0522:2031,2036|0523:2047,2058|0525:2017,2028|0526:2022,2033,2044|0527:2049,2055,2060|0529:2014,2025|0530:2019,2030,2041,2052|0531:2057|0602:2011|0603:2038"},{"id":"pentecost_monday","name":"Lundi de Pentecôte","en":"Pentecost Monday","category":"public","dates":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038"},{"id":"assumption_day","name":"Assomption","en":"Assumption Day","category":"public","dates":"d|0815:2010-2060"},{"id":"all_saints_day","name":"Toussaint","en":"All Saints' Day","category":"public","dates":"d|1101:2010-2060"},{"id":"christmas_day","name":"Noël","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"}]} diff --git a/app/Support/Holidays/data/SO.json b/app/Support/Holidays/data/SO.json new file mode 100644 index 000000000..cc822599d --- /dev/null +++ b/app/Support/Holidays/data/SO.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"SO","language":null,"years":[2010,2060],"subdivisions":{},"holidays":[{"id":"isra_and_mi_raj","name":"Isra' and Mi'raj","en":"Isra' and Mi'raj","category":"public","dates":"y|2010:0709|2011:0629|2012:0617|2013:0606|2014:0526|2015:0516|2016:0504|2017:0424|2018:0413|2019:0403|2020:0322|2021:0311|2022:0228|2023:0218|2024:0208|2025:0127|2026:0116|2027:0105,1225|2028:1214|2029:1203|2030:1123|2031:1112|2032:1101|2033:1021|2034:1010|2035:0929|2036:0918|2037:0907|2038:0828|2039:0817|2040:0805|2041:0725|2042:0715|2043:0704|2044:0623|2045:0613|2046:0602|2047:0522|2048:0510|2049:0429|2050:0419|2051:0409|2052:0328|2053:0318|2054:0307|2055:0224|2056:0213|2057:0201|2058:0122|2059:0112|2060:0101,1220","estimated":"y|2010:0709|2011:0629|2012:0617|2013:0606|2014:0526|2015:0516|2016:0504|2017:0424|2018:0413|2019:0403|2020:0322|2021:0311|2022:0228|2023:0218|2024:0208|2025:0127|2026:0116|2027:0105,1225|2028:1214|2029:1203|2030:1123|2031:1112|2032:1101|2033:1021|2034:1010|2035:0929|2036:0918|2037:0907|2038:0828|2039:0817|2040:0805|2041:0725|2042:0715|2043:0704|2044:0623|2045:0613|2046:0602|2047:0522|2048:0510|2049:0429|2050:0419|2051:0409|2052:0328|2053:0318|2054:0307|2055:0224|2056:0213|2057:0201|2058:0122|2059:0112|2060:0101,1220"},{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"ashura","name":"Ashura","en":"Ashura","category":"public","dates":"y|2010:1216|2011:1205|2012:1124|2013:1113|2014:1103|2015:1023|2016:1011|2017:0930|2018:0920|2019:0909|2020:0829|2021:0818|2022:0808|2023:0728|2024:0716|2025:0705|2026:0625|2027:0615|2028:0603|2029:0523|2030:0512|2031:0502|2032:0420|2033:0410|2034:0330|2035:0320|2036:0308|2037:0225|2038:0214|2039:0204|2040:0124|2041:0113|2042:0102,1223|2043:1212|2044:1130|2045:1119|2046:1109|2047:1029|2048:1018|2049:1007|2050:0926|2051:0915|2052:0904|2053:0824|2054:0814|2055:0803|2056:0723|2057:0712|2058:0701|2059:0620|2060:0609","estimated":"y|2010:1216|2011:1205|2012:1124|2013:1113|2014:1103|2015:1023|2016:1011|2017:0930|2018:0920|2019:0909|2020:0829|2021:0818|2022:0808|2023:0728|2024:0716|2025:0705|2026:0625|2027:0615|2028:0603|2029:0523|2030:0512|2031:0502|2032:0420|2033:0410|2034:0330|2035:0320|2036:0308|2037:0225|2038:0214|2039:0204|2040:0124|2041:0113|2042:0102,1223|2043:1212|2044:1130|2045:1119|2046:1109|2047:1029|2048:1018|2049:1007|2050:0926|2051:0915|2052:0904|2053:0824|2054:0814|2055:0803|2056:0723|2057:0712|2058:0701|2059:0620|2060:0609"},{"id":"eid_al_fitr","name":"Eid al-Fitr","en":"Eid al-Fitr","category":"public","dates":"y|2010:0910|2011:0830|2012:0819|2013:0808|2014:0728|2015:0717|2016:0706|2017:0625|2018:0615|2019:0604|2020:0524|2021:0513|2022:0502|2023:0421|2024:0410|2025:0330|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304","estimated":"y|2010:0910|2011:0830|2012:0819|2013:0808|2014:0728|2015:0717|2016:0706|2017:0625|2018:0615|2019:0604|2020:0524|2021:0513|2022:0502|2023:0421|2024:0410|2025:0330|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304"},{"id":"prophet_s_birthday","name":"Prophet's Birthday","en":"Prophet's Birthday","category":"public","dates":"y|2010:0226|2011:0215|2012:0204|2013:0124|2014:0113|2015:0103,1223|2016:1211|2017:1130|2018:1120|2019:1109|2020:1029|2021:1018|2022:1008|2023:0927|2024:0915|2025:0904|2026:0825|2027:0814|2028:0803|2029:0724|2030:0713|2031:0702|2032:0620|2033:0609|2034:0530|2035:0520|2036:0508|2037:0428|2038:0417|2039:0406|2040:0325|2041:0315|2042:0304|2043:0222|2044:0211|2045:0130|2046:0119|2047:0108,1229|2048:1218|2049:1207|2050:1126|2051:1116|2052:1104|2053:1024|2054:1013|2055:1003|2056:0922|2057:0911|2058:0831|2059:0820|2060:0808","estimated":"y|2010:0226|2011:0215|2012:0204|2013:0124|2014:0113|2015:0103,1223|2016:1211|2017:1130|2018:1120|2019:1109|2020:1029|2021:1018|2022:1008|2023:0927|2024:0915|2025:0904|2026:0825|2027:0814|2028:0803|2029:0724|2030:0713|2031:0702|2032:0620|2033:0609|2034:0530|2035:0520|2036:0508|2037:0428|2038:0417|2039:0406|2040:0325|2041:0315|2042:0304|2043:0222|2044:0211|2045:0130|2046:0119|2047:0108,1229|2048:1218|2049:1207|2050:1126|2051:1116|2052:1104|2053:1024|2054:1013|2055:1003|2056:0922|2057:0911|2058:0831|2059:0820|2060:0808"},{"id":"islamic_new_year","name":"Islamic New Year","en":"Islamic New Year","category":"public","dates":"y|2010:1207|2011:1126|2012:1115|2013:1104|2014:1025|2015:1014|2016:1002|2017:0921|2018:0911|2019:0831|2020:0820|2021:0809|2022:0730|2023:0719|2024:0707|2025:0626|2026:0616|2027:0606|2028:0525|2029:0514|2030:0503|2031:0423|2032:0411|2033:0401|2034:0321|2035:0311|2036:0228|2037:0216|2038:0205|2039:0126|2040:0115|2041:0104,1224|2042:1214|2043:1203|2044:1121|2045:1110|2046:1031|2047:1020|2048:1009|2049:0928|2050:0917|2051:0906|2052:0826|2053:0815|2054:0805|2055:0725|2056:0714|2057:0703|2058:0622|2059:0611|2060:0531","estimated":"y|2010:1207|2011:1126|2012:1115|2013:1104|2014:1025|2015:1014|2016:1002|2017:0921|2018:0911|2019:0831|2020:0820|2021:0809|2022:0730|2023:0719|2024:0707|2025:0626|2026:0616|2027:0606|2028:0525|2029:0514|2030:0503|2031:0423|2032:0411|2033:0401|2034:0321|2035:0311|2036:0228|2037:0216|2038:0205|2039:0126|2040:0115|2041:0104,1224|2042:1214|2043:1203|2044:1121|2045:1110|2046:1031|2047:1020|2048:1009|2049:0928|2050:0917|2051:0906|2052:0826|2053:0815|2054:0805|2055:0725|2056:0714|2057:0703|2058:0622|2059:0611|2060:0531"},{"id":"eid_al_adha","name":"Eid al-Adha","en":"Eid al-Adha","category":"public","dates":"y|2010:1116|2011:1106|2012:1026|2013:1015|2014:1004|2015:0923|2016:0911|2017:0901|2018:0821|2019:0811|2020:0731|2021:0720|2022:0709|2023:0628|2024:0616|2025:0606|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510","estimated":"y|2010:1116|2011:1106|2012:1026|2013:1015|2014:1004|2015:0923|2016:0911|2017:0901|2018:0821|2019:0811|2020:0731|2021:0720|2022:0709|2023:0628|2024:0616|2025:0606|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510"},{"id":"labour_day","name":"Labour Day","en":"Labour Day","category":"public","dates":"d|0501:2010-2060"},{"id":"independence_day","name":"Independence Day","en":"Independence Day","category":"public","dates":"d|0626:2010-2060"},{"id":"republic_day","name":"Republic Day","en":"Republic Day","category":"public","dates":"d|0701:2010-2060"}]} diff --git a/app/Support/Holidays/data/SR.json b/app/Support/Holidays/data/SR.json new file mode 100644 index 000000000..6cf871eb2 --- /dev/null +++ b/app/Support/Holidays/data/SR.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"SR","language":"nl","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Nieuwjaarsdag","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"eid_al_fitr","name":"Ied-Ul-Fitre","en":"Eid al-Fitr","category":"public","dates":"y|2010:0910|2011:0830|2012:0819|2013:0808|2014:0729|2015:0718|2016:0707|2017:0626|2018:0615|2019:0605|2020:0524|2021:0513|2022:0502|2023:0422|2024:0410|2025:0331|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304","estimated":"y|2010:0910|2011:0830|2012:0819|2013:0808|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304"},{"id":"eid_al_adha","name":"Ied-Ul-Adha","en":"Eid al-Adha","category":"public","dates":"y|2012:1026|2013:1015|2014:1005|2015:0924|2016:0913|2017:0902|2018:0821|2019:0812|2020:0731|2021:0720|2022:0709|2023:0629|2024:0616|2025:0607|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510","estimated":"y|2012:1026|2013:1015|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510"},{"id":"chinese_new_year","name":"Chinees Nieuwjaar","en":"Chinese New Year","category":"public","dates":"d|0122:2023,2042|0123:2031,2050|0124:2039,2058|0126:2028,2047|0128:2036,2055|0129:2025|0130:2044|0131:2033|0201:2022,2041,2052|0202:2049,2060|0203:2030|0204:2038,2057|0206:2027,2046|0208:2035,2054|0210:2024,2043|0211:2032,2051|0212:2040,2059|0213:2029|0214:2048|0215:2037,2056|0217:2026,2045|0219:2034,2053"},{"id":"holi","name":"Holi-Phagwa","en":"Holi","category":"public","dates":"d|0301:2010|0302:2018|0303:2026|0305:2034|0306:2015|0307:2023|0308:2012|0309:2020,2031|0311:2028|0312:2017|0314:2025|0316:2033|0317:2014|0318:2022|0319:2030|0320:2011|0321:2019|0322:2027|0323:2016|0324:2035|0325:2024|0327:2013,2032|0328:2021,2029"},{"id":"good_friday","name":"Goede vrijdag","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_monday","name":"Tweede paasdag","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"labor_day","name":"Dag van de Arbeid","en":"Labor Day","category":"public","dates":"d|0501:2010-2060"},{"id":"day_of_freedoms","name":"Keti Koti Dey","en":"Day of Freedoms","category":"public","dates":"d|0701:2010-2060"},{"id":"indigenous_people_day","name":"Dag der Inheemsen","en":"Indigenous People Day","category":"public","dates":"d|0809:2010-2060"},{"id":"day_of_the_maroons","name":"Dag der Marrons","en":"Day of the Maroons","category":"public","dates":"d|1010:2012-2060"},{"id":"diwali","name":"Divali","en":"Diwali","category":"public","dates":"d|1017:2028|1019:2017|1020:2025|1022:2033|1023:2014,2060|1024:2022,2041|1025:2030|1026:2049|1027:2019,2038,2057|1028:2027,2046|1030:2016,2035,2054|1031:2024|1101:2043|1102:2013,2032,2051|1104:2021,2040,2059|1105:2029,2048|1106:2037,2056|1107:2018|1108:2026,2045|1109:2053|1110:2034|1111:2015|1112:2023,2042|1113:2012,2050|1114:2020,2031|1115:2039,2058|1116:2047|1117:2036|1118:2044,2055|1120:2052"},{"id":"independence_day","name":"Onafhankelijkheidsdag","en":"Independence Day","category":"public","dates":"d|1125:2010-2060"},{"id":"christmas_day","name":"Eerste kerstdag","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"},{"id":"second_day_of_christmas","name":"Tweede kerstdag","en":"Second Day of Christmas","category":"public","dates":"d|1226:2010-2060"}]} diff --git a/app/Support/Holidays/data/SS.json b/app/Support/Holidays/data/SS.json new file mode 100644 index 000000000..d5450c8b1 --- /dev/null +++ b/app/Support/Holidays/data/SS.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"SS","language":null,"years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2012-2060"},{"id":"eid_al_fitr","name":"Eid al-Fitr","en":"Eid al-Fitr","category":"public","dates":"y|2012:0819|2013:0808|2014:0728|2015:0717|2016:0706|2017:0625|2018:0614|2019:0604|2020:0522|2021:0513|2022:0503|2023:0421|2024:0410|2025:0331|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304","estimated":"y|2012:0819|2013:0808|2014:0728|2015:0717|2016:0706|2017:0625|2019:0604|2023:0421|2024:0410|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304"},{"id":"eid_al_adha","name":"Eid al-Adha","en":"Eid al-Adha","category":"public","dates":"y|2012:1026|2013:1015|2014:1004|2015:0923|2016:0911|2017:0901|2018:0821|2019:0811|2020:0731|2021:0718|2022:0709|2023:0628|2024:0616|2025:0606|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510","estimated":"y|2012:1026|2013:1015|2014:1004|2015:0923|2016:0911|2017:0901|2019:0811|2023:0628|2024:0616|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510"},{"id":"peace_agreement_day","name":"Peace Agreement Day","en":"Peace Agreement Day","category":"public","dates":"d|0109:2012-2060"},{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0423:2038"},{"id":"holy_saturday","name":"Holy Saturday","en":"Holy Saturday","category":"public","dates":"d|0324:2035,2046|0326:2016|0327:2027,2032|0328:2043,2054|0329:2059|0330:2013,2024|0331:2018,2029,2040|0401:2051,2056|0403:2021|0404:2015,2026,2037,2048|0405:2042,2053|0407:2012|0408:2023,2034,2045|0409:2039,2050|0411:2020|0412:2031,2036|0413:2047,2058|0415:2017,2028|0416:2022,2033,2044|0417:2049,2055,2060|0419:2014,2025|0420:2019,2030,2041,2052|0421:2057|0424:2038"},{"id":"easter_sunday","name":"Easter Sunday","en":"Easter Sunday","category":"public","dates":"d|0325:2035,2046|0327:2016|0328:2027,2032|0329:2043,2054|0330:2059|0331:2013,2024|0401:2018,2029,2040|0402:2051,2056|0404:2021|0405:2015,2026,2037,2048|0406:2042,2053|0408:2012|0409:2023,2034,2045|0410:2039,2050|0412:2020|0413:2031,2036|0414:2047,2058|0416:2017,2028|0417:2022,2033,2044|0418:2049,2055,2060|0420:2014,2025|0421:2019,2030,2041,2052|0422:2057|0425:2038"},{"id":"easter_monday","name":"Easter Monday","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0426:2038"},{"id":"international_labour_day","name":"International Labour Day","en":"International Labour Day","category":"public","dates":"d|0501:2012-2060"},{"id":"spla_day","name":"SPLA Day","en":"SPLA Day","category":"public","dates":"d|0516:2012-2060"},{"id":"independence_day","name":"Independence Day","en":"Independence Day","category":"public","dates":"d|0709:2012-2060"},{"id":"martyrs_day","name":"Martyrs' Day","en":"Martyrs' Day","category":"public","dates":"d|0730:2012-2060"},{"id":"christmas_eve","name":"Christmas Eve","en":"Christmas Eve","category":"public","dates":"d|1224:2012-2060"},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1225:2012-2060"},{"id":"second_day_of_christmas","name":"Second Day of Christmas","en":"Second Day of Christmas","category":"public","dates":"d|1226:2012-2060"},{"id":"eid_al_fitr_holiday_islamic","name":"Eid al-Fitr Holiday","en":"Eid al-Fitr Holiday","category":"islamic","dates":"y|2012:0820|2013:0809|2014:0729|2015:0718|2016:0707|2017:0626|2018:0615|2019:0605|2020:0523|2021:0514|2022:0504|2023:0422|2024:0411|2025:0401|2026:0321|2027:0310|2028:0227|2029:0215|2030:0205|2031:0125|2032:0115|2033:0103,1224|2034:1213|2035:1202|2036:1120|2037:1109|2038:1030|2039:1020|2040:1008|2041:0927|2042:0916|2043:0905|2044:0825|2045:0815|2046:0804|2047:0725|2048:0713|2049:0702|2050:0621|2051:0611|2052:0530|2053:0520|2054:0510|2055:0429|2056:0417|2057:0406|2058:0326|2059:0316|2060:0305","estimated":"y|2012:0820|2013:0809|2014:0729|2015:0718|2016:0707|2017:0626|2019:0605|2023:0422|2024:0411|2026:0321|2027:0310|2028:0227|2029:0215|2030:0205|2031:0125|2032:0115|2033:0103,1224|2034:1213|2035:1202|2036:1120|2037:1109|2038:1030|2039:1020|2040:1008|2041:0927|2042:0916|2043:0905|2044:0825|2045:0815|2046:0804|2047:0725|2048:0713|2049:0702|2050:0621|2051:0611|2052:0530|2053:0520|2054:0510|2055:0429|2056:0417|2057:0406|2058:0326|2059:0316|2060:0305"},{"id":"eid_al_adha_holiday_islamic","name":"Eid al-Adha Holiday","en":"Eid al-Adha Holiday","category":"islamic","dates":"y|2012:1027|2013:1016|2014:1005|2015:0924|2016:0912|2017:0902|2018:0822|2019:0812|2020:0801|2021:0719|2022:0710|2023:0629|2024:0617|2025:0607|2026:0528|2027:0517|2028:0506|2029:0425|2030:0414|2031:0403|2032:0323|2033:0312|2034:0302|2035:0219|2036:0208|2037:0127|2038:0117|2039:0106,1227|2040:1215|2041:1205|2042:1124|2043:1113|2044:1101|2045:1022|2046:1011|2047:1001|2048:0920|2049:0909|2050:0829|2051:0818|2052:0806|2053:0727|2054:0716|2055:0706|2056:0624|2057:0613|2058:0602|2059:0523|2060:0511","estimated":"y|2012:1027|2013:1016|2014:1005|2015:0924|2016:0912|2017:0902|2019:0812|2023:0629|2024:0617|2026:0528|2027:0517|2028:0506|2029:0425|2030:0414|2031:0403|2032:0323|2033:0312|2034:0302|2035:0219|2036:0208|2037:0127|2038:0117|2039:0106,1227|2040:1215|2041:1205|2042:1124|2043:1113|2044:1101|2045:1022|2046:1011|2047:1001|2048:0920|2049:0909|2050:0829|2051:0818|2052:0806|2053:0727|2054:0716|2055:0706|2056:0624|2057:0613|2058:0602|2059:0523|2060:0511"}]} diff --git a/app/Support/Holidays/data/ST.json b/app/Support/Holidays/data/ST.json new file mode 100644 index 000000000..de1e0c170 --- /dev/null +++ b/app/Support/Holidays/data/ST.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"ST","language":"pt_ST","years":[2010,2060],"subdivisions":{"01":"Água Grande","02":"Cantagalo","03":"Caué","04":"Lembá","05":"Lobata","06":"Mé-Zóchi","P":"Príncipe"},"holidays":[{"id":"new_year_s_day","name":"Ano Novo","en":"New Year's Day","category":"public","dates":"d|0101:2014-2060|0102:2023,2034,2040,2045,2051|1231:2021,2027,2032,2038,2049,2055,2060"},{"id":"day_of_king_amador","name":"Dia do Rei Amador","en":"Day of King Amador","category":"public","dates":"d|0103:2020,2025,2031,2042,2048,2053,2059|0104:2014-2060|0105:2026,2032,2037,2043,2054,2060"},{"id":"discovery_of_principe_island","name":"Descobrimento da Ilha do Príncipe","en":"Discovery of Príncipe Island","category":"public","regional":{"P":"d|0116:2026,2032,2037,2043,2054,2060|0117:2014-2060|0118:2021,2027,2038,2044,2049,2055"}},{"id":"martyrs_day","name":"Dia dos Mártires","en":"Martyrs' Day","category":"public","dates":"d|0202:2024,2029,2035,2046,2052,2057|0203:2014-2060|0204:2030,2036,2041,2047,2058"},{"id":"autonomy_day","name":"Dia da Autonomia do Príncipe","en":"Autonomy Day","category":"public","regional":{"P":"d|0428:2023,2028,2034,2045,2051,2056|0429:2014-2060|0430:2029,2035,2040,2046,2057"}},{"id":"worker_s_day","name":"Dia do Trabalhador","en":"Worker's Day","category":"public","dates":"d|0430:2021,2027,2032,2038,2049,2055,2060|0501:2014-2060|0502:2022,2033,2039,2044,2050"},{"id":"independence_day","name":"Dia da Independência","en":"Independence Day","category":"public","dates":"d|0711:2025,2031,2036,2042,2053,2059|0712:2014-2060|0713:2020,2026,2037,2043,2048,2054"},{"id":"sao_lourenco_day","name":"Dia de São Lourenço","en":"São Lourenço Day","category":"public","regional":{"P":"d|0814:2020,2026,2037,2043,2048,2054|0815:2014-2060|0816:2021,2027,2032,2038,2049,2055,2060"}},{"id":"armed_forces_day","name":"Dia das Forças Armadas","en":"Armed Forces Day","category":"public","dates":"d|0905:2025,2031,2036,2042,2053,2059|0906:2014-2060|0907:2020,2026,2037,2043,2048,2054"},{"id":"agricultural_reform_day","name":"Dia da Reforma Agrária","en":"Agricultural Reform Day","category":"public","dates":"d|0929:2023,2028,2034,2045,2051,2056|0930:2014-2060|1001:2029,2035,2040,2046,2057"},{"id":"sao_tome_day","name":"Dia de São Tomé","en":"São Tomé Day","category":"public","dates":"d|1220:2024,2030,2041,2047,2052,2058|1221:2019-2060|1222:2025,2031,2036,2042,2053,2059"},{"id":"christmas_day","name":"Natal","en":"Christmas Day","category":"public","dates":"d|1224:2021,2027,2032,2038,2049,2055,2060|1225:2014-2060|1226:2022,2033,2039,2044,2050"}]} diff --git a/app/Support/Holidays/data/SV.json b/app/Support/Holidays/data/SV.json new file mode 100644 index 000000000..28cf17e7c --- /dev/null +++ b/app/Support/Holidays/data/SV.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"SV","language":"es","years":[2010,2060],"subdivisions":{"AH":"Ahuachapán","CA":"Cabañas","CH":"Chalatenango","CU":"Cuscatlán","LI":"La Libertad","MO":"Morazán","PA":"La Paz","SA":"Santa Ana","SM":"San Miguel","SO":"Sonsonate","SS":"San Salvador","SV":"San Vicente","UN":"La Unión","US":"Usulután"},"holidays":[{"id":"new_year_s_day","name":"Año Nuevo","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"maundy_thursday","name":"Jueves Santo","en":"Maundy Thursday","category":"public","dates":"d|0322:2035,2046|0324:2016|0325:2027,2032|0326:2043,2054|0327:2059|0328:2013,2024|0329:2018,2029,2040|0330:2051,2056|0401:2010,2021|0402:2015,2026,2037,2048|0403:2042,2053|0405:2012|0406:2023,2034,2045|0407:2039,2050|0409:2020|0410:2031,2036|0411:2047,2058|0413:2017,2028|0414:2022,2033,2044|0415:2049,2055,2060|0417:2014,2025|0418:2019,2030,2041,2052|0419:2057|0421:2011|0422:2038"},{"id":"good_friday","name":"Viernes Santo","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"holy_saturday","name":"Sábado Santo","en":"Holy Saturday","category":"public","dates":"d|0324:2035,2046|0326:2016|0327:2027,2032|0328:2043,2054|0329:2059|0330:2013,2024|0331:2018,2029,2040|0401:2051,2056|0403:2010,2021|0404:2015,2026,2037,2048|0405:2042,2053|0407:2012|0408:2023,2034,2045|0409:2039,2050|0411:2020|0412:2031,2036|0413:2047,2058|0415:2017,2028|0416:2022,2033,2044|0417:2049,2055,2060|0419:2014,2025|0420:2019,2030,2041,2052|0421:2057|0423:2011|0424:2038"},{"id":"labor_day","name":"Día del Trabajo","en":"Labor Day","category":"public","dates":"d|0501:2010-2060"},{"id":"mother_s_day","name":"Día de la Madre","en":"Mother's Day","category":"public","dates":"d|0510:2016-2060"},{"id":"father_s_day","name":"Día del Padre","en":"Father's Day","category":"public","dates":"d|0617:2013-2060"},{"id":"feast_of_san_salvador","name":"Fiesta de San Salvador","en":"Feast of San Salvador","category":"public","regional":{"SS":"d|0803:2010-2060|0805:2010-2060"}},{"id":"celebrations_of_san_salvador","name":"Celebración del Divino Salvador del Mundo","en":"Celebrations of San Salvador","category":"public","dates":"d|0806:2010-2060"},{"id":"independence_day","name":"Día de la Independencia","en":"Independence Day","category":"public","dates":"d|0915:2010-2060"},{"id":"all_souls_day","name":"Día de los Difuntos","en":"All Souls' Day","category":"public","dates":"d|1102:2010-2060"},{"id":"christmas_day","name":"Navidad","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"}]} diff --git a/app/Support/Holidays/data/SX.json b/app/Support/Holidays/data/SX.json new file mode 100644 index 000000000..3bac16946 --- /dev/null +++ b/app/Support/Holidays/data/SX.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"SX","language":"nl","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Nieuwjaarsdag","en":"New Year's Day","category":"public","dates":"d|0101:2011-2060"},{"id":"good_friday","name":"Goede vrijdag","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_sunday","name":"Eerste paasdag","en":"Easter Sunday","category":"public","dates":"d|0325:2035,2046|0327:2016|0328:2027,2032|0329:2043,2054|0330:2059|0331:2013,2024|0401:2018,2029,2040|0402:2051,2056|0404:2021|0405:2015,2026,2037,2048|0406:2042,2053|0408:2012|0409:2023,2034,2045|0410:2039,2050|0412:2020|0413:2031,2036|0414:2047,2058|0416:2017,2028|0417:2022,2033,2044|0418:2049,2055,2060|0420:2014,2025|0421:2019,2030,2041,2052|0422:2057|0424:2011|0425:2038"},{"id":"easter_monday","name":"Tweede paasdag","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"king_s_day","name":"Koningsdag","en":"King's Day","category":"public","dates":"d|0426:2014,2025,2031,2036,2042,2053,2059|0427:2015-2024,2026-2030,2032-2035,2037-2041,2043-2052,2054-2058,2060"},{"id":"carnival_day","name":"Carnavalsdag","en":"Carnival Day","category":"public","dates":"d|0429:2011-2013,2016,2022,2033,2039,2044,2050|0430:2014-2015,2018-2021,2024-2027,2029-2032,2035-2038,2040-2043,2046-2049,2052-2055,2057-2060|0502:2017,2023,2028,2034,2045,2051,2056"},{"id":"labor_day","name":"Dag van de Arbeid","en":"Labor Day","category":"public","dates":"d|0501:2011-2060"},{"id":"ascension_day","name":"Hemelvaartsdag","en":"Ascension Day","category":"public","dates":"d|0503:2035,2046|0505:2016|0506:2027,2032|0507:2043,2054|0508:2059|0509:2013,2024|0510:2018,2029,2040|0511:2051,2056|0513:2021|0514:2015,2026,2037,2048|0515:2042,2053|0517:2012|0518:2023,2034,2045|0519:2039,2050|0521:2020|0522:2031,2036|0523:2047,2058|0525:2017,2028|0526:2022,2033,2044|0527:2049,2055,2060|0529:2014,2025|0530:2019,2030,2041,2052|0531:2057|0602:2011|0603:2038"},{"id":"pentecost","name":"Eerste pinksterdag","en":"Pentecost","category":"public","dates":"d|0513:2035,2046|0515:2016|0516:2027,2032|0517:2043,2054|0518:2059|0519:2013,2024|0520:2018,2029,2040|0521:2051,2056|0523:2021|0524:2015,2026,2037,2048|0525:2042,2053|0527:2012|0528:2023,2034,2045|0529:2039,2050|0531:2020|0601:2031,2036|0602:2047,2058|0604:2017,2028|0605:2022,2033,2044|0606:2049,2055,2060|0608:2014,2025|0609:2019,2030,2041,2052|0610:2057|0612:2011|0613:2038"},{"id":"emancipation_day","name":"Dag van de Bevrijding","en":"Emancipation Day","category":"public","dates":"d|0701:2013-2017,2019-2028,2030-2034,2036-2039,2041-2045,2047-2056,2058-2060|0702:2012,2018,2029,2035,2040,2046,2057"},{"id":"constitution_day","name":"Dag van de Constitutie","en":"Constitution Day","category":"public","dates":"d|1008:2018,2029,2035,2040,2046,2057|1009:2017,2023,2028,2034,2045,2051,2056|1010:2016,2022,2033,2039,2044,2050|1011:2021,2027,2032,2038,2049,2055,2060|1012:2015,2020,2026,2037,2043,2048,2054|1013:2025,2031,2036,2042,2053,2059|1014:2019,2024,2030,2041,2047,2052,2058"},{"id":"sint_maarten_day","name":"Sint-Maartensdag","en":"Sint Maarten Day","category":"public","dates":"d|1111:2011-2060"},{"id":"christmas_day","name":"Eerste kerstdag","en":"Christmas Day","category":"public","dates":"d|1225:2011-2060"},{"id":"second_day_of_christmas","name":"Tweede kerstdag","en":"Second Day of Christmas","category":"public","dates":"d|1226:2011-2060"}]} diff --git a/app/Support/Holidays/data/SY.json b/app/Support/Holidays/data/SY.json new file mode 100644 index 000000000..3c145c206 --- /dev/null +++ b/app/Support/Holidays/data/SY.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"SY","language":"ar","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"عيد رأس السنة الميلادية","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"eid_al_fitr","name":"عيد الفطر","en":"Eid al-Fitr","category":"public","dates":"y|2010:0910-0912|2011:0830-0901|2012:0819-0821|2013:0808-0810|2014:0728-0730|2015:0717-0719|2016:0706-0708|2017:0625-0627|2018:0615-0617|2019:0604-0606|2020:0524-0526|2021:0513-0515|2022:0502-0504|2023:0421-0423|2024:0410-0412|2025:0331-0402|2026:0320-0322|2027:0309-0311|2028:0226-0228|2029:0214-0216|2030:0204-0206|2031:0124-0126|2032:0114-0116|2033:0102-0104,1223-1225|2034:1212-1214|2035:1201-1203|2036:1119-1121|2037:1108-1110|2038:1029-1031|2039:1019-1021|2040:1007-1009|2041:0926-0928|2042:0915-0917|2043:0904-0906|2044:0824-0826|2045:0814-0816|2046:0803-0805|2047:0724-0726|2048:0712-0714|2049:0701-0703|2050:0620-0622|2051:0610-0612|2052:0529-0531|2053:0519-0521|2054:0509-0511|2055:0428-0430|2056:0416-0418|2057:0405-0407|2058:0325-0327|2059:0315-0317|2060:0304-0306","estimated":"y|2010:0910-0912|2011:0830-0901|2012:0819-0821|2013:0808-0810|2014:0728-0730|2015:0717-0719|2016:0706-0708|2017:0625-0627|2018:0615-0617|2019:0604-0606|2026:0320-0322|2027:0309-0311|2028:0226-0228|2029:0214-0216|2030:0204-0206|2031:0124-0126|2032:0114-0116|2033:0102-0104,1223-1225|2034:1212-1214|2035:1201-1203|2036:1119-1121|2037:1108-1110|2038:1029-1031|2039:1019-1021|2040:1007-1009|2041:0926-0928|2042:0915-0917|2043:0904-0906|2044:0824-0826|2045:0814-0816|2046:0803-0805|2047:0724-0726|2048:0712-0714|2049:0701-0703|2050:0620-0622|2051:0610-0612|2052:0529-0531|2053:0519-0521|2054:0509-0511|2055:0428-0430|2056:0416-0418|2057:0405-0407|2058:0325-0327|2059:0315-0317|2060:0304-0306"},{"id":"prophet_s_birthday","name":"عيد المولد النبوي الشريف","en":"Prophet's Birthday","category":"public","dates":"y|2010:0226|2011:0215|2012:0204|2013:0124|2014:0113|2015:0103,1223|2016:1211|2017:1130|2018:1120|2019:1109|2020:1029|2021:1019|2022:1008|2023:0927|2024:0915|2025:0904|2026:0825|2027:0814|2028:0803|2029:0724|2030:0713|2031:0702|2032:0620|2033:0609|2034:0530|2035:0520|2036:0508|2037:0428|2038:0417|2039:0406|2040:0325|2041:0315|2042:0304|2043:0222|2044:0211|2045:0130|2046:0119|2047:0108,1229|2048:1218|2049:1207|2050:1126|2051:1116|2052:1104|2053:1024|2054:1013|2055:1003|2056:0922|2057:0911|2058:0831|2059:0820|2060:0808","estimated":"y|2010:0226|2011:0215|2012:0204|2013:0124|2014:0113|2015:0103,1223|2016:1211|2017:1130|2018:1120|2019:1109|2025:0904|2026:0825|2027:0814|2028:0803|2029:0724|2030:0713|2031:0702|2032:0620|2033:0609|2034:0530|2035:0520|2036:0508|2037:0428|2038:0417|2039:0406|2040:0325|2041:0315|2042:0304|2043:0222|2044:0211|2045:0130|2046:0119|2047:0108,1229|2048:1218|2049:1207|2050:1126|2051:1116|2052:1104|2053:1024|2054:1013|2055:1003|2056:0922|2057:0911|2058:0831|2059:0820|2060:0808"},{"id":"eid_al_adha","name":"عيد الأضحى","en":"Eid al-Adha","category":"public","dates":"y|2010:1115-1118|2011:1105-1108|2012:1025-1028|2013:1014-1017|2014:1003-1006|2015:0922-0925|2016:0910-0913|2017:0831-0903|2018:0820-0823|2019:0810-0813|2020:0730-0802|2021:0719-0722|2022:0708-0711|2023:0627-0630|2024:0615-0618|2025:0605-0608|2026:0526-0529|2027:0515-0518|2028:0504-0507|2029:0423-0426|2030:0412-0415|2031:0401-0404|2032:0321-0324|2033:0310-0313|2034:0228-0303|2035:0217-0220|2036:0206-0209|2037:0125-0128|2038:0115-0118|2039:0104-0107,1225-1228|2040:1213-1216|2041:1203-1206|2042:1122-1125|2043:1111-1114|2044:1030-1102|2045:1020-1023|2046:1009-1012|2047:0929-1002|2048:0918-0921|2049:0907-0910|2050:0827-0830|2051:0816-0819|2052:0804-0807|2053:0725-0728|2054:0714-0717|2055:0704-0707|2056:0622-0625|2057:0611-0614|2058:0531-0603|2059:0521-0524|2060:0509-0512","estimated":"y|2010:1115-1118|2011:1105-1108|2012:1025-1028|2013:1014-1017|2014:1003-1006|2015:0922-0925|2016:0910-0913|2017:0831-0903|2018:0820-0823|2019:0810-0813|2026:0526-0529|2027:0515-0518|2028:0504-0507|2029:0423-0426|2030:0412-0415|2031:0401-0404|2032:0321-0324|2033:0310-0313|2034:0228-0303|2035:0217-0220|2036:0206-0209|2037:0125-0128|2038:0115-0118|2039:0104-0107,1225-1228|2040:1213-1216|2041:1203-1206|2042:1122-1125|2043:1111-1114|2044:1030-1102|2045:1020-1023|2046:1009-1012|2047:0929-1002|2048:0918-0921|2049:0907-0910|2050:0827-0830|2051:0816-0819|2052:0804-0807|2053:0725-0728|2054:0714-0717|2055:0704-0707|2056:0622-0625|2057:0611-0614|2058:0531-0603|2059:0521-0524|2060:0509-0512"},{"id":"islamic_new_year","name":"رأس السنة الهجرية","en":"Islamic New Year","category":"public","dates":"y|2010:1207|2011:1126|2012:1115|2013:1104|2014:1025|2015:1014|2016:1002|2017:0921|2018:0911|2019:0831|2020:0820|2021:0809|2022:0730|2023:0719|2024:0707|2025:0626|2026:0616|2027:0606|2028:0525|2029:0514|2030:0503|2031:0423|2032:0411|2033:0401|2034:0321|2035:0311|2036:0228|2037:0216|2038:0205|2039:0126|2040:0115|2041:0104,1224|2042:1214|2043:1203|2044:1121|2045:1110|2046:1031|2047:1020|2048:1009|2049:0928|2050:0917|2051:0906|2052:0826|2053:0815|2054:0805|2055:0725|2056:0714|2057:0703|2058:0622|2059:0611|2060:0531","estimated":"y|2010:1207|2011:1126|2012:1115|2013:1104|2014:1025|2015:1014|2016:1002|2017:0921|2018:0911|2019:0831|2026:0616|2027:0606|2028:0525|2029:0514|2030:0503|2031:0423|2032:0411|2033:0401|2034:0321|2035:0311|2036:0228|2037:0216|2038:0205|2039:0126|2040:0115|2041:0104,1224|2042:1214|2043:1203|2044:1121|2045:1110|2046:1031|2047:1020|2048:1009|2049:0928|2050:0917|2051:0906|2052:0826|2053:0815|2054:0805|2055:0725|2056:0714|2057:0703|2058:0622|2059:0611|2060:0531"},{"id":"revolution_day","name":"الثورة السورية","en":"Revolution Day","category":"public","dates":"d|0308:2010-2060"},{"id":"mother_s_day","name":"عيد الأم","en":"Mother's Day","category":"public","dates":"d|0321:2010-2060"},{"id":"gregorian_easter_sunday","name":"عيد الفصح حسب التقويم الشرقي","en":"Gregorian Easter Sunday","category":"public","dates":"d|0325:2035,2046|0327:2016|0328:2027,2032|0329:2043,2054|0330:2059|0331:2013,2024|0401:2018,2029,2040|0402:2051,2056|0404:2010,2021|0405:2015,2026,2037,2048|0406:2042,2053|0408:2012|0409:2023,2034,2045|0410:2039,2050|0412:2020|0413:2031,2036|0414:2047,2058|0416:2017,2028|0417:2022,2033,2044|0418:2049,2055,2060|0420:2014,2025|0421:2019,2030,2041,2052|0422:2057|0424:2011|0425:2038"},{"id":"julian_easter_sunday","name":"عيد الفصح حسب التقويم الغربي","en":"Julian Easter Sunday","category":"public","dates":"d|0404:2010|0405:2037,2048|0408:2018,2029|0409:2034,2045,2056|0412:2015,2026|0413:2031,2042,2053|0414:2058|0415:2012|0416:2017,2023,2028|0417:2039,2050|0418:2055|0419:2020|0420:2014,2025,2036|0421:2041,2047,2052|0424:2011,2022,2033,2044|0425:2038,2049,2060|0428:2019,2030|0429:2035,2046,2057|0501:2016|0502:2021,2027,2032|0503:2043,2054|0504:2059|0505:2013,2024|0506:2040|0507:2051"},{"id":"independence_day","name":"عيد الاستقلال","en":"Independence Day","category":"public","dates":"d|0417:2010-2060"},{"id":"labor_day","name":"عيد العمال","en":"Labor Day","category":"public","dates":"d|0501:2010-2060"},{"id":"martyrs_day","name":"عيد الشهداء","en":"Martyrs' Day","category":"public","dates":"d|0506:2010-2060"},{"id":"tishreen_liberation_war_day","name":"ذكرى حرب تشرين التحريرية","en":"Tishreen Liberation War Day","category":"public","dates":"d|1006:2010-2060"},{"id":"liberation_day","name":"عيد التحرير","en":"Liberation Day","category":"public","dates":"d|1208:2025-2060"},{"id":"christmas_day","name":"عيد الميلاد","en":"Christmas Day","category":"public","dates":"d|1225:2024-2060"},{"id":"boxing_day","name":"يوم الصناديق","en":"Boxing Day","category":"public","dates":"d|1226:2024-2060"}]} diff --git a/app/Support/Holidays/data/SZ.json b/app/Support/Holidays/data/SZ.json new file mode 100644 index 000000000..56b83bee0 --- /dev/null +++ b/app/Support/Holidays/data/SZ.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"SZ","language":null,"years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2023,2034,2040,2045,2051"},{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_monday","name":"Easter Monday","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"king_s_birthday","name":"King's Birthday","en":"King's Birthday","category":"public","dates":"d|0419:2010-2060|0420:2026,2037,2043,2048,2054"},{"id":"national_flag_day","name":"National Flag Day","en":"National Flag Day","category":"public","dates":"d|0425:2010-2060|0426:2021,2027,2032,2049,2055,2060|0427:2038"},{"id":"worker_s_day","name":"Worker's Day","en":"Worker's Day","category":"public","dates":"d|0501:2010-2060|0502:2022,2033,2039,2044,2050"},{"id":"ascension_day","name":"Ascension Day","en":"Ascension Day","category":"public","dates":"d|0503:2035,2046|0505:2016|0506:2027,2032|0507:2043,2054|0508:2059|0509:2013,2024|0510:2018,2029,2040|0511:2051,2056|0513:2010,2021|0514:2015,2026,2037,2048|0515:2042,2053|0517:2012|0518:2023,2034,2045|0519:2039,2050|0521:2020|0522:2031,2036|0523:2047,2058|0525:2017,2028|0526:2022,2033,2044|0527:2049,2055,2060|0529:2014,2025|0530:2019,2030,2041,2052|0531:2057|0602:2011|0603:2038"},{"id":"birthday_of_late_king_sobhuza","name":"Birthday of Late King Sobhuza","en":"Birthday of Late King Sobhuza","category":"public","dates":"d|0722:2010-2060|0723:2029,2035,2040,2046,2057"},{"id":"independence_day","name":"Independence Day","en":"Independence Day","category":"public","dates":"d|0906:2010-2060|0907:2026,2037,2043,2048,2054"},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060|1227:2022,2033,2039,2044,2050"},{"id":"boxing_day","name":"Boxing Day","en":"Boxing Day","category":"public","dates":"d|1226:2010-2060|1227:2021,2027,2032,2038,2049,2055,2060"}]} diff --git a/app/Support/Holidays/data/TC.json b/app/Support/Holidays/data/TC.json new file mode 100644 index 000000000..bd420f110 --- /dev/null +++ b/app/Support/Holidays/data/TC.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"TC","language":"en_TC","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051|0103:2011,2022,2028,2033,2039,2050,2056"},{"id":"commonwealth_day","name":"Commonwealth Day","en":"Commonwealth Day","category":"public","dates":"d|0308:2010,2021,2027,2032,2038,2049,2055,2060|0309:2015,2020,2026,2037,2043,2048,2054|0310:2014,2025,2031,2036,2042,2053,2059|0311:2013,2019,2024,2030,2041,2047,2052,2058|0312:2012,2018,2029,2035,2040,2046,2057|0313:2017,2023,2028,2034,2045,2051,2056|0314:2011,2016,2022,2033,2039,2044,2050"},{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_monday","name":"Easter Monday","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"jags_mccartney_day","name":"JAGS McCartney Day","en":"JAGS McCartney Day","category":"public","dates":"d|0525:2020,2026,2037,2043,2048,2054|0526:2025,2031,2036,2042,2053,2059|0527:2024,2030,2041,2047,2052,2058|0528:2029,2035,2040,2046,2057|0529:2023,2028,2034,2045,2051,2056|0530:2022,2033,2039,2044,2050|0531:2021,2027,2032,2038,2049,2055,2060"},{"id":"king_s_birthday","name":"King's Birthday","en":"King's Birthday","category":"public","dates":"d|0608:2026,2037,2043,2048,2054|0609:2031,2036,2042,2053,2059|0610:2030,2041,2047,2052,2058|0611:2029,2035,2040,2046,2057|0612:2028,2034,2045,2051,2056|0613:2033,2039,2044,2050|0614:2027,2032,2038,2049,2055,2060|0617:2024|0619:2023|0623:2025"},{"id":"emancipation_day","name":"Emancipation Day","en":"Emancipation Day","category":"public","dates":"d|0801:2010-2060|0802:2010,2021,2027,2032,2038,2049,2055,2060|0803:2015,2020,2026,2037,2043,2048,2054"},{"id":"national_day_of_thanksgiving","name":"National Day of Thanksgiving","en":"National Day of Thanksgiving","category":"public","dates":"d|0824:2031,2036,2042,2053,2059|0825:2030,2041,2047,2052,2058|0826:2029,2035,2040,2046,2057|0827:2028,2034,2045,2051,2056|0828:2033,2039,2044,2050|0829:2027,2032,2038,2049,2055,2060|0830:2026,2037,2043,2048,2054|1122:2019,2024|1123:2018|1124:2017,2023|1125:2016,2022|1126:2021|1127:2015,2020|1128:2014,2025"},{"id":"constitution_day","name":"Constitution Day","en":"Constitution Day","category":"public","dates":"d|0825:2031,2036,2042,2053,2059|0826:2030,2041,2047,2052,2058|0827:2029,2035,2040,2046,2057|0828:2028,2034,2045,2051,2056|0829:2033,2039,2044,2050|0830:2027,2032,2038,2049,2055,2060|0831:2026,2037,2043,2048,2054"},{"id":"national_youth_day","name":"National Youth Day","en":"National Youth Day","category":"public","dates":"d|0924:2010,2021,2027,2032,2038,2049,2055,2060|0925:2015,2020,2026,2037,2043,2048,2054|0926:2014,2025,2031,2036,2042,2053,2059|0927:2013,2019,2024,2030,2041,2047,2052,2058|0928:2012,2018,2029,2035,2040,2046,2057|0929:2017,2023,2028,2034,2045,2051,2056|0930:2011,2016,2022,2033,2039,2044,2050"},{"id":"national_heritage_day","name":"National Heritage Day","en":"National Heritage Day","category":"public","dates":"d|1008:2018,2029,2035,2040,2046,2057|1009:2017,2023,2028,2034,2045,2051,2056|1010:2016,2022,2033,2039,2044,2050|1011:2021,2027,2032,2038,2049,2055,2060|1012:2015,2020,2026,2037,2043,2048,2054|1013:2014,2025,2031,2036,2042,2053,2059|1014:2019,2024,2030,2041,2047,2052,2058"},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060|1227:2010-2011,2016,2021-2022,2027,2032-2033,2038-2039,2044,2049-2050,2055,2060"},{"id":"boxing_day","name":"Boxing Day","en":"Boxing Day","category":"public","dates":"d|1226:2010-2060|1228:2010,2015,2020-2021,2026-2027,2032,2037-2038,2043,2048-2049,2054-2055,2060"}]} diff --git a/app/Support/Holidays/data/TD.json b/app/Support/Holidays/data/TD.json new file mode 100644 index 000000000..1045d6272 --- /dev/null +++ b/app/Support/Holidays/data/TD.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"TD","language":"fr","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Jour de l'An","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051"},{"id":"eid_al_fitr","name":"Aïd El Fitir","en":"Eid al-Fitr","category":"public","dates":"y|2010:0910|2011:0830|2012:0819|2013:0808|2014:0728|2015:0718|2016:0707|2017:0626|2018:0615|2019:0604|2020:0524|2021:0513|2022:0502|2023:0421|2024:0410|2025:0330|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304","estimated":"y|2010:0910|2011:0830|2012:0819|2013:0808|2014:0728|2025:0330|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304"},{"id":"prophet_s_birthday","name":"Maouloud El Nebi","en":"Prophet's Birthday","category":"public","dates":"y|2010:0226|2011:0215|2012:0204|2013:0124|2014:0113|2015:0103,1224|2016:1212|2017:1201|2018:1121|2019:1109|2020:1029|2021:1018|2022:1008|2023:0927|2024:0915|2025:0904|2026:0825|2027:0814|2028:0803|2029:0724|2030:0713|2031:0702|2032:0620|2033:0609|2034:0530|2035:0520|2036:0508|2037:0428|2038:0417|2039:0406|2040:0325|2041:0315|2042:0304|2043:0222|2044:0211|2045:0130|2046:0119|2047:0108,1229|2048:1218|2049:1207|2050:1126|2051:1116|2052:1104|2053:1024|2054:1013|2055:1003|2056:0922|2057:0911|2058:0831|2059:0820|2060:0808","estimated":"y|2010:0226|2011:0215|2012:0204|2013:0124|2014:0113|2023:0927|2024:0915|2025:0904|2026:0825|2027:0814|2028:0803|2029:0724|2030:0713|2031:0702|2032:0620|2033:0609|2034:0530|2035:0520|2036:0508|2037:0428|2038:0417|2039:0406|2040:0325|2041:0315|2042:0304|2043:0222|2044:0211|2045:0130|2046:0119|2047:0108,1229|2048:1218|2049:1207|2050:1126|2051:1116|2052:1104|2053:1024|2054:1013|2055:1003|2056:0922|2057:0911|2058:0831|2059:0820|2060:0808"},{"id":"eid_al_adha","name":"Aïd El Adha","en":"Eid al-Adha","category":"public","dates":"y|2010:1116|2011:1106|2012:1026|2013:1015|2014:1004|2015:0924|2016:0913|2017:0902|2018:0822|2019:0811|2020:0731|2021:0720|2022:0709|2023:0628|2024:0616|2025:0606|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510","estimated":"y|2010:1116|2011:1106|2012:1026|2013:1015|2014:1004|2024:0616|2025:0606|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510"},{"id":"international_women_s_day","name":"Journée internationale de la femme","en":"International Women's Day","category":"public","dates":"d|0308:2019-2060|0309:2020,2026,2037,2043,2048,2054"},{"id":"easter_monday","name":"Lundi de Pâques","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"labor_day","name":"Fête du Travail","en":"Labor Day","category":"public","dates":"d|0501:2010-2060|0502:2011,2016,2022,2033,2039,2044,2050"},{"id":"independence_day","name":"Anniversaire de la Proclamation de l'Indépendance","en":"Independence Day","category":"public","dates":"d|0811:2010-2060|0812:2013,2019,2024,2030,2041,2047,2052,2058"},{"id":"all_saints_day","name":"Fête de la Toussaint","en":"All Saints' Day","category":"public","dates":"d|1101:2010-2060"},{"id":"republic_day","name":"Anniversaire de la Proclamation de la République","en":"Republic Day","category":"public","dates":"d|1128:2010-2060|1129:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"freedom_and_democracy_day","name":"Journée de la Liberté et de la Démocratie","en":"Freedom and Democracy Day","category":"public","dates":"d|1201:2010-2060|1202:2013,2019,2024,2030,2041,2047,2052,2058"},{"id":"christmas_day","name":"Fête de Noël","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"}]} diff --git a/app/Support/Holidays/data/TF.json b/app/Support/Holidays/data/TF.json new file mode 100644 index 000000000..323d920b9 --- /dev/null +++ b/app/Support/Holidays/data/TF.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"TF","language":"fr","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Jour de l'an","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"easter_monday","name":"Lundi de Pâques","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"labor_day","name":"Fête du Travail","en":"Labor Day","category":"public","dates":"d|0501:2010-2060"},{"id":"ascension_day","name":"Ascension","en":"Ascension Day","category":"public","dates":"d|0503:2035,2046|0505:2016|0506:2027,2032|0507:2043,2054|0508:2059|0509:2013,2024|0510:2018,2029,2040|0511:2051,2056|0513:2010,2021|0514:2015,2026,2037,2048|0515:2042,2053|0517:2012|0518:2023,2034,2045|0519:2039,2050|0521:2020|0522:2031,2036|0523:2047,2058|0525:2017,2028|0526:2022,2033,2044|0527:2049,2055,2060|0529:2014,2025|0530:2019,2030,2041,2052|0531:2057|0602:2011|0603:2038"},{"id":"victory_day","name":"Fête de la Victoire","en":"Victory Day","category":"public","dates":"d|0508:2010-2060"},{"id":"pentecost_monday","name":"Lundi de Pentecôte","en":"Pentecost Monday","category":"public","dates":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038"},{"id":"national_day","name":"Fête nationale","en":"National Day","category":"public","dates":"d|0714:2010-2060"},{"id":"assumption_day","name":"Assomption","en":"Assumption Day","category":"public","dates":"d|0815:2010-2060"},{"id":"all_saints_day","name":"Toussaint","en":"All Saints' Day","category":"public","dates":"d|1101:2010-2060"},{"id":"armistice_day","name":"Armistice","en":"Armistice Day","category":"public","dates":"d|1111:2010-2060"},{"id":"christmas_day","name":"Noël","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"}]} diff --git a/app/Support/Holidays/data/TG.json b/app/Support/Holidays/data/TG.json new file mode 100644 index 000000000..071748409 --- /dev/null +++ b/app/Support/Holidays/data/TG.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"TG","language":"fr","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Jour de l'an","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"eid_al_fitr","name":"l'Aïd El-Fitr","en":"Eid al-Fitr","category":"public","dates":"y|2010:0910|2011:0831|2012:0819|2013:0808|2014:0729|2015:0718|2016:0707|2017:0626|2018:0615|2019:0605|2020:0524|2021:0513|2022:0502|2023:0421|2024:0410|2025:0330|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304","estimated":"y|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304"},{"id":"eid_al_adha","name":"Tabaski","en":"Eid al-Adha","category":"public","dates":"y|2010:1117|2011:1107|2012:1026|2013:1015|2014:1005|2015:0924|2016:0913|2017:0902|2018:0822|2019:0811|2020:0731|2021:0720|2022:0709|2023:0628|2024:0616|2025:0607|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510","estimated":"y|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510"},{"id":"first_day_of_ramadan","name":"Ramadan","en":"First Day of Ramadan","category":"public","dates":"y|2010:0811|2011:0801|2012:0720|2013:0709|2014:0628|2015:0618|2016:0606|2017:0527|2018:0517|2019:0506|2020:0424|2021:0413|2022:0402|2023:0323|2024:0311|2025:0301|2026:0218|2027:0208|2028:0128|2029:0116|2030:0105,1226|2031:1215|2032:1204|2033:1123|2034:1112|2035:1101|2036:1020|2037:1010|2038:0930|2039:0919|2040:0907|2041:0828|2042:0817|2043:0806|2044:0726|2045:0715|2046:0705|2047:0624|2048:0612|2049:0602|2050:0522|2051:0511|2052:0430|2053:0420|2054:0409|2055:0329|2056:0317|2057:0306|2058:0224|2059:0214|2060:0203","estimated":"y|2010:0811|2011:0801|2012:0720|2013:0709|2014:0628|2026:0218|2027:0208|2028:0128|2029:0116|2030:0105,1226|2031:1215|2032:1204|2033:1123|2034:1112|2035:1101|2036:1020|2037:1010|2038:0930|2039:0919|2040:0907|2041:0828|2042:0817|2043:0806|2044:0726|2045:0715|2046:0705|2047:0624|2048:0612|2049:0602|2050:0522|2051:0511|2052:0430|2053:0420|2054:0409|2055:0329|2056:0317|2057:0306|2058:0224|2059:0214|2060:0203"},{"id":"easter_monday","name":"Lundi de Pâques","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"independence_day","name":"Fête de l'indépendance","en":"Independence Day","category":"public","dates":"d|0427:2010-2060"},{"id":"labor_day","name":"Fête du travail","en":"Labor Day","category":"public","dates":"d|0501:2010-2060"},{"id":"ascension_day","name":"Fête de l'Ascension","en":"Ascension Day","category":"public","dates":"d|0503:2035,2046|0505:2016|0506:2027,2032|0507:2043,2054|0508:2059|0509:2013,2024|0510:2018,2029,2040|0511:2051,2056|0513:2010,2021|0514:2015,2026,2037,2048|0515:2042,2053|0517:2012|0518:2023,2034,2045|0519:2039,2050|0521:2020|0522:2031,2036|0523:2047,2058|0525:2017,2028|0526:2022,2033,2044|0527:2049,2055,2060|0529:2014,2025|0530:2019,2030,2041,2052|0531:2057|0602:2011|0603:2038"},{"id":"pentecost_monday","name":"Lundi de Pentecôte","en":"Pentecost Monday","category":"public","dates":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038"},{"id":"martyrs_day","name":"Fête des Martyrs","en":"Martyrs' Day","category":"public","dates":"d|0621:2010-2060"},{"id":"assumption_day","name":"Assomption","en":"Assumption Day","category":"public","dates":"d|0815:2010-2060"},{"id":"all_saints_day","name":"Toussaint","en":"All Saints' Day","category":"public","dates":"d|1101:2010-2060"},{"id":"christmas_day","name":"Noël","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"}]} diff --git a/app/Support/Holidays/data/TH.json b/app/Support/Holidays/data/TH.json new file mode 100644 index 000000000..69d1aa66f --- /dev/null +++ b/app/Support/Holidays/data/TH.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"TH","language":"th","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"วันขึ้นปีใหม่","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051|0103:2011,2022,2028,2033,2039,2050,2056"},{"id":"bridge_public_holiday","name":"วันหยุดพิเศษ (เพิ่มเติม)","en":"Bridge Public Holiday","category":"public","dates":"y|2010:0520-0521,0813|2011:0516|2012:0409|2013:1230|2014:0811|2015:0102,0504|2016:0506,0718|2020:1119-1120,1211|2021:0212,0412,0924|2022:0715,0729,1014,1230|2023:0505,0731,1229|2024:0412,1230|2025:0602,0811|2026:0102"},{"id":"new_year_s_eve","name":"วันสิ้นปี","en":"New Year's Eve","category":"public","dates":"d|0102:2018,2029,2035,2046,2052,2057|0103:2012,2017,2023,2034,2040,2045,2051|1231:2010-2060"},{"id":"national_children_s_day","name":"วันเด็กแห่งชาติ","en":"National Children's Day","category":"public","dates":"d|0108:2011,2022,2028,2033,2039,2050,2056|0109:2010,2016,2021,2027,2038,2044,2049,2055|0110:2015,2026,2032,2037,2043,2054,2060|0111:2014,2020,2025,2031,2042,2048,2053,2059|0112:2013,2019,2030,2036,2041,2047,2058|0113:2018,2024,2029,2035,2046,2052,2057|0114:2012,2017,2023,2034,2040,2045,2051"},{"id":"makha_bucha","name":"วันมาฆบูชา","en":"Makha Bucha","category":"public","dates":"d|0208:2020,2039,2058|0210:2020,2028,2047|0211:2017,2047,2055|0212:2025,2036|0213:2017,2044|0214:2014,2033,2052|0215:2041,2044|0216:2022|0217:2030,2049,2060|0218:2011,2030|0219:2019,2038,2057|0220:2046|0221:2027|0222:2016,2027,2035,2054|0223:2043,2054|0224:2024|0225:2013,2032,2051|0226:2021,2024,2040|0227:2029,2040,2051,2059|0228:2010,2048|0301:2010,2018,2037,2056|0302:2037,2045|0303:2026|0304:2015,2034,2053|0305:2042|0306:2023,2034|0307:2012,2031,2050"},{"id":"chakri_memorial_day","name":"วันพระบาทสมเด็จพระพุทธยอดฟ้าจุฬาโลกมหาราช และวันที่ระลึกมหาจักรีบรมราชวงศ์","en":"Chakri Memorial Day","category":"public","dates":"d|0406:2010-2060|0407:2014,2025,2031,2036,2042,2053,2059|0408:2013,2019,2024,2030,2041,2047,2052,2058"},{"id":"songkran_festival","name":"วันสงกรานต์","en":"Songkran Festival","category":"public","dates":"d|0413:2010-2019,2021-2060|0414:2010-2019,2021-2060|0415:2010-2019,2021-2060|0416:2012-2014,2018-2019,2024-2025,2029-2031,2035-2036,2040-2042,2046-2047,2052-2053,2057-2059|0417:2017,2023,2028,2034,2045,2051,2056|0727:2020|0904:2020|0907:2020"},{"id":"national_labor_day","name":"วันแรงงานแห่งชาติ","en":"National Labor Day","category":"public","dates":"d|0501:2010-2060|0502:2011,2016,2022,2033,2039,2044,2050|0503:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"coronation_day","name":"วันฉัตรมงคล","en":"Coronation Day","category":"public","dates":"d|0504:2020-2060|0505:2010-2016,2025,2031,2036,2042,2053,2059|0506:2013,2024,2030,2041,2047,2052,2058|0507:2012"},{"id":"visakha_bucha","name":"วันวิสาขบูชา","en":"Visakha Bucha","category":"public","dates":"d|0506:2020|0507:2039,2058|0508:2028|0509:2039,2047|0510:2017,2036,2055|0511:2025,2044|0512:2025,2036,2052|0513:2014,2033,2052|0514:2041|0515:2022,2060|0516:2022,2030,2049|0517:2011,2049,2060|0518:2019,2038,2057|0519:2046|0520:2016,2019,2027|0521:2035,2046,2054|0522:2024,2043|0523:2032|0524:2013,2032,2051|0525:2040|0526:2021|0527:2029,2048,2059|0528:2010,2029|0529:2018,2037,2056|0530:2045|0531:2026|0601:2015,2026,2034,2053|0602:2042,2053|0603:2023|0604:2012,2031,2050|0605:2023|0606:2050"},{"id":"hm_queen_suthida_s_birthday","name":"วันเฉลิมพระชนมพรรษาสมเด็จพระนางเจ้าสุทิดา พัชรสุธาพิมลลักษณ พระบรมราชินี","en":"HM Queen Suthida's Birthday","category":"public","dates":"d|0603:2019-2060|0604:2029,2035,2040,2046,2057|0605:2023,2028,2034,2045,2051,2056"},{"id":"asarnha_bucha","name":"วันอาสาฬหบูชา","en":"Asarnha Bucha","category":"public","dates":"d|0705:2020,2039|0706:2028,2058|0707:2020,2047|0708:2017,2036,2058|0709:2044,2047,2055|0710:2017,2025|0711:2014,2033,2044,2052|0712:2041|0713:2022,2060|0714:2030,2049|0715:2011|0716:2019,2030,2038,2057|0718:2027,2046|0719:2016,2054|0720:2024,2027,2035|0721:2043,2054|0722:2013,2024,2032,2051|0723:2040|0724:2021,2051|0725:2029,2048,2059|0726:2010,2021|0727:2018,2037,2048,2056|0728:2045|0729:2026|0730:2015,2034,2053|0731:2042|0801:2023,2034|0802:2012,2031,2050|0804:2031"},{"id":"buddhist_lent_day","name":"วันเข้าพรรษา","en":"Buddhist Lent Day","category":"public","dates":"d|0706:2020,2039|0707:2028,2058|0708:2047|0709:2017,2036|0710:2044,2055|0711:2025|0712:2014,2033,2052,2055|0713:2041|0714:2014,2022,2060|0715:2030,2041,2049|0716:2011|0717:2019,2038,2057|0718:2011|0719:2027,2038,2046|0720:2016,2054|0721:2024,2035|0722:2043|0723:2013,2032,2035,2051|0724:2040|0725:2021|0726:2029,2048,2059|0727:2010|0728:2018,2037,2056,2059|0729:2045|0730:2018,2026|0731:2015,2034,2045,2053|0801:2042|0802:2023|0803:2012,2031,2050"},{"id":"hm_king_maha_vajiralongkorn_s_birthday","name":"วันเฉลิมพระชนมพรรษาพระบาทสมเด็จพระปรเมนทรรามาธิบดีศรีสินทรมหาวชิราลงกรณ พระวชิรเกล้าเจ้าอยู่หัว","en":"HM King Maha Vajiralongkorn's Birthday","category":"public","dates":"d|0728:2017-2060|0729:2019,2024,2030,2041,2047,2052,2058|0730:2018,2029,2035,2040,2046,2057"},{"id":"hm_queen_sirikit_the_queen_mother_s_birthday","name":"วันเฉลิมพระชนมพรรษาสมเด็จพระบรมราชชนนีพันปีหลวง","en":"HM Queen Sirikit The Queen Mother's Birthday","category":"public","dates":"d|0812:2017-2060|0813:2018,2029,2035,2040,2046,2057|0814:2017,2023,2028,2034,2045,2051,2056"},{"id":"national_mother_s_day","name":"วันแม่แห่งชาติ","en":"National Mother's Day","category":"public","dates":"d|0812:2010-2060|0813:2012,2018,2029,2035,2040,2046,2057|0814:2017,2023,2028,2034,2045,2051,2056"},{"id":"hm_king_bhumibol_adulyadej_memorial_day","name":"วันคล้ายวันสวรรคตพระบาทสมเด็จพระปรมินทรมหาภูมิพลอดุลยเดช บรมนาถบพิตร","en":"HM King Bhumibol Adulyadej Memorial Day","category":"public","dates":"d|1013:2017-2018,2023-2060|1014:2024,2030,2041,2047,2052,2058|1015:2018,2029,2035,2040,2046,2057"},{"id":"hm_king_chulalongkorn_memorial_day","name":"วันปิยมหาราช","en":"HM King Chulalongkorn Memorial Day","category":"public","dates":"d|1023:2010-2060|1024:2011,2016,2022,2033,2039,2044,2050|1025:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"hm_king_bhumibol_adulyadej_the_great_s_birthday","name":"วันคล้ายวันเฉลิมพระชนมพรรษาพระบาทสมเด็จพระบรมชนกาธิเบศร มหาภูมิพลอดุลยเดชมหาราช บรมนาถบพิตร","en":"HM King Bhumibol Adulyadej the Great's Birthday","category":"public","dates":"d|1205:2019-2060|1206:2021,2027,2032,2038,2049,2055,2060|1207:2020,2026,2037,2043,2048,2054"},{"id":"national_day","name":"วันพ่อแห่งชาติ","en":"National Day","category":"public","dates":"d|1205:2010-2060|1206:2010,2021,2027,2032,2038,2049,2055,2060|1207:2015,2020,2026,2037,2043,2048,2054"},{"id":"national_father_s_day","name":"วันเฉลิมพระชนมพรรษาพระบาทสมเด็จพระปรมินทรมหาภูมิพลอดุลยเดช บรมนาถบพิตร","en":"National Father's Day","category":"public","dates":"d|1205:2010-2060|1206:2010,2021,2027,2032,2038,2049,2055,2060|1207:2015,2020,2026,2037,2043,2048,2054"},{"id":"constitution_day","name":"วันรัฐธรรมนูญ","en":"Constitution Day","category":"public","dates":"d|1210:2010-2060|1211:2017,2023,2028,2034,2045,2051,2056|1212:2011,2016,2022,2033,2039,2044,2050"},{"id":"teacher_s_day_school","name":"วันครู","en":"Teacher's Day","category":"school","dates":"d|0116:2010-2060"},{"id":"royal_thai_armed_forces_day_armed_forces","name":"วันกองทัพไทย","en":"Royal Thai Armed Forces Day","category":"armed_forces","dates":"d|0118:2010-2060"},{"id":"royal_ploughing_ceremony_government","name":"วันพืชมงคล","en":"Royal Ploughing Ceremony","category":"government","dates":"d|0509:2012,2014,2016,2019,2025|0510:2021,2024|0511:2020|0512:2017|0513:2010-2011,2013,2015,2022,2026|0514:2018|0517:2023"}]} diff --git a/app/Support/Holidays/data/TJ.json b/app/Support/Holidays/data/TJ.json new file mode 100644 index 000000000..75c7ffa8c --- /dev/null +++ b/app/Support/Holidays/data/TJ.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"TJ","language":"tg","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Соли Нав","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"eid_al_fitr","name":"Рӯзи иди Рамазон","en":"Eid al-Fitr","category":"public","dates":"y|2010:0910|2011:0830|2012:0819|2013:0808|2014:0728|2015:0717|2016:0706|2017:0625|2018:0615|2019:0604|2020:0524|2021:0513|2022:0502|2023:0422|2024:0410|2025:0330|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304","estimated":"y|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304"},{"id":"eid_al_adha","name":"Рӯзи иди Қурбон","en":"Eid al-Adha","category":"public","dates":"y|2010:1116|2011:1106|2012:1026|2013:1015|2014:1004|2015:0923|2016:0911|2017:0901|2018:0821|2019:0811|2020:0731|2021:0720|2022:0709|2023:0628|2024:0616|2025:0606|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510","estimated":"y|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510"},{"id":"mother_s_day","name":"Рӯзи Модар","en":"Mother's Day","category":"public","dates":"d|0308:2010-2060"},{"id":"international_nowruz_day","name":"Иди байналмилалии Наврӯз","en":"International Nowruz Day","category":"public","dates":"d|0321:2010-2060|0322:2010-2060|0323:2010-2060|0324:2010-2060"},{"id":"victory_day","name":"Рӯзи Ғалаба дар Ҷанги Бузурги Ватанӣ","en":"Victory Day","category":"public","dates":"d|0509:2010-2060"},{"id":"national_unity_day","name":"Рӯзи Ваҳдати миллӣ","en":"National Unity Day","category":"public","dates":"d|0627:2010-2060"},{"id":"independence_day","name":"Рӯзи Истиқлолияти давлатии Ҷумҳурии Тоҷикистон","en":"Independence Day","category":"public","dates":"d|0909:2010-2060"},{"id":"constitution_day","name":"Рӯзи Конститутсияи Ҷумҳурии Тоҷикистон","en":"Constitution Day","category":"public","dates":"d|1106:2010-2060"}]} diff --git a/app/Support/Holidays/data/TK.json b/app/Support/Holidays/data/TK.json new file mode 100644 index 000000000..14c31c780 --- /dev/null +++ b/app/Support/Holidays/data/TK.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"TK","language":"en_TK","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_monday","name":"Easter Monday","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"tokehega_day","name":"Tokehega Day","en":"Tokehega Day","category":"public","dates":"d|0903:2010-2060"},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"},{"id":"boxing_day","name":"Boxing Day","en":"Boxing Day","category":"public","dates":"d|1226:2010-2060"}]} diff --git a/app/Support/Holidays/data/TL.json b/app/Support/Holidays/data/TL.json new file mode 100644 index 000000000..3ab83eda2 --- /dev/null +++ b/app/Support/Holidays/data/TL.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"TL","language":"pt_TL","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Dia de Ano Novo","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"eid_al_fitr","name":"Idul Fitri","en":"Eid al-Fitr","category":"public","dates":"y|2010:0910|2011:0831|2012:0820|2013:0808|2014:0728|2015:0717|2016:0707|2017:0626|2018:0615|2019:0606|2020:0524|2021:0513|2022:0502|2023:0422|2024:0410|2025:0331|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304","estimated":"y|2010:0910|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304"},{"id":"eid_al_adha","name":"Idul Adha","en":"Eid al-Adha","category":"public","dates":"y|2010:1116|2011:1107|2012:1026|2013:1015|2014:1004|2015:0924|2016:0918|2017:0901|2018:0821|2019:0811|2020:0731|2021:0719|2022:0709|2023:0629|2024:0617|2025:0606|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510","estimated":"y|2010:1116|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510"},{"id":"veteran_s_day","name":"Dia dos Veteranos","en":"Veteran's Day","category":"public","dates":"d|0303:2017-2060"},{"id":"good_friday","name":"Sexta-Feira Santa","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"international_worker_s_day","name":"Dia Mundial do Trabalhador","en":"International Worker's Day","category":"public","dates":"d|0501:2010-2060"},{"id":"restoration_of_independence_day","name":"Dia da Restauração da Independência","en":"Restoration of Independence Day","category":"public","dates":"d|0520:2010-2060"},{"id":"corpus_christi","name":"Festa do Corpo de Deus","en":"Corpus Christi","category":"public","dates":"d|0524:2035,2046|0526:2016|0527:2027,2032|0528:2043,2054|0529:2059|0530:2013,2024|0531:2018,2029,2040|0601:2051,2056|0603:2010,2021|0604:2015,2026,2037,2048|0605:2042,2053|0607:2012|0608:2023,2034,2045|0609:2039,2050|0611:2020|0612:2031,2036|0613:2047,2058|0615:2017,2028|0616:2022,2033,2044|0617:2049,2055,2060|0619:2014,2025|0620:2019,2030,2041,2052|0621:2057|0623:2011|0624:2038"},{"id":"popular_consultation_day","name":"Dia da Consulta Popular","en":"Popular Consultation Day","category":"public","dates":"d|0830:2010-2060"},{"id":"all_saints_day","name":"Dia de Todos os Santos","en":"All Saints' Day","category":"public","dates":"d|1101:2010-2060"},{"id":"all_souls_day","name":"Dia de Todos os Fiéis Defuntos","en":"All Souls' Day","category":"public","dates":"d|1102:2010-2060"},{"id":"national_women_s_day","name":"Dia Nacional da Mulher","en":"National Women's Day","category":"public","dates":"d|1103:2023-2060"},{"id":"national_youth_day","name":"Dia Nacional da Juventude","en":"National Youth Day","category":"public","dates":"d|1112:2010-2060"},{"id":"proclamation_of_independence_day","name":"Dia da Proclamação da Independência","en":"Proclamation of Independence Day","category":"public","dates":"d|1128:2010-2060"},{"id":"memorial_day","name":"Dia da Memória","en":"Memorial Day","category":"public","dates":"d|1207:2017-2060"},{"id":"national_heroes_day","name":"Dia dos Heróis Nacionais","en":"National Heroes Day","category":"public","dates":"d|1207:2010-2016|1231:2017-2060"},{"id":"day_of_our_lady_of_immaculate_conception_and_tim","name":"Dia de Nossa Senhora da Imaculada Conceição, padroeira de Timor-Leste","en":"Day of Our Lady of Immaculate Conception and Timor-Leste Patroness","category":"public","dates":"d|1208:2010-2060"},{"id":"christmas_day","name":"Dia de Natal","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"}]} diff --git a/app/Support/Holidays/data/TM.json b/app/Support/Holidays/data/TM.json new file mode 100644 index 000000000..53d844c27 --- /dev/null +++ b/app/Support/Holidays/data/TM.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"TM","language":"tk","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Täze ýyl","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051"},{"id":"eid_al_fitr","name":"Oraza baýramy","en":"Eid al-Fitr","category":"public","dates":"y|2010:0910|2011:0831|2012:0819-0820|2013:0808|2014:0729|2015:0718|2016:0707|2017:0626|2018:0615|2019:0604|2020:0524-0525|2021:0513|2022:0502|2023:0421|2024:0410|2025:0330-0331|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102-0103,1223|2034:1212|2035:1201|2036:1119|2037:1108-1109|2038:1029|2039:1019|2040:1007-1008|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712-0713|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416-0417|2057:0405|2058:0325|2059:0315|2060:0304","estimated":"y|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304"},{"id":"non_working_day","name":"Iş güni däl","en":"Non-working day","category":"public","dates":"y|2021:0102|2025:1211,1213|2026:0928"},{"id":"eid_al_adha","name":"Gurban baýramy","en":"Eid al-Adha","category":"public","dates":"y|2010:1117|2011:1107|2012:1026|2013:1015|2014:1005,1007|2015:0924|2016:0913|2017:0902|2018:0822|2019:0811-0812|2020:0731|2021:0720|2022:0709|2023:0628|2024:0616-0617|2025:0606|2026:0527|2027:0516-0517|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218-0219|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123-1124|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828-0829|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510","estimated":"y|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510"},{"id":"memorial_day","name":"Hatyra güni","en":"Memorial Day","category":"public","dates":"d|0112:2010-2014|0113:2014|1006:2015-2060|1007:2019,2024,2030,2041,2047,2052,2058"},{"id":"international_women_s_day","name":"Halkara zenanlar güni","en":"International Women's Day","category":"public","dates":"d|0308:2010-2060|0309:2015,2020,2026,2037,2043,2048,2054"},{"id":"spring_festival","name":"Milli bahar baýramy","en":"Spring Festival","category":"public","dates":"d|0321:2010-2060|0322:2010-2060|0323:2010,2015,2020-2021,2026-2027,2032,2037-2038,2043,2048-2049,2054-2055,2060"},{"id":"constitution_and_state_flag_day","name":"Türkmenistanyň Konstitusiýasynyň we Döwlet baýdagynyň güni","en":"Constitution and State Flag Day","category":"public","dates":"d|0518:2018-2060|0519:2025,2031,2036,2042,2059|0520:2053"},{"id":"independence_day","name":"Türkmenistanyň Garaşsyzlyk güni","en":"Independence Day","category":"public","dates":"d|0927:2018-2060|0928:2020,2037,2043,2048,2054|0929:2026|1027:2010-2017|1028:2010-2017|1029:2012-2013"},{"id":"international_neutrality_day","name":"Halkara Bitaraplyk güni","en":"International Neutrality Day","category":"public","dates":"d|1212:2010-2060|1213:2010,2021,2027,2032,2038,2049,2055,2060"}]} diff --git a/app/Support/Holidays/data/TN.json b/app/Support/Holidays/data/TN.json new file mode 100644 index 000000000..9d22d6da8 --- /dev/null +++ b/app/Support/Holidays/data/TN.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"TN","language":"ar","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"رأس السنة الميلادية","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"eid_al_fitr","name":"عيد الفطر","en":"Eid al-Fitr","category":"public","dates":"y|2010:0910|2011:0830|2012:0819|2013:0808|2014:0728|2015:0717|2016:0706|2017:0625|2018:0615|2019:0604|2020:0524|2021:0513|2022:0502|2023:0421|2024:0410|2025:0330|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304","estimated":"y|2010:0910|2011:0830|2012:0819|2013:0808|2014:0728|2015:0717|2016:0706|2017:0625|2018:0615|2019:0604|2020:0524|2021:0513|2022:0502|2023:0421|2024:0410|2025:0330|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304"},{"id":"eid_al_fitr_holiday","name":"عطلة عيد الفطر","en":"Eid al-Fitr Holiday","category":"public","dates":"y|2010:0911-0912|2011:0831-0901|2012:0820-0821|2013:0809-0810|2014:0729-0730|2015:0718-0719|2016:0707-0708|2017:0626-0627|2018:0616-0617|2019:0605-0606|2020:0525-0526|2021:0514-0515|2022:0503-0504|2023:0422-0423|2024:0411-0412|2025:0331-0401|2026:0321-0322|2027:0310-0311|2028:0227-0228|2029:0215-0216|2030:0205-0206|2031:0125-0126|2032:0115-0116|2033:0103-0104,1224-1225|2034:1213-1214|2035:1202-1203|2036:1120-1121|2037:1109-1110|2038:1030-1031|2039:1020-1021|2040:1008-1009|2041:0927-0928|2042:0916-0917|2043:0905-0906|2044:0825-0826|2045:0815-0816|2046:0804-0805|2047:0725-0726|2048:0713-0714|2049:0702-0703|2050:0621-0622|2051:0611-0612|2052:0530-0531|2053:0520-0521|2054:0510-0511|2055:0429-0430|2056:0417-0418|2057:0406-0407|2058:0326-0327|2059:0316-0317|2060:0305-0306","estimated":"y|2010:0911-0912|2011:0831-0901|2012:0820-0821|2013:0809-0810|2014:0729-0730|2015:0718-0719|2016:0707-0708|2017:0626-0627|2018:0616-0617|2019:0605-0606|2020:0525-0526|2021:0514-0515|2022:0503-0504|2023:0422-0423|2024:0411-0412|2025:0331-0401|2026:0321-0322|2027:0310-0311|2028:0227-0228|2029:0215-0216|2030:0205-0206|2031:0125-0126|2032:0115-0116|2033:0103-0104,1224-1225|2034:1213-1214|2035:1202-1203|2036:1120-1121|2037:1109-1110|2038:1030-1031|2039:1020-1021|2040:1008-1009|2041:0927-0928|2042:0916-0917|2043:0905-0906|2044:0825-0826|2045:0815-0816|2046:0804-0805|2047:0725-0726|2048:0713-0714|2049:0702-0703|2050:0621-0622|2051:0611-0612|2052:0530-0531|2053:0520-0521|2054:0510-0511|2055:0429-0430|2056:0417-0418|2057:0406-0407|2058:0326-0327|2059:0316-0317|2060:0305-0306"},{"id":"prophet_s_birthday","name":"عيد المولد النبوي","en":"Prophet's Birthday","category":"public","dates":"y|2010:0226|2011:0215|2012:0204|2013:0124|2014:0113|2015:0103,1223|2016:1211|2017:1130|2018:1120|2019:1109|2020:1029|2021:1018|2022:1008|2023:0927|2024:0915|2025:0904|2026:0825|2027:0814|2028:0803|2029:0724|2030:0713|2031:0702|2032:0620|2033:0609|2034:0530|2035:0520|2036:0508|2037:0428|2038:0417|2039:0406|2040:0325|2041:0315|2042:0304|2043:0222|2044:0211|2045:0130|2046:0119|2047:0108,1229|2048:1218|2049:1207|2050:1126|2051:1116|2052:1104|2053:1024|2054:1013|2055:1003|2056:0922|2057:0911|2058:0831|2059:0820|2060:0808","estimated":"y|2010:0226|2011:0215|2012:0204|2013:0124|2014:0113|2015:0103,1223|2016:1211|2017:1130|2018:1120|2019:1109|2020:1029|2021:1018|2022:1008|2023:0927|2024:0915|2025:0904|2026:0825|2027:0814|2028:0803|2029:0724|2030:0713|2031:0702|2032:0620|2033:0609|2034:0530|2035:0520|2036:0508|2037:0428|2038:0417|2039:0406|2040:0325|2041:0315|2042:0304|2043:0222|2044:0211|2045:0130|2046:0119|2047:0108,1229|2048:1218|2049:1207|2050:1126|2051:1116|2052:1104|2053:1024|2054:1013|2055:1003|2056:0922|2057:0911|2058:0831|2059:0820|2060:0808"},{"id":"day_of_arafah","name":"يوم عرفة","en":"Day of Arafah","category":"public","dates":"y|2010:1115|2011:1105|2012:1025|2013:1014|2014:1003|2015:0922|2016:0910|2017:0831|2018:0820|2019:0810|2020:0730|2021:0719|2022:0708|2023:0627|2024:0615|2025:0605|2026:0526|2027:0515|2028:0504|2029:0423|2030:0412|2031:0401|2032:0321|2033:0310|2034:0228|2035:0217|2036:0206|2037:0125|2038:0115|2039:0104,1225|2040:1213|2041:1203|2042:1122|2043:1111|2044:1030|2045:1020|2046:1009|2047:0929|2048:0918|2049:0907|2050:0827|2051:0816|2052:0804|2053:0725|2054:0714|2055:0704|2056:0622|2057:0611|2058:0531|2059:0521|2060:0509","estimated":"y|2010:1115|2011:1105|2012:1025|2013:1014|2014:1003|2015:0922|2016:0910|2017:0831|2018:0820|2019:0810|2020:0730|2021:0719|2022:0708|2023:0627|2024:0615|2025:0605|2026:0526|2027:0515|2028:0504|2029:0423|2030:0412|2031:0401|2032:0321|2033:0310|2034:0228|2035:0217|2036:0206|2037:0125|2038:0115|2039:0104,1225|2040:1213|2041:1203|2042:1122|2043:1111|2044:1030|2045:1020|2046:1009|2047:0929|2048:0918|2049:0907|2050:0827|2051:0816|2052:0804|2053:0725|2054:0714|2055:0704|2056:0622|2057:0611|2058:0531|2059:0521|2060:0509"},{"id":"islamic_new_year","name":"رأس السنة الهجرية","en":"Islamic New Year","category":"public","dates":"y|2010:1207|2011:1126|2012:1115|2013:1104|2014:1025|2015:1014|2016:1002|2017:0921|2018:0911|2019:0831|2020:0820|2021:0809|2022:0730|2023:0719|2024:0707|2025:0626|2026:0616|2027:0606|2028:0525|2029:0514|2030:0503|2031:0423|2032:0411|2033:0401|2034:0321|2035:0311|2036:0228|2037:0216|2038:0205|2039:0126|2040:0115|2041:0104,1224|2042:1214|2043:1203|2044:1121|2045:1110|2046:1031|2047:1020|2048:1009|2049:0928|2050:0917|2051:0906|2052:0826|2053:0815|2054:0805|2055:0725|2056:0714|2057:0703|2058:0622|2059:0611|2060:0531","estimated":"y|2010:1207|2011:1126|2012:1115|2013:1104|2014:1025|2015:1014|2016:1002|2017:0921|2018:0911|2019:0831|2020:0820|2021:0809|2022:0730|2023:0719|2024:0707|2025:0626|2026:0616|2027:0606|2028:0525|2029:0514|2030:0503|2031:0423|2032:0411|2033:0401|2034:0321|2035:0311|2036:0228|2037:0216|2038:0205|2039:0126|2040:0115|2041:0104,1224|2042:1214|2043:1203|2044:1121|2045:1110|2046:1031|2047:1020|2048:1009|2049:0928|2050:0917|2051:0906|2052:0826|2053:0815|2054:0805|2055:0725|2056:0714|2057:0703|2058:0622|2059:0611|2060:0531"},{"id":"eid_al_adha","name":"عيد الأضحى","en":"Eid al-Adha","category":"public","dates":"y|2010:1116|2011:1106|2012:1026|2013:1015|2014:1004|2015:0923|2016:0911|2017:0901|2018:0821|2019:0811|2020:0731|2021:0720|2022:0709|2023:0628|2024:0616|2025:0606|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510","estimated":"y|2010:1116|2011:1106|2012:1026|2013:1015|2014:1004|2015:0923|2016:0911|2017:0901|2018:0821|2019:0811|2020:0731|2021:0720|2022:0709|2023:0628|2024:0616|2025:0606|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510"},{"id":"eid_al_adha_holiday","name":"عطلة عيد الأضحى","en":"Eid al-Adha Holiday","category":"public","dates":"y|2010:1117-1118|2011:1107-1108|2012:1027-1028|2013:1016-1017|2014:1005-1006|2015:0924-0925|2016:0912-0913|2017:0902-0903|2018:0822-0823|2019:0812-0813|2020:0801-0802|2021:0721-0722|2022:0710-0711|2023:0629-0630|2024:0617-0618|2025:0607-0608|2026:0528-0529|2027:0517-0518|2028:0506-0507|2029:0425-0426|2030:0414-0415|2031:0403-0404|2032:0323-0324|2033:0312-0313|2034:0302-0303|2035:0219-0220|2036:0208-0209|2037:0127-0128|2038:0117-0118|2039:0106-0107,1227-1228|2040:1215-1216|2041:1205-1206|2042:1124-1125|2043:1113-1114|2044:1101-1102|2045:1022-1023|2046:1011-1012|2047:1001-1002|2048:0920-0921|2049:0909-0910|2050:0829-0830|2051:0818-0819|2052:0806-0807|2053:0727-0728|2054:0716-0717|2055:0706-0707|2056:0624-0625|2057:0613-0614|2058:0602-0603|2059:0523-0524|2060:0511-0512","estimated":"y|2010:1117-1118|2011:1107-1108|2012:1027-1028|2013:1016-1017|2014:1005-1006|2015:0924-0925|2016:0912-0913|2017:0902-0903|2018:0822-0823|2019:0812-0813|2020:0801-0802|2021:0721-0722|2022:0710-0711|2023:0629-0630|2024:0617-0618|2025:0607-0608|2026:0528-0529|2027:0517-0518|2028:0506-0507|2029:0425-0426|2030:0414-0415|2031:0403-0404|2032:0323-0324|2033:0312-0313|2034:0302-0303|2035:0219-0220|2036:0208-0209|2037:0127-0128|2038:0117-0118|2039:0106-0107,1227-1228|2040:1215-1216|2041:1205-1206|2042:1124-1125|2043:1113-1114|2044:1101-1102|2045:1022-1023|2046:1011-1012|2047:1001-1002|2048:0920-0921|2049:0909-0910|2050:0829-0830|2051:0818-0819|2052:0806-0807|2053:0727-0728|2054:0716-0717|2055:0706-0707|2056:0624-0625|2057:0613-0614|2058:0602-0603|2059:0523-0524|2060:0511-0512"},{"id":"revolution_and_youth_day","name":"عيد الثورة والشباب","en":"Revolution and Youth Day","category":"public","dates":"d|0114:2010-2060"},{"id":"independence_day","name":"عيد الاستقلال","en":"Independence Day","category":"public","dates":"d|0320:2010-2060"},{"id":"martyrs_day","name":"عيد الشهداء","en":"Martyrs' Day","category":"public","dates":"d|0409:2010-2060"},{"id":"labor_day","name":"عيد العمال","en":"Labor Day","category":"public","dates":"d|0501:2010-2060"},{"id":"republic_day","name":"عيد الجمهورية","en":"Republic Day","category":"public","dates":"d|0725:2010-2060"},{"id":"women_s_day","name":"عيد المرأة","en":"Women's Day","category":"public","dates":"d|0813:2010-2060"},{"id":"evacuation_day","name":"عيد الجلاء","en":"Evacuation Day","category":"public","dates":"d|1015:2010-2060"}]} diff --git a/app/Support/Holidays/data/TO.json b/app/Support/Holidays/data/TO.json new file mode 100644 index 000000000..cd4aad610 --- /dev/null +++ b/app/Support/Holidays/data/TO.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"TO","language":"to","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"ʻUluaki ʻAho ʻo e Taʻu Foʻou","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2012"},{"id":"good_friday","name":"Falaite Lelei","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_monday","name":"Monite ʻo e Toetuʻu","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"anzac_day","name":"ʻAho Anzac","en":"ANZAC Day","category":"public","dates":"d|0425:2010-2060|0426:2010"},{"id":"birthday_of_the_reigning_sovereign_of_tonga","name":"ʻAho ʻAloʻi ʻo ʻEne ʻAfio ko e Tuʻi ʻo Tonga ʻoku lolotonga Pule","en":"Birthday of the Reigning Sovereign of Tonga","category":"public","dates":"d|0502:2011|0504:2010|0704:2013-2060|0705:2021,2027,2032,2038,2049,2055,2060"},{"id":"emancipation_day","name":"ʻAho Tauʻataina","en":"Emancipation Day","category":"public","dates":"d|0602:2014,2025,2031,2036,2042,2053,2059|0603:2013,2019,2024,2030,2041,2047,2052,2058|0604:2012,2018,2029,2035,2040,2046,2057|0605:2017,2023,2028,2034,2045,2051,2056|0606:2011,2016,2022,2033,2039,2044,2050|0607:2010,2021,2027,2032,2038,2049,2055,2060|0608:2015,2020,2026,2037,2043,2048,2054"},{"id":"birthday_of_the_heir_to_the_crown_of_tonga","name":"ʻAho ʻAloʻi ʻo e ʻEa ki he Kalauni ʻo Tonga","en":"Birthday of the Heir to the Crown of Tonga","category":"public","dates":"d|0711:2011|0712:2010|0917:2012-2060|0918:2017,2023,2028,2034,2045,2051,2056"},{"id":"constitution_day","name":"ʻAho Konisitutone","en":"Constitution Day","category":"public","dates":"d|1102:2015,2020,2026,2037,2043,2048,2054|1103:2014,2025,2031,2036,2042,2053,2059|1104:2013,2019,2024,2030,2041,2047,2052,2058|1105:2012,2018,2029,2035,2040,2046,2057|1106:2017,2023,2028,2034,2045,2051,2056|1107:2011,2016,2022,2033,2039,2044,2050|1108:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"anniversary_of_the_coronation_of_hm_king_george_","name":"ʻAho Fakamanatu ʻo e Hilifaki Kalauni ʻo ʻEne ʻAfio ko Siaosi Tupou I","en":"Anniversary of the Coronation of HM King George Tupou I","category":"public","dates":"d|1202:2013,2019,2024,2030,2041,2047,2052,2058|1203:2012,2018,2029,2035,2040,2046,2057|1204:2017,2023,2028,2034,2045,2051,2056|1205:2011,2016,2022,2033,2039,2044,2050|1206:2010,2021,2027,2032,2038,2049,2055,2060|1207:2015,2020,2026,2037,2043,2048,2054|1208:2014,2025,2031,2036,2042,2053,2059"},{"id":"christmas_day","name":"ʻAho Kilisimasi","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"},{"id":"boxing_day","name":"ʻAho 2 ʻo e Kilisimasi","en":"Boxing Day","category":"public","dates":"d|1226:2010-2060|1227:2021"}]} diff --git a/app/Support/Holidays/data/TR.json b/app/Support/Holidays/data/TR.json new file mode 100644 index 000000000..dd44fe2ec --- /dev/null +++ b/app/Support/Holidays/data/TR.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"TR","language":"tr","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Yılbaşı","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"eid_al_fitr","name":"Ramazan Bayramı","en":"Eid al-Fitr","category":"public","dates":"y|2010:0909-0911|2011:0830-0901|2012:0819-0821|2013:0808-0810|2014:0728-0730|2015:0717-0719|2016:0705-0707|2017:0625-0627|2018:0615-0617|2019:0604-0606|2020:0524-0526|2021:0513-0515|2022:0502-0504|2023:0421-0423|2024:0410-0412|2025:0330-0401|2026:0320-0322|2027:0309-0311|2028:0226-0228|2029:0214-0216|2030:0204-0206|2031:0124-0126|2032:0114-0116|2033:0102-0104,1223-1225|2034:1212-1214|2035:1201-1203|2036:1119-1121|2037:1108-1110|2038:1029-1031|2039:1019-1021|2040:1007-1009|2041:0926-0928|2042:0915-0917|2043:0904-0906|2044:0824-0826|2045:0814-0816|2046:0803-0805|2047:0724-0726|2048:0712-0714|2049:0701-0703|2050:0620-0622|2051:0610-0612|2052:0529-0531|2053:0519-0521|2054:0509-0511|2055:0428-0430|2056:0416-0418|2057:0405-0407|2058:0325-0327|2059:0315-0317|2060:0304-0306","estimated":"y|2033:0102-0104,1223-1225|2034:1212-1214|2035:1201-1203|2036:1119-1121|2037:1108-1110|2038:1029-1031|2039:1019-1021|2040:1007-1009|2041:0926-0928|2042:0915-0917|2043:0904-0906|2044:0824-0826|2045:0814-0816|2046:0803-0805|2047:0724-0726|2048:0712-0714|2049:0701-0703|2050:0620-0622|2051:0610-0612|2052:0529-0531|2053:0519-0521|2054:0509-0511|2055:0428-0430|2056:0416-0418|2057:0405-0407|2058:0325-0327|2059:0315-0317|2060:0304-0306"},{"id":"eid_al_adha","name":"Kurban Bayramı","en":"Eid al-Adha","category":"public","dates":"y|2010:1116-1119|2011:1106-1109|2012:1025-1028|2013:1015-1018|2014:1004-1007|2015:0924-0927|2016:0912-0915|2017:0901-0904|2018:0821-0824|2019:0811-0814|2020:0731-0803|2021:0720-0723|2022:0709-0712|2023:0628-0701|2024:0616-0619|2025:0606-0609|2026:0527-0530|2027:0516-0519|2028:0505-0508|2029:0424-0427|2030:0413-0416|2031:0402-0405|2032:0322-0325|2033:0311-0314|2034:0301-0304|2035:0218-0221|2036:0207-0210|2037:0126-0129|2038:0116-0119|2039:0105-0108,1226-1229|2040:1214-1217|2041:1204-1207|2042:1123-1126|2043:1112-1115|2044:1031-1103|2045:1021-1024|2046:1010-1013|2047:0930-1003|2048:0919-0922|2049:0908-0911|2050:0828-0831|2051:0817-0820|2052:0805-0808|2053:0726-0729|2054:0715-0718|2055:0705-0708|2056:0623-0626|2057:0612-0615|2058:0601-0604|2059:0522-0525|2060:0510-0513","estimated":"y|2033:0311-0314|2034:0301-0304|2035:0218-0221|2036:0207-0210|2037:0126-0129|2038:0116-0119|2039:0105-0108,1226-1229|2040:1214-1217|2041:1204-1207|2042:1123-1126|2043:1112-1115|2044:1031-1103|2045:1021-1024|2046:1010-1013|2047:0930-1003|2048:0919-0922|2049:0908-0911|2050:0828-0831|2051:0817-0820|2052:0805-0808|2053:0726-0729|2054:0715-0718|2055:0705-0708|2056:0623-0626|2057:0612-0615|2058:0601-0604|2059:0522-0525|2060:0510-0513"},{"id":"national_sovereignty_and_children_s_day","name":"Ulusal Egemenlik ve Çocuk Bayramı","en":"National Sovereignty and Children's Day","category":"public","dates":"d|0423:2010-2060"},{"id":"labour_and_solidarity_day","name":"Emek ve Dayanışma Günü","en":"Labour and Solidarity Day","category":"public","dates":"d|0501:2010-2060"},{"id":"commemoration_of_ataturk_youth_and_sports_day","name":"Atatürk'ü Anma, Gençlik ve Spor Bayramı","en":"Commemoration of Atatürk, Youth and Sports Day","category":"public","dates":"d|0519:2010-2060"},{"id":"democracy_and_national_unity_day","name":"Demokrasi ve Millî Birlik Günü","en":"Democracy and National Unity Day","category":"public","dates":"d|0715:2017-2060"},{"id":"victory_day","name":"Zafer Bayramı","en":"Victory Day","category":"public","dates":"d|0830:2010-2060"},{"id":"republic_day","name":"Cumhuriyet Bayramı","en":"Republic Day","category":"public","dates":"d|1029:2010-2060"},{"id":"eid_al_fitr_half_day","name":"Ramazan Bayramı","en":"Eid al-Fitr","category":"half_day","dates":"y|2010:0908|2011:0829|2012:0818|2013:0807|2014:0727|2015:0716|2016:0704|2017:0624|2018:0614|2019:0603|2020:0523|2021:0512|2022:0501|2023:0420|2024:0409|2025:0329|2026:0319|2027:0308|2028:0225|2029:0213|2030:0203|2031:0123|2032:0113|2033:0101,1222|2034:1211|2035:1130|2036:1118|2037:1107|2038:1028|2039:1018|2040:1006|2041:0925|2042:0914|2043:0903|2044:0823|2045:0813|2046:0802|2047:0723|2048:0711|2049:0630|2050:0619|2051:0609|2052:0528|2053:0518|2054:0508|2055:0427|2056:0415|2057:0404|2058:0324|2059:0314|2060:0303","estimated":"y|2033:0101,1222|2034:1211|2035:1130|2036:1118|2037:1107|2038:1028|2039:1018|2040:1006|2041:0925|2042:0914|2043:0903|2044:0823|2045:0813|2046:0802|2047:0723|2048:0711|2049:0630|2050:0619|2051:0609|2052:0528|2053:0518|2054:0508|2055:0427|2056:0415|2057:0404|2058:0324|2059:0314|2060:0303"},{"id":"eid_al_adha_half_day","name":"Kurban Bayramı","en":"Eid al-Adha","category":"half_day","dates":"y|2010:1115|2011:1105|2012:1024|2013:1014|2014:1003|2015:0923|2016:0911|2017:0831|2018:0820|2019:0810|2020:0730|2021:0719|2022:0708|2023:0627|2024:0615|2025:0605|2026:0526|2027:0515|2028:0504|2029:0423|2030:0412|2031:0401|2032:0321|2033:0310|2034:0228|2035:0217|2036:0206|2037:0125|2038:0115|2039:0104,1225|2040:1213|2041:1203|2042:1122|2043:1111|2044:1030|2045:1020|2046:1009|2047:0929|2048:0918|2049:0907|2050:0827|2051:0816|2052:0804|2053:0725|2054:0714|2055:0704|2056:0622|2057:0611|2058:0531|2059:0521|2060:0509","estimated":"y|2033:0310|2034:0228|2035:0217|2036:0206|2037:0125|2038:0115|2039:0104,1225|2040:1213|2041:1203|2042:1122|2043:1111|2044:1030|2045:1020|2046:1009|2047:0929|2048:0918|2049:0907|2050:0827|2051:0816|2052:0804|2053:0725|2054:0714|2055:0704|2056:0622|2057:0611|2058:0531|2059:0521|2060:0509"},{"id":"republic_day_half_day","name":"Cumhuriyet Bayramı","en":"Republic Day","category":"half_day","dates":"d|1028:2010-2060"}]} diff --git a/app/Support/Holidays/data/TT.json b/app/Support/Holidays/data/TT.json new file mode 100644 index 000000000..6bca8a880 --- /dev/null +++ b/app/Support/Holidays/data/TT.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"TT","language":"en_TT","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051"},{"id":"eid_al_fitr","name":"Eid-Ul-Fitr","en":"Eid al-Fitr","category":"public","dates":"y|2010:0910|2011:0830|2012:0819-0820|2013:0808|2014:0729|2015:0718|2016:0706|2017:0626|2018:0615|2019:0605|2020:0524-0525|2021:0513|2022:0502|2023:0422|2024:0410|2025:0331|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102-0103,1223|2034:1212|2035:1201|2036:1119|2037:1108-1109|2038:1029|2039:1019|2040:1007-1008|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712-0713|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416-0417|2057:0405|2058:0325|2059:0315|2060:0304","estimated":"y|2010:0910|2011:0830|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304"},{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2018,2026,2029,2037,2040,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_monday","name":"Easter Monday","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2043,2054,2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"spiritual_baptist_liberation_day","name":"Spiritual Baptist Liberation Day","en":"Spiritual Baptist Liberation Day","category":"public","dates":"d|0330:2010-2060|0331:2014,2031,2036,2042-2043,2053-2054|0401:2025,2059|0403:2018,2029,2040"},{"id":"corpus_christi","name":"Corpus Christi","en":"Corpus Christi","category":"public","dates":"d|0524:2035,2046|0526:2016|0527:2027,2032|0528:2043,2054|0529:2059|0530:2013,2024|0531:2013,2018,2024,2029,2040|0601:2051,2056|0603:2010,2021|0604:2015,2026,2037,2048|0605:2042,2053|0607:2012|0608:2023,2034,2045|0609:2039,2050|0611:2020|0612:2031,2036|0613:2047,2058|0615:2017,2028|0616:2022,2033,2044|0617:2049,2055,2060|0619:2014,2025|0620:2014,2019,2025,2030,2041,2052|0621:2057|0623:2011|0624:2038"},{"id":"indian_arrival_day","name":"Indian Arrival Day","en":"Indian Arrival Day","category":"public","dates":"d|0530:2010-2060|0531:2010,2013,2021,2024,2027,2032,2038,2049,2055,2060"},{"id":"labor_day","name":"Labour Day","en":"Labor Day","category":"public","dates":"d|0619:2010-2060|0620:2011,2014,2016,2022,2025,2033,2039,2044|0621:2050"},{"id":"african_emancipation_day","name":"African Emancipation Day","en":"African Emancipation Day","category":"public","dates":"d|0801:2010-2060|0802:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"independence_day","name":"Independence Day","en":"Independence Day","category":"public","dates":"d|0831:2010-2060|0901:2014,2025,2031,2036,2042,2053,2059"},{"id":"republic_day","name":"Republic Day","en":"Republic Day","category":"public","dates":"d|0924:2010-2060|0925:2017,2023,2028,2034,2045,2051,2056"},{"id":"diwali","name":"Divali","en":"Diwali","category":"public","dates":"d|1019:2017|1020:2025|1022:2033|1023:2014,2060|1024:2022,2041|1026:2011,2030,2049|1027:2019,2038,2057|1028:2019,2027,2046|1029:2016,2046|1030:2035,2054|1031:2024|1101:2043|1102:2032,2043,2051|1104:2013,2021,2040,2059|1105:2010,2029,2040,2048|1106:2037,2056|1107:2018|1108:2026,2045|1109:2026,2053|1110:2034,2053|1111:2015|1112:2023,2042|1113:2012,2023,2050|1114:2020,2031,2050|1115:2028,2039,2058|1116:2047|1117:2036|1118:2044,2055|1120:2052"},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060|1227:2011,2016,2022,2033,2039,2044,2050"},{"id":"boxing_day","name":"Boxing Day","en":"Boxing Day","category":"public","dates":"d|1226:2010-2060|1227:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"carnival_monday_optional","name":"Carnival Monday","en":"Carnival Monday","category":"optional","dates":"d|0205:2035,2046|0208:2016,2027|0209:2032,2043,2054|0210:2059|0211:2013|0212:2018,2024,2029|0213:2040,2051|0214:2056|0215:2010,2021|0216:2015,2026,2037|0217:2042,2048,2053|0220:2012,2023,2034,2045|0221:2039,2050|0224:2020,2031|0225:2036,2047,2058|0227:2017|0228:2022,2028,2033|0229:2044|0301:2049,2055,2060|0303:2014,2025|0304:2019,2030,2041,2052|0305:2057|0307:2011|0308:2038"},{"id":"carnival_tuesday_optional","name":"Carnival Tuesday","en":"Carnival Tuesday","category":"optional","dates":"d|0206:2035,2046|0209:2016,2027|0210:2032,2043,2054|0211:2059|0212:2013|0213:2018,2024,2029|0214:2040,2051|0215:2056|0216:2010,2021|0217:2015,2026,2037|0218:2042,2048,2053|0221:2012,2023,2034,2045|0222:2039,2050|0225:2020,2031|0226:2036,2047,2058|0228:2017|0229:2028|0301:2022,2033,2044|0302:2049,2055,2060|0304:2014,2025|0305:2019,2030,2041,2052|0306:2057|0308:2011|0309:2038"}]} diff --git a/app/Support/Holidays/data/TV.json b/app/Support/Holidays/data/TV.json new file mode 100644 index 000000000..ce29ac582 --- /dev/null +++ b/app/Support/Holidays/data/TV.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"TV","language":"tvl","years":[2010,2060],"subdivisions":{"FUN":"Funafuti","NIT":"Niutao","NKF":"Nukufetau","NKL":"Nukulaelae","NMA":"Nanumea","NMG":"Nanumaga","NUI":"Nui","VAI":"Vaitupu"},"holidays":[{"id":"new_year_s_day","name":"Tausaga Fou","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051|0103:2011,2022,2028,2033,2039,2050,2056"},{"id":"golden_jubilee","name":"Te Po o Tefolaha","en":"Golden Jubilee","category":"public","regional":{"NMA":"d|0108:2010-2060"}},{"id":"big_day","name":"Po Lahi","en":"Big Day","category":"public","regional":{"NMA":"d|0203:2010-2060"}},{"id":"nukufetau_day","name":"Te Aso O Tutasi","en":"Nukufetau Day","category":"public","regional":{"NKF":"d|0211:2010-2060"}},{"id":"day_of_the_flood","name":"Bogin te Ieka","en":"Day of the Flood","category":"public","regional":{"NUI":"d|0216:2010-2060"}},{"id":"good_friday","name":"Aso toe tu","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_monday","name":"Toe Tu aso gafua","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"nanumaga_day","name":"Aho o te Fakavae","en":"Nanumaga Day","category":"public","regional":{"NMG":"d|0415:2010-2060"}},{"id":"the_day_of_the_bombing","name":"Te Aso o te Paula","en":"The Day of the Bombing","category":"public","regional":{"FUN":"d|0423:2010-2060"}},{"id":"gospel_day","name":"Te Aso o te Tala Lei","en":"Gospel Day","category":"public","dates":"d|0509:2011,2016,2022,2033,2039,2044,2050|0510:2010,2021,2027,2032,2038,2049,2055,2060|0511:2015,2020,2026,2037,2043,2048,2054|0512:2014,2025,2031,2036,2042,2053,2059|0513:2013,2019,2024,2030,2041,2047,2052,2058|0514:2012,2018,2029,2035,2040,2046,2057|0515:2017,2023,2028,2034,2045,2051,2056","regional":{"NKL":"d|0510:2011-2020,2022-2026,2028-2031,2033-2037,2039-2048,2050-2054,2056-2059"}},{"id":"king_s_birthday","name":"Asofanau Tupu","en":"King's Birthday","category":"public","dates":"d|0608:2024,2030,2041,2047,2052,2058|0609:2029,2035,2040,2046,2057|0610:2023,2028,2034,2045,2051,2056|0611:2033,2039,2044,2050|0612:2027,2032,2038,2049,2055,2060|0613:2026,2037,2043,2048,2054|0614:2025,2031,2036,2042,2053,2059"},{"id":"national_children_s_day","name":"Aso Tamaliki","en":"National Children's Day","category":"public","dates":"d|0801:2011,2016|0802:2010|0803:2015|0804:2014|0805:2013|0806:2012,2018|0807:2017|1009:2023,2028,2034,2045,2051,2056|1010:2022,2033,2039,2044,2050|1011:2021,2027,2032,2038,2049,2055,2060|1012:2020,2026,2037,2043,2048,2054|1013:2025,2031,2036,2042,2053,2059|1014:2019,2024,2030,2041,2047,2052,2058|1015:2029,2035,2040,2046,2057"},{"id":"national_youth_day","name":"Aso tupulaga","en":"National Youth Day","category":"public","dates":"d|0801:2022,2033,2039,2044,2050|0802:2021,2027,2032,2038,2049,2055,2060|0803:2020,2026,2037,2043,2048,2054|0804:2025,2031,2036,2042,2053,2059|0805:2019,2024,2030,2041,2047,2052,2058|0806:2029,2035,2040,2046,2057|0807:2023,2028,2034,2045,2051,2056"},{"id":"niutao_day","name":"Te Aso o te Setema","en":"Niutao Day","category":"public","regional":{"NIT":"d|0917:2010-2060"}},{"id":"tuvalu_day","name":"Tutokotasi","en":"Tuvalu Day","category":"public","dates":"d|1001:2010-2060|1002:2010-2060|1003:2011,2016-2017,2022-2023,2028,2033-2034,2039,2044-2045,2050-2051,2056|1004:2010-2011,2016,2021-2022,2027,2032-2033,2038-2039,2044,2049-2050,2055,2060"},{"id":"cyclone_day","name":"Aso o te matagi","en":"Cyclone Day","category":"public","regional":{"FUN":"d|1021:2010-2060"}},{"id":"happy_day","name":"Te Aso Fiafia","en":"Happy Day","category":"public","regional":{"VAI":"d|1125:2010-2060"}},{"id":"christmas_day","name":"Kilisimasi","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060|1227:2010-2011,2016,2021-2022,2027,2032-2033,2038-2039,2044,2049-2050,2055,2060"},{"id":"boxing_day","name":"Aso Faipele","en":"Boxing Day","category":"public","dates":"d|1226:2010-2060|1228:2010,2015,2020-2021,2026-2027,2032,2037-2038,2043,2048-2049,2054-2055,2060"}]} diff --git a/app/Support/Holidays/data/TW.json b/app/Support/Holidays/data/TW.json new file mode 100644 index 000000000..99fffd2ac --- /dev/null +++ b/app/Support/Holidays/data/TW.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"TW","language":"zh_TW","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"founding_day_of_the_republic_of_china","name":"中華民國開國紀念日","en":"Founding Day of the Republic of China","category":"public","dates":"d|0101:2010-2060|0102:2017,2023,2034,2040,2045,2051|1231:2021,2027,2032,2038,2049,2055,2060"},{"id":"chinese_new_year_s_eve","name":"農曆除夕","en":"Chinese New Year's Eve","category":"public","dates":"d|0120:2042|0121:2023,2031,2042,2050|0122:2012,2031,2039,2050,2058|0123:2039,2058|0124:2020,2028,2047|0125:2023,2028,2047|0126:2012,2036,2050,2055|0127:2017,2036,2039,2055|0128:2025,2039,2044|0129:2033,2044|0130:2014,2033,2041,2052|0131:2022,2036,2041,2049,2052,2060|0201:2030,2036,2049,2060|0202:2011,2030,2038,2057|0203:2033,2038,2057|0204:2019,2027,2033,2046|0205:2027,2046,2049,2060|0206:2030,2035,2054,2060|0207:2016,2035,2054,2057|0208:2043|0209:2013,2024,2032,2043,2046,2051|0210:2032,2040,2051,2059|0211:2016,2021,2029,2040,2054,2059|0212:2029,2048|0213:2010,2013,2037,2043,2048,2056|0214:2037,2056|0215:2018,2026,2040,2045|0216:2026,2029,2045|0217:2010,2034,2053|0218:2015,2034,2037,2053,2056|0220:2026|0222:2034"},{"id":"chinese_new_year","name":"春節","en":"Chinese New Year","category":"public","dates":"y|2010:0214-0216,0218|2011:0203-0205,0207|2012:0123-0125|2013:0210-0212,0214|2014:0131-0204|2015:0219-0221,0223|2016:0208-0210|2017:0128-0201|2018:0216-0220|2019:0205-0207|2020:0125-0129|2021:0212-0216|2022:0201-0203|2023:0122-0124,0126|2024:0210-0214|2025:0129-0131|2026:0217-0219|2027:0206-0210|2028:0126-0128|2029:0213-0215|2030:0203-0205,0207|2031:0123-0125,0127|2032:0211-0213|2033:0131-0202|2034:0219-0221,0223|2035:0208-0210,0212|2036:0128-0130|2037:0215-0217,0219|2038:0204-0206,0208|2039:0124-0126|2040:0212-0214,0216|2041:0201-0205|2042:0122-0124|2043:0210-0212|2044:0130-0203|2045:0217-0221|2046:0206-0208|2047:0126-0130|2048:0214-0218|2049:0202-0204|2050:0123-0125,0127|2051:0211-0215|2052:0201-0203,0205|2053:0219-0221|2054:0208-0210,0212|2055:0128-0130,0201|2056:0215-0217|2057:0204-0206,0208|2058:0124-0126,0128|2059:0212-0214|2060:0202-0204"},{"id":"peace_memorial_day","name":"和平紀念日","en":"Peace Memorial Day","category":"public","dates":"d|0227:2015,2026,2032,2037,2043,2054,2060|0228:2010-2060|0229:2016,2044|0301:2021,2027,2038,2049,2055"},{"id":"children_s_day","name":"兒童節","en":"Children's Day","category":"public","dates":"d|0402:2021,2032,2049,2060|0403:2015,2017,2020,2025-2026,2028-2029,2036-2037,2040,2042-2043,2045-2046,2048,2053-2054,2056-2057|0404:2011-2060|0405:2013,2016,2024,2033,2041,2044,2050,2052,2058|0406:2027,2038,2055"},{"id":"tomb_sweeping_day","name":"民族掃墓節","en":"Tomb-Sweeping Day","category":"public","dates":"d|0402:2020,2037,2048,2054|0403:2031,2059|0404:2012-2013,2016-2017,2020-2021,2024-2025,2028-2029,2032-2033,2036-2037,2040-2042,2044-2046,2048-2050,2052-2054,2056-2058,2060|0405:2010-2011,2014-2015,2018-2019,2021-2023,2026-2027,2030-2032,2034-2035,2038-2039,2043,2047,2049,2051,2055,2059-2060|0406:2015,2026,2043"},{"id":"labor_day","name":"勞動節","en":"Labor Day","category":"public","dates":"d|0430:2027,2032,2038,2049,2055,2060|0501:2026-2060|0502:2033,2039,2044,2050"},{"id":"dragon_boat_festival","name":"端午節","en":"Dragon Boat Festival","category":"public","dates":"d|0527:2039|0528:2028|0529:2028,2047|0530:2017,2025,2036,2055|0531:2025,2044,2052,2055|0601:2033,2052|0602:2014|0603:2022,2041,2060|0604:2049|0605:2030|0606:2011,2057|0607:2019,2038|0608:2046|0609:2016,2027|0610:2024,2035,2054|0611:2032,2035,2043|0612:2013,2032|0613:2051,2059|0614:2021,2040,2059|0615:2029,2048|0616:2010,2029,2056|0617:2056|0618:2018,2037|0619:2015,2026,2045|0620:2015,2034,2053|0622:2023,2042|0623:2012,2042,2050|0624:2031|0625:2020,2058"},{"id":"mid_autumn_festival","name":"中秋節","en":"Mid-Autumn Festival","category":"public","dates":"d|0906:2052|0907:2052|0908:2014,2033|0909:2022,2060|0910:2022,2041,2049|0911:2049|0912:2011,2030|0913:2019,2038,2057|0914:2046|0915:2016,2027,2046|0916:2035,2054|0917:2024,2035,2043|0919:2013,2032,2051|0920:2032,2040|0921:2021,2029,2059|0922:2010,2029,2048,2059|0924:2018,2037,2056|0925:2026,2045,2056|0926:2053|0927:2015,2034|0928:2015,2042|0929:2023|0930:2012,2042,2050|1001:2020,2031|1002:2039,2058|1003:2028,2036,2039|1004:2017,2036,2047|1005:2044,2055|1006:2025"},{"id":"confucius_birthday","name":"孔子誕辰紀念日","en":"Confucius' Birthday","category":"public","dates":"d|0927:2030,2041,2047,2052,2058|0928:2025-2060|0929:2025,2031,2036,2042,2053,2059"},{"id":"national_day","name":"國慶日","en":"National Day","category":"public","dates":"d|1009:2015,2020,2026,2037,2043,2048,2054|1010:2010-2060|1011:2021,2027,2032,2038,2049,2055,2060"},{"id":"taiwan_restoration_and_guningtou_victory_memoria","name":"臺灣光復暨金門古寧頭大捷紀念日","en":"Taiwan Restoration and Guningtou Victory Memorial Day","category":"public","dates":"d|1024:2025,2031,2036,2042,2053,2059|1025:2025-2060|1026:2026,2037,2043,2048,2054"},{"id":"constitution_day","name":"行憲紀念日","en":"Constitution Day","category":"public","dates":"d|1224:2027,2032,2038,2049,2055,2060|1225:2025-2060|1226:2033,2039,2044,2050"},{"id":"armed_forces_day_optional","name":"軍人節","en":"Armed Forces Day","category":"optional","dates":"d|0903:2010-2060"}]} diff --git a/app/Support/Holidays/data/TZ.json b/app/Support/Holidays/data/TZ.json new file mode 100644 index 000000000..f647c97d2 --- /dev/null +++ b/app/Support/Holidays/data/TZ.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"TZ","language":"sw","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Mwaka Mpya","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2012,2017|0103:2011,2022"},{"id":"eid_al_fitr","name":"Eid El-Fitri","en":"Eid al-Fitr","category":"public","dates":"y|2010:0911-0913|2011:0831-0901|2012:0820-0821|2013:0808-0809|2014:0729-0730|2015:0718-0720|2016:0707-0708|2017:0626-0627|2018:0615-0616,0618|2019:0605-0606|2020:0524-0526|2021:0514-0515,0517|2022:0503-0504|2023:0422-0423|2024:0410-0411|2025:0331-0401|2026:0321-0322|2027:0310-0311|2028:0227-0228|2029:0215-0216|2030:0205-0206|2031:0125-0126|2032:0115-0116|2033:0103-0104,1224-1225|2034:1213-1214|2035:1202-1203|2036:1120-1121|2037:1109-1110|2038:1030-1031|2039:1020-1021|2040:1008-1009|2041:0927-0928|2042:0916-0917|2043:0905-0906|2044:0825-0826|2045:0815-0816|2046:0804-0805|2047:0725-0726|2048:0713-0714|2049:0702-0703|2050:0621-0622|2051:0611-0612|2052:0530-0531|2053:0520-0521|2054:0510-0511|2055:0429-0430|2056:0417-0418|2057:0406-0407|2058:0326-0327|2059:0316-0317|2060:0305-0306","estimated":"y|2010:0911-0912|2011:0831-0901|2012:0820-0821|2026:0321-0322|2027:0310-0311|2028:0227-0228|2029:0215-0216|2030:0205-0206|2031:0125-0126|2032:0115-0116|2033:0103-0104,1224-1225|2034:1213-1214|2035:1202-1203|2036:1120-1121|2037:1109-1110|2038:1030-1031|2039:1020-1021|2040:1008-1009|2041:0927-0928|2042:0916-0917|2043:0905-0906|2044:0825-0826|2045:0815-0816|2046:0804-0805|2047:0725-0726|2048:0713-0714|2049:0702-0703|2050:0621-0622|2051:0611-0612|2052:0530-0531|2053:0520-0521|2054:0510-0511|2055:0429-0430|2056:0417-0418|2057:0406-0407|2058:0326-0327|2059:0316-0317|2060:0305-0306"},{"id":"prophet_s_birthday","name":"Maulidi","en":"Prophet's Birthday","category":"public","dates":"y|2010:0227,0301|2011:0216|2012:0205-0206|2013:0124|2014:0114|2015:0103,0105,1224|2016:1212|2017:1201|2018:1121|2019:1110-1111|2020:1029|2021:1019|2022:1009|2023:0928|2024:0916|2025:0904|2026:0826|2027:0815|2028:0804|2029:0725|2030:0714|2031:0703|2032:0621|2033:0610|2034:0531|2035:0521|2036:0509|2037:0429|2038:0418|2039:0407|2040:0326|2041:0316|2042:0305|2043:0223|2044:0212|2045:0131|2046:0120|2047:0109,1230|2048:1219|2049:1208|2050:1127|2051:1117|2052:1105|2053:1025|2054:1014|2055:1004|2056:0923|2057:0912|2058:0901|2059:0821|2060:0809","estimated":"y|2010:0227|2011:0216|2012:0205|2026:0826|2027:0815|2028:0804|2029:0725|2030:0714|2031:0703|2032:0621|2033:0610|2034:0531|2035:0521|2036:0509|2037:0429|2038:0418|2039:0407|2040:0326|2041:0316|2042:0305|2043:0223|2044:0212|2045:0131|2046:0120|2047:0109,1230|2048:1219|2049:1208|2050:1127|2051:1117|2052:1105|2053:1025|2054:1014|2055:1004|2056:0923|2057:0912|2058:0901|2059:0821|2060:0809"},{"id":"eid_al_adha","name":"Eid El Hajj","en":"Eid al-Adha","category":"public","dates":"y|2010:1117|2011:1107|2012:1027,1029|2013:1015|2014:1005-1006|2015:0923|2016:0913|2017:0902,0904|2018:0822|2019:0813|2020:0731|2021:0721|2022:0710-0711|2023:0629|2024:0617|2025:0607|2026:0528|2027:0517|2028:0506|2029:0425|2030:0414|2031:0403|2032:0323|2033:0312|2034:0302|2035:0219|2036:0208|2037:0127|2038:0117|2039:0106,1227|2040:1215|2041:1205|2042:1124|2043:1113|2044:1101|2045:1022|2046:1011|2047:1001|2048:0920|2049:0909|2050:0829|2051:0818|2052:0806|2053:0727|2054:0716|2055:0706|2056:0624|2057:0613|2058:0602|2059:0523|2060:0511","estimated":"y|2010:1117|2011:1107|2012:1027|2026:0528|2027:0517|2028:0506|2029:0425|2030:0414|2031:0403|2032:0323|2033:0312|2034:0302|2035:0219|2036:0208|2037:0127|2038:0117|2039:0106,1227|2040:1215|2041:1205|2042:1124|2043:1113|2044:1101|2045:1022|2046:1011|2047:1001|2048:0920|2049:0909|2050:0829|2051:0818|2052:0806|2053:0727|2054:0716|2055:0706|2056:0624|2057:0613|2058:0602|2059:0523|2060:0511"},{"id":"zanzibar_revolution_day","name":"Mapinduzi ya Zanzibar","en":"Zanzibar Revolution Day","category":"public","dates":"d|0112:2010-2060|0113:2014,2020|0114:2013,2019"},{"id":"good_friday","name":"Ijumaa Kuu","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_monday","name":"Jumatatu ya Pasaka","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"the_sheikh_abeid_amani_karume_day","name":"Siku ya kumbukumbu ya Rais wa Kwanza wa Serikali ya Mapinduzi Zanzibar Sheikh Abeid Amani Karume","en":"The Sheikh Abeid Amani Karume Day","category":"public","dates":"d|0407:2010-2060|0408:2013,2019|0409:2018|0410:2012"},{"id":"union_celebrations","name":"Muungano wa Tanzania","en":"Union Celebrations","category":"public","dates":"d|0426:2010-2060|0427:2015,2020|0428:2014"},{"id":"international_workers_day","name":"Sikukuu ya Wafanyakazi Ulimwenguni","en":"International Workers' Day","category":"public","dates":"d|0501:2010-2060|0502:2011,2016,2022|0503:2010,2021"},{"id":"international_trade_fair","name":"Sabasaba","en":"International Trade Fair","category":"public","dates":"d|0707:2010-2060|0708:2013,2019|0709:2012,2018"},{"id":"peasants_day","name":"Sikukuu ya Wakulima","en":"Peasants' Day","category":"public","dates":"d|0808:2010-2060|0809:2010,2021|0810:2015,2020"},{"id":"the_mwalimu_nyerere_day_and_climax_of_the_uhuru_","name":"Kumbukumbu ya Mwalimu Nyerere na Kilele cha mbio za Mwenge","en":"The Mwalimu Nyerere Day and Climax of the Uhuru Torch Race","category":"public","dates":"d|1014:2010-2060|1015:2012,2018|1016:2017"},{"id":"independence_and_republic_day","name":"Uhuru na Jamhuri","en":"Independence and Republic Day","category":"public","dates":"d|1209:2010-2060|1210:2012,2018|1211:2017"},{"id":"christmas_day","name":"Kuzaliwa Kristo","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060|1227:2010-2011,2016,2021"},{"id":"boxing_day","name":"Siku ya Kupeana Zawadi","en":"Boxing Day","category":"public","dates":"d|1226:2010-2060|1227:2010,2021|1228:2015,2020"},{"id":"easter_sunday_bank","name":"Sikukuu ya Pasaka","en":"Easter Sunday","category":"bank","dates":"d|0325:2035,2046|0327:2016|0328:2027,2032|0329:2043,2054|0330:2059|0331:2013,2024|0401:2018,2029,2040|0402:2051,2056|0404:2010,2021|0405:2015,2026,2037,2048|0406:2042,2053|0408:2012|0409:2023,2034,2045|0410:2039,2050|0412:2020|0413:2031,2036|0414:2047,2058|0416:2017,2028|0417:2022,2033,2044|0418:2049,2055,2060|0420:2014,2025|0421:2019,2030,2041,2052|0422:2057|0424:2011|0425:2038"}]} diff --git a/app/Support/Holidays/data/UA.json b/app/Support/Holidays/data/UA.json new file mode 100644 index 000000000..fb98c504e --- /dev/null +++ b/app/Support/Holidays/data/UA.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"UA","language":"uk","years":[2010,2060],"subdivisions":{},"holidays":[]} diff --git a/app/Support/Holidays/data/UG.json b/app/Support/Holidays/data/UG.json new file mode 100644 index 000000000..aa54d2dc7 --- /dev/null +++ b/app/Support/Holidays/data/UG.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"UG","language":null,"years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"eid_al_fitr","name":"Eid al-Fitr","en":"Eid al-Fitr","category":"public","dates":"y|2010:0910|2011:0830|2012:0819|2013:0808|2014:0728|2015:0717|2016:0706|2017:0625|2018:0615|2019:0604|2020:0524|2021:0513|2022:0502|2023:0421|2024:0410|2025:0330|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304","estimated":"y|2010:0910|2011:0830|2012:0819|2013:0808|2014:0728|2015:0717|2016:0706|2017:0625|2018:0615|2019:0604|2020:0524|2021:0513|2022:0502|2023:0421|2024:0410|2025:0330|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304"},{"id":"eid_al_adha","name":"Eid al-Adha","en":"Eid al-Adha","category":"public","dates":"y|2010:1116|2011:1106|2012:1026|2013:1015|2014:1004|2015:0923|2016:0911|2017:0901|2018:0821|2019:0811|2020:0731|2021:0720|2022:0709|2023:0628|2024:0616|2025:0606|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510","estimated":"y|2010:1116|2011:1106|2012:1026|2013:1015|2014:1004|2015:0923|2016:0911|2017:0901|2018:0821|2019:0811|2020:0731|2021:0720|2022:0709|2023:0628|2024:0616|2025:0606|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510"},{"id":"general_elections","name":"General Elections","en":"General Elections","category":"public","dates":"y|2026:0115-0116"},{"id":"nrm_liberation_day","name":"NRM Liberation Day","en":"NRM Liberation Day","category":"public","dates":"d|0126:2010-2060"},{"id":"archbishop_janani_luwum_day","name":"Archbishop Janani Luwum Day","en":"Archbishop Janani Luwum Day","category":"public","dates":"d|0216:2016-2060"},{"id":"women_s_day","name":"Women's Day","en":"Women's Day","category":"public","dates":"d|0308:2010-2060"},{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_monday","name":"Easter Monday","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"labour_day","name":"Labour Day","en":"Labour Day","category":"public","dates":"d|0501:2010-2060"},{"id":"uganda_martyrs_day","name":"Uganda Martyrs' Day","en":"Uganda Martyrs' Day","category":"public","dates":"d|0603:2010-2060"},{"id":"national_heroes_day","name":"National Heroes' Day","en":"National Heroes' Day","category":"public","dates":"d|0609:2010-2060"},{"id":"independence_day","name":"Independence Day","en":"Independence Day","category":"public","dates":"d|1009:2010-2060"},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"},{"id":"boxing_day","name":"Boxing Day","en":"Boxing Day","category":"public","dates":"d|1226:2010-2060"}]} diff --git a/app/Support/Holidays/data/UM.json b/app/Support/Holidays/data/UM.json new file mode 100644 index 000000000..eaa3a9b35 --- /dev/null +++ b/app/Support/Holidays/data/UM.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"UM","language":"en_US","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051|1231:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"martin_luther_king_jr_day","name":"Martin Luther King Jr. Day","en":"Martin Luther King Jr. Day","category":"public","dates":"d|0115:2018,2024,2029,2035,2046,2052,2057|0116:2012,2017,2023,2034,2040,2045,2051|0117:2011,2022,2028,2033,2039,2050,2056|0118:2010,2016,2021,2027,2038,2044,2049,2055|0119:2015,2026,2032,2037,2043,2054,2060|0120:2014,2020,2025,2031,2042,2048,2053,2059|0121:2013,2019,2030,2036,2041,2047,2058"},{"id":"washington_s_birthday","name":"Washington's Birthday","en":"Washington's Birthday","category":"public","dates":"d|0215:2010,2016,2021,2027,2038,2044,2049,2055|0216:2015,2026,2032,2037,2043,2054,2060|0217:2014,2020,2025,2031,2042,2048,2053,2059|0218:2013,2019,2030,2036,2041,2047,2058|0219:2018,2024,2029,2035,2046,2052,2057|0220:2012,2017,2023,2034,2040,2045,2051|0221:2011,2022,2028,2033,2039,2050,2056"},{"id":"memorial_day","name":"Memorial Day","en":"Memorial Day","category":"public","dates":"d|0525:2015,2020,2026,2037,2043,2048,2054|0526:2014,2025,2031,2036,2042,2053,2059|0527:2013,2019,2024,2030,2041,2047,2052,2058|0528:2012,2018,2029,2035,2040,2046,2057|0529:2017,2023,2028,2034,2045,2051,2056|0530:2011,2016,2022,2033,2039,2044,2050|0531:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"juneteenth_national_independence_day","name":"Juneteenth National Independence Day","en":"Juneteenth National Independence Day","category":"public","dates":"d|0618:2021,2027,2032,2038,2049,2055,2060|0619:2021-2060|0620:2022,2033,2039,2044,2050"},{"id":"independence_day","name":"Independence Day","en":"Independence Day","category":"public","dates":"d|0703:2015,2020,2026,2037,2043,2048,2054|0704:2010-2060|0705:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"labor_day","name":"Labor Day","en":"Labor Day","category":"public","dates":"d|0901:2014,2025,2031,2036,2042,2053,2059|0902:2013,2019,2024,2030,2041,2047,2052,2058|0903:2012,2018,2029,2035,2040,2046,2057|0904:2017,2023,2028,2034,2045,2051,2056|0905:2011,2016,2022,2033,2039,2044,2050|0906:2010,2021,2027,2032,2038,2049,2055,2060|0907:2015,2020,2026,2037,2043,2048,2054"},{"id":"veterans_day","name":"Veterans Day","en":"Veterans Day","category":"public","dates":"d|1110:2017,2023,2028,2034,2045,2051,2056|1111:2010-2060|1112:2012,2018,2029,2035,2040,2046,2057"},{"id":"thanksgiving_day","name":"Thanksgiving Day","en":"Thanksgiving Day","category":"public","dates":"d|1122:2012,2018,2029,2035,2040,2046,2057|1123:2017,2023,2028,2034,2045,2051,2056|1124:2011,2016,2022,2033,2039,2044,2050|1125:2010,2021,2027,2032,2038,2049,2055,2060|1126:2015,2020,2026,2037,2043,2048,2054|1127:2014,2025,2031,2036,2042,2053,2059|1128:2013,2019,2024,2030,2041,2047,2052,2058"},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1224:2010,2021,2027,2032,2038,2049,2055,2060|1225:2010-2060|1226:2011,2016,2022,2033,2039,2044,2050"},{"id":"valentine_s_day_unofficial","name":"Valentine's Day","en":"Valentine's Day","category":"unofficial","dates":"d|0214:2010-2060"},{"id":"saint_patrick_s_day_unofficial","name":"Saint Patrick's Day","en":"Saint Patrick's Day","category":"unofficial","dates":"d|0317:2010-2060"},{"id":"good_friday_unofficial","name":"Good Friday","en":"Good Friday","category":"unofficial","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_sunday_unofficial","name":"Easter Sunday","en":"Easter Sunday","category":"unofficial","dates":"d|0325:2035,2046|0327:2016|0328:2027,2032|0329:2043,2054|0330:2059|0331:2013,2024|0401:2018,2029,2040|0402:2051,2056|0404:2010,2021|0405:2015,2026,2037,2048|0406:2042,2053|0408:2012|0409:2023,2034,2045|0410:2039,2050|0412:2020|0413:2031,2036|0414:2047,2058|0416:2017,2028|0417:2022,2033,2044|0418:2049,2055,2060|0420:2014,2025|0421:2019,2030,2041,2052|0422:2057|0424:2011|0425:2038"},{"id":"mother_s_day_unofficial","name":"Mother's Day","en":"Mother's Day","category":"unofficial","dates":"d|0508:2011,2016,2022,2033,2039,2044,2050|0509:2010,2021,2027,2032,2038,2049,2055,2060|0510:2015,2020,2026,2037,2043,2048,2054|0511:2014,2025,2031,2036,2042,2053,2059|0512:2013,2019,2024,2030,2041,2047,2052,2058|0513:2012,2018,2029,2035,2040,2046,2057|0514:2017,2023,2028,2034,2045,2051,2056"},{"id":"father_s_day_unofficial","name":"Father's Day","en":"Father's Day","category":"unofficial","dates":"d|0615:2014,2025,2031,2036,2042,2053,2059|0616:2013,2019,2024,2030,2041,2047,2052,2058|0617:2012,2018,2029,2035,2040,2046,2057|0618:2017,2023,2028,2034,2045,2051,2056|0619:2011,2016,2022,2033,2039,2044,2050|0620:2010,2021,2027,2032,2038,2049,2055,2060|0621:2015,2020,2026,2037,2043,2048,2054"},{"id":"halloween_unofficial","name":"Halloween","en":"Halloween","category":"unofficial","dates":"d|1031:2010-2060"},{"id":"christmas_eve_unofficial","name":"Christmas Eve","en":"Christmas Eve","category":"unofficial","dates":"d|1224:2010-2060"},{"id":"new_year_s_eve_unofficial","name":"New Year's Eve","en":"New Year's Eve","category":"unofficial","dates":"d|1231:2010-2060"}]} diff --git a/app/Support/Holidays/data/US.json b/app/Support/Holidays/data/US.json new file mode 100644 index 000000000..4dcfd3448 --- /dev/null +++ b/app/Support/Holidays/data/US.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"US","language":"en_US","years":[2010,2060],"subdivisions":{"AK":"Alaska","AL":"Alabama","AR":"Arkansas","AS":"American Samoa","AZ":"Arizona","CA":"California","CO":"Colorado","CT":"Connecticut","DC":"District of Columbia","DE":"Delaware","FL":"Florida","GA":"Georgia","GU":"Guam","HI":"Hawaii","IA":"Iowa","ID":"Idaho","IL":"Illinois","IN":"Indiana","KS":"Kansas","KY":"Kentucky","LA":"Louisiana","MA":"Massachusetts","MD":"Maryland","ME":"Maine","MI":"Michigan","MN":"Minnesota","MO":"Missouri","MP":"Northern Mariana Islands","MS":"Mississippi","MT":"Montana","NC":"North Carolina","ND":"North Dakota","NE":"Nebraska","NH":"New Hampshire","NJ":"New Jersey","NM":"New Mexico","NV":"Nevada","NY":"New York","OH":"Ohio","OK":"Oklahoma","OR":"Oregon","PA":"Pennsylvania","PR":"Puerto Rico","RI":"Rhode Island","SC":"South Carolina","SD":"South Dakota","TN":"Tennessee","TX":"Texas","UM":"United States Minor Outlying Islands","UT":"Utah","VA":"Virginia","VI":"Virgin Islands, U.S.","VT":"Vermont","WA":"Washington","WI":"Wisconsin","WV":"West Virginia","WY":"Wyoming"},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051|1231:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"epiphany","name":"Epiphany","en":"Epiphany","category":"public","regional":{"PR":"d|0106:2010-2060"}},{"id":"three_kings_day","name":"Three Kings Day","en":"Three Kings Day","category":"public","regional":{"VI":"d|0106:2010-2060"}},{"id":"dr_martin_luther_king_jr_and_robert_e_lee_s_birt","name":"Dr. Martin Luther King Jr. and Robert E. Lee's Birthdays","en":"Dr. Martin Luther King Jr. and Robert E. Lee's Birthdays","category":"public","regional":{"AR":"d|0116:2012,2017|0117:2011|0118:2010,2016|0119:2015|0120:2014|0121:2013","MS":"d|0115:2018,2024,2029,2035,2046,2052,2057|0116:2012,2017,2023,2034,2040,2045,2051|0117:2011,2022,2028,2033,2039,2050,2056|0118:2010,2016,2021,2027,2038,2044,2049,2055|0119:2015,2026,2032,2037,2043,2054,2060|0120:2014,2020,2025,2031,2042,2048,2053,2059|0121:2013,2019,2030,2036,2041,2047,2058"}},{"id":"dr_martin_luther_king_jr_civil_rights_day","name":"Dr. Martin Luther King Jr. / Civil Rights Day","en":"Dr. Martin Luther King Jr. / Civil Rights Day","category":"public","regional":{"AZ":"d|0115:2018,2024,2029,2035,2046,2052,2057|0116:2012,2017,2023,2034,2040,2045,2051|0117:2011,2022,2028,2033,2039,2050,2056|0118:2010,2016,2021,2027,2038,2044,2049,2055|0119:2015,2026,2032,2037,2043,2054,2060|0120:2014,2020,2025,2031,2042,2048,2053,2059|0121:2013,2019,2030,2036,2041,2047,2058","NH":"d|0115:2018,2024,2029,2035,2046,2052,2057|0116:2012,2017,2023,2034,2040,2045,2051|0117:2011,2022,2028,2033,2039,2050,2056|0118:2010,2016,2021,2027,2038,2044,2049,2055|0119:2015,2026,2032,2037,2043,2054,2060|0120:2014,2020,2025,2031,2042,2048,2053,2059|0121:2013,2019,2030,2036,2041,2047,2058"}},{"id":"martin_luther_king_jr_day","name":"Martin Luther King Jr. Day","en":"Martin Luther King Jr. Day","category":"public","dates":"d|0115:2018,2024,2029,2035,2046,2052,2057|0116:2012,2017,2023,2034,2040,2045,2051|0117:2011,2022,2028,2033,2039,2050,2056|0118:2010,2016,2021,2027,2038,2044,2049,2055|0119:2015,2026,2032,2037,2043,2054,2060|0120:2014,2020,2025,2031,2042,2048,2053,2059|0121:2013,2019,2030,2036,2041,2047,2058"},{"id":"martin_luther_king_jr_idaho_human_rights_day","name":"Martin Luther King Jr. / Idaho Human Rights Day","en":"Martin Luther King Jr. / Idaho Human Rights Day","category":"public","regional":{"ID":"d|0115:2018,2024,2029,2035,2046,2052,2057|0116:2012,2017,2023,2034,2040,2045,2051|0117:2011,2022,2028,2033,2039,2050,2056|0118:2010,2016,2021,2027,2038,2044,2049,2055|0119:2015,2026,2032,2037,2043,2054,2060|0120:2014,2020,2025,2031,2042,2048,2053,2059|0121:2013,2019,2030,2036,2041,2047,2058"}},{"id":"martin_luther_king_jr_robert_e_lee_s_birthday","name":"Martin Luther King, Jr & Robert E. Lee's Birthday","en":"Martin Luther King, Jr & Robert E. Lee's Birthday","category":"public","regional":{"AL":"d|0115:2018,2024,2029,2035,2046,2052,2057|0116:2012,2017,2023,2034,2040,2045,2051|0117:2011,2022,2028,2033,2039,2050,2056|0118:2010,2016,2021,2027,2038,2044,2049,2055|0119:2015,2026,2032,2037,2043,2054,2060|0120:2014,2020,2025,2031,2042,2048,2053,2059|0121:2013,2019,2030,2036,2041,2047,2058"}},{"id":"martin_luther_king_jr_s_birthday","name":"Martin Luther King Jr.'s Birthday","en":"Martin Luther King Jr.'s Birthday","category":"public","regional":{"AK":"d|0115:2018,2024,2029,2035,2046,2052,2057|0116:2012,2017,2023,2034,2040,2045,2051|0117:2011,2022,2028,2033,2039,2050,2056|0118:2010,2016,2021,2027,2038,2044,2049,2055|0119:2015,2026,2032,2037,2043,2054,2060|0120:2014,2020,2025,2031,2042,2048,2053,2059|0121:2013,2019,2030,2036,2041,2047,2058"}},{"id":"confederate_memorial_day","name":"Confederate Memorial Day","en":"Confederate Memorial Day","category":"public","regional":{"AL":"d|0422:2013,2019,2024,2030,2041,2047,2052,2058|0423:2012,2018,2029,2035,2040,2046,2057|0424:2017,2023,2028,2034,2045,2051,2056|0425:2011,2016,2022,2033,2039,2044,2050|0426:2010,2021,2027,2032,2038,2049,2055,2060|0427:2015,2020,2026,2037,2043,2048,2054|0428:2014,2025,2031,2036,2042,2053,2059","GA":"d|0422:2013|0423:2012|0425:2011|0426:2010|0427:2015|0428:2014","MS":"d|0424:2017,2023,2028,2034,2045,2051,2056|0425:2011,2016,2022,2033,2039,2044,2050|0426:2010,2021,2027,2032,2038,2049,2055,2060|0427:2015,2020,2026,2037,2043,2048,2054|0428:2014,2025,2031,2036,2042,2053,2059|0429:2013,2019,2024,2030,2041,2047,2052,2058|0430:2012,2018,2029,2035,2040,2046,2057","SC":"d|0422:2013,2019,2024,2030,2041,2047,2052,2058|0423:2012,2018,2029,2035,2040,2046,2057|0424:2017,2023,2028,2034,2045,2051,2056|0425:2011,2016,2022,2033,2039,2044,2050|0426:2010,2021,2027,2032,2038,2049,2055,2060|0427:2015,2020,2026,2037,2043,2048,2054|0428:2014,2025,2031,2036,2042,2053,2059","TX":"d|0119:2010-2060"}},{"id":"inauguration_day","name":"Inauguration Day","en":"Inauguration Day","category":"public","regional":{"DC":"d|0120:2013,2017,2021,2025,2029,2033,2037,2041,2045,2049,2053,2057|0121:2013,2041","MD":"d|0120:2013,2017,2021,2025,2029,2033,2037,2041,2045,2049,2053,2057|0121:2013,2041","VA":"d|0120:2013,2017,2021,2025,2029,2033,2037,2041,2045,2049,2053,2057|0121:2013,2041"}},{"id":"mardi_gras","name":"Mardi Gras","en":"Mardi Gras","category":"public","regional":{"LA":"d|0206:2035,2046|0209:2016,2027|0210:2032,2043,2054|0211:2059|0212:2013|0213:2018,2024,2029|0214:2040,2051|0215:2056|0216:2010,2021|0217:2015,2026,2037|0218:2042,2048,2053|0221:2012,2023,2034,2045|0222:2039,2050|0225:2020,2031|0226:2036,2047,2058|0228:2017|0229:2028|0301:2022,2033,2044|0302:2049,2055,2060|0304:2014,2025|0305:2019,2030,2041,2052|0306:2057|0308:2011|0309:2038"}},{"id":"lincoln_s_birthday","name":"Lincoln's Birthday","en":"Lincoln's Birthday","category":"public","regional":{"CT":"d|0211:2011,2022,2028,2033,2039,2050,2056|0212:2010-2060|0213:2012,2017,2023,2034,2040,2045,2051","IA":"d|0211:2011,2022,2028,2033,2039,2050,2056|0212:2010-2060|0213:2012,2017,2023,2034,2040,2045,2051","IL":"d|0211:2011,2022,2028,2033,2039,2050,2056|0212:2010-2060|0213:2012,2017,2023,2034,2040,2045,2051","IN":"d|1123:2012,2018,2029,2035,2040,2046,2057|1124:2017,2023,2028,2034,2045,2051,2056|1125:2011,2016,2022,2033,2039,2044,2050|1126:2010,2021,2027,2032,2038,2049,2055,2060|1127:2015,2020,2026,2037,2043,2048,2054|1128:2014,2025,2031,2036,2042,2053,2059|1129:2013,2019,2024,2030,2041,2047,2052,2058","NJ":"d|0211:2011,2022,2028,2033,2039,2050,2056|0212:2010-2060|0213:2012,2017,2023,2034,2040,2045,2051","NY":"d|0211:2011,2022,2028,2033,2039,2050,2056|0212:2010-2060|0213:2012,2017,2023,2034,2040,2045,2051"}},{"id":"george_washington_day","name":"George Washington Day","en":"George Washington Day","category":"public","regional":{"PR":"d|0215:2010|0217:2014|0218:2013|0220:2012|0221:2011","VA":"d|0215:2010,2016,2021,2027,2038,2044,2049,2055|0216:2015,2026,2032,2037,2043,2054,2060|0217:2014,2020,2025,2031,2042,2048,2053,2059|0218:2013,2019,2030,2036,2041,2047,2058|0219:2018,2024,2029,2035,2046,2052,2057|0220:2012,2017,2023,2034,2040,2045,2051|0221:2011,2022,2028,2033,2039,2050,2056"}},{"id":"george_washington_day_presidents_day_and_the_day","name":"George Washington Day, Presidents' Day, and the Day of the Hero and the Illustrious Woman of Puerto Rico","en":"George Washington Day, Presidents' Day, and the Day of the Hero and the Illustrious Woman of Puerto Rico","category":"public","regional":{"PR":"d|0215:2016,2021,2027,2038,2044,2049,2055|0216:2026,2032,2037,2043,2054,2060|0217:2020,2025,2031,2042,2048,2053,2059|0218:2019,2030,2036,2041,2047,2058|0219:2018,2024,2029,2035,2046,2052,2057|0220:2017,2023,2034,2040,2045,2051|0221:2022,2028,2033,2039,2050,2056"}},{"id":"george_washington_s_birthday_and_daisy_gatson_ba","name":"George Washington's Birthday and Daisy Gatson Bates Day","en":"George Washington's Birthday and Daisy Gatson Bates Day","category":"public","regional":{"AR":"d|0215:2010,2016,2021,2027,2038,2044,2049,2055|0216:2015,2026,2032,2037,2043,2054,2060|0217:2014,2020,2025,2031,2042,2048,2053,2059|0218:2013,2019,2030,2036,2041,2047,2058|0219:2018,2024,2029,2035,2046,2052,2057|0220:2012,2017,2023,2034,2040,2045,2051|0221:2011,2022,2028,2033,2039,2050,2056"}},{"id":"george_washington_thomas_jefferson_s_birthday","name":"George Washington & Thomas Jefferson's Birthday","en":"George Washington & Thomas Jefferson's Birthday","category":"public","regional":{"AL":"d|0215:2010,2016,2021,2027,2038,2044,2049,2055|0216:2015,2026,2032,2037,2043,2054,2060|0217:2014,2020,2025,2031,2042,2048,2053,2059|0218:2013,2019,2030,2036,2041,2047,2058|0219:2018,2024,2029,2035,2046,2052,2057|0220:2012,2017,2023,2034,2040,2045,2051|0221:2011,2022,2028,2033,2039,2050,2056"}},{"id":"lincoln_s_and_washington_s_birthdays","name":"Lincoln's and Washington's Birthdays","en":"Lincoln's and Washington's Birthdays","category":"public","regional":{"MT":"d|0215:2010,2016,2021,2027,2038,2044,2049,2055|0216:2015,2026,2032,2037,2043,2054,2060|0217:2014,2020,2025,2031,2042,2048,2053,2059|0218:2013,2019,2030,2036,2041,2047,2058|0219:2018,2024,2029,2035,2046,2052,2057|0220:2012,2017,2023,2034,2040,2045,2051|0221:2011,2022,2028,2033,2039,2050,2056"}},{"id":"lincoln_washington_presidents_day","name":"Lincoln/Washington Presidents' Day","en":"Lincoln/Washington Presidents' Day","category":"public","regional":{"AZ":"d|0215:2010,2016,2021,2027,2038,2044,2049,2055|0216:2015,2026,2032,2037,2043,2054,2060|0217:2014,2020,2025,2031,2042,2048,2053,2059|0218:2013,2019,2030,2036,2041,2047,2058|0219:2018,2024,2029,2035,2046,2052,2057|0220:2012,2017,2023,2034,2040,2045,2051|0221:2011,2022,2028,2033,2039,2050,2056"}},{"id":"president_s_day","name":"President's Day","en":"President's Day","category":"public","regional":{"SC":"d|0215:2010,2016,2021,2027,2038,2044,2049,2055|0216:2015,2026,2032,2037,2043,2054,2060|0217:2014,2020,2025,2031,2042,2048,2053,2059|0218:2013,2019,2030,2036,2041,2047,2058|0219:2018,2024,2029,2035,2046,2052,2057|0220:2012,2017,2023,2034,2040,2045,2051|0221:2011,2022,2028,2033,2039,2050,2056","TN":"d|0215:2010,2016,2021,2027,2038,2044,2049,2055|0216:2015,2026,2032,2037,2043,2054,2060|0217:2014,2020,2025,2031,2042,2048,2053,2059|0218:2013,2019,2030,2036,2041,2047,2058|0219:2018,2024,2029,2035,2046,2052,2057|0220:2012,2017,2023,2034,2040,2045,2051|0221:2011,2022,2028,2033,2039,2050,2056","WY":"d|0215:2010,2016,2021,2027,2038,2044,2049,2055|0216:2015,2026,2032,2037,2043,2054,2060|0217:2014,2020,2025,2031,2042,2048,2053,2059|0218:2013,2019,2030,2036,2041,2047,2058|0219:2018,2024,2029,2035,2046,2052,2057|0220:2012,2017,2023,2034,2040,2045,2051|0221:2011,2022,2028,2033,2039,2050,2056"}},{"id":"presidents_day","name":"Presidents' Day","en":"Presidents' Day","category":"public","regional":{"AK":"d|0215:2010,2016,2021,2027,2038,2044,2049,2055|0216:2015,2026,2032,2037,2043,2054,2060|0217:2014,2020,2025,2031,2042,2048,2053,2059|0218:2013,2019,2030,2036,2041,2047,2058|0219:2018,2024,2029,2035,2046,2052,2057|0220:2012,2017,2023,2034,2040,2045,2051|0221:2011,2022,2028,2033,2039,2050,2056","CA":"d|0215:2010,2016,2021,2027,2038,2044,2049,2055|0216:2015,2026,2032,2037,2043,2054,2060|0217:2014,2020,2025,2031,2042,2048,2053,2059|0218:2013,2019,2030,2036,2041,2047,2058|0219:2018,2024,2029,2035,2046,2052,2057|0220:2012,2017,2023,2034,2040,2045,2051|0221:2011,2022,2028,2033,2039,2050,2056","HI":"d|0215:2010,2016,2021,2027,2038,2044,2049,2055|0216:2015,2026,2032,2037,2043,2054,2060|0217:2014,2020,2025,2031,2042,2048,2053,2059|0218:2013,2019,2030,2036,2041,2047,2058|0219:2018,2024,2029,2035,2046,2052,2057|0220:2012,2017,2023,2034,2040,2045,2051|0221:2011,2022,2028,2033,2039,2050,2056","ID":"d|0215:2010,2016,2021,2027,2038,2044,2049,2055|0216:2015,2026,2032,2037,2043,2054,2060|0217:2014,2020,2025,2031,2042,2048,2053,2059|0218:2013,2019,2030,2036,2041,2047,2058|0219:2018,2024,2029,2035,2046,2052,2057|0220:2012,2017,2023,2034,2040,2045,2051|0221:2011,2022,2028,2033,2039,2050,2056","MD":"d|0215:2010,2016,2021,2027,2038,2044,2049,2055|0216:2015,2026,2032,2037,2043,2054,2060|0217:2014,2020,2025,2031,2042,2048,2053,2059|0218:2013,2019,2030,2036,2041,2047,2058|0219:2018,2024,2029,2035,2046,2052,2057|0220:2012,2017,2023,2034,2040,2045,2051|0221:2011,2022,2028,2033,2039,2050,2056","NJ":"d|0215:2010,2016,2021,2027,2038,2044,2049,2055|0216:2015,2026,2032,2037,2043,2054,2060|0217:2014,2020,2025,2031,2042,2048,2053,2059|0218:2013,2019,2030,2036,2041,2047,2058|0219:2018,2024,2029,2035,2046,2052,2057|0220:2012,2017,2023,2034,2040,2045,2051|0221:2011,2022,2028,2033,2039,2050,2056","NM":"d|1123:2012,2018,2029,2035,2040,2046,2057|1124:2017,2023,2028,2034,2045,2051,2056|1125:2011,2016,2022,2033,2039,2044,2050|1126:2010,2021,2027,2032,2038,2049,2055,2060|1127:2015,2020,2026,2037,2043,2048,2054|1128:2014,2025,2031,2036,2042,2053,2059|1129:2013,2019,2024,2030,2041,2047,2052,2058","OK":"d|0215:2010,2016,2021,2027,2038,2044,2049,2055|0216:2015,2026,2032,2037,2043,2054,2060|0217:2014,2020,2025,2031,2042,2048,2053,2059|0218:2013,2019,2030,2036,2041,2047,2058|0219:2018,2024,2029,2035,2046,2052,2057|0220:2012,2017,2023,2034,2040,2045,2051|0221:2011,2022,2028,2033,2039,2050,2056","OR":"d|0215:2010,2016,2021,2027,2038,2044,2049,2055|0216:2015,2026,2032,2037,2043,2054,2060|0217:2014,2020,2025,2031,2042,2048,2053,2059|0218:2013,2019,2030,2036,2041,2047,2058|0219:2018,2024,2029,2035,2046,2052,2057|0220:2012,2017,2023,2034,2040,2045,2051|0221:2011,2022,2028,2033,2039,2050,2056","PA":"d|0215:2010,2016,2021,2027,2038,2044,2049,2055|0216:2015,2026,2032,2037,2043,2054,2060|0217:2014,2020,2025,2031,2042,2048,2053,2059|0218:2013,2019,2030,2036,2041,2047,2058|0219:2018,2024,2029,2035,2046,2052,2057|0220:2012,2017,2023,2034,2040,2045,2051|0221:2011,2022,2028,2033,2039,2050,2056","TX":"d|0215:2010,2016,2021,2027,2038,2044,2049,2055|0216:2015,2026,2032,2037,2043,2054,2060|0217:2014,2020,2025,2031,2042,2048,2053,2059|0218:2013,2019,2030,2036,2041,2047,2058|0219:2018,2024,2029,2035,2046,2052,2057|0220:2012,2017,2023,2034,2040,2045,2051|0221:2011,2022,2028,2033,2039,2050,2056","VI":"d|0215:2010,2016,2021,2027,2038,2044,2049,2055|0216:2015,2026,2032,2037,2043,2054,2060|0217:2014,2020,2025,2031,2042,2048,2053,2059|0218:2013,2019,2030,2036,2041,2047,2058|0219:2018,2024,2029,2035,2046,2052,2057|0220:2012,2017,2023,2034,2040,2045,2051|0221:2011,2022,2028,2033,2039,2050,2056","VT":"d|0215:2010,2016,2021,2027,2038,2044,2049,2055|0216:2015,2026,2032,2037,2043,2054,2060|0217:2014,2020,2025,2031,2042,2048,2053,2059|0218:2013,2019,2030,2036,2041,2047,2058|0219:2018,2024,2029,2035,2046,2052,2057|0220:2012,2017,2023,2034,2040,2045,2051|0221:2011,2022,2028,2033,2039,2050,2056","WA":"d|0215:2010,2016,2021,2027,2038,2044,2049,2055|0216:2015,2026,2032,2037,2043,2054,2060|0217:2014,2020,2025,2031,2042,2048,2053,2059|0218:2013,2019,2030,2036,2041,2047,2058|0219:2018,2024,2029,2035,2046,2052,2057|0220:2012,2017,2023,2034,2040,2045,2051|0221:2011,2022,2028,2033,2039,2050,2056","WV":"d|0215:2010,2016,2021,2027,2038,2044,2049,2055|0216:2015,2026,2032,2037,2043,2054,2060|0217:2014,2020,2025,2031,2042,2048,2053,2059|0218:2013,2019,2030,2036,2041,2047,2058|0219:2018,2024,2029,2035,2046,2052,2057|0220:2012,2017,2023,2034,2040,2045,2051|0221:2011,2022,2028,2033,2039,2050,2056"}},{"id":"susan_b_anthony_day","name":"Susan B. Anthony Day","en":"Susan B. Anthony Day","category":"public","regional":{"CA":"d|0215:2014-2060","FL":"d|0215:2011-2060","NY":"d|0215:2010-2060","WI":"d|0215:2010-2060"}},{"id":"washington_and_lincoln_day","name":"Washington and Lincoln Day","en":"Washington and Lincoln Day","category":"public","regional":{"UT":"d|0215:2010,2016,2021,2027,2038,2044,2049,2055|0216:2015,2026,2032,2037,2043,2054,2060|0217:2014,2020,2025,2031,2042,2048,2053,2059|0218:2013,2019,2030,2036,2041,2047,2058|0219:2018,2024,2029,2035,2046,2052,2057|0220:2012,2017,2023,2034,2040,2045,2051|0221:2011,2022,2028,2033,2039,2050,2056"}},{"id":"washington_lincoln_day","name":"Washington-Lincoln Day","en":"Washington-Lincoln Day","category":"public","regional":{"CO":"d|0215:2010,2016,2021,2027,2038,2044,2049,2055|0216:2015,2026,2032,2037,2043,2054,2060|0217:2014,2020,2025,2031,2042,2048,2053,2059|0218:2013,2019,2030,2036,2041,2047,2058|0219:2018,2024,2029,2035,2046,2052,2057|0220:2012,2017,2023,2034,2040,2045,2051|0221:2011,2022,2028,2033,2039,2050,2056","OH":"d|0215:2010,2016,2021,2027,2038,2044,2049,2055|0216:2015,2026,2032,2037,2043,2054,2060|0217:2014,2020,2025,2031,2042,2048,2053,2059|0218:2013,2019,2030,2036,2041,2047,2058|0219:2018,2024,2029,2035,2046,2052,2057|0220:2012,2017,2023,2034,2040,2045,2051|0221:2011,2022,2028,2033,2039,2050,2056"}},{"id":"washington_s_and_lincoln_s_birthday","name":"Washington's and Lincoln's Birthday","en":"Washington's and Lincoln's Birthday","category":"public","regional":{"MN":"d|0215:2010,2016,2021,2027,2038,2044,2049,2055|0216:2015,2026,2032,2037,2043,2054,2060|0217:2014,2020,2025,2031,2042,2048,2053,2059|0218:2013,2019,2030,2036,2041,2047,2058|0219:2018,2024,2029,2035,2046,2052,2057|0220:2012,2017,2023,2034,2040,2045,2051|0221:2011,2022,2028,2033,2039,2050,2056"}},{"id":"washington_s_birthday","name":"Washington's Birthday","en":"Washington's Birthday","category":"public","dates":"d|0215:2010,2016,2021,2027,2038,2044,2049,2055|0216:2015,2026,2032,2037,2043,2054,2060|0217:2014,2020,2025,2031,2042,2048,2053,2059|0218:2013,2019,2030,2036,2041,2047,2058|0219:2018,2024,2029,2035,2046,2052,2057|0220:2012,2017,2023,2034,2040,2045,2051|0221:2011,2022,2028,2033,2039,2050,2056","regional":{"GA":"d|1223:2010-2011,2016,2021-2022,2027,2032-2033,2038-2039,2044,2049-2050,2055,2060|1224:2012-2013,2015,2018-2020,2024,2026,2029-2030,2035,2037,2040-2041,2043,2046-2048,2052,2054,2057-2058|1226:2014,2017,2023,2025,2028,2031,2034,2036,2042,2045,2051,2053,2056,2059","IN":"d|1223:2010-2011,2016,2021-2022,2027,2032-2033,2038-2039,2044,2049-2050,2055,2060|1224:2012-2013,2015,2018-2020,2024,2026,2029-2030,2035,2037,2040-2041,2043,2046-2048,2052,2054,2057-2058|1226:2014,2017,2023,2025,2028,2031,2034,2036,2042,2045,2051,2053,2056,2059"}},{"id":"casimir_pulaski_day","name":"Casimir Pulaski Day","en":"Casimir Pulaski Day","category":"public","regional":{"IL":"d|0301:2010,2021,2027,2032,2038,2049,2055,2060|0302:2015,2020,2026,2037,2043,2048,2054|0303:2014,2025,2031,2036,2042,2053,2059|0304:2013,2019,2024,2030,2041,2047,2052,2058|0305:2012,2018,2029,2035,2040,2046,2057|0306:2017,2023,2028,2034,2045,2051,2056|0307:2011,2016,2022,2033,2039,2044,2050"}},{"id":"guam_discovery_day","name":"Guam Discovery Day","en":"Guam Discovery Day","category":"public","regional":{"GU":"d|0301:2010,2021,2027,2032,2038,2049,2055,2060|0302:2015,2020,2026,2037,2043,2048,2054|0303:2014,2025,2031,2036,2042,2053,2059|0304:2013,2019,2024,2030,2041,2047,2052,2058|0305:2012,2018,2029,2035,2040,2046,2057|0306:2017,2023,2028,2034,2045,2051,2056|0307:2011,2016,2022,2033,2039,2044,2050"}},{"id":"town_meeting_day","name":"Town Meeting Day","en":"Town Meeting Day","category":"public","regional":{"VT":"d|0301:2011,2016,2022,2033,2039,2044,2050|0302:2010,2021,2027,2032,2038,2049,2055,2060|0303:2015,2020,2026,2037,2043,2048,2054|0304:2014,2025,2031,2036,2042,2053,2059|0305:2013,2019,2024,2030,2041,2047,2052,2058|0306:2012,2018,2029,2035,2040,2046,2057|0307:2017,2023,2028,2034,2045,2051,2056"}},{"id":"texas_independence_day","name":"Texas Independence Day","en":"Texas Independence Day","category":"public","regional":{"TX":"d|0302:2010-2060"}},{"id":"emancipation_day","name":"Emancipation Day","en":"Emancipation Day","category":"public","regional":{"DC":"d|0415:2011,2016,2022,2033,2039,2044,2050|0416:2010-2060|0417:2017,2023,2028,2034,2045,2051,2056","PR":"d|0322:2010-2060|0323:2015,2020,2026,2037,2043,2048,2054","VI":"d|0703:2010-2060"}},{"id":"holy_thursday","name":"Holy Thursday","en":"Holy Thursday","category":"public","regional":{"VI":"d|0322:2035,2046|0324:2016|0325:2027,2032|0326:2043,2054|0327:2059|0328:2013,2024|0329:2018,2029,2040|0330:2051,2056|0401:2010,2021|0402:2015,2026,2037,2048|0403:2042,2053|0405:2012|0406:2023,2034,2045|0407:2039,2050|0409:2020|0410:2031,2036|0411:2047,2058|0413:2017,2028|0414:2022,2033,2044|0415:2049,2055,2060|0417:2014,2025|0418:2019,2030,2041,2052|0419:2057|0421:2011|0422:2038"}},{"id":"commonwealth_covenant_day","name":"Commonwealth Covenant Day","en":"Commonwealth Covenant Day","category":"public","regional":{"MP":"d|0323:2012,2018,2029,2035,2040,2046,2057|0324:2010-2060|0325:2013,2019,2024,2030,2041,2047,2052,2058"}},{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","regional":{"CT":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038","DE":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038","GU":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038","IN":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038","KY":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038","LA":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038","MP":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038","NC":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038","NJ":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038","PR":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038","TN":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038","TX":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038","VI":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"}},{"id":"state_holiday","name":"State Holiday","en":"State Holiday","category":"public","regional":{"GA":"d|0323:2035,2046|0326:2027,2032|0327:2043,2054|0328:2059|0329:2024|0330:2029,2040|0331:2051,2056|0402:2021|0403:2026,2037,2048|0404:2042,2053|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2025|0419:2030,2041,2052|0420:2057|0422:2019|0423:2018,2038|0424:2017|0425:2016|1123:2018,2029,2035,2040,2046,2057|1124:2017,2023,2028,2034,2045,2051,2056|1125:2016,2022,2033,2039,2044,2050|1126:2021,2027,2032,2038,2049,2055,2060|1127:2020,2026,2037,2043,2048,2054|1128:2025,2031,2036,2042,2053,2059|1129:2019,2024,2030,2041,2047,2052,2058"}},{"id":"prince_jonah_kuhio_kalanianaole_day","name":"Prince Jonah Kuhio Kalanianaole Day","en":"Prince Jonah Kuhio Kalanianaole Day","category":"public","regional":{"HI":"d|0325:2011,2016,2022,2033,2039,2044,2050|0326:2010-2060|0327:2017,2023,2028,2034,2045,2051,2056"}},{"id":"seward_s_day","name":"Seward's Day","en":"Seward's Day","category":"public","regional":{"AK":"d|0325:2013,2019,2024,2030,2041,2047,2052,2058|0326:2012,2018,2029,2035,2040,2046,2057|0327:2017,2023,2028,2034,2045,2051,2056|0328:2011,2016,2022,2033,2039,2044,2050|0329:2010,2021,2027,2032,2038,2049,2055,2060|0330:2015,2020,2026,2037,2043,2048,2054|0331:2014,2025,2031,2036,2042,2053,2059"}},{"id":"easter_monday","name":"Easter Monday","en":"Easter Monday","category":"public","regional":{"VI":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"}},{"id":"cesar_chavez_day","name":"Cesar Chavez Day","en":"Cesar Chavez Day","category":"public","regional":{"CA":"d|0331:2010-2060|0401:2013,2019,2024,2030,2041,2047,2052,2058","CO":"d|0331:2010-2060","TX":"d|0331:2010-2060"}},{"id":"transfer_day","name":"Transfer Day","en":"Transfer Day","category":"public","regional":{"VI":"d|0331:2010-2060"}},{"id":"patriots_day","name":"Patriots' Day","en":"Patriots' Day","category":"public","regional":{"MA":"d|0415:2013,2019,2024,2030,2041,2047,2052,2058|0416:2012,2018,2029,2035,2040,2046,2057|0417:2017,2023,2028,2034,2045,2051,2056|0418:2011,2016,2022,2033,2039,2044,2050|0419:2010,2021,2027,2032,2038,2049,2055,2060|0420:2015,2020,2026,2037,2043,2048,2054|0421:2014,2025,2031,2036,2042,2053,2059","ME":"d|0415:2013,2019,2024,2030,2041,2047,2052,2058|0416:2012,2018,2029,2035,2040,2046,2057|0417:2017,2023,2028,2034,2045,2051,2056|0418:2011,2016,2022,2033,2039,2044,2050|0419:2010,2021,2027,2032,2038,2049,2055,2060|0420:2015,2020,2026,2037,2043,2048,2054|0421:2014,2025,2031,2036,2042,2053,2059"}},{"id":"american_samoa_flag_day","name":"American Samoa Flag Day","en":"American Samoa Flag Day","category":"public","regional":{"AS":"d|0416:2010,2021,2027,2032,2038,2049,2055,2060|0417:2010-2060|0418:2011,2016,2022,2033,2039,2044,2050"}},{"id":"san_jacinto_day","name":"San Jacinto Day","en":"San Jacinto Day","category":"public","regional":{"TX":"d|0421:2010-2060"}},{"id":"arbor_day","name":"Arbor Day","en":"Arbor Day","category":"public","regional":{"NE":"d|0424:2015,2020,2026,2037,2043,2048,2054|0425:2014,2025,2031,2036,2042,2053,2059|0426:2013,2019,2024,2030,2041,2047,2052,2058|0427:2012,2018,2029,2035,2040,2046,2057|0428:2017,2023,2028,2034,2045,2051,2056|0429:2011,2016,2022,2033,2039,2044,2050|0430:2010,2021,2027,2032,2038,2049,2055,2060"}},{"id":"primary_election_day","name":"Primary Election Day","en":"Primary Election Day","category":"public","regional":{"IN":"d|0502:2017,2023,2028,2034,2045,2051,2056|0503:2016,2022,2033,2039,2044,2050|0504:2010,2021,2027,2032,2038,2049,2055,2060|0505:2015,2020,2026,2037,2043,2048,2054|0506:2014,2025,2031,2036,2042,2053,2059|0507:2019,2024,2030,2041,2047,2052,2058|0508:2012,2018,2029,2035,2040,2046,2057"}},{"id":"truman_day","name":"Truman Day","en":"Truman Day","category":"public","regional":{"MO":"d|0507:2010,2021,2027,2032,2038,2049,2055,2060|0508:2010-2060|0509:2011,2016,2022,2033,2039,2044,2050"}},{"id":"memorial_day","name":"Memorial Day","en":"Memorial Day","category":"public","dates":"d|0525:2015,2020,2026,2037,2043,2048,2054|0526:2014,2025,2031,2036,2042,2053,2059|0527:2013,2019,2024,2030,2041,2047,2052,2058|0528:2012,2018,2029,2035,2040,2046,2057|0529:2017,2023,2028,2034,2045,2051,2056|0530:2011,2016,2022,2033,2039,2044,2050|0531:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"jefferson_davis_birthday","name":"Jefferson Davis Birthday","en":"Jefferson Davis Birthday","category":"public","regional":{"AL":"d|0601:2015,2020,2026,2037,2043,2048,2054|0602:2014,2025,2031,2036,2042,2053,2059|0603:2013,2019,2024,2030,2041,2047,2052,2058|0604:2012,2018,2029,2035,2040,2046,2057|0605:2017,2023,2028,2034,2045,2051,2056|0606:2011,2016,2022,2033,2039,2044,2050|0607:2010,2021,2027,2032,2038,2049,2055,2060"}},{"id":"kamehameha_day","name":"Kamehameha Day","en":"Kamehameha Day","category":"public","regional":{"HI":"d|0610:2011,2016,2022,2033,2039,2044,2050|0611:2010-2060|0612:2017,2023,2028,2034,2045,2051,2056"}},{"id":"juneteenth_national_independence_day","name":"Juneteenth National Independence Day","en":"Juneteenth National Independence Day","category":"public","dates":"d|0618:2021,2027,2032,2038,2049,2055,2060|0619:2021-2060|0620:2022,2033,2039,2044,2050"},{"id":"emancipation_day_in_texas","name":"Emancipation Day In Texas","en":"Emancipation Day In Texas","category":"public","regional":{"TX":"d|0619:2010-2060"}},{"id":"west_virginia_day","name":"West Virginia Day","en":"West Virginia Day","category":"public","regional":{"WV":"d|0619:2015,2020,2026,2037,2043,2048,2054|0620:2010-2060|0621:2010,2021,2027,2032,2038,2049,2055,2060"}},{"id":"independence_day","name":"Independence Day","en":"Independence Day","category":"public","dates":"d|0703:2015,2020,2026,2037,2043,2048,2054|0704:2010-2060|0705:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"manu_a_islands_cession_day","name":"Manu'a Islands Cession Day","en":"Manu'a Islands Cession Day","category":"public","regional":{"AS":"d|0715:2011,2016,2022,2033,2039,2044,2050|0716:2010-2060|0717:2017,2023,2028,2034,2045,2051,2056"}},{"id":"liberation_day_guam","name":"Liberation Day (Guam)","en":"Liberation Day (Guam)","category":"public","regional":{"GU":"d|0721:2010-2060"}},{"id":"pioneer_day","name":"Pioneer Day","en":"Pioneer Day","category":"public","regional":{"UT":"d|0723:2010,2021,2027,2032,2038,2049,2055,2060|0724:2010-2060|0725:2011,2016,2022,2033,2039,2044,2050"}},{"id":"puerto_rico_constitution_day","name":"Puerto Rico Constitution Day","en":"Puerto Rico Constitution Day","category":"public","regional":{"PR":"d|0725:2010-2060|0726:2010,2021,2027,2032,2038,2049,2055,2060"}},{"id":"victory_day","name":"Victory Day","en":"Victory Day","category":"public","regional":{"RI":"d|0808:2011,2016,2022,2033,2039,2044,2050|0809:2010,2021,2027,2032,2038,2049,2055,2060|0810:2015,2020,2026,2037,2043,2048,2054|0811:2014,2025,2031,2036,2042,2053,2059|0812:2013,2019,2024,2030,2041,2047,2052,2058|0813:2012,2018,2029,2035,2040,2046,2057|0814:2017,2023,2028,2034,2045,2051,2056"}},{"id":"bennington_battle_day","name":"Bennington Battle Day","en":"Bennington Battle Day","category":"public","regional":{"VT":"d|0815:2014,2025,2031,2036,2042,2053,2059|0816:2010-2060|0817:2015,2020,2026,2037,2043,2048,2054"}},{"id":"statehood_day","name":"Statehood Day","en":"Statehood Day","category":"public","regional":{"HI":"d|0815:2014,2025,2031,2036,2042,2053,2059|0816:2013,2019,2024,2030,2041,2047,2052,2058|0817:2012,2018,2029,2035,2040,2046,2057|0818:2017,2023,2028,2034,2045,2051,2056|0819:2011,2016,2022,2033,2039,2044,2050|0820:2010,2021,2027,2032,2038,2049,2055,2060|0821:2015,2020,2026,2037,2043,2048,2054"}},{"id":"lyndon_baines_johnson_day","name":"Lyndon Baines Johnson Day","en":"Lyndon Baines Johnson Day","category":"public","regional":{"TX":"d|0827:2010-2060"}},{"id":"labor_day","name":"Labor Day","en":"Labor Day","category":"public","dates":"d|0901:2014,2025,2031,2036,2042,2053,2059|0902:2013,2019,2024,2030,2041,2047,2052,2058|0903:2012,2018,2029,2035,2040,2046,2057|0904:2017,2023,2028,2034,2045,2051,2056|0905:2011,2016,2022,2033,2039,2044,2050|0906:2010,2021,2027,2032,2038,2049,2055,2060|0907:2015,2020,2026,2037,2043,2048,2054"},{"id":"frances_xavier_cabrini_day","name":"Frances Xavier Cabrini Day","en":"Frances Xavier Cabrini Day","category":"public","regional":{"CO":"d|1001:2029,2035,2040,2046,2057|1002:2023,2028,2034,2045,2051,2056|1003:2022,2033,2039,2044,2050|1004:2021,2027,2032,2038,2049,2055,2060|1005:2020,2026,2037,2043,2048,2054|1006:2025,2031,2036,2042,2053,2059|1007:2024,2030,2041,2047,2052,2058"}},{"id":"columbus_day","name":"Columbus Day","en":"Columbus Day","category":"public","dates":"d|1008:2012,2018,2029,2035,2040,2046,2057|1009:2017,2023,2028,2034,2045,2051,2056|1010:2011,2016,2022,2033,2039,2044,2050|1011:2010,2021,2027,2032,2038,2049,2055,2060|1012:2015,2020,2026,2037,2043,2048,2054|1013:2014,2025,2031,2036,2042,2053,2059|1014:2013,2019,2024,2030,2041,2047,2052,2058","regional":{"PR":"d|1012:2010-2013"}},{"id":"columbus_day_american_indian_heritage_day_frater","name":"Columbus Day / American Indian Heritage Day / Fraternal Day","en":"Columbus Day / American Indian Heritage Day / Fraternal Day","category":"public","regional":{"AL":"d|1008:2012,2018,2029,2035,2040,2046,2057|1009:2017,2023,2028,2034,2045,2051,2056|1010:2011,2016,2022,2033,2039,2044,2050|1011:2010,2021,2027,2032,2038,2049,2055,2060|1012:2015,2020,2026,2037,2043,2048,2054|1013:2014,2025,2031,2036,2042,2053,2059|1014:2013,2019,2024,2030,2041,2047,2052,2058"}},{"id":"columbus_day_and_puerto_rico_friendship_day","name":"Columbus Day and Puerto Rico Friendship Day","en":"Columbus Day and Puerto Rico Friendship Day","category":"public","regional":{"VI":"d|1008:2012,2018,2029,2035,2040,2046,2057|1009:2017,2023,2028,2034,2045,2051,2056|1010:2011,2016,2022,2033,2039,2044,2050|1011:2010,2021,2027,2032,2038,2049,2055,2060|1012:2015,2020,2026,2037,2043,2048,2054|1013:2014,2025,2031,2036,2042,2053,2059|1014:2013,2019,2024,2030,2041,2047,2052,2058"}},{"id":"commonwealth_cultural_day","name":"Commonwealth Cultural Day","en":"Commonwealth Cultural Day","category":"public","regional":{"MP":"d|1008:2012,2018,2029,2035,2040,2046,2057|1009:2017,2023,2028,2034,2045,2051,2056|1010:2011,2016,2022,2033,2039,2044,2050|1011:2010,2021,2027,2032,2038,2049,2055,2060|1012:2015,2020,2026,2037,2043,2048,2054|1013:2014,2025,2031,2036,2042,2053,2059|1014:2013,2019,2024,2030,2041,2047,2052,2058"}},{"id":"indigenous_peoples_day","name":"Indigenous Peoples' Day","en":"Indigenous Peoples' Day","category":"public","regional":{"AK":"d|1008:2018,2029,2035,2040,2046,2057|1009:2017,2023,2028,2034,2045,2051,2056|1010:2016,2022,2033,2039,2044,2050|1011:2021,2027,2032,2038,2049,2055,2060|1012:2015,2020,2026,2037,2043,2048,2054|1013:2025,2031,2036,2042,2053,2059|1014:2019,2024,2030,2041,2047,2052,2058","DC":"d|1008:2029,2035,2040,2046,2057|1009:2023,2028,2034,2045,2051,2056|1010:2022,2033,2039,2044,2050|1011:2021,2027,2032,2038,2049,2055,2060|1012:2020,2026,2037,2043,2048,2054|1013:2025,2031,2036,2042,2053,2059|1014:2019,2024,2030,2041,2047,2052,2058","ME":"d|1008:2029,2035,2040,2046,2057|1009:2023,2028,2034,2045,2051,2056|1010:2022,2033,2039,2044,2050|1011:2021,2027,2032,2038,2049,2055,2060|1012:2020,2026,2037,2043,2048,2054|1013:2025,2031,2036,2042,2053,2059|1014:2019,2024,2030,2041,2047,2052,2058","NE":"d|1008:2029,2035,2040,2046,2057|1009:2023,2028,2034,2045,2051,2056|1010:2022,2033,2039,2044,2050|1011:2021,2027,2032,2038,2049,2055,2060|1012:2020,2026,2037,2043,2048,2054|1013:2025,2031,2036,2042,2053,2059|1014:2024,2030,2041,2047,2052,2058","NM":"d|1008:2029,2035,2040,2046,2057|1009:2023,2028,2034,2045,2051,2056|1010:2022,2033,2039,2044,2050|1011:2021,2027,2032,2038,2049,2055,2060|1012:2020,2026,2037,2043,2048,2054|1013:2025,2031,2036,2042,2053,2059|1014:2019,2024,2030,2041,2047,2052,2058","VA":"d|1008:2029,2035,2040,2046,2057|1009:2023,2028,2034,2045,2051,2056|1010:2022,2033,2039,2044,2050|1011:2021,2027,2032,2038,2049,2055,2060|1012:2020,2026,2037,2043,2048,2054|1013:2025,2031,2036,2042,2053,2059|1014:2024,2030,2041,2047,2052,2058"}},{"id":"indigenous_peoples_day_columbus_day","name":"Indigenous Peoples' Day / Columbus Day","en":"Indigenous Peoples' Day / Columbus Day","category":"public","regional":{"RI":"d|1008:2029,2035,2040,2046,2057|1009:2023,2028,2034,2045,2051,2056|1010:2022,2033,2039,2044,2050|1011:2027,2032,2038,2049,2055,2060|1012:2026,2037,2043,2048,2054|1013:2025,2031,2036,2042,2053,2059|1014:2024,2030,2041,2047,2052,2058"}},{"id":"native_americans_day","name":"Native Americans' Day","en":"Native Americans' Day","category":"public","regional":{"SD":"d|1008:2012,2018,2029,2035,2040,2046,2057|1009:2017,2023,2028,2034,2045,2051,2056|1010:2011,2016,2022,2033,2039,2044,2050|1011:2010,2021,2027,2032,2038,2049,2055,2060|1012:2015,2020,2026,2037,2043,2048,2054|1013:2014,2025,2031,2036,2042,2053,2059|1014:2013,2019,2024,2030,2041,2047,2052,2058"}},{"id":"white_sunday","name":"White Sunday","en":"White Sunday","category":"public","regional":{"AS":"d|1008:2017,2023,2028,2034,2045,2051,2056|1009:2011,2016,2022,2033,2039,2044,2050|1010:2010,2021,2027,2032,2038,2049,2055,2060|1011:2015,2020,2026,2037,2043,2048,2054|1012:2014,2025,2031,2036,2042,2053,2059|1013:2013,2019,2024,2030,2041,2047,2052,2058|1014:2012,2018,2029,2035,2040,2046,2057"}},{"id":"alaska_day","name":"Alaska Day","en":"Alaska Day","category":"public","regional":{"AK":"d|1017:2014,2025,2031,2036,2042,2053,2059|1018:2010-2060|1019:2015,2020,2026,2037,2043,2048,2054"}},{"id":"diwali","name":"Diwali","en":"Diwali","category":"public","regional":{"CA":"d|1017:2028|1022:2033|1026:2030|1029:2027|1030:2035|1102:2032|1105:2029|1108:2026|1110:2034|1114:2031"}},{"id":"nevada_day","name":"Nevada Day","en":"Nevada Day","category":"public","regional":{"NV":"d|1025:2013,2019,2024,2030,2041,2047,2052,2058|1026:2012,2018,2029,2035,2040,2046,2057|1027:2017,2023,2028,2034,2045,2051,2056|1028:2011,2016,2022,2033,2039,2044,2050|1029:2010,2021,2027,2032,2038,2049,2055,2060|1030:2015,2020,2026,2037,2043,2048,2054|1031:2014,2025,2031,2036,2042,2053,2059"}},{"id":"liberty_day","name":"Liberty Day","en":"Liberty Day","category":"public","regional":{"VI":"d|1101:2010-2060"}},{"id":"all_souls_day","name":"All Souls' Day","en":"All Souls' Day","category":"public","regional":{"GU":"d|1102:2010-2060"}},{"id":"election_day","name":"Election Day","en":"Election Day","category":"public","regional":{"DE":"d|1102:2010,2032,2038,2060|1103:2020,2026,2048,2054|1104:2014,2036,2042|1105:2024,2030,2052,2058|1106:2012,2018,2040,2046|1107:2028,2034,2056|1108:2016,2022,2044,2050","HI":"d|1102:2010,2032,2038,2060|1103:2020,2026,2048,2054|1104:2014,2036,2042|1105:2024,2030,2052,2058|1106:2012,2018,2040,2046|1107:2028,2034,2056|1108:2016,2022,2044,2050","IL":"d|1102:2010,2032,2038,2060|1103:2020,2026,2048,2054|1104:2014,2036,2042|1105:2024,2030,2052,2058|1106:2012,2018,2040,2046|1107:2028,2034,2056|1108:2016,2022,2044,2050","IN":"d|1102:2010,2021,2027,2032,2038,2049,2055,2060|1103:2015,2020,2026,2037,2043,2048,2054|1104:2014,2025,2031,2036,2042,2053,2059|1105:2019,2024,2030,2041,2047,2052,2058|1106:2012,2018,2029,2035,2040,2046,2057|1107:2017,2023,2028,2034,2045,2051,2056|1108:2016,2022,2033,2039,2044,2050","LA":"d|1102:2010,2032,2038,2060|1103:2020,2026,2048,2054|1104:2014,2036,2042|1105:2024,2030,2052,2058|1106:2012,2018,2040,2046|1107:2028,2034,2056|1108:2016,2022,2044,2050","MI":"d|1102:2010,2032,2038,2060|1103:2020,2026,2048,2054|1104:2014,2036,2042|1105:2024,2030,2052,2058|1106:2012,2018,2040,2046|1107:2028,2034,2056|1108:2016,2022,2044,2050","MP":"d|1102:2010,2032,2038,2060|1103:2020,2026,2048,2054|1104:2014,2036,2042|1105:2024,2030,2052,2058|1106:2012,2018,2040,2046|1107:2028,2034,2056|1108:2016,2022,2044,2050","MT":"d|1102:2010,2032,2038,2060|1103:2020,2026,2048,2054|1104:2014,2036,2042|1105:2024,2030,2052,2058|1106:2012,2018,2040,2046|1107:2028,2034,2056|1108:2016,2022,2044,2050","NH":"d|1102:2010,2032,2038,2060|1103:2020,2026,2048,2054|1104:2014,2036,2042|1105:2024,2030,2052,2058|1106:2012,2018,2040,2046|1107:2028,2034,2056|1108:2016,2022,2044,2050","NJ":"d|1102:2010,2032,2038,2060|1103:2020,2026,2048,2054|1104:2014,2036,2042|1105:2024,2030,2052,2058|1106:2012,2018,2040,2046|1107:2028,2034,2056|1108:2016,2022,2044,2050","NY":"d|1102:2010,2021,2027,2032,2038,2049,2055,2060|1103:2015,2020,2026,2037,2043,2048,2054|1104:2014,2025,2031,2036,2042,2053,2059|1105:2019,2024,2030,2041,2047,2052,2058|1106:2012,2018,2029,2035,2040,2046,2057|1107:2017,2023,2028,2034,2045,2051,2056|1108:2016,2022,2033,2039,2044,2050","WV":"d|1102:2010,2032,2038,2060|1103:2020,2026,2048,2054|1104:2014,2036,2042|1105:2024,2030,2052,2058|1106:2012,2018,2040,2046|1107:2028,2034,2056|1108:2016,2022,2044,2050"}},{"id":"citizenship_day","name":"Citizenship Day","en":"Citizenship Day","category":"public","regional":{"MP":"d|1103:2017,2023,2028,2034,2045,2051,2056|1104:2010-2060|1105:2012,2018,2029,2035,2040,2046,2057"}},{"id":"veterans_day","name":"Veterans Day","en":"Veterans Day","category":"public","dates":"d|1110:2017,2023,2028,2034,2045,2051,2056|1111:2010-2060|1112:2012,2018,2029,2035,2040,2046,2057"},{"id":"puerto_rican_identity_day","name":"Puerto Rican Identity Day","en":"Puerto Rican Identity Day","category":"public","regional":{"PR":"d|1119:2023-2060|1120:2023,2028,2034,2045,2051,2056"}},{"id":"thanksgiving_day","name":"Thanksgiving Day","en":"Thanksgiving Day","category":"public","dates":"d|1122:2012,2018,2029,2035,2040,2046,2057|1123:2017,2023,2028,2034,2045,2051,2056|1124:2011,2016,2022,2033,2039,2044,2050|1125:2010,2021,2027,2032,2038,2049,2055,2060|1126:2015,2020,2026,2037,2043,2048,2054|1127:2014,2025,2031,2036,2042,2053,2059|1128:2013,2019,2024,2030,2041,2047,2052,2058"},{"id":"american_indian_heritage_day","name":"American Indian Heritage Day","en":"American Indian Heritage Day","category":"public","regional":{"MD":"d|1123:2012,2018,2029,2035,2040,2046,2057|1124:2017,2023,2028,2034,2045,2051,2056|1125:2011,2016,2022,2033,2039,2044,2050|1126:2010,2021,2027,2032,2038,2049,2055,2060|1127:2015,2020,2026,2037,2043,2048,2054|1128:2014,2025,2031,2036,2042,2053,2059|1129:2013,2019,2024,2030,2041,2047,2052,2058"}},{"id":"day_after_thanksgiving","name":"Day After Thanksgiving","en":"Day After Thanksgiving","category":"public","regional":{"CA":"d|1123:2012,2018,2029,2035,2040,2046,2057|1124:2017,2023,2028,2034,2045,2051,2056|1125:2011,2016,2022,2033,2039,2044,2050|1126:2010,2021,2027,2032,2038,2049,2055,2060|1127:2015,2020,2026,2037,2043,2048,2054|1128:2014,2025,2031,2036,2042,2053,2059|1129:2013,2019,2024,2030,2041,2047,2052,2058","DE":"d|1123:2012,2018,2029,2035,2040,2046,2057|1124:2017,2023,2028,2034,2045,2051,2056|1125:2011,2016,2022,2033,2039,2044,2050|1126:2010,2021,2027,2032,2038,2049,2055,2060|1127:2015,2020,2026,2037,2043,2048,2054|1128:2014,2025,2031,2036,2042,2053,2059|1129:2013,2019,2024,2030,2041,2047,2052,2058","MI":"d|1123:2018,2029,2035,2040,2046,2057|1124:2017,2023,2028,2034,2045,2051,2056|1125:2022,2033,2039,2044,2050|1126:2021,2027,2032,2038,2049,2055,2060|1127:2020,2026,2037,2043,2048,2054|1128:2025,2031,2036,2042,2053,2059|1129:2019,2024,2030,2041,2047,2052,2058","NC":"d|1123:2012,2018,2029,2035,2040,2046,2057|1124:2017,2023,2028,2034,2045,2051,2056|1125:2011,2016,2022,2033,2039,2044,2050|1126:2010,2021,2027,2032,2038,2049,2055,2060|1127:2015,2020,2026,2037,2043,2048,2054|1128:2014,2025,2031,2036,2042,2053,2059|1129:2013,2019,2024,2030,2041,2047,2052,2058","NH":"d|1123:2012,2018,2029,2035,2040,2046,2057|1124:2017,2023,2028,2034,2045,2051,2056|1125:2011,2016,2022,2033,2039,2044,2050|1126:2010,2021,2027,2032,2038,2049,2055,2060|1127:2015,2020,2026,2037,2043,2048,2054|1128:2014,2025,2031,2036,2042,2053,2059|1129:2013,2019,2024,2030,2041,2047,2052,2058","OK":"d|1123:2012,2018,2029,2035,2040,2046,2057|1124:2017,2023,2028,2034,2045,2051,2056|1125:2011,2016,2022,2033,2039,2044,2050|1126:2010,2021,2027,2032,2038,2049,2055,2060|1127:2015,2020,2026,2037,2043,2048,2054|1128:2014,2025,2031,2036,2042,2053,2059|1129:2013,2019,2024,2030,2041,2047,2052,2058","PA":"d|1123:2012,2018,2029,2035,2040,2046,2057|1124:2017,2023,2028,2034,2045,2051,2056|1125:2011,2016,2022,2033,2039,2044,2050|1126:2010,2021,2027,2032,2038,2049,2055,2060|1127:2015,2020,2026,2037,2043,2048,2054|1128:2014,2025,2031,2036,2042,2053,2059|1129:2013,2019,2024,2030,2041,2047,2052,2058","WV":"d|1123:2012,2018,2029,2035,2040,2046,2057|1124:2017,2023,2028,2034,2045,2051,2056|1125:2011,2016,2022,2033,2039,2044,2050|1126:2010,2021,2027,2032,2038,2049,2055,2060|1127:2015,2020,2026,2037,2043,2048,2054|1128:2014,2025,2031,2036,2042,2053,2059|1129:2013,2019,2024,2030,2041,2047,2052,2058"}},{"id":"family_day","name":"Family Day","en":"Family Day","category":"public","regional":{"NV":"d|1123:2012,2018,2029,2035,2040,2046,2057|1124:2017,2023,2028,2034,2045,2051,2056|1125:2011,2016,2022,2033,2039,2044,2050|1126:2010,2021,2027,2032,2038,2049,2055,2060|1127:2015,2020,2026,2037,2043,2048,2054|1128:2014,2025,2031,2036,2042,2053,2059|1129:2013,2019,2024,2030,2041,2047,2052,2058"}},{"id":"friday_after_thanksgiving","name":"Friday After Thanksgiving","en":"Friday After Thanksgiving","category":"public","regional":{"FL":"d|1123:2012,2018,2029,2035,2040,2046,2057|1124:2017,2023,2028,2034,2045,2051,2056|1125:2011,2016,2022,2033,2039,2044,2050|1126:2010,2021,2027,2032,2038,2049,2055,2060|1127:2015,2020,2026,2037,2043,2048,2054|1128:2014,2025,2031,2036,2042,2053,2059|1129:2013,2019,2024,2030,2041,2047,2052,2058","TX":"d|1123:2012,2018,2029,2035,2040,2046,2057|1124:2017,2023,2028,2034,2045,2051,2056|1125:2011,2016,2022,2033,2039,2044,2050|1126:2010,2021,2027,2032,2038,2049,2055,2060|1127:2015,2020,2026,2037,2043,2048,2054|1128:2014,2025,2031,2036,2042,2053,2059|1129:2013,2019,2024,2030,2041,2047,2052,2058"}},{"id":"native_american_heritage_day","name":"Native American Heritage Day","en":"Native American Heritage Day","category":"public","regional":{"WA":"d|1123:2018,2029,2035,2040,2046,2057|1124:2017,2023,2028,2034,2045,2051,2056|1125:2016,2022,2033,2039,2044,2050|1126:2021,2027,2032,2038,2049,2055,2060|1127:2015,2020,2026,2037,2043,2048,2054|1128:2014,2025,2031,2036,2042,2053,2059|1129:2019,2024,2030,2041,2047,2052,2058"}},{"id":"constitution_day","name":"Constitution Day","en":"Constitution Day","category":"public","regional":{"MP":"d|1207:2012,2018,2029,2035,2040,2046,2057|1208:2010-2060|1209:2013,2019,2024,2030,2041,2047,2052,2058"}},{"id":"lady_of_camarin_day","name":"Lady of Camarin Day","en":"Lady of Camarin Day","category":"public","regional":{"GU":"d|1208:2010-2060"}},{"id":"christmas_eve","name":"Christmas Eve","en":"Christmas Eve","category":"public","regional":{"KS":"d|1222:2017,2023,2028,2034,2045,2051,2056|1223:2016,2021-2022,2027,2032-2033,2038-2039,2044,2049-2050,2055,2060|1224:2013-2060","MI":"d|1222:2017,2023,2028,2034,2045,2051,2056|1223:2016,2021-2022,2027,2032-2033,2038-2039,2044,2049-2050,2055,2060|1224:2013-2060","NC":"d|1222:2017,2023,2028,2034,2045,2051,2056|1223:2016,2021-2022,2027,2032-2033,2038-2039,2044,2049-2050,2055,2060|1224:2013-2060","TX":"d|1222:2017,2023,2028,2034,2045,2051,2056|1223:2010-2011,2016,2021-2022,2027,2032-2033,2038-2039,2044,2049-2050,2055,2060|1224:2010-2060","WI":"d|1222:2017,2023,2028,2034,2045,2051,2056|1223:2016,2021-2022,2027,2032-2033,2038-2039,2044,2049-2050,2055,2060|1224:2012-2060"}},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1224:2010,2021,2027,2032,2038,2049,2055,2060|1225:2010-2060|1226:2011,2016,2022,2033,2039,2044,2050"},{"id":"christmas_second_day","name":"Christmas Second Day","en":"Christmas Second Day","category":"public","regional":{"VI":"d|1226:2010-2060"}},{"id":"day_after_christmas","name":"Day After Christmas","en":"Day After Christmas","category":"public","regional":{"NC":"d|1226:2013-2060|1227:2016,2021-2022,2027,2032-2033,2038-2039,2044,2049-2050,2055,2060|1228:2015,2020,2026,2037,2043,2048,2054","TX":"d|1226:2010-2060"}},{"id":"new_year_s_eve","name":"New Year's Eve","en":"New Year's Eve","category":"public","regional":{"KY":"d|1230:2016,2022,2033,2039,2044,2050|1231:2013-2060","MI":"d|1230:2016,2022,2033,2039,2044,2050|1231:2013-2060","WI":"d|1230:2016,2022,2033,2039,2044,2050|1231:2012-2060"}},{"id":"new_year_s_day_government","name":"New Year's Day","en":"New Year's Day","category":"government","dates":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051|1231:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"birthday_of_martin_luther_king_jr_government","name":"Birthday of Martin Luther King, Jr.","en":"Birthday of Martin Luther King, Jr.","category":"government","dates":"d|0115:2018,2024,2029,2035,2046,2052,2057|0116:2012,2017,2023,2034,2040,2045,2051|0117:2011,2022,2028,2033,2039,2050,2056|0118:2010,2016,2021,2027,2038,2044,2049,2055|0119:2015,2026,2032,2037,2043,2054,2060|0120:2014,2020,2025,2031,2042,2048,2053,2059|0121:2013,2019,2030,2036,2041,2047,2058"},{"id":"groundhog_day_unofficial","name":"Groundhog Day","en":"Groundhog Day","category":"unofficial","dates":"d|0202:2010-2060"},{"id":"valentine_s_day_unofficial","name":"Valentine's Day","en":"Valentine's Day","category":"unofficial","dates":"d|0214:2010-2060"},{"id":"washington_s_birthday_government","name":"Washington's Birthday","en":"Washington's Birthday","category":"government","dates":"d|0215:2010,2016,2021,2027,2038,2044,2049,2055|0216:2015,2026,2032,2037,2043,2054,2060|0217:2014,2020,2025,2031,2042,2048,2053,2059|0218:2013,2019,2030,2036,2041,2047,2058|0219:2018,2024,2029,2035,2046,2052,2057|0220:2012,2017,2023,2034,2040,2045,2051|0221:2011,2022,2028,2033,2039,2050,2056"},{"id":"american_citizenship_day_government","name":"American Citizenship Day","en":"American Citizenship Day","category":"government","regional":{"PR":"d|0302:2010-2060"}},{"id":"saint_patrick_s_day_unofficial","name":"Saint Patrick's Day","en":"Saint Patrick's Day","category":"unofficial","dates":"d|0317:2010-2060"},{"id":"good_friday_unofficial","name":"Good Friday","en":"Good Friday","category":"unofficial","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_sunday_unofficial","name":"Easter Sunday","en":"Easter Sunday","category":"unofficial","dates":"d|0325:2035,2046|0327:2016|0328:2027,2032|0329:2043,2054|0330:2059|0331:2013,2024|0401:2018,2029,2040|0402:2051,2056|0404:2010,2021|0405:2015,2026,2037,2048|0406:2042,2053|0408:2012|0409:2023,2034,2045|0410:2039,2050|0412:2020|0413:2031,2036|0414:2047,2058|0416:2017,2028|0417:2022,2033,2044|0418:2049,2055,2060|0420:2014,2025|0421:2019,2030,2041,2052|0422:2057|0424:2011|0425:2038"},{"id":"mother_s_day_unofficial","name":"Mother's Day","en":"Mother's Day","category":"unofficial","dates":"d|0508:2011,2016,2022,2033,2039,2044,2050|0509:2010,2021,2027,2032,2038,2049,2055,2060|0510:2015,2020,2026,2037,2043,2048,2054|0511:2014,2025,2031,2036,2042,2053,2059|0512:2013,2019,2024,2030,2041,2047,2052,2058|0513:2012,2018,2029,2035,2040,2046,2057|0514:2017,2023,2028,2034,2045,2051,2056"},{"id":"memorial_day_government","name":"Memorial Day","en":"Memorial Day","category":"government","dates":"d|0525:2015,2020,2026,2037,2043,2048,2054|0526:2014,2025,2031,2036,2042,2053,2059|0527:2013,2019,2024,2030,2041,2047,2052,2058|0528:2012,2018,2029,2035,2040,2046,2057|0529:2017,2023,2028,2034,2045,2051,2056|0530:2011,2016,2022,2033,2039,2044,2050|0531:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"father_s_day_unofficial","name":"Father's Day","en":"Father's Day","category":"unofficial","dates":"d|0615:2014,2025,2031,2036,2042,2053,2059|0616:2013,2019,2024,2030,2041,2047,2052,2058|0617:2012,2018,2029,2035,2040,2046,2057|0618:2017,2023,2028,2034,2045,2051,2056|0619:2011,2016,2022,2033,2039,2044,2050|0620:2010,2021,2027,2032,2038,2049,2055,2060|0621:2015,2020,2026,2037,2043,2048,2054"},{"id":"juneteenth_national_independence_day_government","name":"Juneteenth National Independence Day","en":"Juneteenth National Independence Day","category":"government","dates":"d|0618:2021,2027,2032,2038,2049,2055,2060|0619:2021-2060|0620:2022,2033,2039,2044,2050"},{"id":"independence_day_government","name":"Independence Day","en":"Independence Day","category":"government","dates":"d|0703:2015,2020,2026,2037,2043,2048,2054|0704:2010-2060|0705:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"labor_day_government","name":"Labor Day","en":"Labor Day","category":"government","dates":"d|0901:2014,2025,2031,2036,2042,2053,2059|0902:2013,2019,2024,2030,2041,2047,2052,2058|0903:2012,2018,2029,2035,2040,2046,2057|0904:2017,2023,2028,2034,2045,2051,2056|0905:2011,2016,2022,2033,2039,2044,2050|0906:2010,2021,2027,2032,2038,2049,2055,2060|0907:2015,2020,2026,2037,2043,2048,2054"},{"id":"columbus_day_government","name":"Columbus Day","en":"Columbus Day","category":"government","dates":"d|1008:2012,2018,2029,2035,2040,2046,2057|1009:2017,2023,2028,2034,2045,2051,2056|1010:2011,2016,2022,2033,2039,2044,2050|1011:2010,2021,2027,2032,2038,2049,2055,2060|1012:2015,2020,2026,2037,2043,2048,2054|1013:2014,2025,2031,2036,2042,2053,2059|1014:2013,2019,2024,2030,2041,2047,2052,2058"},{"id":"halloween_unofficial","name":"Halloween","en":"Halloween","category":"unofficial","dates":"d|1031:2010-2060"},{"id":"election_day_unofficial","name":"Election Day","en":"Election Day","category":"unofficial","dates":"d|1102:2032,2060|1103:2020,2048|1104:2036|1105:2024,2052|1106:2012,2040|1107:2028,2056|1108:2016,2044"},{"id":"veterans_day_government","name":"Veterans Day","en":"Veterans Day","category":"government","dates":"d|1110:2017,2023,2028,2034,2045,2051,2056|1111:2010-2060|1112:2012,2018,2029,2035,2040,2046,2057"},{"id":"thanksgiving_day_government","name":"Thanksgiving Day","en":"Thanksgiving Day","category":"government","dates":"d|1122:2012,2018,2029,2035,2040,2046,2057|1123:2017,2023,2028,2034,2045,2051,2056|1124:2011,2016,2022,2033,2039,2044,2050|1125:2010,2021,2027,2032,2038,2049,2055,2060|1126:2015,2020,2026,2037,2043,2048,2054|1127:2014,2025,2031,2036,2042,2053,2059|1128:2013,2019,2024,2030,2041,2047,2052,2058"},{"id":"christmas_day_government","name":"Christmas Day","en":"Christmas Day","category":"government","dates":"d|1224:2010,2021,2027,2032,2038,2049,2055,2060|1225:2010-2060|1226:2011,2016,2022,2033,2039,2044,2050"},{"id":"christmas_eve_half_day","name":"Christmas Eve","en":"Christmas Eve","category":"half_day","regional":{"PR":"d|1224:2010-2060"}},{"id":"christmas_eve_unofficial","name":"Christmas Eve","en":"Christmas Eve","category":"unofficial","dates":"d|1224:2010-2060"},{"id":"new_year_s_eve_unofficial","name":"New Year's Eve","en":"New Year's Eve","category":"unofficial","dates":"d|1231:2010-2060"}]} diff --git a/app/Support/Holidays/data/UY.json b/app/Support/Holidays/data/UY.json new file mode 100644 index 000000000..5fb1df329 --- /dev/null +++ b/app/Support/Holidays/data/UY.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"UY","language":"es","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Año Nuevo","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"workers_day","name":"Día de los Trabajadores","en":"Workers' Day","category":"public","dates":"d|0501:2010-2060"},{"id":"constitution_day","name":"Jura de la Constitución","en":"Constitution Day","category":"public","dates":"d|0718:2010-2060"},{"id":"independence_declaration_day","name":"Declaratoria de la Independencia","en":"Independence Declaration Day","category":"public","dates":"d|0825:2010-2060"},{"id":"family_day","name":"Día de la Familia","en":"Family Day","category":"public","dates":"d|1225:2010-2060"},{"id":"children_s_day_bank","name":"Día de los Niños","en":"Children's Day","category":"bank","dates":"d|0106:2010-2060"},{"id":"carnival_bank","name":"Carnaval","en":"Carnival","category":"bank","dates":"d|0205:2035,2046|0206:2035,2046|0208:2016,2027|0209:2016,2027,2032,2043,2054|0210:2032,2043,2054,2059|0211:2013,2059|0212:2013,2018,2024,2029|0213:2018,2024,2029,2040,2051|0214:2040,2051,2056|0215:2010,2021,2056|0216:2010,2015,2021,2026,2037|0217:2015,2026,2037,2042,2048,2053|0218:2042,2048,2053|0220:2012,2023,2034,2045|0221:2012,2023,2034,2039,2045,2050|0222:2039,2050|0224:2020,2031|0225:2020,2031,2036,2047,2058|0226:2036,2047,2058|0227:2017|0228:2017,2022,2028,2033|0229:2028,2044|0301:2022,2033,2044,2049,2055,2060|0302:2049,2055,2060|0303:2014,2025|0304:2014,2019,2025,2030,2041,2052|0305:2019,2030,2041,2052,2057|0306:2057|0307:2011|0308:2011,2038|0309:2038"},{"id":"tourism_week_bank","name":"Semana de Turismo","en":"Tourism Week","category":"bank","dates":"y|2010:0329-0402|2011:0418-0422|2012:0402-0406|2013:0325-0329|2014:0414-0418|2015:0330-0403|2016:0321-0325|2017:0410-0414|2018:0326-0330|2019:0415-0419|2020:0406-0410|2021:0329-0402|2022:0411-0415|2023:0403-0407|2024:0325-0329|2025:0414-0418|2026:0330-0403|2027:0322-0326|2028:0410-0414|2029:0326-0330|2030:0415-0419|2031:0407-0411|2032:0322-0326|2033:0411-0415|2034:0403-0407|2035:0319-0323|2036:0407-0411|2037:0330-0403|2038:0419-0423|2039:0404-0408|2040:0326-0330|2041:0415-0419|2042:0331-0404|2043:0323-0327|2044:0411-0415|2045:0403-0407|2046:0319-0323|2047:0408-0412|2048:0330-0403|2049:0412-0416|2050:0404-0408|2051:0327-0331|2052:0415-0419|2053:0331-0404|2054:0323-0327|2055:0412-0416|2056:0327-0331|2057:0416-0420|2058:0408-0412|2059:0324-0328|2060:0412-0416"},{"id":"landing_of_the_33_patriots_bank","name":"Desembarco de los 33 Orientales","en":"Landing of the 33 Patriots","category":"bank","dates":"d|0417:2017,2023,2028,2034,2045,2051,2056|0418:2011,2016,2022,2033,2039,2044,2050|0419:2010,2014-2015,2020-2021,2025-2027,2031-2032,2036-2038,2042-2043,2048-2049,2053-2055,2059-2060|0422:2013,2019,2024,2030,2041,2047,2052,2058|0423:2012,2018,2029,2035,2040,2046,2057"},{"id":"battle_of_las_piedras_bank","name":"Batalla de Las Piedras","en":"Battle of Las Piedras","category":"bank","dates":"d|0516:2011,2016,2022,2033,2039,2044,2050|0517:2010,2021,2027,2032,2038,2049,2055,2060|0518:2013-2015,2019-2020,2024-2026,2030-2031,2036-2037,2041-2043,2047-2048,2052-2054,2058-2059|0521:2012,2018,2029,2035,2040,2046,2057|0522:2017,2023,2028,2034,2045,2051,2056"},{"id":"birthday_of_artigas_bank","name":"Natalicio de Artigas","en":"Birthday of Artigas","category":"bank","dates":"d|0619:2010-2060"},{"id":"cultural_diversity_day_bank","name":"Día de la Diversidad Cultural","en":"Cultural Diversity Day","category":"bank","dates":"d|1010:2016,2022,2033,2039,2044,2050|1011:2021,2027,2032,2038,2049,2055,2060|1012:2014-2015,2019-2020,2024-2026,2030-2031,2036-2037,2041-2043,2047-2048,2052-2054,2058-2059|1015:2018,2029,2035,2040,2046,2057|1016:2017,2023,2028,2034,2045,2051,2056"},{"id":"all_souls_day_bank","name":"Día de los Difuntos","en":"All Souls' Day","category":"bank","dates":"d|1102:2010-2060"}]} diff --git a/app/Support/Holidays/data/UZ.json b/app/Support/Holidays/data/UZ.json new file mode 100644 index 000000000..d30fa9e3a --- /dev/null +++ b/app/Support/Holidays/data/UZ.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"UZ","language":"uz","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Yangi yil","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2034,2040,2045,2051|0103:2028,2033,2039,2050,2056"},{"id":"additional_day_off_by_presidential_decree","name":"Prezidentining farmoni bilan qoʻshimcha dam olish kuni","en":"Additional day off by Presidential decree","category":"public","dates":"y|2018:0102,0320,0831|2019:0102,0322,0902|2020:0323|2021:0514,0902-0903,1231|2022:0103,0322-0323,0503,0711,0902|2023:0102,0424,0629-0630|2024:0322,0411,0618,0903,1231|2025:1231|2026:0102,0528-0529,0831"},{"id":"day_off","name":"Dam olish kuni","en":"Day off","category":"public","dates":"y|2018:0103,0319,0322,0823-0824,0903-0904,1231|2019:0103,0606,0903,1231|2020:0102,0831|2021:0322,0721-0722|2022:0104,0504,0712|2023:0103,0320,0322|2024:0102,0412,1230|2025:0102|2026:1231"},{"id":"eid_al_fitr","name":"Roʻza hayit","en":"Eid al-Fitr","category":"public","dates":"y|2010:0910|2011:0831|2012:0819|2013:0809|2014:0728|2015:0718|2016:0706|2017:0626|2018:0615|2019:0605|2020:0524|2021:0513|2022:0502|2023:0421|2024:0410|2025:0330-0331|2026:0320|2027:0309|2028:0226,0228|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,0104,1223|2034:1212|2035:1201,1203|2036:1119|2037:1108-1109|2038:1029|2039:1019|2040:1007-1008|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712-0713|2049:0701|2050:0620|2051:0610,0612|2052:0529|2053:0519|2054:0509,0511|2055:0428|2056:0416-0417|2057:0405|2058:0325|2059:0315,0317|2060:0304","estimated":"y|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304"},{"id":"eid_al_adha","name":"Qurbon hayit","en":"Eid al-Adha","category":"public","dates":"y|2010:1116|2011:1106|2012:1026|2013:1015|2014:1004|2015:0924|2016:0912|2017:0901|2018:0821|2019:0811|2020:0731|2021:0720|2022:0709|2023:0628|2024:0616-0617|2025:0606|2026:0527|2027:0516-0517|2028:0505|2029:0424|2030:0413,0415|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218-0219|2036:0207|2037:0126|2038:0116,0118|2039:0105,1226|2040:1214|2041:1204|2042:1123-1124|2043:1112|2044:1031|2045:1021,1023|2046:1010|2047:0930|2048:0919,0921|2049:0908|2050:0828-0829|2051:0817|2052:0805|2053:0726,0728|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601,0603|2059:0522|2060:0510","estimated":"y|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510"},{"id":"women_s_day","name":"Xotin-qizlar kuni","en":"Women's Day","category":"public","dates":"d|0308:2010-2060|0309:2026,2037,2043,2048,2054|0310:2025,2031,2036,2042,2053,2059"},{"id":"nowruz","name":"Navroʻz bayrami","en":"Nowruz","category":"public","dates":"d|0321:2010-2060|0322:2027,2038,2049,2055,2060|0323:2026,2032,2037,2043,2048,2054"},{"id":"day_of_memory_and_honor","name":"Xotira va qadrlash kuni","en":"Day of Memory and Honor","category":"public","dates":"d|0509:2010-2060|0510:2027,2032,2038,2049,2055|0511:2026,2037,2043,2048,2054,2060"},{"id":"independence_day","name":"Mustaqillik kuni","en":"Independence Day","category":"public","dates":"d|0901:2010-2060|0902:2024,2030,2041,2047,2052,2058|0903:2029,2035,2040,2046,2057"},{"id":"teachers_and_instructors_day","name":"Oʻqituvchi va murabbiylar kuni","en":"Teachers and Instructors Day","category":"public","dates":"d|1001:2010-2060|1002:2023,2028,2034,2045,2051,2056|1003:2033,2039,2044,2050"},{"id":"constitution_day","name":"Oʻzbekiston Respublikasi Konstitutsiyasi kuni","en":"Constitution Day","category":"public","dates":"d|1208:2010-2060|1209:2024,2030,2041,2047,2052,2058|1210:2029,2035,2040,2046,2057"}]} diff --git a/app/Support/Holidays/data/VA.json b/app/Support/Holidays/data/VA.json new file mode 100644 index 000000000..17506b7dc --- /dev/null +++ b/app/Support/Holidays/data/VA.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"VA","language":"it","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"solemnity_of_mary_mother_of_god","name":"Solennità di Maria Santissima Madre di Dio","en":"Solemnity of Mary, Mother of God","category":"public","dates":"d|0101:2010-2060"},{"id":"epiphany","name":"Epifania del Signore","en":"Epiphany","category":"public","dates":"d|0106:2010-2060"},{"id":"anniversary_of_the_foundation_of_vatican_city","name":"Anniversario della istituzione dello Stato della Città del Vaticano","en":"Anniversary of the Foundation of Vatican City","category":"public","dates":"d|0211:2010-2060"},{"id":"anniversary_of_the_election_of_the_holy_father","name":"Anniversario dell'Elezione del Santo Padre","en":"Anniversary of the Election of the Holy Father","category":"public","dates":"d|0313:2014-2025|0419:2010-2012|0508:2026-2060"},{"id":"name_day_of_the_holy_father","name":"Onomastico del Santo Padre","en":"Name Day of the Holy Father","category":"public","dates":"d|0319:2010-2012|0423:2013-2025|0917:2025-2060"},{"id":"saint_joseph_s_day","name":"San Giuseppe","en":"Saint Joseph's Day","category":"public","dates":"d|0319:2010-2060"},{"id":"maundy_thursday","name":"Giovedì Santo","en":"Maundy Thursday","category":"public","dates":"d|0322:2035,2046|0324:2016|0325:2027,2032|0326:2043,2054|0327:2059|0328:2013,2024|0329:2018,2029,2040|0330:2051,2056|0401:2010,2021|0402:2015,2026,2037,2048|0403:2042,2053|0405:2012|0406:2023,2034,2045|0407:2039,2050|0409:2020|0410:2031,2036|0411:2047,2058|0413:2017,2028|0414:2022,2033,2044|0415:2049,2055,2060|0417:2014,2025|0418:2019,2030,2041,2052|0419:2057|0421:2011|0422:2038"},{"id":"good_friday","name":"Venerdì Santo","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"holy_saturday","name":"Sabato Santo","en":"Holy Saturday","category":"public","dates":"d|0324:2035,2046|0326:2016|0327:2027,2032|0328:2043,2054|0329:2059|0330:2013,2024|0331:2018,2029,2040|0401:2051,2056|0403:2010,2021|0404:2015,2026,2037,2048|0405:2042,2053|0407:2012|0408:2023,2034,2045|0409:2039,2050|0411:2020|0412:2031,2036|0413:2047,2058|0415:2017,2028|0416:2022,2033,2044|0417:2049,2055,2060|0419:2014,2025|0420:2019,2030,2041,2052|0421:2057|0423:2011|0424:2038"},{"id":"easter_sunday","name":"Pasqua di Resurrezione","en":"Easter Sunday","category":"public","dates":"d|0325:2035,2046|0327:2016|0328:2027,2032|0329:2043,2054|0330:2059|0331:2013,2024|0401:2018,2029,2040|0402:2051,2056|0404:2010,2021|0405:2015,2026,2037,2048|0406:2042,2053|0408:2012|0409:2023,2034,2045|0410:2039,2050|0412:2020|0413:2031,2036|0414:2047,2058|0416:2017,2028|0417:2022,2033,2044|0418:2049,2055,2060|0420:2014,2025|0421:2019,2030,2041,2052|0422:2057|0424:2011|0425:2038"},{"id":"easter_monday","name":"Lunedì dell'Angelo","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"easter_tuesday","name":"Martedì in Albis","en":"Easter Tuesday","category":"public","dates":"d|0327:2035,2046|0329:2016|0330:2027,2032|0331:2043,2054|0401:2059|0402:2013,2024|0403:2018,2029,2040|0404:2051,2056|0406:2010,2021|0407:2015,2026,2037,2048|0408:2042,2053|0410:2012|0411:2023,2034,2045|0412:2039,2050|0414:2020|0415:2031,2036|0416:2047,2058|0418:2017,2028|0419:2022,2033,2044|0420:2049,2055,2060|0422:2014,2025|0423:2019,2030,2041,2052|0424:2057|0426:2011|0427:2038"},{"id":"saint_joseph_the_worker_s_day","name":"San Giuseppe Artigiano","en":"Saint Joseph the Worker's Day","category":"public","dates":"d|0501:2010-2060"},{"id":"ascension_day","name":"Ascensione del Signore","en":"Ascension Day","category":"public","dates":"d|0503:2035,2046|0505:2016|0506:2027,2032|0507:2043,2054|0508:2059|0509:2013,2024|0510:2018,2029,2040|0511:2051,2056|0513:2010,2021|0514:2015,2026,2037,2048|0515:2042,2053|0517:2012|0518:2023,2034,2045|0519:2039,2050|0521:2020|0522:2031,2036|0523:2047,2058|0525:2017,2028|0526:2022,2033,2044|0527:2049,2055,2060|0529:2014,2025|0530:2019,2030,2041,2052|0531:2057|0602:2011|0603:2038"},{"id":"solemnity_of_pentecost","name":"Solennità della Pentecoste","en":"Solemnity of Pentecost","category":"public","dates":"d|0513:2035,2046|0515:2016|0516:2027,2032|0517:2043,2054|0518:2059|0519:2013,2024|0520:2018,2029,2040|0521:2051,2056|0523:2010,2021|0524:2015,2026,2037,2048|0525:2042,2053|0527:2012|0528:2023,2034,2045|0529:2039,2050|0531:2020|0601:2031,2036|0602:2047,2058|0604:2017,2028|0605:2022,2033,2044|0606:2049,2055,2060|0608:2014,2025|0609:2019,2030,2041,2052|0610:2057|0612:2011|0613:2038"},{"id":"solemnity_of_holy_trinity","name":"Solennità della Santissima Trinità","en":"Solemnity of Holy Trinity","category":"public","dates":"d|0520:2035,2046|0522:2016|0523:2027,2032|0524:2043,2054|0525:2059|0526:2013,2024|0527:2018,2029,2040|0528:2051,2056|0530:2010,2021|0531:2015,2026,2037,2048|0601:2042,2053|0603:2012|0604:2023,2034,2045|0605:2039,2050|0607:2020|0608:2031,2036|0609:2047,2058|0611:2017,2028|0612:2022,2033,2044|0613:2049,2055,2060|0615:2014,2025|0616:2019,2030,2041,2052|0617:2057|0619:2011|0620:2038"},{"id":"corpus_domini","name":"Corpus Domini","en":"Corpus Domini","category":"public","dates":"d|0524:2035,2046|0526:2016|0527:2027,2032|0528:2043,2054|0529:2059|0530:2013,2024|0531:2018,2029,2040|0601:2051,2056|0603:2010,2021|0604:2015,2026,2037,2048|0605:2042,2053|0607:2012|0608:2023,2034,2045|0609:2039,2050|0611:2020|0612:2031,2036|0613:2047,2058|0615:2017,2028|0616:2022,2033,2044|0617:2049,2055,2060|0619:2014,2025|0620:2019,2030,2041,2052|0621:2057|0623:2011|0624:2038"},{"id":"saints_peter_and_paul_s_day","name":"Santi Pietro e Paolo","en":"Saints Peter and Paul's Day","category":"public","dates":"d|0629:2010-2060"},{"id":"day_before_assumption_of_mary","name":"Vigilia dell'Assunzione di Maria Santissima","en":"Day Before Assumption of Mary","category":"public","dates":"d|0814:2010-2060"},{"id":"assumption_of_mary_day","name":"Assunzione di Maria Santissima","en":"Assumption of Mary Day","category":"public","dates":"d|0815:2010-2060"},{"id":"day_after_assumption_of_mary","name":"Giorno Successivo all'Assunzione di Maria Santissima","en":"Day After Assumption of Mary","category":"public","dates":"d|0816:2010-2060"},{"id":"all_saints_day","name":"Tutti i Santi","en":"All Saints' Day","category":"public","dates":"d|1101:2010-2060"},{"id":"all_souls_day","name":"Tutti i Fedeli Defunti","en":"All Souls' Day","category":"public","dates":"d|1102:2010-2060"},{"id":"immaculate_conception","name":"Immacolata Concezione","en":"Immaculate Conception","category":"public","dates":"d|1208:2010-2060"},{"id":"christmas_eve","name":"Vigilia di Natale","en":"Christmas Eve","category":"public","dates":"d|1224:2010-2060"},{"id":"christmas_day","name":"Natale","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"},{"id":"saint_stephen_s_day","name":"Santo Stefano","en":"Saint Stephen's Day","category":"public","dates":"d|1226:2010-2060"},{"id":"saint_john_the_evangelist_s_day","name":"San Giovanni","en":"Saint John the Evangelist's Day","category":"public","dates":"d|1227:2010-2060"},{"id":"last_day_of_the_year","name":"Ultimo giorno dell'anno","en":"Last Day of the Year","category":"public","dates":"d|1231:2010-2060"}]} diff --git a/app/Support/Holidays/data/VC.json b/app/Support/Holidays/data/VC.json new file mode 100644 index 000000000..0386dd2a2 --- /dev/null +++ b/app/Support/Holidays/data/VC.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"VC","language":"en_VC","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051"},{"id":"national_heroes_day","name":"National Heroes' Day","en":"National Heroes' Day","category":"public","dates":"d|0314:2010-2060|0315:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_monday","name":"Easter Monday","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"national_workers_day","name":"National Workers' Day","en":"National Workers' Day","category":"public","dates":"d|0501:2010-2060|0502:2011,2016,2022,2033,2039,2044,2050"},{"id":"pentecost_monday","name":"Whit Monday","en":"Pentecost Monday","category":"public","dates":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038"},{"id":"national_spiritual_baptist_day","name":"National Spiritual Baptist Day","en":"National Spiritual Baptist Day","category":"public","dates":"d|0521:2025-2060"},{"id":"carnival_monday","name":"Carnival Monday","en":"Carnival Monday","category":"public","dates":"d|0701:2030,2041,2047,2052,2058|0702:2012,2029,2035,2040,2046,2057|0703:2017,2028,2034,2045,2051,2056|0704:2011,2016,2022,2033,2039,2044,2050|0705:2010,2027,2032,2038,2049,2055,2060|0706:2015,2026,2037,2043,2048,2054|0707:2014,2025,2031,2036,2042,2053,2059|0708:2013,2019,2024|0709:2018|0710:2023|0803:2020|0906:2021"},{"id":"carnival_tuesday","name":"Carnival Tuesday","en":"Carnival Tuesday","category":"public","dates":"d|0702:2030,2041,2047,2052,2058|0703:2012,2029,2035,2040,2046,2057|0704:2017,2028,2034,2045,2051,2056|0705:2011,2016,2022,2033,2039,2044,2050|0706:2010,2027,2032,2038,2049,2055,2060|0707:2015,2026,2037,2043,2048,2054|0708:2014,2025,2031,2036,2042,2053,2059|0709:2013,2019,2024|0710:2018|0711:2023|0804:2020|0907:2021"},{"id":"emancipation_day","name":"Emancipation Day","en":"Emancipation Day","category":"public","dates":"d|0801:2010-2060|0802:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"independence_day","name":"Independence Day","en":"Independence Day","category":"public","dates":"d|1027:2010-2060|1028:2013,2019,2024,2030,2041,2047,2052,2058"},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060|1227:2011,2016,2022,2033,2039,2044,2050"},{"id":"boxing_day","name":"Boxing Day","en":"Boxing Day","category":"public","dates":"d|1226:2010-2060|1227:2010,2021,2027,2032,2038,2049,2055,2060"}]} diff --git a/app/Support/Holidays/data/VE.json b/app/Support/Holidays/data/VE.json new file mode 100644 index 000000000..4f84f1aa9 --- /dev/null +++ b/app/Support/Holidays/data/VE.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"VE","language":"es","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Año Nuevo","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"carnival_monday","name":"Lunes de Carnaval","en":"Carnival Monday","category":"public","dates":"d|0205:2035,2046|0208:2016,2027|0209:2032,2043,2054|0210:2059|0211:2013|0212:2018,2024,2029|0213:2040,2051|0214:2056|0215:2010,2021|0216:2015,2026,2037|0217:2042,2048,2053|0220:2012,2023,2034,2045|0221:2039,2050|0224:2020,2031|0225:2036,2047,2058|0227:2017|0228:2022,2028,2033|0229:2044|0301:2049,2055,2060|0303:2014,2025|0304:2019,2030,2041,2052|0305:2057|0307:2011|0308:2038"},{"id":"carnival_tuesday","name":"Martes de Carnaval","en":"Carnival Tuesday","category":"public","dates":"d|0206:2035,2046|0209:2016,2027|0210:2032,2043,2054|0211:2059|0212:2013|0213:2018,2024,2029|0214:2040,2051|0215:2056|0216:2010,2021|0217:2015,2026,2037|0218:2042,2048,2053|0221:2012,2023,2034,2045|0222:2039,2050|0225:2020,2031|0226:2036,2047,2058|0228:2017|0229:2028|0301:2022,2033,2044|0302:2049,2055,2060|0304:2014,2025|0305:2019,2030,2041,2052|0306:2057|0308:2011|0309:2038"},{"id":"maundy_thursday","name":"Jueves Santo","en":"Maundy Thursday","category":"public","dates":"d|0322:2035,2046|0324:2016|0325:2027,2032|0326:2043,2054|0327:2059|0328:2013,2024|0329:2018,2029,2040|0330:2051,2056|0401:2010,2021|0402:2015,2026,2037,2048|0403:2042,2053|0405:2012|0406:2023,2034,2045|0407:2039,2050|0409:2020|0410:2031,2036|0411:2047,2058|0413:2017,2028|0414:2022,2033,2044|0415:2049,2055,2060|0417:2014,2025|0418:2019,2030,2041,2052|0419:2057|0421:2011|0422:2038"},{"id":"good_friday","name":"Viernes Santo","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"declaration_of_independence","name":"Declaración de la Independencia","en":"Declaration of Independence","category":"public","dates":"d|0419:2010-2060"},{"id":"international_worker_s_day","name":"Dia Mundial del Trabajador","en":"International Worker's Day","category":"public","dates":"d|0501:2010-2060"},{"id":"family_day","name":"Día de la Familia","en":"Family Day","category":"public","dates":"d|0515:2024-2060"},{"id":"battle_of_carabobo","name":"Batalla de Carabobo","en":"Battle of Carabobo","category":"public","dates":"d|0624:2010-2060"},{"id":"independence_day","name":"Día de la Independencia","en":"Independence Day","category":"public","dates":"d|0705:2010-2060"},{"id":"birthday_of_simon_bolivar","name":"Natalicio de Simón Bolívar","en":"Birthday of Simon Bolivar","category":"public","dates":"d|0724:2010-2060"},{"id":"day_of_indigenous_resistance","name":"Día de la Resistencia Indígena","en":"Day of Indigenous Resistance","category":"public","dates":"d|1012:2010-2060"},{"id":"christmas_eve","name":"Nochebuena","en":"Christmas Eve","category":"public","dates":"d|1224:2010-2060"},{"id":"christmas_day","name":"Día de Navidad","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"},{"id":"new_year_s_eve","name":"Fiesta de Fin de Año","en":"New Year's Eve","category":"public","dates":"d|1231:2010-2060"}]} diff --git a/app/Support/Holidays/data/VG.json b/app/Support/Holidays/data/VG.json new file mode 100644 index 000000000..b2d3880ac --- /dev/null +++ b/app/Support/Holidays/data/VG.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"VG","language":"en_VG","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051|0103:2011,2022,2028,2033,2039,2050,2056"},{"id":"lavity_stoutt_s_birthday","name":"The Anniversary of the Birth of Hamilton Lavity Stoutt","en":"Lavity Stoutt's Birthday","category":"public","dates":"d|0301:2010,2021,2027,2032,2038,2049,2055,2060|0302:2015,2020,2026,2037,2043,2048,2054|0303:2014,2025,2031,2036,2042,2053,2059|0304:2013,2019,2024,2030,2041,2047,2052,2058|0305:2012,2018,2029,2035,2040,2046,2057|0306:2017,2023,2028,2034,2045,2051,2056|0307:2011,2016,2022,2033,2039,2044,2050"},{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_monday","name":"Easter Monday","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"pentecost_monday","name":"Whit Monday","en":"Pentecost Monday","category":"public","dates":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038"},{"id":"sovereign_s_birthday","name":"Sovereign's Birthday","en":"Sovereign's Birthday","category":"public","dates":"d|0607:2019|0608:2013,2029,2035,2040,2046,2057|0609:2012,2018,2028,2034,2045,2051,2056|0610:2017,2022,2033,2039,2044,2050|0611:2011,2016,2021,2027,2032,2038,2049,2055,2060|0612:2010,2020,2026,2037,2043,2048,2054|0613:2015,2025,2031,2036,2042,2053,2059|0614:2014,2024,2030,2041,2047,2052,2058|0616:2023"},{"id":"virgin_islands_day","name":"Virgin Islands Day","en":"Virgin Islands Day","category":"public","dates":"d|0701:2024,2030,2041,2047,2052,2058|0702:2029,2035,2040,2046,2057|0703:2023,2028,2034,2045,2051,2056|0704:2022,2033,2039,2044,2050|0705:2021,2027,2032,2038,2049,2055,2060|0706:2026,2037,2043,2048,2054|0707:2025,2031,2036,2042,2053,2059"},{"id":"emancipation_monday","name":"Emancipation Monday","en":"Emancipation Monday","category":"public","dates":"d|0801:2022,2033,2039,2044,2050|0802:2021,2027,2032,2038,2049,2055,2060|0803:2026,2037,2043,2048,2054|0804:2025,2031,2036,2042,2053,2059|0805:2024,2030,2041,2047,2052,2058|0806:2029,2035,2040,2046,2057|0807:2023,2028,2034,2045,2051,2056"},{"id":"emancipation_tuesday","name":"Emancipation Tuesday","en":"Emancipation Tuesday","category":"public","dates":"d|0802:2022,2033,2039,2044,2050|0803:2021,2027,2032,2038,2049,2055,2060|0804:2026,2037,2043,2048,2054|0805:2025,2031,2036,2042,2053,2059|0806:2024,2030,2041,2047,2052,2058|0807:2029,2035,2040,2046,2057|0808:2023,2028,2034,2045,2051,2056"},{"id":"emancipation_wednesday","name":"Emancipation Wednesday","en":"Emancipation Wednesday","category":"public","dates":"d|0803:2022,2033,2039,2044,2050|0804:2021,2027,2032,2038,2049,2055,2060|0805:2026,2037,2043,2048,2054|0806:2025,2031,2036,2042,2053,2059|0807:2024,2030,2041,2047,2052,2058|0808:2029,2035,2040,2046,2057|0809:2023,2028,2034,2045,2051,2056"},{"id":"heroes_and_foreparents_day","name":"Heroes and Foreparents Day","en":"Heroes and Foreparents Day","category":"public","dates":"d|1015:2029,2035,2040,2046,2057|1016:2023,2028,2034,2045,2051,2056|1017:2022,2033,2039,2044,2050|1018:2021,2027,2032,2038,2049,2055,2060|1019:2026,2037,2043,2048,2054|1020:2025,2031,2036,2042,2053,2059|1021:2024,2030,2041,2047,2052,2058"},{"id":"the_great_march_of_1949_and_restoration_day","name":"The Great March of 1949 and Restoration Day","en":"The Great March of 1949 and Restoration Day","category":"public","dates":"d|1122:2021,2027,2032,2038,2049,2055,2060|1123:2026,2037,2043,2048,2054|1124:2025,2031,2036,2042,2053,2059|1125:2024,2030,2041,2047,2052,2058|1126:2029,2035,2040,2046,2057|1127:2023,2028,2034,2045,2051,2056|1128:2022,2033,2039,2044,2050"},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060|1227:2010-2011,2016,2021-2022,2027,2032-2033,2038-2039,2044,2049-2050,2055,2060"},{"id":"boxing_day","name":"Boxing Day","en":"Boxing Day","category":"public","dates":"d|1226:2010-2060|1228:2010,2015,2020-2021,2026-2027,2032,2037-2038,2043,2048-2049,2054-2055,2060"}]} diff --git a/app/Support/Holidays/data/VI.json b/app/Support/Holidays/data/VI.json new file mode 100644 index 000000000..a45f65971 --- /dev/null +++ b/app/Support/Holidays/data/VI.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"VI","language":"en_US","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051|1231:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"three_kings_day","name":"Three Kings Day","en":"Three Kings Day","category":"public","dates":"d|0106:2010-2060"},{"id":"martin_luther_king_jr_day","name":"Martin Luther King Jr. Day","en":"Martin Luther King Jr. Day","category":"public","dates":"d|0115:2018,2024,2029,2035,2046,2052,2057|0116:2012,2017,2023,2034,2040,2045,2051|0117:2011,2022,2028,2033,2039,2050,2056|0118:2010,2016,2021,2027,2038,2044,2049,2055|0119:2015,2026,2032,2037,2043,2054,2060|0120:2014,2020,2025,2031,2042,2048,2053,2059|0121:2013,2019,2030,2036,2041,2047,2058"},{"id":"presidents_day","name":"Presidents' Day","en":"Presidents' Day","category":"public","dates":"d|0215:2010,2016,2021,2027,2038,2044,2049,2055|0216:2015,2026,2032,2037,2043,2054,2060|0217:2014,2020,2025,2031,2042,2048,2053,2059|0218:2013,2019,2030,2036,2041,2047,2058|0219:2018,2024,2029,2035,2046,2052,2057|0220:2012,2017,2023,2034,2040,2045,2051|0221:2011,2022,2028,2033,2039,2050,2056"},{"id":"holy_thursday","name":"Holy Thursday","en":"Holy Thursday","category":"public","dates":"d|0322:2035,2046|0324:2016|0325:2027,2032|0326:2043,2054|0327:2059|0328:2013,2024|0329:2018,2029,2040|0330:2051,2056|0401:2010,2021|0402:2015,2026,2037,2048|0403:2042,2053|0405:2012|0406:2023,2034,2045|0407:2039,2050|0409:2020|0410:2031,2036|0411:2047,2058|0413:2017,2028|0414:2022,2033,2044|0415:2049,2055,2060|0417:2014,2025|0418:2019,2030,2041,2052|0419:2057|0421:2011|0422:2038"},{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_monday","name":"Easter Monday","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"transfer_day","name":"Transfer Day","en":"Transfer Day","category":"public","dates":"d|0331:2010-2060"},{"id":"memorial_day","name":"Memorial Day","en":"Memorial Day","category":"public","dates":"d|0525:2015,2020,2026,2037,2043,2048,2054|0526:2014,2025,2031,2036,2042,2053,2059|0527:2013,2019,2024,2030,2041,2047,2052,2058|0528:2012,2018,2029,2035,2040,2046,2057|0529:2017,2023,2028,2034,2045,2051,2056|0530:2011,2016,2022,2033,2039,2044,2050|0531:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"juneteenth_national_independence_day","name":"Juneteenth National Independence Day","en":"Juneteenth National Independence Day","category":"public","dates":"d|0618:2021,2027,2032,2038,2049,2055,2060|0619:2021-2060|0620:2022,2033,2039,2044,2050"},{"id":"emancipation_day","name":"Emancipation Day","en":"Emancipation Day","category":"public","dates":"d|0703:2010-2060"},{"id":"independence_day","name":"Independence Day","en":"Independence Day","category":"public","dates":"d|0703:2015,2020,2026,2037,2043,2048,2054|0704:2010-2060|0705:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"labor_day","name":"Labor Day","en":"Labor Day","category":"public","dates":"d|0901:2014,2025,2031,2036,2042,2053,2059|0902:2013,2019,2024,2030,2041,2047,2052,2058|0903:2012,2018,2029,2035,2040,2046,2057|0904:2017,2023,2028,2034,2045,2051,2056|0905:2011,2016,2022,2033,2039,2044,2050|0906:2010,2021,2027,2032,2038,2049,2055,2060|0907:2015,2020,2026,2037,2043,2048,2054"},{"id":"columbus_day_and_puerto_rico_friendship_day","name":"Columbus Day and Puerto Rico Friendship Day","en":"Columbus Day and Puerto Rico Friendship Day","category":"public","dates":"d|1008:2012,2018,2029,2035,2040,2046,2057|1009:2017,2023,2028,2034,2045,2051,2056|1010:2011,2016,2022,2033,2039,2044,2050|1011:2010,2021,2027,2032,2038,2049,2055,2060|1012:2015,2020,2026,2037,2043,2048,2054|1013:2014,2025,2031,2036,2042,2053,2059|1014:2013,2019,2024,2030,2041,2047,2052,2058"},{"id":"liberty_day","name":"Liberty Day","en":"Liberty Day","category":"public","dates":"d|1101:2010-2060"},{"id":"veterans_day","name":"Veterans Day","en":"Veterans Day","category":"public","dates":"d|1110:2017,2023,2028,2034,2045,2051,2056|1111:2010-2060|1112:2012,2018,2029,2035,2040,2046,2057"},{"id":"thanksgiving_day","name":"Thanksgiving Day","en":"Thanksgiving Day","category":"public","dates":"d|1122:2012,2018,2029,2035,2040,2046,2057|1123:2017,2023,2028,2034,2045,2051,2056|1124:2011,2016,2022,2033,2039,2044,2050|1125:2010,2021,2027,2032,2038,2049,2055,2060|1126:2015,2020,2026,2037,2043,2048,2054|1127:2014,2025,2031,2036,2042,2053,2059|1128:2013,2019,2024,2030,2041,2047,2052,2058"},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1224:2010,2021,2027,2032,2038,2049,2055,2060|1225:2010-2060|1226:2011,2016,2022,2033,2039,2044,2050"},{"id":"christmas_second_day","name":"Christmas Second Day","en":"Christmas Second Day","category":"public","dates":"d|1226:2010-2060"},{"id":"valentine_s_day_unofficial","name":"Valentine's Day","en":"Valentine's Day","category":"unofficial","dates":"d|0214:2010-2060"},{"id":"saint_patrick_s_day_unofficial","name":"Saint Patrick's Day","en":"Saint Patrick's Day","category":"unofficial","dates":"d|0317:2010-2060"},{"id":"good_friday_unofficial","name":"Good Friday","en":"Good Friday","category":"unofficial","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_sunday_unofficial","name":"Easter Sunday","en":"Easter Sunday","category":"unofficial","dates":"d|0325:2035,2046|0327:2016|0328:2027,2032|0329:2043,2054|0330:2059|0331:2013,2024|0401:2018,2029,2040|0402:2051,2056|0404:2010,2021|0405:2015,2026,2037,2048|0406:2042,2053|0408:2012|0409:2023,2034,2045|0410:2039,2050|0412:2020|0413:2031,2036|0414:2047,2058|0416:2017,2028|0417:2022,2033,2044|0418:2049,2055,2060|0420:2014,2025|0421:2019,2030,2041,2052|0422:2057|0424:2011|0425:2038"},{"id":"mother_s_day_unofficial","name":"Mother's Day","en":"Mother's Day","category":"unofficial","dates":"d|0508:2011,2016,2022,2033,2039,2044,2050|0509:2010,2021,2027,2032,2038,2049,2055,2060|0510:2015,2020,2026,2037,2043,2048,2054|0511:2014,2025,2031,2036,2042,2053,2059|0512:2013,2019,2024,2030,2041,2047,2052,2058|0513:2012,2018,2029,2035,2040,2046,2057|0514:2017,2023,2028,2034,2045,2051,2056"},{"id":"father_s_day_unofficial","name":"Father's Day","en":"Father's Day","category":"unofficial","dates":"d|0615:2014,2025,2031,2036,2042,2053,2059|0616:2013,2019,2024,2030,2041,2047,2052,2058|0617:2012,2018,2029,2035,2040,2046,2057|0618:2017,2023,2028,2034,2045,2051,2056|0619:2011,2016,2022,2033,2039,2044,2050|0620:2010,2021,2027,2032,2038,2049,2055,2060|0621:2015,2020,2026,2037,2043,2048,2054"},{"id":"halloween_unofficial","name":"Halloween","en":"Halloween","category":"unofficial","dates":"d|1031:2010-2060"},{"id":"christmas_eve_unofficial","name":"Christmas Eve","en":"Christmas Eve","category":"unofficial","dates":"d|1224:2010-2060"},{"id":"new_year_s_eve_unofficial","name":"New Year's Eve","en":"New Year's Eve","category":"unofficial","dates":"d|1231:2010-2060"}]} diff --git a/app/Support/Holidays/data/VN.json b/app/Support/Holidays/data/VN.json new file mode 100644 index 000000000..bd0a58d78 --- /dev/null +++ b/app/Support/Holidays/data/VN.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"VN","language":"vi","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Tết Dương lịch","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051|0103:2011,2022,2028,2033,2039,2050,2056"},{"id":"day_off","name":"Ngày nghỉ","en":"Day off","category":"public","dates":"d|0102:2015|0127:2012|0216:2015|0219:2010|0429:2013,2015,2019,2024|0502:2014,2025|0831:2026|0901:2014|1231:2018"},{"id":"29_of_lunar_new_year","name":"29 Tết","en":"29 of Lunar New Year","category":"public","dates":"d|0120:2023,2042|0121:2031,2050|0122:2058|0123:2020|0124:2028,2047|0126:2017,2055|0127:2025|0128:2044|0129:2014|0130:2041,2052|0131:2030|0202:2038,2057|0204:2027|0206:2035,2054|0208:2024|0209:2032,2051|0210:2021,2040,2059|0212:2048|0213:2037|0214:2018|0215:2045|0217:2015,2034"},{"id":"lunar_new_year_s_eve","name":"Giao thừa Tết Nguyên Đán","en":"Lunar New Year's Eve","category":"public","dates":"d|0121:2023,2042|0122:2012,2031,2050|0123:2039,2058|0124:2020|0125:2028,2047|0127:2017,2036,2055|0128:2025|0129:2044|0130:2014,2033|0131:2022,2041,2052|0201:2030,2049,2060|0202:2011|0203:2038,2057|0204:2019|0205:2027,2046|0207:2016,2035,2054|0209:2013,2024,2043|0210:2032,2051|0211:2021,2040,2059|0212:2029|0213:2010,2048|0214:2037,2056|0215:2018|0216:2026,2045|0217:2053|0218:2015,2034"},{"id":"lunar_new_year","name":"Tết Nguyên Đán","en":"Lunar New Year","category":"public","dates":"d|0122:2023,2042|0123:2012,2031,2050|0124:2039,2058|0125:2020|0126:2028,2047|0128:2017,2036,2055|0129:2025|0130:2044|0131:2014,2033|0201:2022,2041,2052|0202:2030,2049,2060|0203:2011|0204:2038,2057|0205:2019|0206:2027,2046|0208:2016,2035,2054|0210:2013,2024,2043|0211:2032,2051|0212:2021,2040,2059|0213:2029|0214:2010,2048|0215:2037,2056|0216:2018|0217:2026,2045|0218:2053|0219:2015,2034"},{"id":"second_day_of_lunar_new_year","name":"Mùng hai Tết Nguyên Đán","en":"Second Day of Lunar New Year","category":"public","dates":"d|0123:2023,2042|0124:2012,2031,2050|0125:2039,2058|0126:2020|0127:2028,2047|0129:2017,2036,2055|0130:2025|0131:2044|0201:2014,2033|0202:2022,2041,2052|0203:2030,2049,2060|0204:2011|0205:2038,2057|0206:2019|0207:2027,2046|0209:2016,2035,2054|0211:2013,2024,2043|0212:2032,2051|0213:2021,2040,2059|0214:2029|0215:2010,2048|0216:2037,2056|0217:2018|0218:2026,2045|0219:2053|0220:2015,2034"},{"id":"third_day_of_lunar_new_year","name":"Mùng ba Tết Nguyên Đán","en":"Third Day of Lunar New Year","category":"public","dates":"d|0124:2023,2042|0125:2012,2031,2050|0126:2039,2058|0127:2020|0128:2028,2047|0130:2017,2036,2055|0131:2025|0201:2044|0202:2014,2033|0203:2022,2041,2052|0204:2030,2049,2060|0205:2011|0206:2038,2057|0207:2019|0208:2027,2046|0210:2016,2035,2054|0212:2013,2024,2043|0213:2032,2051|0214:2021,2040,2059|0215:2029|0216:2010,2048|0217:2037,2056|0218:2018|0219:2026,2045|0220:2053|0221:2015,2034"},{"id":"fourth_day_of_lunar_new_year","name":"Mùng bốn Tết Nguyên Đán","en":"Fourth Day of Lunar New Year","category":"public","dates":"d|0125:2023,2042|0126:2012,2031,2050|0127:2039,2058|0128:2020|0129:2028,2047|0131:2017,2036,2055|0201:2025|0202:2044|0203:2014,2033|0204:2022,2041,2052|0205:2030,2049,2060|0207:2038,2057|0208:2019|0209:2027,2046|0211:2016,2035,2054|0213:2013,2024,2043|0214:2032,2051|0215:2021,2040,2059|0216:2029|0217:2010,2048|0218:2037,2056|0219:2018|0220:2026,2045|0221:2053|0222:2015,2034"},{"id":"fifth_day_of_lunar_new_year","name":"Mùng năm Tết Nguyên Đán","en":"Fifth Day of Lunar New Year","category":"public","dates":"d|0126:2023|0127:2031,2050|0128:2039,2058|0129:2020|0130:2047|0201:2017,2036,2055|0203:2044|0204:2014,2033|0205:2041,2052|0206:2030,2060|0207:2011|0208:2038,2057|0210:2027|0212:2016,2035,2054|0214:2013,2024|0215:2051|0216:2021,2040|0218:2010,2048|0219:2037|0220:2018|0221:2045|0223:2015,2034"},{"id":"hung_kings_commemoration_day","name":"Ngày Giỗ Tổ Hùng Vương","en":"Hung Kings' Commemoration Day","category":"public","dates":"d|0331:2012|0401:2031,2050|0402:2012,2020,2058|0403:2039|0404:2028,2039,2047|0406:2017,2036,2055|0407:2025,2036,2044|0409:2014,2033,2052|0410:2022,2041,2060|0411:2022,2033,2049|0412:2011,2030,2049,2060|0413:2038,2057|0414:2019|0415:2019,2046|0416:2016,2027,2046|0417:2035,2054|0418:2016,2024|0419:2013,2032,2043|0420:2040,2043,2051|0421:2021,2059|0422:2048|0423:2010,2029|0424:2037,2056|0425:2018|0426:2026,2045|0427:2026|0428:2015,2034,2053|0429:2023,2042|0502:2023"},{"id":"liberation_day_reunification_day","name":"Ngày Chiến thắng","en":"Liberation Day/Reunification Day","category":"public","dates":"d|0430:2010-2060|0502:2011,2016-2017,2022,2028,2033-2034,2039,2044-2045,2050-2051,2056|0503:2023"},{"id":"international_labor_day","name":"Ngày Quốc tế Lao động","en":"International Labor Day","category":"public","dates":"d|0501:2010-2060|0503:2010-2011,2016,2021-2022,2027,2032-2033,2038-2039,2044,2049-2050,2055,2060"},{"id":"national_day","name":"Quốc khánh","en":"National Day","category":"public","dates":"d|0901:2022-2023,2025-2026,2028,2031,2033-2034,2036-2037,2039,2042-2045,2048,2050-2051,2053-2054,2056,2059|0902:2010-2060|0903:2012,2018,2021,2024,2027,2029-2030,2032,2035,2038,2040-2041,2046-2047,2049,2052,2055,2057-2058,2060|0904:2017,2023,2028-2029,2034-2035,2040,2045-2046,2051,2056-2057"},{"id":"vietnam_cultural_day","name":"Ngày Văn hóa Việt Nam","en":"Vietnam Cultural Day","category":"public","dates":"d|1124:2026-2060|1125:2030,2041,2047,2052,2058|1126:2029,2035,2040,2046,2057"}]} diff --git a/app/Support/Holidays/data/VU.json b/app/Support/Holidays/data/VU.json new file mode 100644 index 000000000..868907038 --- /dev/null +++ b/app/Support/Holidays/data/VU.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"VU","language":null,"years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051"},{"id":"father_lini_day","name":"Father Lini Day","en":"Father Lini Day","category":"public","dates":"d|0221:2010-2060|0222:2010,2016,2021,2027,2038,2044,2049,2055"},{"id":"custom_chief_s_day","name":"Custom Chief's Day","en":"Custom Chief's Day","category":"public","dates":"d|0305:2010-2060|0306:2017,2023,2028,2034,2045,2051,2056"},{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_monday","name":"Easter Monday","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"labour_day","name":"Labour Day","en":"Labour Day","category":"public","dates":"d|0501:2010-2060|0502:2011,2016,2022,2033,2039,2044,2050"},{"id":"ascension_day","name":"Ascension Day","en":"Ascension Day","category":"public","dates":"d|0503:2035,2046|0505:2016|0506:2027,2032|0507:2043,2054|0508:2059|0509:2013,2024|0510:2018,2029,2040|0511:2051,2056|0513:2010,2021|0514:2015,2026,2037,2048|0515:2042,2053|0517:2012|0518:2023,2034,2045|0519:2039,2050|0521:2020|0522:2031,2036|0523:2047,2058|0525:2017,2028|0526:2022,2033,2044|0527:2049,2055,2060|0529:2014,2025|0530:2019,2030,2041,2052|0531:2057|0602:2011|0603:2038"},{"id":"children_s_day","name":"Children's Day","en":"Children's Day","category":"public","dates":"d|0724:2010-2060|0725:2011,2016,2022,2033,2039,2044,2050"},{"id":"independence_day","name":"Independence Day","en":"Independence Day","category":"public","dates":"d|0730:2010-2060|0731:2017,2023,2028,2034,2045,2051,2056"},{"id":"assumption_day","name":"Assumption Day","en":"Assumption Day","category":"public","dates":"d|0815:2010-2060|0816:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"constitution_day","name":"Constitution Day","en":"Constitution Day","category":"public","dates":"d|1005:2010-2060|1006:2014,2025,2031,2036,2042,2053,2059"},{"id":"unity_day","name":"Unity Day","en":"Unity Day","category":"public","dates":"d|1129:2010-2060|1130:2015,2020,2026,2037,2043,2048,2054"},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"},{"id":"family_day","name":"Family Day","en":"Family Day","category":"public","dates":"d|1226:2010-2060|1227:2010-2011,2016,2021-2022,2027,2032-2033,2038-2039,2044,2049-2050,2055,2060"}]} diff --git a/app/Support/Holidays/data/WF.json b/app/Support/Holidays/data/WF.json new file mode 100644 index 000000000..6d0beea6b --- /dev/null +++ b/app/Support/Holidays/data/WF.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"WF","language":"fr","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Jour de l'an","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"easter_monday","name":"Lundi de Pâques","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"feast_of_saint_peter_chanel","name":"Saint Pierre Chanel","en":"Feast of Saint Peter Chanel","category":"public","dates":"d|0428:2010-2060"},{"id":"labor_day","name":"Fête du Travail","en":"Labor Day","category":"public","dates":"d|0501:2010-2060"},{"id":"ascension_day","name":"Ascension","en":"Ascension Day","category":"public","dates":"d|0503:2035,2046|0505:2016|0506:2027,2032|0507:2043,2054|0508:2059|0509:2013,2024|0510:2018,2029,2040|0511:2051,2056|0513:2010,2021|0514:2015,2026,2037,2048|0515:2042,2053|0517:2012|0518:2023,2034,2045|0519:2039,2050|0521:2020|0522:2031,2036|0523:2047,2058|0525:2017,2028|0526:2022,2033,2044|0527:2049,2055,2060|0529:2014,2025|0530:2019,2030,2041,2052|0531:2057|0602:2011|0603:2038"},{"id":"victory_day","name":"Fête de la Victoire","en":"Victory Day","category":"public","dates":"d|0508:2010-2060"},{"id":"pentecost_monday","name":"Lundi de Pentecôte","en":"Pentecost Monday","category":"public","dates":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038"},{"id":"saints_peter_and_paul_day","name":"Saints Pierre et Paul","en":"Saints Peter and Paul Day","category":"public","dates":"d|0629:2010-2060"},{"id":"national_day","name":"Fête nationale","en":"National Day","category":"public","dates":"d|0714:2010-2060"},{"id":"territory_day","name":"Fête du Territoire","en":"Territory Day","category":"public","dates":"d|0729:2010-2060"},{"id":"assumption_day","name":"Assomption","en":"Assumption Day","category":"public","dates":"d|0815:2010-2060"},{"id":"all_saints_day","name":"Toussaint","en":"All Saints' Day","category":"public","dates":"d|1101:2010-2060"},{"id":"armistice_day","name":"Armistice","en":"Armistice Day","category":"public","dates":"d|1111:2010-2060"},{"id":"christmas_day","name":"Noël","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"}]} diff --git a/app/Support/Holidays/data/WS.json b/app/Support/Holidays/data/WS.json new file mode 100644 index 000000000..9d2a7d572 --- /dev/null +++ b/app/Support/Holidays/data/WS.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"WS","language":null,"years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"the_day_after_new_year_s_day","name":"The Day After New Year's Day","en":"The Day After New Year's Day","category":"public","dates":"d|0102:2010-2060"},{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"day_after_good_friday","name":"Day After Good Friday","en":"Day After Good Friday","category":"public","dates":"d|0324:2035,2046|0326:2016|0327:2027,2032|0328:2043,2054|0329:2059|0330:2013,2024|0331:2018,2029,2040|0401:2051,2056|0403:2010,2021|0404:2015,2026,2037,2048|0405:2042,2053|0407:2012|0408:2023,2034,2045|0409:2039,2050|0411:2020|0412:2031,2036|0413:2047,2058|0415:2017,2028|0416:2022,2033,2044|0417:2049,2055,2060|0419:2014,2025|0420:2019,2030,2041,2052|0421:2057|0423:2011|0424:2038"},{"id":"easter_monday","name":"Easter Monday","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"mother_s_day","name":"Mother's Day","en":"Mother's Day","category":"public","dates":"d|0509:2011,2016,2022,2033,2039,2044,2050|0510:2010,2021,2027,2032,2038,2049,2055,2060|0511:2015,2020,2026,2037,2043,2048,2054|0512:2014,2025,2031,2036,2042,2053,2059|0513:2013,2019,2024,2030,2041,2047,2052,2058|0514:2012,2018,2029,2035,2040,2046,2057|0515:2017,2023,2028,2034,2045,2051,2056"},{"id":"independence_day","name":"Independence Day","en":"Independence Day","category":"public","dates":"d|0601:2010-2060"},{"id":"father_s_day","name":"Father's Day","en":"Father's Day","category":"public","dates":"d|0809:2010,2021,2027,2032,2038,2049,2055,2060|0810:2015,2020,2026,2037,2043,2048,2054|0811:2014,2025,2031,2036,2042,2053,2059|0812:2013,2019,2024,2030,2041,2047,2052,2058|0813:2012,2018,2029,2035,2040,2046,2057|0814:2017,2023,2028,2034,2045,2051,2056|0815:2011,2016,2022,2033,2039,2044,2050"},{"id":"white_sunday_lotu_a_tamaiti","name":"White Sunday (Lotu a Tamaiti)","en":"White Sunday (Lotu a Tamaiti)","category":"public","dates":"d|1009:2017,2023,2028,2034,2045,2051,2056|1010:2011,2016,2022,2033,2039,2044,2050|1011:2010,2021,2027,2032,2038,2049,2055,2060|1012:2015,2020,2026,2037,2043,2048,2054|1013:2014,2025,2031,2036,2042,2053,2059|1014:2013,2019,2024,2030,2041,2047,2052,2058|1015:2012,2018,2029,2035,2040,2046,2057"},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"},{"id":"boxing_day","name":"Boxing Day","en":"Boxing Day","category":"public","dates":"d|1226:2010-2060"}]} diff --git a/app/Support/Holidays/data/XK.json b/app/Support/Holidays/data/XK.json new file mode 100644 index 000000000..906949c3a --- /dev/null +++ b/app/Support/Holidays/data/XK.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"XK","language":"sq","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Viti i Ri","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2010-2060|0103:2011-2012,2017,2022-2023,2028,2033-2034,2039-2040,2045,2050-2051,2056|0104:2010-2011,2016,2021-2022,2027-2028,2033,2038-2039,2044,2049-2050,2055-2056"},{"id":"eid_al_fitr","name":"Bajrami i Madh, dita e parë","en":"Eid al-Fitr","category":"public","dates":"y|2010:0910|2011:0830|2012:0819-0820|2013:0808|2014:0728|2015:0717|2016:0706|2017:0625-0626|2018:0615|2019:0604|2020:0524-0525|2021:0513|2022:0502|2023:0421|2024:0410|2025:0330-0331|2026:0320|2027:0309|2028:0226,0228|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,0104,1223|2034:1212|2035:1201,1203|2036:1119|2037:1108-1109|2038:1029|2039:1019|2040:1007-1008|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712-0713|2049:0701|2050:0620|2051:0610,0612|2052:0529|2053:0519|2054:0509,0511|2055:0428|2056:0416-0417|2057:0405|2058:0325|2059:0315,0317|2060:0304","estimated":"y|2010:0910|2011:0830|2012:0819|2013:0808|2014:0728|2015:0717|2016:0706|2017:0625|2018:0615|2019:0604|2020:0524|2021:0513|2022:0502|2023:0421|2024:0410|2025:0330|2026:0320|2027:0309|2028:0226|2029:0214|2030:0204|2031:0124|2032:0114|2033:0102,1223|2034:1212|2035:1201|2036:1119|2037:1108|2038:1029|2039:1019|2040:1007|2041:0926|2042:0915|2043:0904|2044:0824|2045:0814|2046:0803|2047:0724|2048:0712|2049:0701|2050:0620|2051:0610|2052:0529|2053:0519|2054:0509|2055:0428|2056:0416|2057:0405|2058:0325|2059:0315|2060:0304"},{"id":"eid_al_adha","name":"Bajrami i Vogël, dita e parë","en":"Eid al-Adha","category":"public","dates":"y|2010:1116|2011:1106-1107|2012:1026|2013:1015|2014:1004,1006|2015:0923|2016:0911-0912|2017:0901|2018:0821|2019:0811-0812|2020:0731|2021:0720|2022:0709,0711|2023:0628|2024:0616-0617|2025:0606|2026:0527|2027:0516-0517|2028:0505|2029:0424|2030:0413,0415|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218,0220|2036:0207|2037:0126|2038:0116,0118|2039:0105,1226|2040:1214|2041:1204|2042:1123-1124|2043:1112|2044:1031|2045:1021,1023|2046:1010|2047:0930|2048:0919,0921|2049:0908|2050:0828-0829|2051:0817|2052:0805|2053:0726,0728|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601,0603|2059:0522|2060:0510","estimated":"y|2010:1116|2011:1106|2012:1026|2013:1015|2014:1004|2015:0923|2016:0911|2017:0901|2018:0821|2019:0811|2020:0731|2021:0720|2022:0709|2023:0628|2024:0616|2025:0606|2026:0527|2027:0516|2028:0505|2029:0424|2030:0413|2031:0402|2032:0322|2033:0311|2034:0301|2035:0218|2036:0207|2037:0126|2038:0116|2039:0105,1226|2040:1214|2041:1204|2042:1123|2043:1112|2044:1031|2045:1021|2046:1010|2047:0930|2048:0919|2049:0908|2050:0828|2051:0817|2052:0805|2053:0726|2054:0715|2055:0705|2056:0623|2057:0612|2058:0601|2059:0522|2060:0510"},{"id":"orthodox_christmas_day","name":"Krishtlindjet Ortodokse","en":"Orthodox Christmas Day","category":"public","dates":"d|0107:2010-2060|0108:2018,2024,2029,2035,2046,2052,2057|0109:2012,2017,2023,2034,2040,2045,2051"},{"id":"independence_day","name":"Dita e Pavarësisë së Republikës së Kosovës","en":"Independence Day","category":"public","dates":"d|0217:2010-2060|0218:2013,2019,2030,2036,2041,2047,2058|0219:2018,2024,2029,2035,2046,2052,2057"},{"id":"catholic_easter","name":"Pashkët Katolike","en":"Catholic Easter","category":"public","dates":"d|0325:2035,2046|0326:2035,2046|0327:2016|0328:2016,2027,2032|0329:2027,2032,2043,2054|0330:2043,2054,2059|0331:2013,2024,2059|0401:2013,2018,2024,2029,2040|0402:2018,2029,2040,2051,2056|0403:2051,2056|0404:2010,2021|0405:2010,2015,2021,2026,2037,2048|0406:2015,2026,2037,2042,2048,2053|0407:2042,2053|0408:2012|0409:2023,2034,2045|0410:2012,2023,2034,2039,2045,2050|0412:2020,2039,2050|0413:2020,2031,2036|0414:2031,2036,2047,2058|0415:2047,2058|0416:2017,2028|0417:2017,2022,2028,2033,2044|0418:2022,2033,2044,2049,2055,2060|0419:2049,2055,2060|0420:2014,2025|0421:2014,2019,2025,2030,2041,2052|0422:2019,2030,2041,2052,2057|0423:2057|0424:2011|0425:2011,2038|0426:2038"},{"id":"orthodox_easter","name":"Pashkët Ortodokse","en":"Orthodox Easter","category":"public","dates":"d|0404:2010|0405:2010,2037,2048|0406:2037,2048|0408:2018,2029|0409:2034,2045,2056|0410:2018,2029,2034,2045,2056|0412:2015,2026|0413:2015,2026,2031,2042,2053|0414:2031,2042,2053,2058|0415:2012,2058|0416:2012,2017,2023,2028|0417:2017,2023,2028,2039,2050|0418:2039,2050,2055|0419:2020,2055|0420:2014,2020,2025,2036|0421:2014,2025,2036,2041,2047,2052|0422:2041,2047,2052|0424:2011,2022,2033,2044|0425:2011,2022,2033,2038,2044,2049,2060|0426:2038,2049,2060|0428:2019,2030|0429:2019,2030,2035,2046,2057|0430:2035,2046,2057|0501:2016|0502:2016,2021,2027,2032|0503:2043,2054|0504:2021,2027,2032,2043,2054,2059|0505:2013,2024,2059|0506:2013,2024,2040|0507:2040,2051|0508:2051"},{"id":"constitution_day","name":"Dita e Kushtetutës së Republikës së Kosovës","en":"Constitution Day","category":"public","dates":"d|0409:2010-2060|0410:2017,2023,2028,2034,2045,2051,2056|0411:2011,2016,2022,2033,2039,2044,2050"},{"id":"international_labor_day","name":"Dita Ndërkombëtare e Punës","en":"International Labor Day","category":"public","dates":"d|0501:2010-2060|0502:2011,2016,2033,2039,2044,2050|0503:2010,2021-2022,2027,2032,2038,2049,2055,2060"},{"id":"europe_day","name":"Dita e Evropës","en":"Europe Day","category":"public","dates":"d|0509:2010-2060|0510:2010,2021,2027,2032,2038,2049,2055|0511:2015,2020,2026,2037,2043,2048,2054,2060"},{"id":"catholic_christmas_day","name":"Krishtlindjet Katolike","en":"Catholic Christmas Day","category":"public","dates":"d|1225:2010-2060|1226:2011,2016,2022,2033,2044,2050|1227:2010,2021,2027,2032,2038-2039,2049,2055,2060"}]} diff --git a/app/Support/Holidays/data/YE.json b/app/Support/Holidays/data/YE.json new file mode 100644 index 000000000..045acaac4 --- /dev/null +++ b/app/Support/Holidays/data/YE.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"YE","language":"ar","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"eid_al_fitr","name":"عيد الفطر","en":"Eid al-Fitr","category":"public","dates":"y|2010:0908-0913|2011:0829-0901|2012:0817-0822|2013:0806-0811|2014:0726-0730|2015:0716-0720|2016:0704-0708,0710|2017:0624-0627|2018:0613-0618|2019:0603-0606|2020:0522-0527|2021:0511-0516|2022:0430-0504|2023:0420-0424|2024:0408-0412,0414|2025:0329-0401|2026:0318-0323|2027:0308-0311|2028:0225-0229|2029:0213-0216,0218|2030:0202-0206|2031:0123-0127|2032:0112-0116,0118|2033:0101-0104,1221-1226|2034:1210-1214|2035:1129-1204|2036:1117-1121,1123|2037:1107-1110|2038:1028-1101|2039:1017-1021,1023|2040:1005-1010|2041:0925-0929|2042:0914-0917|2043:0903-0907|2044:0823-0826,0828|2045:0812-0816|2046:0802-0806|2047:0722-0726,0728|2048:0710-0715|2049:0630-0704|2050:0619-0622|2051:0608-0613|2052:0528-0531,0602|2053:0518-0521|2054:0507-0512|2055:0426-0430,0502|2056:0414-0419|2057:0403-0408|2058:0324-0327|2059:0314-0318|2060:0302-0307","estimated":"y|2010:0908-0912|2011:0829-0901|2012:0817-0821|2013:0806-0810|2014:0726-0730|2015:0716-0719|2016:0704-0708|2017:0624-0627|2018:0613-0617|2019:0603-0606|2020:0522|2021:0511|2022:0430|2026:0318-0322|2027:0308-0311|2028:0225-0228|2029:0213-0216|2030:0202-0206|2031:0123-0126|2032:0112-0116|2033:0101-0104,1221-1225|2034:1210-1214|2035:1129-1203|2036:1117-1121|2037:1107-1110|2038:1028-1031|2039:1017-1021|2040:1005-1009|2041:0925-0928|2042:0914-0917|2043:0903-0906|2044:0823-0826|2045:0812-0816|2046:0802-0805|2047:0722-0726|2048:0710-0714|2049:0630-0703|2050:0619-0622|2051:0608-0612|2052:0528-0531|2053:0518-0521|2054:0507-0511|2055:0426-0430|2056:0414-0418|2057:0403-0407|2058:0324-0327|2059:0314-0317|2060:0302-0306"},{"id":"eid_al_adha","name":"عيد الأضحى","en":"Eid al-Adha","category":"public","dates":"y|2010:1115-1120|2011:1105-1109|2012:1025-1030|2013:1014-1018,1020|2014:1003-1008|2015:0922-0927|2016:0910-0914|2017:0831-0905|2018:0820-0824,0826|2019:0810-0814|2020:0730-0804|2021:0719-0723,0725|2022:0708-0713|2023:0627-0702|2024:0615-0619|2025:0605-0610|2026:0526-0531|2027:0515-0519|2028:0504-0509|2029:0423-0427,0429|2030:0412-0417|2031:0401-0406|2032:0321-0325|2033:0310-0315|2034:0228-0305|2035:0217-0221|2036:0206-0211|2037:0125-0129|2038:0115-0120|2039:0104-0109,1225-1229|2040:1213-1218|2041:1203-1208|2042:1122-1126|2043:1111-1116|2044:1030-1103|2045:1020-1025|2046:1009-1013,1015|2047:0929-1003|2048:0918-0923|2049:0907-0912|2050:0827-0831|2051:0816-0821|2052:0804-0808|2053:0725-0730|2054:0714-0719|2055:0704-0708|2056:0622-0627|2057:0611-0615,0617|2058:0531-0605|2059:0521-0526|2060:0509-0513","estimated":"y|2010:1115-1119|2011:1105-1109|2012:1025-1029|2013:1014-1018|2014:1003-1007|2015:0922-0926|2016:0910-0914|2017:0831-0904|2018:0820-0824|2019:0810-0814|2025:0605-0609|2026:0526-0530|2027:0515-0519|2028:0504-0508|2029:0423-0427|2030:0412-0416|2031:0401-0405|2032:0321-0325|2033:0310-0314|2034:0228-0304|2035:0217-0221|2036:0206-0210|2037:0125-0129|2038:0115-0119|2039:0104-0108,1225-1229|2040:1213-1217|2041:1203-1207|2042:1122-1126|2043:1111-1115|2044:1030-1103|2045:1020-1024|2046:1009-1013|2047:0929-1003|2048:0918-0922|2049:0907-0911|2050:0827-0831|2051:0816-0820|2052:0804-0808|2053:0725-0729|2054:0714-0718|2055:0704-0708|2056:0622-0626|2057:0611-0615|2058:0531-0604|2059:0521-0525|2060:0509-0513"},{"id":"islamic_new_year_day","name":"عيد رأس السنة الهجرية","en":"Islamic New Year Day","category":"public","dates":"y|2010:1207|2011:1126|2012:1115|2013:1104|2014:1025|2015:1014|2016:1002|2017:0921|2018:0911|2019:0831|2020:0820|2021:0810|2022:0730|2023:0719|2024:0707|2025:0626|2026:0616|2027:0606|2028:0525|2029:0514|2030:0503,0505|2031:0423|2032:0411|2033:0401,0403|2034:0321|2035:0311|2036:0228|2037:0216|2038:0205,0207|2039:0126|2040:0115|2041:0104,0106,1224|2042:1214|2043:1203|2044:1121|2045:1110,1112|2046:1031|2047:1020|2048:1009,1011|2049:0928|2050:0917|2051:0906|2052:0826|2053:0815,0817|2054:0805|2055:0725|2056:0714,0716|2057:0703|2058:0622|2059:0611|2060:0531","estimated":"y|2010:1207|2011:1126|2012:1115|2013:1104|2014:1025|2015:1014|2016:1002|2017:0921|2018:0911|2019:0831|2025:0626|2026:0616|2027:0606|2028:0525|2029:0514|2030:0503|2031:0423|2032:0411|2033:0401|2034:0321|2035:0311|2036:0228|2037:0216|2038:0205|2039:0126|2040:0115|2041:0104,1224|2042:1214|2043:1203|2044:1121|2045:1110|2046:1031|2047:1020|2048:1009|2049:0928|2050:0917|2051:0906|2052:0826|2053:0815|2054:0805|2055:0725|2056:0714|2057:0703|2058:0622|2059:0611|2060:0531"},{"id":"labor_day","name":"عيد العمال","en":"Labor Day","category":"public","dates":"d|0501:2010-2060|0503:2015,2020,2026,2037,2043,2048,2054"},{"id":"unity_day","name":"اليوم الوطني للجمهورية اليمنية","en":"Unity Day","category":"public","dates":"d|0522:2010-2060|0524:2015,2026,2037,2043,2048,2054|0527:2020"},{"id":"revolution_day","name":"ثورة 26 سبتمبر المجيدة","en":"Revolution Day","category":"public","dates":"d|0926:2010-2060|0928:2014,2025,2031,2036,2042,2053,2059"},{"id":"liberation_day","name":"ثورة 14 أكتوبر المجيدة","en":"Liberation Day","category":"public","dates":"d|1014:2010-2060|1015:2011|1016:2016,2022,2033,2039,2044,2050"},{"id":"evacuation_day","name":"عيد الجلاء","en":"Evacuation Day","category":"public","dates":"d|1130:2010-2060|1201:2012|1202:2018,2029,2040,2046,2057|1204:2035"},{"id":"teacher_s_day_school","name":"عيد المعلم","en":"Teacher's Day","category":"school","dates":"d|0505:2013-2060"}]} diff --git a/app/Support/Holidays/data/YT.json b/app/Support/Holidays/data/YT.json new file mode 100644 index 000000000..ae4e0b738 --- /dev/null +++ b/app/Support/Holidays/data/YT.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"YT","language":"fr","years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"Jour de l'an","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060"},{"id":"easter_monday","name":"Lundi de Pâques","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"abolition_of_slavery","name":"Abolition de l'esclavage","en":"Abolition of Slavery","category":"public","dates":"d|0427:2010-2060"},{"id":"labor_day","name":"Fête du Travail","en":"Labor Day","category":"public","dates":"d|0501:2010-2060"},{"id":"ascension_day","name":"Ascension","en":"Ascension Day","category":"public","dates":"d|0503:2035,2046|0505:2016|0506:2027,2032|0507:2043,2054|0508:2059|0509:2013,2024|0510:2018,2029,2040|0511:2051,2056|0513:2010,2021|0514:2015,2026,2037,2048|0515:2042,2053|0517:2012|0518:2023,2034,2045|0519:2039,2050|0521:2020|0522:2031,2036|0523:2047,2058|0525:2017,2028|0526:2022,2033,2044|0527:2049,2055,2060|0529:2014,2025|0530:2019,2030,2041,2052|0531:2057|0602:2011|0603:2038"},{"id":"victory_day","name":"Fête de la Victoire","en":"Victory Day","category":"public","dates":"d|0508:2010-2060"},{"id":"pentecost_monday","name":"Lundi de Pentecôte","en":"Pentecost Monday","category":"public","dates":"d|0514:2035,2046|0516:2016|0517:2027,2032|0518:2043,2054|0519:2059|0520:2013,2024|0521:2018,2029,2040|0522:2051,2056|0524:2010,2021|0525:2015,2026,2037,2048|0526:2042,2053|0528:2012|0529:2023,2034,2045|0530:2039,2050|0601:2020|0602:2031,2036|0603:2047,2058|0605:2017,2028|0606:2022,2033,2044|0607:2049,2055,2060|0609:2014,2025|0610:2019,2030,2041,2052|0611:2057|0613:2011|0614:2038"},{"id":"national_day","name":"Fête nationale","en":"National Day","category":"public","dates":"d|0714:2010-2060"},{"id":"assumption_day","name":"Assomption","en":"Assumption Day","category":"public","dates":"d|0815:2010-2060"},{"id":"all_saints_day","name":"Toussaint","en":"All Saints' Day","category":"public","dates":"d|1101:2010-2060"},{"id":"armistice_day","name":"Armistice","en":"Armistice Day","category":"public","dates":"d|1111:2010-2060"},{"id":"christmas_day","name":"Noël","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"}]} diff --git a/app/Support/Holidays/data/ZA.json b/app/Support/Holidays/data/ZA.json new file mode 100644 index 000000000..0233093a6 --- /dev/null +++ b/app/Support/Holidays/data/ZA.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"ZA","language":null,"years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051"},{"id":"human_rights_day","name":"Human Rights Day","en":"Human Rights Day","category":"public","dates":"d|0321:2010-2060|0322:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"family_day","name":"Family Day","en":"Family Day","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"freedom_day","name":"Freedom Day","en":"Freedom Day","category":"public","dates":"d|0427:2010-2060|0428:2014,2025,2031,2036,2042,2053,2059"},{"id":"workers_day","name":"Workers' Day","en":"Workers' Day","category":"public","dates":"d|0501:2010-2060|0502:2011,2016,2022,2033,2039,2044,2050"},{"id":"local_government_elections","name":"Local Government Elections","en":"Local Government Elections","category":"public","dates":"d|0518:2011|0803:2016|1104:2026"},{"id":"youth_day","name":"Youth Day","en":"Youth Day","category":"public","dates":"d|0616:2010-2060|0617:2013,2019,2024,2030,2041,2047,2052,2058"},{"id":"national_women_s_day","name":"National Women's Day","en":"National Women's Day","category":"public","dates":"d|0809:2010-2060|0810:2015,2020,2026,2037,2043,2048,2054"},{"id":"heritage_day","name":"Heritage Day","en":"Heritage Day","category":"public","dates":"d|0924:2010-2060|0925:2017,2023,2028,2034,2045,2051,2056"},{"id":"day_of_reconciliation","name":"Day of Reconciliation","en":"Day of Reconciliation","category":"public","dates":"d|1216:2010-2060|1217:2012,2018,2029,2035,2040,2046,2057"},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060"},{"id":"day_of_goodwill","name":"Day of Goodwill","en":"Day of Goodwill","category":"public","dates":"d|1226:2010-2060|1227:2010,2021,2027,2032,2038,2049,2055,2060"}]} diff --git a/app/Support/Holidays/data/ZM.json b/app/Support/Holidays/data/ZM.json new file mode 100644 index 000000000..5eb86fd2e --- /dev/null +++ b/app/Support/Holidays/data/ZM.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"ZM","language":null,"years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051"},{"id":"international_women_s_day","name":"International Women's Day","en":"International Women's Day","category":"public","dates":"d|0308:2010-2060|0309:2015,2020,2026,2037,2043,2048,2054"},{"id":"youth_day","name":"Youth Day","en":"Youth Day","category":"public","dates":"d|0312:2010-2060|0313:2017,2023,2028,2034,2045,2051,2056"},{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"holy_saturday","name":"Holy Saturday","en":"Holy Saturday","category":"public","dates":"d|0324:2035,2046|0326:2016|0327:2027,2032|0328:2043,2054|0329:2059|0330:2013,2024|0331:2018,2029,2040|0401:2051,2056|0403:2010,2021|0404:2015,2026,2037,2048|0405:2042,2053|0407:2012|0408:2023,2034,2045|0409:2039,2050|0411:2020|0412:2031,2036|0413:2047,2058|0415:2017,2028|0416:2022,2033,2044|0417:2049,2055,2060|0419:2014,2025|0420:2019,2030,2041,2052|0421:2057|0423:2011|0424:2038"},{"id":"easter_sunday","name":"Easter Sunday","en":"Easter Sunday","category":"public","dates":"d|0325:2035,2046|0327:2016|0328:2027,2032|0329:2043,2054|0330:2059|0331:2013,2024|0401:2018,2029,2040|0402:2051,2056|0404:2010,2021|0405:2015,2026,2037,2048|0406:2042,2053|0408:2012|0409:2023,2034,2045|0410:2039,2050|0412:2020|0413:2031,2036|0414:2047,2058|0416:2017,2028|0417:2022,2033,2044|0418:2049,2055,2060|0420:2014,2025|0421:2019,2030,2041,2052|0422:2057|0424:2011|0425:2038"},{"id":"easter_monday","name":"Easter Monday","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"kenneth_kaunda_day","name":"Kenneth Kaunda Day","en":"Kenneth Kaunda Day","category":"public","dates":"d|0428:2022-2060|0429:2024,2030,2041,2047,2052,2058"},{"id":"labour_day","name":"Labour Day","en":"Labour Day","category":"public","dates":"d|0501:2010-2060|0502:2011,2016,2022,2033,2039,2044,2050"},{"id":"africa_freedom_day","name":"Africa Freedom Day","en":"Africa Freedom Day","category":"public","dates":"d|0525:2010-2060|0526:2014,2025,2031,2036,2042,2053,2059"},{"id":"heroes_day","name":"Heroes' Day","en":"Heroes' Day","category":"public","dates":"d|0701:2013,2019,2024,2030,2041,2047,2052,2058|0702:2012,2018,2029,2035,2040,2046,2057|0703:2017,2023,2028,2034,2045,2051,2056|0704:2011,2016,2022,2033,2039,2044,2050|0705:2010,2021,2027,2032,2038,2049,2055,2060|0706:2015,2020,2026,2037,2043,2048,2054|0707:2014,2025,2031,2036,2042,2053,2059"},{"id":"unity_day","name":"Unity Day","en":"Unity Day","category":"public","dates":"d|0702:2013,2019,2024,2030,2041,2047,2052,2058|0703:2012,2018,2029,2035,2040,2046,2057|0704:2017,2023,2028,2034,2045,2051,2056|0705:2011,2016,2022,2033,2039,2044,2050|0706:2010,2021,2027,2032,2038,2049,2055,2060|0707:2015,2020,2026,2037,2043,2048,2054|0708:2014,2025,2031,2036,2042,2053,2059"},{"id":"farmers_day","name":"Farmers' Day","en":"Farmers' Day","category":"public","dates":"d|0801:2011,2016,2022,2033,2039,2044,2050|0802:2010,2021,2027,2032,2038,2049,2055,2060|0803:2015,2020,2026,2037,2043,2048,2054|0804:2014,2025,2031,2036,2042,2053,2059|0805:2013,2019,2024,2030,2041,2047,2052,2058|0806:2012,2018,2029,2035,2040,2046,2057|0807:2017,2023,2028,2034,2045,2051,2056"},{"id":"general_election_day","name":"General Election Day","en":"General Election Day","category":"public","dates":"d|0808:2041|0809:2046|0810:2051,2056|0811:2016|0812:2021|0813:2026|0814:2031,2036"},{"id":"independence_day","name":"Independence Day","en":"Independence Day","category":"public","dates":"d|1024:2010-2060|1025:2010,2021,2027,2032,2038,2049,2055,2060"},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060|1226:2011,2016,2022,2033,2039,2044,2050"},{"id":"national_day_of_prayer_and_christian_heritage","name":"National Day of Prayer and Christian Heritage","en":"National Day of Prayer and Christian Heritage","category":"public","dates":"d|1229:2026-2060|1230:2030,2041,2047,2052,2058"}]} diff --git a/app/Support/Holidays/data/ZW.json b/app/Support/Holidays/data/ZW.json new file mode 100644 index 000000000..da68ab542 --- /dev/null +++ b/app/Support/Holidays/data/ZW.json @@ -0,0 +1 @@ +{"source":"python-holidays 0.107","country":"ZW","language":null,"years":[2010,2060],"subdivisions":{},"holidays":[{"id":"new_year_s_day","name":"New Year's Day","en":"New Year's Day","category":"public","dates":"d|0101:2010-2060|0102:2012,2017,2023,2034,2040,2045,2051"},{"id":"robert_gabriel_mugabe_national_youth_day","name":"Robert Gabriel Mugabe National Youth Day","en":"Robert Gabriel Mugabe National Youth Day","category":"public","dates":"d|0221:2018-2060|0222:2021,2027,2038,2044,2049,2055"},{"id":"good_friday","name":"Good Friday","en":"Good Friday","category":"public","dates":"d|0323:2035,2046|0325:2016|0326:2027,2032|0327:2043,2054|0328:2059|0329:2013,2024|0330:2018,2029,2040|0331:2051,2056|0402:2010,2021|0403:2015,2026,2037,2048|0404:2042,2053|0406:2012|0407:2023,2034,2045|0408:2039,2050|0410:2020|0411:2031,2036|0412:2047,2058|0414:2017,2028|0415:2022,2033,2044|0416:2049,2055,2060|0418:2014,2025|0419:2019,2030,2041,2052|0420:2057|0422:2011|0423:2038"},{"id":"easter_saturday","name":"Easter Saturday","en":"Easter Saturday","category":"public","dates":"d|0324:2035,2046|0326:2016|0327:2027,2032|0328:2043,2054|0329:2059|0330:2013,2024|0331:2018,2029,2040|0401:2051,2056|0403:2010,2021|0404:2015,2026,2037,2048|0405:2042,2053|0407:2012|0408:2023,2034,2045|0409:2039,2050|0411:2020|0412:2031,2036|0413:2047,2058|0415:2017,2028|0416:2022,2033,2044|0417:2049,2055,2060|0419:2014,2025|0420:2019,2030,2041,2052|0421:2057|0423:2011|0424:2038"},{"id":"easter_monday","name":"Easter Monday","en":"Easter Monday","category":"public","dates":"d|0326:2035,2046|0328:2016|0329:2027,2032|0330:2043,2054|0331:2059|0401:2013,2024|0402:2018,2029,2040|0403:2051,2056|0405:2010,2021|0406:2015,2026,2037,2048|0407:2042,2053|0409:2012|0410:2023,2034,2045|0411:2039,2050|0413:2020|0414:2031,2036|0415:2047,2058|0417:2017,2028|0418:2022,2033,2044|0419:2049,2055,2060|0421:2014,2025|0422:2019,2030,2041,2052|0423:2057|0425:2011|0426:2038"},{"id":"independence_day","name":"Independence Day","en":"Independence Day","category":"public","dates":"d|0418:2010-2060|0419:2010,2021,2027,2032,2038|0420:2049,2055,2060"},{"id":"workers_day","name":"Workers' Day","en":"Workers' Day","category":"public","dates":"d|0501:2010-2060|0502:2011,2016,2022,2033,2039,2044,2050"},{"id":"africa_day","name":"Africa Day","en":"Africa Day","category":"public","dates":"d|0525:2010-2060|0526:2014,2025,2031,2036,2042,2053,2059"},{"id":"zimbabwe_heroes_day","name":"Zimbabwe Heroes' Day","en":"Zimbabwe Heroes' Day","category":"public","dates":"d|0808:2011,2016,2022,2033,2039,2044,2050|0809:2010,2021,2027,2032,2038,2049,2055,2060|0810:2015,2020,2026,2037,2043,2048,2054|0811:2014,2025,2031,2036,2042,2053,2059|0812:2013,2019,2024,2030,2041,2047,2052,2058|0813:2012,2018,2029,2035,2040,2046,2057|0814:2017,2023,2028,2034,2045,2051,2056"},{"id":"defense_forces_day","name":"Defense Forces Day","en":"Defense Forces Day","category":"public","dates":"d|0809:2011,2016,2022,2033,2039,2044,2050|0810:2010,2021,2027,2032,2038,2049,2055,2060|0811:2015,2020,2026,2037,2043,2048,2054|0812:2014,2025,2031,2036,2042,2053,2059|0813:2013,2019,2024,2030,2041,2047,2052,2058|0814:2012,2018,2029,2035,2040,2046,2057|0815:2017,2023,2028,2034,2045,2051,2056"},{"id":"unity_day","name":"Unity Day","en":"Unity Day","category":"public","dates":"d|1222:2010-2060|1223:2013,2019,2024,2030,2041,2047,2052,2058"},{"id":"christmas_day","name":"Christmas Day","en":"Christmas Day","category":"public","dates":"d|1225:2010-2060|1227:2011,2016,2022,2033,2039,2044,2050"},{"id":"boxing_day","name":"Boxing Day","en":"Boxing Day","category":"public","dates":"d|1226:2010-2060|1227:2010,2021,2027,2032,2038,2049,2055,2060"}]} diff --git a/app/Support/HttpClient.php b/app/Support/HttpClient.php index dec8cc6f6..c5695aaf3 100644 --- a/app/Support/HttpClient.php +++ b/app/Support/HttpClient.php @@ -28,7 +28,8 @@ * transport errors, mirroring the previous `curl_exec() === false` handling: * ['ok' => bool, 'status' => int, 'body' => string] * `ok` is false on a transport-level failure (DNS, connection, TLS); `status` - * is then 0. HTTP 4xx/5xx are returned as-is (ok=true, status set) so callers + * is then 0 and `error` says which kind of failure it was (FAILURE_* below), + * so a caller can retry a refused connection but not a hung server. HTTP 4xx/5xx are returned as-is (ok=true, status set) so callers * keep deciding what a "successful" response means, exactly as before. */ final class HttpClient @@ -38,6 +39,15 @@ final class HttpClient private const DEFAULT_CONNECT_TIMEOUT = 10; private const DEFAULT_MAX_REDIRECTS = 3; + /** The request was refused before it left (scheme, SSRF guard). */ + public const FAILURE_BLOCKED = 'blocked'; + /** DNS, refused or reset connection, TLS: nothing usable was answered. */ + public const FAILURE_CONNECT = 'connect'; + /** The connect or the whole request ran past its timeout. */ + public const FAILURE_TIMEOUT = 'timeout'; + /** The answer was larger than max_bytes. */ + public const FAILURE_TOO_LARGE = 'too_large'; + private static ?Client $client = null; /** @@ -49,7 +59,7 @@ final class HttpClient * query (array), https_only (bool), * ssrf_guard (bool), max_bytes (int — * abort the download past this size) - * @return array{ok:bool,status:int,body:string} + * @return array{ok:bool,status:int,body:string,error?:string} */ public static function get(string $url, array $headers = [], array $options = []): array { @@ -63,7 +73,7 @@ public static function get(string $url, array $headers = [], array $options = [] * array sent as form params * @param array $headers Extra request headers * @param array $options See {@see self::get()} - * @return array{ok:bool,status:int,body:string} + * @return array{ok:bool,status:int,body:string,error?:string} */ public static function post(string $url, string|array|null $body = null, array $headers = [], array $options = []): array { @@ -99,7 +109,7 @@ public static function getJson(string $url, array $headers = [], array $options /** * @param array $headers * @param array $options - * @return array{ok:bool,status:int,body:string} + * @return array{ok:bool,status:int,body:string,error?:string} */ private static function request(string $method, string $url, array $headers, array $options): array { @@ -117,7 +127,7 @@ private static function request(string $method, string $url, array $headers, arr $scheme = strtolower((string) parse_url($url, PHP_URL_SCHEME)); if (!in_array($scheme, $allowedSchemes, true)) { - return ['ok' => false, 'status' => 0, 'body' => '']; + return ['ok' => false, 'status' => 0, 'body' => '', 'error' => self::FAILURE_BLOCKED]; } // For caller-configurable endpoints, fail closed unless the host @@ -129,12 +139,12 @@ private static function request(string $method, string $url, array $headers, arr $pinnedPorts = []; if ($ssrfGuard) { if ($pinnedHost === '' || !\extension_loaded('curl')) { - return ['ok' => false, 'status' => 0, 'body' => '']; + return ['ok' => false, 'status' => 0, 'body' => '', 'error' => self::FAILURE_BLOCKED]; } $pinnedIp = SsrfGuard::resolvePinnedIp($pinnedHost); if ($pinnedIp === null) { SecureLogger::warning('HttpClient SSRF guard rejected endpoint host', ['host' => $pinnedHost]); - return ['ok' => false, 'status' => 0, 'body' => '']; + return ['ok' => false, 'status' => 0, 'body' => '', 'error' => self::FAILURE_BLOCKED]; } $options['pin_ip'] = $pinnedIp; $initialPort = (int) (parse_url($url, PHP_URL_PORT) ?? ($scheme === 'https' ? 443 : 80)); @@ -254,8 +264,39 @@ private static function request(string $method, string $url, array $headers, arr 'error' => preg_replace('/([?&](?:key|api_key|apikey|token|access_token)=)[^&\s]+/i', '$1[redacted]', $e->getMessage()), ]); - return ['ok' => false, 'status' => 0, 'body' => '']; + return ['ok' => false, 'status' => 0, 'body' => '', 'error' => self::failureKind($e)]; + } + } + + /** + * The kind of a transport failure: the cURL error number when Guzzle + * carries one, and the max_bytes guards above, which throw from inside + * the transfer (Guzzle wraps them, or cURL aborts the callback). + */ + private static function failureKind(\Throwable $e): string + { + for ($t = $e; $t !== null; $t = $t->getPrevious()) { + if (str_contains($t->getMessage(), 'exceeds max_bytes')) { + return self::FAILURE_TOO_LARGE; + } + if ($t->getMessage() === 'SSRF guard rejected redirect target') { + return self::FAILURE_BLOCKED; + } + } + $errno = 0; + if (method_exists($e, 'getHandlerContext')) { + $errno = (int) ($e->getHandlerContext()['errno'] ?? 0); + } + // 28 CURLE_OPERATION_TIMEDOUT (connect or transfer timeout) + if ($errno === 28 || stripos($e->getMessage(), 'timed out') !== false) { + return self::FAILURE_TIMEOUT; + } + // 42 CURLE_ABORTED_BY_CALLBACK: only the max_bytes progress guard aborts; + // 63 CURLE_FILESIZE_EXCEEDED + if ($errno === 42 || $errno === 63) { + return self::FAILURE_TOO_LARGE; } + return self::FAILURE_CONNECT; } private static function client(): Client diff --git a/app/Support/ImportLogger.php b/app/Support/ImportLogger.php index cb63b18e6..b9d4bdb05 100644 --- a/app/Support/ImportLogger.php +++ b/app/Support/ImportLogger.php @@ -67,6 +67,39 @@ public function __construct(\mysqli $db, string $importType, string $fileName, ? $stmt->close(); } + /** + * The logger of an import_logs row created earlier (a retry of a final + * save that failed): complete() and fail() update that row instead of a + * new one. Null when the row does not exist. + */ + public static function resume(\mysqli $db, string $importId): ?self + { + $stmt = $db->prepare('SELECT 1 FROM import_logs WHERE import_id = ? LIMIT 1'); + if ($stmt === false) { + return null; + } + $stmt->bind_param('s', $importId); + $stmt->execute(); + $found = $stmt->get_result()->fetch_row() !== null; + $stmt->close(); + if (!$found) { + return null; + } + $logger = (new \ReflectionClass(self::class))->newInstanceWithoutConstructor(); + $logger->db = $db; + $logger->importId = $importId; + $logger->stats = [ + 'imported' => 0, + 'updated' => 0, + 'failed' => 0, + 'authors_created' => 0, + 'publishers_created' => 0, + 'scraped' => 0 + ]; + $logger->errors = []; + return $logger; + } + /** * Increment a statistic counter * diff --git a/app/Support/LibraryCalendar.php b/app/Support/LibraryCalendar.php new file mode 100644 index 000000000..46e797a6f --- /dev/null +++ b/app/Support/LibraryCalendar.php @@ -0,0 +1,892 @@ + */ + private array $closedWeekdays = []; + + /** @var list */ + private array $closures = []; + + /** @var list> selected holiday rules, evaluated for any year (dataset countries: the years of their file) */ + private array $holidayRules; + + /** @var array>> per year: date => branches ('*' = every branch) */ + private array $holidayDates = []; + + /** @var array per-connection cache, keyed by spl_object_id */ + private static array $instances = []; + + /** @var array branchOfCopy() memo: copy id => sede_id (null when none) */ + private array $copyBranches = []; + + /** + * @param list|array $closedWeekdays 0 = Sunday … 6 = Saturday + * @param list $closures + * @param list> $holidayRules + */ + public function __construct(array $closedWeekdays = [], array $closures = [], array $holidayRules = []) + { + $this->holidayRules = $holidayRules; + foreach ($closedWeekdays as $weekday) { + $weekday = (int) $weekday; + if ($weekday >= 0 && $weekday <= 6) { + $this->closedWeekdays[$weekday] = true; + } + } + // Seven closed weekdays would leave no day to move a date to. Treat + // that misconfiguration as "no weekly closure" rather than letting + // every due date run to the scan limit. + if (count($this->closedWeekdays) === 7) { + $this->closedWeekdays = []; + } + foreach ($closures as $row) { + $start = (string) ($row['data_inizio'] ?? $row['start'] ?? ''); + $end = (string) ($row['data_fine'] ?? $row['end'] ?? $start); + if (!DateHelper::isISODateFormat($start) || !DateHelper::isISODateFormat($end)) { + continue; + } + if ($end < $start) { + [$start, $end] = [$end, $start]; + } + $sede = $row['sede_id'] ?? $row['sede'] ?? null; + // A yearly closure repeats its month-day span, which only makes + // sense for a span shorter than a year. A longer row (the form + // refuses one; an old import may have stored one) is applied once, + // as the interval it names, instead of closing every day. + $yearly = (bool) ($row['ricorrente_annuale'] ?? $row['yearly'] ?? false) + && self::spanDays($start, $end) <= self::MAX_YEARLY_SPAN_DAYS; + $this->closures[] = [ + 'start' => $start, + 'end' => $end, + 'yearly' => $yearly, + 'sede' => ($sede === null || $sede === '' || (int) $sede <= 0) ? null : (int) $sede, + ]; + } + } + + /** The calendar stored in the database (settings + closures table). */ + public static function fromDatabase(\mysqli $db, bool $configuration = false): self + { + // Closures calendar module off (Settings -> Loans; also off in + // catalogue mode): an empty calendar, so due dates, pickup deadlines + // and fine days are those of a library open every day, as before + // 0.9.0. The stored closures stay for when it is switched back on. + if (!$configuration && !Features::enabled('closures_calendar')) { + return new self([], []); + } + // A selection that cannot be read falls back to the stored closure rows, + // as the other loaders fall back instead of failing a due date. + try { + $presetRules = Holidays\HolidayPresets::calendarRules($db); + } catch (\Throwable $e) { + SecureLogger::warning('Calendar: holiday selection not read: ' . $e->getMessage()); + $presetRules = null; + } + // The library's own holidays (any calendar, optionally for one branch) join the presets. + try { + $customRules = Holidays\CustomHolidays::calendarRules($db); + } catch (\Throwable $e) { + SecureLogger::warning('Calendar: custom holidays not read: ' . $e->getMessage()); + $customRules = []; + } + return new self(self::loadClosedWeekdays($db), self::loadClosures($db, $presetRules !== null), array_merge($presetRules ?? [], $customRules)); + } + + /** + * One calendar per connection and request. Writers call reset() after a + * change so the same request sees it. + */ + public static function forDb(\mysqli $db): self + { + $key = spl_object_id($db); + return self::$instances[$key] ??= self::fromDatabase($db); + } + + public static function reset(): void + { + self::$instances = []; + } + + // ── Funnels used by the loan code ─────────────────────────────────────── + + /** + * THE due-date rule: a due date that falls on a closed day moves forward + * to the next open day (plan §13, decision 7). Every place that stores a + * data_scadenza calls this with the date it computed or received. + * $copiaId, when known, applies that copy's branch closures too. + */ + public static function dueDate(\mysqli $db, string $ymd, ?int $copiaId = null): string + { + if (!DateHelper::isISODateFormat($ymd)) { + return $ymd; + } + $calendar = self::forDb($db); + if (!$calendar->hasRules()) { + return $ymd; + } + return $calendar->nextOpenDay($ymd, self::branchOfCopy($db, $copiaId)); + } + + /** + * The pickup deadline: $days OPEN days after $from, never past $capAt (the + * loan's due date). With no closures this is exactly $from + $days. + */ + public static function pickupDeadline(\mysqli $db, string $from, int $days, ?string $capAt = null, ?int $copiaId = null): string + { + $days = max(0, $days); + $calendar = self::forDb($db); + $deadline = $calendar->hasRules() + ? $calendar->addOpenDays($from, $days, self::branchOfCopy($db, $copiaId)) + : self::shift($from, $days); + if ($capAt !== null && $capAt !== '' && $deadline > $capAt) { + $deadline = $capAt; + } + return $deadline; + } + + /** + * After a calendar change: a pickup still waiting whose deadline now falls + * on a closed day (of the copy's branch) lasts until the next open day, + * because the reader never had that day to come in. The new deadline is + * stored in prestiti.pickup_deadline, so every check that reads it (pickup + * confirmation, the reader's cancellation, the expiry sweep, the expired + * badges) applies the same rule. A deadline whose next open day is already + * past is left to the expiry sweep. + * + * The deadline never passes the loan's due date (prestiti.data_scadenza): + * pickupDeadline() caps it there on creation, and confirmPickup() refuses + * a pickup after the loan window, so an extension past it would promise a + * day the desk cannot honour. The due date of an existing loan is not + * moved here; when the cap leaves nothing to add the row is left alone. + * Returns the number of pickups moved. + */ + public static function extendPickupDeadlines(\mysqli $db): int + { + $calendar = self::forDb($db); + if (!$calendar->hasRules()) { + return 0; + } + $today = DateHelper::today(); + $result = $db->query( + "SELECT p.id, p.pickup_deadline, p.data_scadenza, c.sede_id + FROM prestiti p + LEFT JOIN copie c ON c.id = p.copia_id + WHERE p.stato = 'da_ritirare' AND p.attivo = 1 AND p.pickup_deadline IS NOT NULL" + ); + if (!$result instanceof \mysqli_result) { + return 0; + } + $rows = $result->fetch_all(MYSQLI_ASSOC); + $result->free(); + + $moved = 0; + $stmt = $db->prepare( + "UPDATE prestiti SET pickup_deadline = ? + WHERE id = ? AND stato = 'da_ritirare' AND attivo = 1 AND pickup_deadline = ?" + ); + foreach ($rows as $row) { + $deadline = substr((string) $row['pickup_deadline'], 0, 10); + $next = $calendar->nextOpenDay($deadline, $row['sede_id'] !== null ? (int) $row['sede_id'] : null); + $dueDate = $row['data_scadenza'] !== null ? substr((string) $row['data_scadenza'], 0, 10) : ''; + if (DateHelper::isISODateFormat($dueDate) && $next > $dueDate) { + $next = $dueDate; + } + if ($next <= $deadline || $next < $today) { + continue; + } + $id = (int) $row['id']; + $stmt->bind_param('sis', $next, $id, $deadline); + $stmt->execute(); + $moved += max(0, $stmt->affected_rows); + } + $stmt->close(); + return $moved; + } + + // ── Queries ───────────────────────────────────────────────────────────── + + /** True when weekly closures, closure rows or selected holidays apply. */ + public function hasRules(): bool + { + return $this->closedWeekdays !== [] || $this->closures !== [] || $this->holidayRules !== []; + } + + /** + * Whether the library is open on $ymd. A branch-specific closure counts + * only when that branch is asked for; a closure without a branch counts + * for every branch and for the library as a whole ($sedeId = null). + */ + public function isOpen(string $ymd, ?int $sedeId = null): bool + { + if (!DateHelper::isISODateFormat($ymd)) { + return true; + } + if ($this->closedWeekdays !== [] && isset($this->closedWeekdays[self::weekday($ymd)])) { + return false; + } + $monthDay = substr($ymd, 5); + foreach ($this->closures as $closure) { + if ($closure['sede'] !== null && $closure['sede'] !== $sedeId) { + continue; + } + if ($closure['yearly']) { + if (self::recurringCovers($closure['start'], $closure['end'], $ymd, $monthDay)) { + return false; + } + } elseif ($ymd >= $closure['start'] && $ymd <= $closure['end']) { + return false; + } + } + return !$this->isHoliday($ymd, $sedeId); + } + + /** + * Selected holidays apply beyond the admin list's materialised years. A + * rule with 'sede' (a custom holiday of one branch) closes that branch + * only, like a branch closure. + */ + private function isHoliday(string $ymd, ?int $sedeId = null): bool + { + if ($this->holidayRules === []) { + return false; + } + $year = (int) substr($ymd, 0, 4); + if (!isset($this->holidayDates[$year])) { + $dates = []; + // An observed New Year's Day can land on 31 December of the + // preceding year. Include adjacent source years before filtering. + for ($sourceYear = max(1, $year - 1); $sourceYear <= min(9999, $year + 1); $sourceYear++) { + foreach ($this->holidayRules as $rule) { + $branch = isset($rule['sede']) && is_int($rule['sede']) ? $rule['sede'] : '*'; + foreach (Holidays\HolidayCalculator::datesOf($rule, $sourceYear) as $date) { + if ((int) substr($date, 0, 4) === $year) { + $dates[$date][$branch] = true; + } + } + } + } + $this->holidayDates[$year] = $dates; + } + $branches = $this->holidayDates[$year][$ymd] ?? null; + return $branches !== null && (isset($branches['*']) || ($sedeId !== null && isset($branches[$sedeId]))); + } + + /** + * $ymd itself when open, otherwise the first open day after it. A day + * inside a one-off closure jumps to the day after that closure; the + * scan bound only applies to closed weekdays and yearly closures. With + * every day closed (two yearly closures covering the year) $ymd is + * returned after the bound, as before. + */ + public function nextOpenDay(string $ymd, ?int $sedeId = null): string + { + if (!DateHelper::isISODateFormat($ymd)) { + return $ymd; + } + $day = $ymd; + for ($i = 0; $i < self::MAX_SCAN_DAYS; $i++) { + if ($this->isOpen($day, $sedeId)) { + return $day; + } + $day = self::shift($this->closedThrough($day, $sedeId) ?? $day, 1); + } + return $ymd; + } + + /** Same as nextOpenDay(): the name the loan code reads best with. */ + public function shiftForward(string $ymd, ?int $sedeId = null): string + { + return $this->nextOpenDay($ymd, $sedeId); + } + + /** + * $ymd itself when open, otherwise the last open day before it: the + * mirror of nextOpenDay(), jumping to the day before a one-off closure. + */ + public function previousOpenDay(string $ymd, ?int $sedeId = null): string + { + if (!DateHelper::isISODateFormat($ymd)) { + return $ymd; + } + $day = $ymd; + for ($i = 0; $i < self::MAX_SCAN_DAYS; $i++) { + if ($this->isOpen($day, $sedeId)) { + return $day; + } + $day = self::shift($this->closedFrom($day, $sedeId) ?? $day, -1); + } + return $ymd; + } + + /** + * The day $n open days after $from ($from itself excluded). A one-off + * closure on the way is stepped over whole; after the scan bound (every + * day closed) the plain $from + $n is returned, as before. + */ + public function addOpenDays(string $from, int $n, ?int $sedeId = null): string + { + if (!DateHelper::isISODateFormat($from) || $n <= 0) { + return $from; + } + $day = $from; + $counted = 0; + for ($i = 0; $i < self::MAX_SCAN_DAYS && $counted < $n; $i++) { + $day = self::shift($day, 1); + if ($this->isOpen($day, $sedeId)) { + $counted++; + } else { + $day = $this->closedThrough($day, $sedeId) ?? $day; + } + } + return $counted < $n ? self::shift($from, $n) : $day; + } + + /** + * Closed days in [$from, $to], both included, for the request calendars. + * Listed day by day, so the list stops MAX_SCAN_DAYS days after $from: + * the callers ask for a month or a 400-day horizon, never a span that + * long, and a ten-year closure is not expanded into ten years of dates. + * + * @return list + */ + public function closedDatesBetween(string $from, string $to, ?int $sedeId = null): array + { + if (!$this->hasRules() || !DateHelper::isISODateFormat($from) || !DateHelper::isISODateFormat($to) || $to < $from) { + return []; + } + $closed = []; + $day = $from; + for ($i = 0; $i < self::MAX_SCAN_DAYS && $day <= $to; $i++) { + if (!$this->isOpen($day, $sedeId)) { + $closed[] = $day; + } + $day = self::shift($day, 1); + } + return $closed; + } + + /** + * The last day of the one-off closures of $sedeId (or of every branch) + * that contain $ymd, the latest when several overlap; null when none + * does (the day is closed by a weekday or a yearly closure, or open). + * Yearly closures are not considered: they are shorter than a year and + * a scan walks them day by day, within its bound. + */ + private function closedThrough(string $ymd, ?int $sedeId): ?string + { + $through = null; + foreach ($this->closures as $closure) { + if ($closure['yearly'] || ($closure['sede'] !== null && $closure['sede'] !== $sedeId)) { + continue; + } + if ($ymd >= $closure['start'] && $ymd <= $closure['end'] && ($through === null || $closure['end'] > $through)) { + $through = $closure['end']; + } + } + return $through; + } + + /** The first day of the one-off closures containing $ymd (see closedThrough()). */ + private function closedFrom(string $ymd, ?int $sedeId): ?string + { + $from = null; + foreach ($this->closures as $closure) { + if ($closure['yearly'] || ($closure['sede'] !== null && $closure['sede'] !== $sedeId)) { + continue; + } + if ($ymd >= $closure['start'] && $ymd <= $closure['end'] && ($from === null || $closure['start'] < $from)) { + $from = $closure['start']; + } + } + return $from; + } + + /** + * Open days after $from up to and including $to: the days a late loan + * counts when closed days do not (fines). 0 when $to <= $from. + */ + public function openDaysBetween(string $from, string $to, ?int $sedeId = null): int + { + if (!DateHelper::isISODateFormat($from) || !DateHelper::isISODateFormat($to) || $to <= $from) { + return 0; + } + if (!$this->hasRules()) { + return (int) (new \DateTimeImmutable($from, new \DateTimeZone('UTC'))) + ->diff(new \DateTimeImmutable($to, new \DateTimeZone('UTC')))->days; + } + $count = 0; + $day = $from; + while (true) { + $day = self::shift($day, 1); + if ($day > $to) { + break; + } + if ($this->isOpen($day, $sedeId)) { + $count++; + } + } + return $count; + } + + /** @return list */ + public function closedWeekdays(): array + { + $days = array_keys($this->closedWeekdays); + sort($days); + return $days; + } + + // ── Presets and import ────────────────────────────────────────────────── + + /** + * Easter Sunday (Gregorian) with the anonymous Meeus/Jones/Butcher + * algorithm: no dependency on the `calendar` extension. + */ + public static function easterSunday(int $year): string + { + $a = $year % 19; + $b = intdiv($year, 100); + $c = $year % 100; + $d = intdiv($b, 4); + $e = $b % 4; + $f = intdiv($b + 8, 25); + $g = intdiv($b - $f + 1, 3); + $h = (19 * $a + $b - $d - $g + 15) % 30; + $i = intdiv($c, 4); + $k = $c % 4; + $l = (32 + 2 * $e + 2 * $i - $h - $k) % 7; + $m = intdiv($a + 11 * $h + 22 * $l, 451); + $month = intdiv($h + $l - 7 * $m + 114, 31); + $day = (($h + $l - 7 * $m + 114) % 31) + 1; + return sprintf('%04d-%02d-%02d', $year, $month, $day); + } + + /** + * Italian national holidays for $year, from the Italy preset + * (Holidays\HolidayRules): fixed dates recur every year, movable ones + * (Easter, Easter Monday) and those that did not always exist are dated + * for that year only. Names are the Italian ones the preset stores. + * + * @return list + */ + public static function italianHolidays(int $year): array + { + $holidays = []; + foreach (Holidays\HolidayCalculator::forYear('IT', '', $year) as $holiday) { + if ($holiday['date'] === null) { + continue; + } + $holidays[] = ['start' => $holiday['date'], 'end' => $holiday['date'], 'description' => $holiday['name'], 'yearly' => $holiday['yearly']]; + } + usort($holidays, static fn (array $x, array $y): int => strcmp($x['start'], $y['start'])); + return $holidays; + } + + /** + * Closed intervals from an iCalendar file: every VEVENT with a DTSTART. + * DTEND is exclusive (RFC 5545): for an all-day event the last closed day + * is the day before it, and so it is for a timed DTEND at midnight (local + * time) after DTSTART. A plain RRULE:FREQ=YEARLY makes the row recurring; + * any other recurrence (a bounded one with COUNT/UNTIL, an interval of + * more than a year, a positional one with BYDAY/BYSETPOS, a BYMONTH or + * BYMONTHDAY that is not DTSTART's…) is imported as its first occurrence + * only. So is a yearly event with an EXDATE: a stored closure cannot skip + * a year. An event whose DTSTART is itself excluded has no occurrence to + * import and is left out. A UTC DATE-TIME (trailing Z) and one with a + * TZID parameter are read in $timezone (app.timezone by default) before + * their day is taken. The spans are not validated here: the import + * applies the same limits as the form. + * + * @return list + */ + public static function parseIcs(string $ics, int $maxEvents = 1000, ?string $timezone = null): array + { + $timezone ??= self::appTimezone(); + // Unfold continuation lines (RFC 5545 §3.1). + $ics = str_replace(["\r\n", "\r"], "\n", $ics); + $ics = preg_replace("/\n[ \t]/", '', $ics) ?? $ics; + $events = []; + $current = null; + foreach (explode("\n", $ics) as $line) { + $line = rtrim($line); + if (strcasecmp($line, 'BEGIN:VEVENT') === 0) { + $current = ['start' => null, 'startKey' => null, 'end' => null, 'endIsDate' => false, 'endAtMidnight' => false, 'summary' => '', 'rrule' => null, 'exdates' => []]; + continue; + } + if ($current === null) { + continue; + } + if (strcasecmp($line, 'END:VEVENT') === 0) { + // RRULE and EXDATE may come before DTSTART: judged at the end. + // An EXDATE removes the occurrence with DTSTART's date and + // time (RFC 5545): another time the same day leaves it. + if ($current['start'] !== null && !isset($current['exdates'][$current['startKey']])) { + $start = $current['start']; + $end = $current['end']; + if ($end === null) { + $end = $start; + } elseif ($current['endIsDate'] || ($current['endAtMidnight'] && $end > $start)) { + $end = self::shift($end, -1); + } + if ($end < $start) { + $end = $start; + } + $events[] = [ + 'start' => $start, + 'end' => $end, + 'description' => mb_substr(trim($current['summary']), 0, 255), + 'yearly' => $current['rrule'] !== null && $current['exdates'] === [] && self::isPlainYearlyRule($current['rrule'], $start), + ]; + if (count($events) >= $maxEvents) { + break; + } + } + $current = null; + continue; + } + $colon = strpos($line, ':'); + if ($colon === false) { + continue; + } + $rawParams = substr($line, 0, $colon); + $nameAndParams = strtoupper($rawParams); + $value = substr($line, $colon + 1); + $name = explode(';', $nameAndParams)[0]; + if ($name === 'DTSTART' || $name === 'DTEND') { + $tzid = preg_match('/;TZID=("?)([^;:"]+)\1/i', $rawParams, $tz) === 1 ? $tz[2] : null; + $parsed = self::icsDateTime($value, $timezone, $tzid); + if ($parsed === null) { + continue; + } + $date = $parsed['date']; + $isDate = (str_contains($nameAndParams, 'VALUE=DATE') && !str_contains($nameAndParams, 'VALUE=DATE-TIME')) + || preg_match('/^\d{8}$/', trim($value)) === 1; + if ($name === 'DTSTART') { + $current['start'] = $date; + $current['startKey'] = $parsed['key']; + } else { + $current['end'] = $date; + $current['endIsDate'] = $isDate; + $current['endAtMidnight'] = !$isDate && $parsed['midnight']; + } + } elseif ($name === 'SUMMARY') { + $current['summary'] = str_replace(['\\,', '\\;', '\\n', '\\N', '\\\\'], [',', ';', ' ', ' ', '\\'], $value); + } elseif ($name === 'RRULE') { + $current['rrule'] = $value; + } elseif ($name === 'EXDATE') { + $tzid = preg_match('/;TZID=("?)([^;:"]+)\1/i', $rawParams, $tz) === 1 ? $tz[2] : null; + foreach (explode(',', $value) as $excluded) { + $parsed = self::icsDateTime($excluded, $timezone, $tzid); + if ($parsed !== null) { + $current['exdates'][$parsed['key']] = true; + } + } + } + } + return $events; + } + + // ── Internals ─────────────────────────────────────────────────────────── + + /** Y-m-d plus $days, in UTC (no DST: a day is a day). */ + public static function shift(string $ymd, int $days): string + { + $date = \DateTimeImmutable::createFromFormat('!Y-m-d', $ymd, new \DateTimeZone('UTC')); + if ($date === false) { + return $ymd; + } + return $days === 0 ? $date->format('Y-m-d') : $date->modify(($days > 0 ? '+' : '') . $days . ' days')->format('Y-m-d'); + } + + private static function weekday(string $ymd): int + { + $date = \DateTimeImmutable::createFromFormat('!Y-m-d', $ymd, new \DateTimeZone('UTC')); + return $date === false ? -1 : (int) $date->format('w'); + } + + /** Days between two Y-m-d dates (0 for the same day); -1 when either is not a date. */ + public static function spanDays(string $start, string $end): int + { + $from = \DateTimeImmutable::createFromFormat('!Y-m-d', $start, new \DateTimeZone('UTC')); + $to = \DateTimeImmutable::createFromFormat('!Y-m-d', $end, new \DateTimeZone('UTC')); + if ($from === false || $to === false) { + return -1; + } + return (int) $from->diff($to)->days; + } + + /** + * A yearly closure repeats its month-day span every year. A span that + * crosses New Year (12-24 → 01-06) wraps. The constructor keeps only + * spans shorter than a year recurring, so $ymd is closed only when its + * month-day falls inside the span: no span closes every day. + */ + private static function recurringCovers(string $start, string $end, string $ymd, string $monthDay): bool + { + $from = substr($start, 5); + $to = substr($end, 5); + if ($to < $from) { + return $monthDay >= $from || $monthDay <= $to; // wraps over New Year + } + return $monthDay >= $from && $monthDay <= $to; + } + + /** + * True for a rule that repeats the same month-day span every year with + * no end: FREQ=YEARLY, INTERVAL 1 (or absent), no COUNT/UNTIL, no + * positional or weekday part. BYMONTH/BYMONTHDAY are accepted only as the + * restatement of DTSTART ($start, Y-m-d) that calendar apps write: any + * other value names a different day than the one the row would close. + */ + private static function isPlainYearlyRule(string $rule, string $start): bool + { + $parts = []; + foreach (explode(';', strtoupper(trim($rule))) as $part) { + if ($part === '') { + continue; + } + [$key, $val] = array_pad(explode('=', $part, 2), 2, ''); + $parts[$key] = $val; + } + if (($parts['FREQ'] ?? '') !== 'YEARLY') { + return false; + } + if (isset($parts['INTERVAL']) && $parts['INTERVAL'] !== '1') { + return false; + } + foreach (['COUNT', 'UNTIL', 'BYDAY', 'BYSETPOS', 'BYWEEKNO', 'BYYEARDAY', 'BYHOUR', 'BYMINUTE', 'BYSECOND'] as $bounded) { + if (isset($parts[$bounded])) { + return false; + } + } + // Several months or month-days would be several closures, not one. + foreach (['BYMONTH', 'BYMONTHDAY'] as $list) { + if (isset($parts[$list]) && str_contains($parts[$list], ',')) { + return false; + } + } + if (isset($parts['BYMONTH']) && (!ctype_digit($parts['BYMONTH']) || (int) $parts['BYMONTH'] !== (int) substr($start, 5, 2))) { + return false; + } + if (isset($parts['BYMONTHDAY']) && (!ctype_digit($parts['BYMONTHDAY']) || (int) $parts['BYMONTHDAY'] !== (int) substr($start, 8, 2))) { + return false; + } + return true; + } + + /** + * The day of an iCalendar DATE or DATE-TIME, whether that local time is + * midnight, and the local date and time as one key (the date alone for a + * DATE) that matches an EXDATE to its DTSTART. A DATE and a floating + * DATE-TIME are taken as written; a UTC DATE-TIME (trailing Z) or one with + * a valid TZID is converted to $timezone first, so 20260410T230000Z in + * Europe/Rome is 2026-04-11. An unknown TZID is read as floating. Anything + * after the date or time makes the value invalid. + * + * @return array{date: string, midnight: bool, key: string}|null + */ + private static function icsDateTime(string $value, string $timezone, ?string $tzid = null): ?array + { + $value = trim($value); + if (preg_match('/^(\d{4})(\d{2})(\d{2})(?:T(\d{2})(\d{2})(\d{2})?(Z?))?$/i', $value, $m) !== 1) { + return null; + } + $date = "{$m[1]}-{$m[2]}-{$m[3]}"; + if (!DateHelper::isISODateFormat($date)) { + return null; + } + if (($m[4] ?? '') === '') { + return ['date' => $date, 'midnight' => true, 'key' => $date]; + } + $time = sprintf('%s:%s:%s', $m[4], $m[5], ($m[6] ?? '') !== '' ? $m[6] : '00'); + $source = null; + if (($m[7] ?? '') !== '') { + $source = 'UTC'; + } elseif ($tzid !== null && $tzid !== '') { + $source = $tzid; + } + if ($source !== null) { + try { + $local = (new \DateTimeImmutable($date . 'T' . $time, new \DateTimeZone($source))) + ->setTimezone(new \DateTimeZone($timezone)); + return ['date' => $local->format('Y-m-d'), 'midnight' => $local->format('H:i:s') === '00:00:00', 'key' => $local->format('Y-m-d\TH:i:s')]; + } catch (\Throwable $e) { + // An unknown TZID: the time as written. + } + } + return ['date' => $date, 'midnight' => $time === '00:00:00', 'key' => $date . 'T' . $time]; + } + + /** app.timezone, with the same fallback as DateHelper::today(). */ + private static function appTimezone(): string + { + try { + $tz = (string) ConfigStore::get('app.timezone', 'Europe/Rome'); + new \DateTimeZone($tz); + return $tz; + } catch (\Throwable $e) { + return 'Europe/Rome'; + } + } + + /** + * @return list + */ + public static function parseWeekdays(?string $raw): array + { + $days = []; + foreach (explode(',', (string) $raw) as $part) { + $part = trim($part); + if ($part !== '' && ctype_digit($part) && (int) $part <= 6) { + $days[(int) $part] = (int) $part; + } + } + ksort($days); + return array_values($days); + } + + /** + * Thrown like loadClosures(), never read as "no weekly closure": the + * calendar would be cached without it and a due date stored on a closed + * weekday. + * + * @return list + */ + private static function loadClosedWeekdays(\mysqli $db): array + { + try { + return self::parseWeekdays((new SettingsRepository($db))->get('loans', 'closed_weekdays', '')); + } catch (\Throwable $e) { + SecureLogger::warning('LibraryCalendar: closed weekdays not readable: ' . $e->getMessage()); + throw new \RuntimeException('LibraryCalendar: closed weekdays not readable', 0, $e); + } + } + + /** + * Before the 0.9.0 migration ran the table does not exist: no closures. + * Any other failure is thrown, never read as "no closures": an empty + * calendar would let the loan write that asked for it store a due date + * on a closed day. forDb() caches nothing for a failed load. + * + * @return list> + */ + private static function loadClosures(\mysqli $db, bool $rulesFromSelection = false): array + { + try { + $exists = $db->query("SHOW TABLES LIKE 'calendario_chiusure'"); + if ($exists === false) { + throw new \RuntimeException('table lookup failed'); + } + if ($exists->num_rows === 0) { + return []; + } + // A closure switched off (attiva = 0) stays stored but closes + // nothing. Before the holiday-presets section of the migration + // the column is missing and every row counts, as before. + $hasActive = $db->query("SHOW COLUMNS FROM calendario_chiusure LIKE 'attiva'"); + $filter = ($hasActive instanceof \mysqli_result && $hasActive->num_rows > 0) ? ' WHERE attiva = 1' : ''; + // Once a selection exists it alone defines preset holidays. Old + // materialised rows cannot reintroduce unticked holidays, and a + // missing future row cannot make a selected holiday an open day. + if ($rulesFromSelection) { + $filter .= ($filter === '' ? ' WHERE ' : ' AND ') . 'regola_festivita IS NULL'; + } + $result = $db->query('SELECT data_inizio, data_fine, ricorrente_annuale, sede_id FROM calendario_chiusure' . $filter); + if ($result === false) { + throw new \RuntimeException('closures query failed'); + } + return $result->fetch_all(MYSQLI_ASSOC); + } catch (\Throwable $e) { + SecureLogger::warning('LibraryCalendar: closures not readable: ' . $e->getMessage()); + throw new \RuntimeException('LibraryCalendar: closures not readable', 0, $e); + } + } + + /** + * The branch (copie.sede_id) of a copy, null when the copy has none or + * does not exist. A lookup that fails is thrown, not read as "no branch": + * the branch's closures would be skipped. One query per copy and + * connection: the answer is kept on the forDb() calendar, which reset() + * discards with it. + */ + public static function branchOfCopy(\mysqli $db, ?int $copiaId): ?int + { + if ($copiaId === null || $copiaId <= 0) { + return null; + } + $calendar = self::forDb($db); + if (array_key_exists($copiaId, $calendar->copyBranches)) { + return $calendar->copyBranches[$copiaId]; + } + return $calendar->copyBranches[$copiaId] = self::queryBranchOfCopy($db, $copiaId); + } + + private static function queryBranchOfCopy(\mysqli $db, int $copiaId): ?int + { + try { + $stmt = $db->prepare('SELECT sede_id FROM copie WHERE id = ?'); + if ($stmt === false) { + throw new \RuntimeException('copy branch query not prepared'); + } + $stmt->bind_param('i', $copiaId); + $stmt->execute(); + $result = $stmt->get_result(); + if ($result === false) { + throw new \RuntimeException('copy branch query failed'); + } + $row = $result->fetch_row(); + $stmt->close(); + return ($row === null || $row[0] === null) ? null : (int) $row[0]; + } catch (\Throwable $e) { + SecureLogger::warning('LibraryCalendar: copy branch not readable: ' . $e->getMessage()); + throw new \RuntimeException('LibraryCalendar: copy branch not readable', 0, $e); + } + } +} diff --git a/app/Support/LoanEligibility.php b/app/Support/LoanEligibility.php index e7e122e40..6b9623a80 100644 --- a/app/Support/LoanEligibility.php +++ b/app/Support/LoanEligibility.php @@ -22,6 +22,11 @@ * solo per tipo_utente standard/premium: admin e staff non hanno tessera * (un trigger DB la azzera). * + * - saldo a debito oltre la soglia della tipologia lettore + * (tipologie_lettore.soglia_blocco_debito), altrimenti di quella globale + * (Impostazioni → Prestiti → Multe, loans.debt_block_threshold); + * 0/vuoto = nessun blocco -> 'debt_block'. + * * Il confronto date usa DateHelper::today() così PHP e MySQL concordano * sul confine di giornata (vedi docblock di DateHelper::today()). */ @@ -31,7 +36,7 @@ class LoanEligibility * Verifica l'idoneità di un utente a richiedere/ricevere un prestito. * * @return string|null Codice errore ('user_not_found', 'user_suspended', - * 'card_expired') oppure null se l'utente è idoneo. + * 'card_expired', 'debt_block') oppure null se l'utente è idoneo. * @phpstan-impure Reads mutable database state; repeated calls can differ. */ public static function checkUser(\mysqli $db, int $userId): ?string @@ -62,6 +67,12 @@ public static function checkUser(\mysqli $db, int $userId): ?string return 'card_expired'; } + // Debt block (plan §7): threshold of the patron type, else the global one. + $debt = (new \App\Services\CirculationPolicy($db))->debtBlockError($userId); + if ($debt !== null) { + return $debt; + } + return null; } @@ -77,6 +88,19 @@ public static function errorMessage(string $code): string return __('Utente sospeso: prestiti non consentiti'); case 'card_expired': return __('Tessera scaduta: rinnovala per richiedere prestiti'); + // Circulation rules (CirculationPolicy: patron type × material). + case 'max_loans_reached': + return __('Hai raggiunto il numero massimo di prestiti attivi consentiti'); + case 'media_limit_reached': + return __('Hai raggiunto il numero massimo di prestiti per questo tipo di materiale'); + case 'not_loanable': + return __('Questo materiale è solo in consultazione: non può essere preso in prestito'); + case 'reservations_disabled': + return __('Le prenotazioni non sono consentite per la tua tipologia di lettore'); + case 'reservation_limit_reached': + return __('Hai raggiunto il numero massimo di prenotazioni e richieste in attesa'); + case 'debt_block': + return __('Saldo a debito oltre la soglia consentita: prestiti bloccati fino al pagamento'); default: return __('Utente non idoneo al prestito'); } @@ -117,8 +141,16 @@ public static function promotableReservationWhere(string $alias): string * * Requires ONE positional placeholder bound to the application date. * $alias is a fixed table alias supplied by application code. + * + * The debt block reads its thresholds (patron type, default type, global + * setting) inside the fragment, so no caller has to bind an extra value: + * with no threshold anywhere the predicate is the pre-0.9.0 one. The + * balance subquery is served by the covering index + * movimenti_lettore(utente_id, importo). $db is probed once per request + * for the 0.9.0 tables (code ships before its migration runs): without + * the ledger table the fragment carries no debt predicate at all. */ - public static function eligibleUserWhere(string $alias): string + public static function eligibleUserWhere(string $alias, \mysqli $db): string { return "( {$alias}.stato = 'attivo' @@ -127,6 +159,57 @@ public static function eligibleUserWhere(string $alias): string OR {$alias}.data_scadenza_tessera IS NULL OR {$alias}.data_scadenza_tessera >= ? ) + AND " . self::debtBlockSql($alias, $db) . " )"; } + + /** + * SQL twin of CirculationPolicy::debtBlockError(): true when the user is + * NOT debt-blocked. $alias is the `utenti` alias, a fixed literal. + * + * Threshold, first non-NULL of: the patron's own type, the default type + * (first predefinita = 1 in display order, as CirculationPolicy::types()), + * the global loans.debt_block_threshold when it is a well-formed amount. + * A threshold of NULL or 0 never blocks. With no movimenti_lettore table + * (PatronLedger::schemaReadyOrFail()) or with the fines module off + * (PatronLedger::activeOrFail()) nobody is blocked: "1 = 1"; a failed + * probe throws rather than dropping the predicate. While the legacy + * penalty backfill is pending the unconverted prestiti.sanzione amounts + * count too (PatronLedger::debtBalanceSql()). + */ + public static function debtBlockSql(string $alias, \mysqli $db): string + { + if (!\App\Services\PatronLedger::activeOrFail($db) || !Features::enabled('debt_block')) { + return '1 = 1'; + } + $threshold = self::debtThresholdSql($alias, $db); + return "( + COALESCE({$threshold}, 0) <= 0 + OR " . \App\Services\PatronLedger::debtBalanceSql($alias . '.id', $db) . " <= {$threshold} + )"; + } + + /** + * SQL expression for the debt threshold that applies to the `utenti` row + * $alias (NULL = none). Without the patron type tables + * (CirculationPolicy::schemaReady()) only the global setting applies. + */ + public static function debtThresholdSql(string $alias, \mysqli $db): string + { + $global = "(SELECT CAST(REPLACE(dbt_g.setting_value, ',', '.') AS DECIMAL(10,2)) FROM system_settings dbt_g + WHERE dbt_g.category = 'loans' AND dbt_g.setting_key = 'debt_block_threshold' + AND dbt_g.setting_value REGEXP '^[0-9]{1,8}([.,][0-9]{1,2}){0,1}$' + LIMIT 1)"; + if (!(new \App\Services\CirculationPolicy($db))->rulesActive()) { + return $global; + } + return "COALESCE( + (SELECT dbt_t.soglia_blocco_debito FROM tipologie_lettore dbt_t + WHERE dbt_t.id = COALESCE( + (SELECT dbt_o.id FROM tipologie_lettore dbt_o WHERE dbt_o.id = {$alias}.tipologia_id), + (SELECT dbt_d.id FROM tipologie_lettore dbt_d WHERE dbt_d.predefinita = 1 + ORDER BY dbt_d.ordinamento ASC, dbt_d.nome ASC, dbt_d.id ASC LIMIT 1))), + {$global} + )"; + } } diff --git a/app/Support/LoanPdfGenerator.php b/app/Support/LoanPdfGenerator.php index 792887031..68fb1abc3 100644 --- a/app/Support/LoanPdfGenerator.php +++ b/app/Support/LoanPdfGenerator.php @@ -15,7 +15,7 @@ */ class LoanPdfGenerator { - private mysqli $db; + protected mysqli $db; public function __construct(mysqli $db) { @@ -64,7 +64,7 @@ public function generate(int $loanId): string /** * Configure PDF document metadata and settings */ - private function configurePdf(TCPDF $pdf, string $appName): void + protected function configurePdf(TCPDF $pdf, string $appName): void { // Document metadata $pdf->SetCreator($appName); @@ -86,7 +86,7 @@ private function configurePdf(TCPDF $pdf, string $appName): void * * @return string|null Absolute path to logo file, or null if not found */ - private function resolveLogoPath(): ?string + protected function resolveLogoPath(): ?string { $logoRelative = Branding::fullLogo(); // e.g., '/assets/brand/logo.png' @@ -110,7 +110,7 @@ private function resolveLogoPath(): ?string /** * Render PDF header with logo and library name */ - private function renderHeader(TCPDF $pdf, string $appName, ?string $logoPath): void + protected function renderHeader(TCPDF $pdf, string $appName, ?string $logoPath, ?string $documentTitle = null): void { // Logo (scaled to fit max 60x25mm) if ($logoPath) { @@ -156,7 +156,7 @@ private function renderHeader(TCPDF $pdf, string $appName, ?string $logoPath): v // Document title (centered, regular, medium) $pdf->SetFont('dejavusans', '', 14); - $pdf->Cell(0, 8, __('Ricevuta di Prestito'), 0, 1, 'C'); + $pdf->Cell(0, 8, $documentTitle ?? __('Ricevuta di Prestito'), 0, 1, 'C'); // Separator line $pdf->Ln(3); @@ -231,7 +231,7 @@ private function renderLoanDetails(TCPDF $pdf, array $loan): void /** * Render a section with title and field-value pairs */ - private function renderSection(TCPDF $pdf, string $title, array $fields): void + protected function renderSection(TCPDF $pdf, string $title, array $fields): void { // Section title (gray background) $pdf->SetFont('dejavusans', 'B', 12); @@ -256,7 +256,7 @@ private function renderSection(TCPDF $pdf, string $title, array $fields): void /** * Render PDF footer with generation timestamp */ - private function renderFooter(TCPDF $pdf): void + protected function renderFooter(TCPDF $pdf): void { $pdf->Ln(10); diff --git a/app/Support/LoanWindow.php b/app/Support/LoanWindow.php index 33b55a942..0987af5a5 100644 --- a/app/Support/LoanWindow.php +++ b/app/Support/LoanWindow.php @@ -133,10 +133,17 @@ public static function explainFromParams(array $params): ?string * later commitment on the loan's own copy. * @param string|null $extensionBase Bulk extensions of overdue loans count * their days from today, not the past due date. + * @param LibraryCalendar|null $calendar The closures calendar: the proposed + * due date must be an open day, and a due date moved forward past a + * closure would run into the conflict, so it steps back to the last + * open day before the first blocked one. + * @param int|null $sedeId The branch of the loan's copy: renewals move due + * dates forward with its closures too, so the step back uses them as + * well (otherwise "renew until D" could move D onto the conflict). * @return array{first_conflict_date: string, max_due_date: string, max_extension_days: int}|null * null when nothing bounds the extension. */ - public static function renewalCap(string $currentDue, array $conflictDates, ?string $extensionBase = null): ?array + public static function renewalCap(string $currentDue, array $conflictDates, ?string $extensionBase = null, ?LibraryCalendar $calendar = null, ?int $sedeId = null): ?array { $future = array_filter($conflictDates, static fn (string $d): bool => $d > $currentDue); if ($future === []) { @@ -144,6 +151,9 @@ public static function renewalCap(string $currentDue, array $conflictDates, ?str } $firstConflict = min($future); $maxDue = (new DateTime($firstConflict))->modify('-1 day')->format('Y-m-d'); + if ($calendar !== null && $calendar->hasRules()) { + $maxDue = $calendar->previousOpenDay($maxDue, $sedeId); + } $base = max($currentDue, $extensionBase ?? $currentDue); // An existing conflict before today's bulk-extension base leaves no // extension possible; never propose a due date in the past. @@ -185,9 +195,9 @@ public static function explainRenewal(int $maxDays, string $maxDueDate, string $ * @param string[] $conflictDates * @return array{max_extension_days?: int, max_due_date?: string, blocked_from?: string} */ - public static function renewalRedirectParams(string $currentDue, array $conflictDates, ?string $extensionBase = null): array + public static function renewalRedirectParams(string $currentDue, array $conflictDates, ?string $extensionBase = null, ?LibraryCalendar $calendar = null, ?int $sedeId = null): array { - $cap = self::renewalCap($currentDue, $conflictDates, $extensionBase); + $cap = self::renewalCap($currentDue, $conflictDates, $extensionBase, $calendar, $sedeId); if ($cap === null) { return []; } diff --git a/app/Support/MaintenanceService.php b/app/Support/MaintenanceService.php index f58160d02..86eb17e03 100644 --- a/app/Support/MaintenanceService.php +++ b/app/Support/MaintenanceService.php @@ -43,7 +43,7 @@ public function __construct(mysqli $db) * non basta, due admin in sessioni diverse eseguirebbero entrambi runAll(). * * @param int $cooldownMinutes Minimum minutes between runs (default: 60) - * @return array{skipped?: bool, reason?: string, scheduled_loans_activated?: int, invalid_ready_pickups_repaired?: int, expired_waitlist_reservations?: int, reservations_converted?: int, expired_reservations?: int, expired_pickups?: int, overdue_loans_updated?: int, expiration_warnings?: int, overdue_notifications?: int, wishlist_notifications?: int, reservation_notifications_retried?: int, pickup_notifications_retried?: int, ics_generated?: bool, errors?: array} Results or skip status + * @return array{skipped?: bool, reason?: string, scheduled_loans_activated?: int, invalid_ready_pickups_repaired?: int, expired_waitlist_reservations?: int, reservations_converted?: int, expired_reservations?: int, expired_pickups?: int, overdue_loans_updated?: int, expiration_warnings?: int, overdue_notifications?: int, loan_recalls?: int, pickup_reminders?: int, card_expiry_warnings?: int, wishlist_notifications?: int, reservation_notifications_retried?: int, pickup_notifications_retried?: int, ics_generated?: bool, errors?: array} Results or skip status */ public function runIfNeeded(int $cooldownMinutes = 60): array { @@ -100,6 +100,18 @@ public function runIfNeeded(int $cooldownMinutes = 60): array return $this->runAll(); } + /** Apply retention independently of new visit collection when explicitly enabled. */ + public function purgeAttendance(): int + { + if (!Features::enabled('visitor_log') && !Features::enabled('attendance_retention')) { + return 0; + } + $attendance = new \App\Models\AttendanceRepository($this->db); + return $attendance->isAvailable() + ? $attendance->purge(\App\Models\AttendanceRepository::retentionDays(new \App\Models\SettingsRepository($this->db))) + : 0; + } + /** * Run all maintenance tasks immediately * @@ -107,7 +119,7 @@ public function runIfNeeded(int $cooldownMinutes = 60): array * overdue loan updates, expired pickups, notifications, and ICS calendar generation. * Each task is wrapped in try-catch to prevent failures from blocking others. * - * @return array{skipped: true, reason: 'in_progress'}|array{scheduled_loans_activated: int, invalid_ready_pickups_repaired: int, expired_waitlist_reservations: int, reservations_converted: int, expired_reservations: int, expired_pickups: int, overdue_loans_updated: int, expiration_warnings: int, overdue_notifications: int, wishlist_notifications: int, reservation_notifications_retried: int, pickup_notifications_retried: int, queued_emails_retried: int, ics_generated: bool, errors: array} Results for each maintenance task, or the skip marker when another connection holds the maintenance lock + * @return array{skipped: true, reason: 'in_progress'}|array{scheduled_loans_activated: int, invalid_ready_pickups_repaired: int, expired_waitlist_reservations: int, reservations_converted: int, expired_reservations: int, expired_pickups: int, overdue_loans_updated: int, expiration_warnings: int, overdue_notifications: int, loan_recalls: int, pickup_reminders: int, card_expiry_warnings: int, wishlist_notifications: int, reservation_notifications_retried: int, pickup_notifications_retried: int, queued_emails_retried: int, ics_generated: bool, attendance_purged?: int, holiday_rows_added?: int, errors: array} Results for each maintenance task, or the skip marker when another connection holds the maintenance lock */ public function runAll(): array { @@ -147,7 +159,7 @@ public function runAll(): array } } - /** @return array{scheduled_loans_activated: int, invalid_ready_pickups_repaired: int, expired_waitlist_reservations: int, reservations_converted: int, expired_reservations: int, expired_pickups: int, overdue_loans_updated: int, expiration_warnings: int, overdue_notifications: int, wishlist_notifications: int, reservation_notifications_retried: int, pickup_notifications_retried: int, queued_emails_retried: int, ics_generated: bool, errors: array} */ + /** @return array{scheduled_loans_activated: int, invalid_ready_pickups_repaired: int, expired_waitlist_reservations: int, reservations_converted: int, expired_reservations: int, expired_pickups: int, overdue_loans_updated: int, expiration_warnings: int, overdue_notifications: int, loan_recalls: int, pickup_reminders: int, card_expiry_warnings: int, wishlist_notifications: int, reservation_notifications_retried: int, pickup_notifications_retried: int, queued_emails_retried: int, ics_generated: bool, attendance_purged?: int, holiday_rows_added?: int, errors: array} */ private function runAllLocked(): array { $results = [ @@ -160,6 +172,9 @@ private function runAllLocked(): array 'overdue_loans_updated' => 0, 'expiration_warnings' => 0, 'overdue_notifications' => 0, + 'loan_recalls' => 0, + 'pickup_reminders' => 0, + 'card_expiry_warnings' => 0, 'wishlist_notifications' => 0, 'reservation_notifications_retried' => 0, 'pickup_notifications_retried' => 0, @@ -179,6 +194,29 @@ private function runAllLocked(): array $results['errors'][] = 'contributorBackfill: ' . $e->getMessage(); } + // 0.9.0 (plan §7): convert any prestiti.sanzione the migration's + // batches did not reach into ledger rows. Marker-guarded so the scan + // runs until it completes once; new penalties are written live with + // the same idempotency key. The marker is written only after a scan + // that ran to the end: with the ledger table still missing (code + // deployed before its migration) the backfill returns null and a + // failed batch throws, so the next pass tries again. + try { + \App\Services\PatronLedger::completeLegacyPenaltyBackfill($this->db); + } catch (\Throwable $e) { + $results['errors'][] = 'legacyPenaltyBackfill: ' . $e->getMessage(); + } + + // Holiday presets (0.9.0): the movable holidays the library ticked get + // their rows up to two years ahead, so a new year never starts with + // Easter missing. Before the due dates and pickups of this pass. + try { + $results['holiday_rows_added'] = $this->extendHolidayRows(); + } catch (\Throwable $e) { + $results['errors'][] = 'extendHolidayRows: ' . $e->getMessage(); + SecureLogger::error(__('MaintenanceService errore festività'), ['error' => $e->getMessage()]); + } + // Overdue flip FIRST (#366 residual): flip date-overdue 'in_corso' loans // to 'in_ritardo' BEFORE expiries/activations/promotions, so every gate // in this same pass that special-cases 'in_ritardo' (capacity clamps, @@ -248,7 +286,9 @@ private function runAllLocked(): array $notificationResults = $this->runNotifications(); $results['expiration_warnings'] = $notificationResults['expiration_warnings']; $results['overdue_notifications'] = $notificationResults['overdue_notifications']; + $results['loan_recalls'] = $notificationResults['loan_recalls']; $results['pickup_reminders'] = $notificationResults['pickup_reminders']; + $results['card_expiry_warnings'] = $notificationResults['card_expiry_warnings']; $results['wishlist_notifications'] = $notificationResults['wishlist_notifications']; } catch (\Throwable $e) { $results['errors'][] = 'runNotifications: ' . $e->getMessage(); @@ -321,6 +361,16 @@ private function runAllLocked(): array SecureLogger::error(__('MaintenanceService errore pulizia sessioni scadute'), ['error' => $e->getMessage()]); } + // Visitor log retention (GDPR, 0.9.0): visits older than the + // configured number of days are deleted. 0 days = keep forever; with + // no setting the default retention applies. + try { + $results['attendance_purged'] = $this->purgeAttendance(); + } catch (\Throwable $e) { + $results['errors'][] = 'purgeAttendance: ' . $e->getMessage(); + SecureLogger::error(__('MaintenanceService errore pulizia presenze'), ['error' => $e->getMessage()]); + } + // Generic post-maintenance hook, fired as the TRUE last step so // listeners (e.g. Book Club poll auto-close + reminders) only run // once the whole maintenance pass — ICS generation included — is @@ -426,6 +476,26 @@ public static function icsHasPersonalData(string $ics): bool return false; } + /** + * The rows of the chosen movable holidays up to two years ahead + * (HolidayPresets::ensureYears()). Skipped while the closures calendar + * is switched off (features.closures_calendar): no row is written, and the + * ones missing are added by the first pass after it is switched back on. + * Pending pickups are re-checked when rows were added. + */ + public function extendHolidayRows(): int + { + if (!Features::enabled('closures_calendar')) { + return 0; + } + $added = \App\Support\Holidays\HolidayPresets::ensureYears($this->db); + if ($added > 0) { + LibraryCalendar::reset(); + LibraryCalendar::extendPickupDeadlines($this->db); + } + return $added; + } + /** * Generate ICS calendar file for loans and reservations * @@ -450,14 +520,16 @@ public function generateIcsCalendar(): bool * Delegates to NotificationService to send loan expiration warnings, * overdue loan notifications, and wishlist availability alerts. * - * @return array{expiration_warnings: int, overdue_notifications: int, pickup_reminders: int, wishlist_notifications: int, errors: array} Notification counts and any errors + * @return array{expiration_warnings: int, overdue_notifications: int, loan_recalls: int, pickup_reminders: int, card_expiry_warnings: int, wishlist_notifications: int, errors: array} Notification counts and any errors */ public function runNotifications(): array { $results = [ 'expiration_warnings' => 0, 'overdue_notifications' => 0, + 'loan_recalls' => 0, 'pickup_reminders' => 0, + 'card_expiry_warnings' => 0, 'wishlist_notifications' => 0, 'errors' => [] ]; @@ -466,9 +538,14 @@ public function runNotifications(): array $notificationService = new NotificationService($this->db); $notifResults = $notificationService->runAutomaticNotifications(); + // Every counter runAutomaticNotifications() reports, so the cron + // log of full-maintenance.php names the recalls and the card + // expiry warnings it sent, like automatic-notifications.php does. $results['expiration_warnings'] = $notifResults['expiration_warnings'] ?? 0; $results['overdue_notifications'] = $notifResults['overdue_notifications'] ?? 0; + $results['loan_recalls'] = $notifResults['loan_recalls'] ?? 0; $results['pickup_reminders'] = $notifResults['pickup_reminders'] ?? 0; + $results['card_expiry_warnings'] = $notifResults['card_expiry_warnings'] ?? 0; $results['wishlist_notifications'] = $notifResults['wishlist_notifications'] ?? 0; if (!empty($notifResults['errors'])) { @@ -924,6 +1001,7 @@ public function activateScheduledLoans(): int FROM copie c WHERE c.libro_id = ? AND c.stato IN ('disponibile', 'prenotato') + AND " . (new \App\Services\CirculationPolicy($this->db))->lendableCopySql('c') . " AND NOT EXISTS ( SELECT 1 FROM prestiti own @@ -977,7 +1055,8 @@ public function activateScheduledLoans(): int // data_scadenza (L1): senza il cap un prestito con finestra corta // restava ritirabile (e la copia bloccata) oltre la fine del // prestito stesso. - $pickupDeadline = date('Y-m-d', strtotime($today . " +{$pickupDays} days")); + // Open days only (closures calendar); identical to +N days without closures. + $pickupDeadline = LibraryCalendar::pickupDeadline($this->db, $today, $pickupDays, null, $copiaId); if (!empty($loan['data_scadenza']) && $loan['data_scadenza'] < $pickupDeadline) { $pickupDeadline = $loan['data_scadenza']; } @@ -1077,7 +1156,7 @@ public function processScheduledReservations(): int JOIN utenti u ON p.utente_id = u.id WHERE p.stato = 'attiva' AND " . \App\Support\LoanEligibility::promotableReservationWhere('p') . " - AND " . \App\Support\LoanEligibility::eligibleUserWhere('u') . " + AND " . \App\Support\LoanEligibility::eligibleUserWhere('u', $this->db) . " ORDER BY p.libro_id, p.queue_position ASC "); @@ -1178,6 +1257,10 @@ public function processScheduledReservations(): int public function checkExpiredReservations(): int { $today = DateHelper::today(); + // Closures calendar: nothing expires on a day the library is closed. + if (!LibraryCalendar::forDb($this->db)->isOpen($today)) { + return 0; + } // Find expired reservations. Oltre alle conversioni promosse // (origine='prenotazione'), anche le richieste dirette e NCIP mai @@ -1256,6 +1339,12 @@ public function checkExpiredReservations(): int continue; } $copiaId = $lockedReservation['copia_id'] ? (int) $lockedReservation['copia_id'] : null; + // A closure of the assigned copy's branch counts too: the + // reader could not come in there today either. + if ($copiaId !== null && !LibraryCalendar::forDb($this->db)->isOpen($today, LibraryCalendar::branchOfCopy($this->db, $copiaId))) { + $this->db->rollback(); + continue; + } // Audit: snapshot pre-mutazione, l'evento è registrato DENTRO la // stessa transazione dell'UPDATE (regola di progetto). @@ -1391,10 +1480,15 @@ public function checkExpiredReservations(): int public function checkExpiredPickups(): int { $today = DateHelper::today(); + // Closures calendar: nothing expires on a day the library is closed. + $calendar = LibraryCalendar::forDb($this->db); + if (!$calendar->isOpen($today)) { + return 0; + } // Find expired pickups (da_ritirare with pickup_deadline passed) $stmt = $this->db->prepare(" - SELECT id, libro_id, copia_id, utente_id + SELECT id, libro_id, copia_id, utente_id, pickup_deadline FROM prestiti WHERE stato = 'da_ritirare' AND attivo = 1 @@ -1411,6 +1505,10 @@ public function checkExpiredPickups(): int $result = $stmt->get_result(); $expiredPickups = $result ? $result->fetch_all(MYSQLI_ASSOC) : []; $stmt->close(); + // A deadline that fell on a day later declared closed was already moved + // to the next open day when the calendar changed + // (LibraryCalendar::extendPickupDeadlines()), so the stored deadline is + // the one rule every path reads. $expiredCount = 0; $integrity = new DataIntegrity($this->db); @@ -1457,6 +1555,12 @@ public function checkExpiredPickups(): int continue; } $copiaId = $lockedPickup['copia_id'] ? (int) $lockedPickup['copia_id'] : null; + // A closure of the copy's branch today: the pickup cannot + // expire on a day the reader could not come in. + if ($copiaId !== null && !$calendar->isOpen($today, LibraryCalendar::branchOfCopy($this->db, $copiaId))) { + $this->db->rollback(); + continue; + } // Audit: snapshot pre-mutazione, l'evento è registrato DENTRO la // stessa transazione dell'UPDATE (regola di progetto). diff --git a/app/Support/Marc/BookMarcBuilder.php b/app/Support/Marc/BookMarcBuilder.php new file mode 100644 index 000000000..55db4cfda --- /dev/null +++ b/app/Support/Marc/BookMarcBuilder.php @@ -0,0 +1,652 @@ + $row + * @param list> $authors + * @param list> $publishers + * @param array|null $genre + */ + public static function marc21(array $row, array $authors, array $publishers, ?array $genre, bool $extended = false): MarcRecord + { + $isSerial = self::isSerialRecord($row); + $tipoMedia = isset($row['tipo_media']) ? (string) $row['tipo_media'] : null; + // Leader: type of record from the media type (a language material, + // i/j sound recording, g projected medium), bibliographic level 'm' + // (monograph), 's' for a serial. Leader/18 stays blank: the record + // carries no ISBD punctuation, so 'i' (ISBD) would be a false claim. + $record = new MarcRecord('00000n' . MarcMediaType::leaderType($tipoMedia) . ($isSerial ? 's' : 'm') . ' a2200000 4500'); + + // 001 — Control number (book id); 003 — MARC organization code + $record->addControlField('001', (string) $row['id']); + $record->addControlField('003', 'IT-Pinakes'); + // 005 — Date/time of latest transaction + $ts = strtotime((string) ($row['updated_at'] ?? 'now')) ?: time(); + $record->addControlField('005', gmdate('YmdHis', $ts) . '.0'); + // 007 — Physical description: the category of material the media + // type says (text, sound disc, videodisc); none for "altro". + $f007 = MarcMediaType::field007($tipoMedia); + if ($f007 !== null) { + $record->addControlField('007', $f007); + } + // 008 — Fixed-length data elements (exactly 40 characters) + $record->addControlField('008', self::marc21Field008($row)); + + // 020 — ISBN + if (!empty($row['isbn13'])) { + $record->addDataField('020', ' ', ' ', [['a', (string) $row['isbn13']]]); + } + if (!empty($row['isbn10'])) { + $record->addDataField('020', ' ', ' ', [['a', (string) $row['isbn10']]]); + } + + // 022 — ISSN of the resource itself: serials only. On a monograph the + // ISSN belongs to its series and is carried in 490 $x below. + if (!empty($row['issn']) && $isSerial) { + $record->addDataField('022', ' ', ' ', [['a', (string) $row['issn']]]); + } + + // 024 — EAN (extended), when it is not the ISBN-13 again + if ($extended) { + $ean = trim((string) ($row['ean'] ?? '')); + if ($ean !== '' && preg_replace('/\D/', '', $ean) !== preg_replace('/\D/', '', (string) ($row['isbn13'] ?? ''))) { + $record->addDataField('024', '3', ' ', [['a', $ean]]); + } + } + + // 040 — Cataloging source + $record->addDataField('040', ' ', ' ', [ + ['a', 'IT-Pinakes'], + ['b', 'ita'], + ['e', 'rda'], + ['c', 'IT-Pinakes'], + ]); + + // 041 — Languages: one $a per language ("inglese, italiano" is two) + if (!empty($row['lingua'])) { + $codes = []; + foreach (MarcLanguage::listOf((string) $row['lingua']) as $language) { + $code = MarcLanguage::codeFor($language); + if ($code !== null && !in_array($code, $codes, true)) { + $codes[] = $code; + } + } + if ($codes === []) { + $codes = ['und']; + } + $record->addDataField('041', '0', ' ', array_map(static fn (string $c): array => ['a', $c], $codes)); + } + + // 050 — LC classification (extended) + if ($extended && trim((string) ($row['lc_classification'] ?? '')) !== '') { + $record->addDataField('050', ' ', '4', [['a', trim((string) $row['lc_classification'])]]); + } + + // 082 — Dewey classification + if (!empty($row['classificazione_dewey'])) { + $record->addDataField('082', '0', '4', [ + ['a', (string) $row['classificazione_dewey']], + ['2', '23'], + ]); + } + + // 100/700 — creators and role-aware contributors. + $primaryCreatorIndex = self::primaryCreatorIndex($authors); + foreach ($authors as $index => $a) { + $name = (string) $a['nome']; + $relator = match ((string) ($a['ruolo'] ?? '')) { + 'traduttore' => 'translator', + 'illustratore' => 'illustrator', + 'curatore' => 'editor', + 'colorista' => 'colorist', + default => 'author', + }; + $record->addDataField($index === $primaryCreatorIndex ? '100' : '700', '1', ' ', [ + ['a', $name], + ['e', $relator], + ]); + } + + // 245 — Title statement + $titleSubs = [['a', (string) ($row['titolo'] ?? '')]]; + if (!empty($row['sottotitolo'])) { + $titleSubs[] = ['b', (string) $row['sottotitolo']]; + } + $entityRoles = array_map(static fn (array $a): string => (string) ($a['ruolo'] ?? ''), $authors); + if (!empty($row['traduttore']) && !in_array('traduttore', $entityRoles, true)) { + $titleSubs[] = ['c', 'traduzione di ' . (string) $row['traduttore']]; + } + $record->addDataField('245', $primaryCreatorIndex !== null ? '1' : '0', '0', $titleSubs); + + // 250 — Edition + if (!empty($row['edizione'])) { + $record->addDataField('250', ' ', ' ', [['a', (string) $row['edizione']]]); + } + + // 264 — Production/Publication (RDA). MARC21 $b (publisher name) is + // repeatable, so multiple publishers become repeated $b in one 264. + $pubSubs = []; + if (!empty($row['luogo_pubblicazione'])) { + $pubSubs[] = ['a', (string) $row['luogo_pubblicazione']]; + } + foreach ($publishers as $pub) { + if (!empty($pub['nome'])) { + $pubSubs[] = ['b', (string) $pub['nome']]; + } + } + if (!empty($row['anno_pubblicazione'])) { + $pubSubs[] = ['c', (string) $row['anno_pubblicazione']]; + } + if ($pubSubs !== []) { + $record->addDataField('264', ' ', '1', $pubSubs); + } + + // 300 — Physical description + $physSubs = []; + if (!empty($row['numero_pagine'])) { + $physSubs[] = ['a', (string) $row['numero_pagine'] . ' pages']; + } + if (!empty($row['dimensioni'])) { + $physSubs[] = ['c', (string) $row['dimensioni']]; + } + if ($physSubs !== []) { + $record->addDataField('300', ' ', ' ', $physSubs); + } + + // 336 / 337 / 338 — RDA content, media and carrier types (040 $e + // says rda): what the media type implies, or the book's own codes. + $rda = MarcMediaType::rdaCodes($row); + foreach (['336' => ['content', 'rdacontent'], '337' => ['media', 'rdamedia'], '338' => ['carrier', 'rdacarrier']] as $tag => [$kind, $source]) { + $subs = []; + $term = MarcMediaType::term($kind, $rda[$kind]); + if ($term !== null) { + $subs[] = ['a', $term]; + } + $subs[] = ['b', $rda[$kind]]; + $subs[] = ['2', $source]; + $record->addDataField((string) $tag, ' ', ' ', $subs); + } + + // 490 — Series statement + if (!empty($row['collana'])) { + $serSubs = [['a', (string) $row['collana']]]; + if (!empty($row['issn']) && !$isSerial) { + $serSubs[] = ['x', (string) $row['issn']]; + } + if (!empty($row['numero_serie'])) { + $serSubs[] = ['v', (string) $row['numero_serie']]; + } + $record->addDataField('490', '0', ' ', $serSubs); + } + + // 520 — Summary + $desc = self::plainDescription($row); + if ($desc !== null) { + $record->addDataField('520', ' ', ' ', [['a', $desc]]); + } + + // 650 — Subject: the keywords (source not specified) + foreach (self::keywords($row) as $kw) { + $record->addDataField('650', ' ', '4', [['a', $kw]]); + } + // 655 — Genre, as the catalogue's own term (ind2 7, $2 local), so an + // import tells it from a keyword. + if ($genre !== null && !empty($genre['nome'])) { + $record->addDataField('655', ' ', '7', [['a', (string) $genre['nome']], ['2', 'local']]); + } + + // 700 — legacy contributors not already represented by entities. + foreach (['traduttore' => 'translator', 'illustratore' => 'illustrator', 'curatore' => 'editor'] as $col => $role) { + if (!empty($row[$col]) && !in_array($col, $entityRoles, true)) { + $record->addDataField('700', '1', ' ', [ + ['a', (string) $row[$col]], + ['e', $role], + ]); + } + } + + // 856 — cover image (extended) + if ($extended) { + $cover = self::coverUrl($row); + if ($cover !== '') { + $record->addDataField('856', '4', '2', [['3', 'Cover image'], ['u', $cover]]); + } + } + + return $record; + } + + /** + * The UNIMARC field of a book's genre: a 608 form / genre access point + * with $2 local, the counterpart of the MARC 21 655 the genre goes to. + * A 606 is a topical subject, so another catalogue's 606 $2 local stays a + * keyword on re-import (UnimarcLibriMapper). Every UNIMARC writer of this + * application (this builder, the SRU server's UNIMARC/XML formatter) uses + * it, so a Pinakes-to-Pinakes copy keeps the genre. Null without a genre. + * + * @return array{0: string, 1: string, 2: string, 3: list}|null + */ + public static function unimarcGenreField(string $genre): ?array + { + $genre = trim($genre); + return $genre === '' ? null : ['608', ' ', ' ', [['a', $genre], ['2', 'local']]]; + } + + /** + * @param array $row + * @param list> $authors + * @param list> $publishers + * @param array|null $genre + */ + public static function unimarc(array $row, array $authors, array $publishers, ?array $genre, bool $extended = false): MarcRecord + { + // Label/06 (type of record) from the media type, as in MARC 21; + // label/07 (bibliographic level) is 's' for a serial, 'm' otherwise. + $isSerial = self::isSerialRecord($row); + $leader = substr_replace(self::UNIMARC_LEADER_TEMPLATE, MarcMediaType::leaderType(isset($row['tipo_media']) ? (string) $row['tipo_media'] : null), 6, 1); + $record = new MarcRecord(substr_replace($leader, $isSerial ? 's' : 'm', 7, 1)); + $record->setFormat('UNIMARC'); + + // 001 — Record identifier + $record->addControlField('001', (string) $row['id']); + // 003 — Persistent record identifier source (repository base URL) + $record->addControlField('003', function_exists('absoluteUrl') ? \absoluteUrl('/') : '/'); + // 005 — Version identifier: date/time of the latest modification + $updated = strtotime((string) ($row['updated_at'] ?? '')) ?: time(); + $record->addControlField('005', gmdate('YmdHis', $updated) . '.0'); + + // 010 — ISBN, one field per form the book has (ISBN-13 and ISBN-10, + // as MARC 21 repeats 020): a re-import gets both back. 073 — EAN (an + // EAN that is just the ISBN-13 again is not repeated). + foreach (['isbn13', 'isbn10'] as $col) { + if (!empty($row[$col])) { + $record->addDataField('010', ' ', ' ', [['a', (string) $row[$col]]]); + } + } + // 011 — ISSN, for a serial (the UNIMARC counterpart of MARC 21 022). + if ($isSerial && !empty($row['issn'])) { + $record->addDataField('011', ' ', ' ', [['a', (string) $row['issn']]]); + } + $ean = trim((string) ($row['ean'] ?? '')); + $isbn13Digits = preg_replace('/\D/', '', (string) ($row['isbn13'] ?? '')); + if ($ean !== '' && preg_replace('/\D/', '', $ean) !== $isbn13Digits) { + $record->addDataField('073', ' ', ' ', [['a', $ean]]); + } + + // 100 — General processing data + $record->addDataField('100', ' ', ' ', [['a', self::unimarcField100a($row)]]); + + // 101 — Language of the item, one $a per language ("inglese, + // italiano" is two, as in the MARC 21 041); 102 — Country of publication + $langCodes = []; + foreach (MarcLanguage::listOf((string) ($row['lingua'] ?? '')) as $language) { + $code = MarcLanguage::codeFor($language); + if ($code !== null && !in_array($code, $langCodes, true)) { + $langCodes[] = $code; + } + } + if ($langCodes === []) { + $langCodes = [MarcLanguage::marcCodeOf((string) ($row['lingua'] ?? 'italiano'))]; + } + $record->addDataField('101', '0', ' ', array_map(static fn (string $c): array => ['a', $c], $langCodes)); + $record->addDataField('102', ' ', ' ', [['a', 'IT']]); + + // 181 — RDA content type ($c, $2 rdacontent), when it is not plain + // text: the label/06 alone cannot tell an "altro" (content "other") + // from a book, nor performed music from spoken word. + $rdaContent = MarcMediaType::rdaCodes($row)['content']; + if ($rdaContent !== 'txt') { + $record->addDataField('181', ' ', ' ', [['c', $rdaContent], ['2', 'rdacontent']]); + } + + // 200 — Title and statement of responsibility + $subs200 = [['a', (string) ($row['titolo'] ?? '')]]; + if (!empty($row['sottotitolo'])) { + $subs200[] = ['e', (string) $row['sottotitolo']]; + } + $primaryCreatorIndex = self::primaryCreatorIndex($authors); + if ($primaryCreatorIndex !== null) { + $subs200[] = ['f', (string) $authors[$primaryCreatorIndex]['nome']]; + } + $record->addDataField('200', '1', ' ', $subs200); + + // 205 — Edition statement + if (!empty($row['edizione'])) { + $record->addDataField('205', ' ', ' ', [['a', (string) $row['edizione']]]); + } + + // 210 — Publication, distribution. $c (publisher name) is repeatable, + // so multiple publishers become repeated $c in one 210. + $subs210 = []; + if (!empty($row['luogo_pubblicazione'])) { + $subs210[] = ['a', (string) $row['luogo_pubblicazione']]; + } + foreach ($publishers as $pub) { + if (!empty($pub['nome'])) { + $subs210[] = ['c', (string) $pub['nome']]; + } + } + $year = (string) ($row['anno_pubblicazione'] ?? ''); + if ($year !== '') { + $subs210[] = ['d', $year]; + } + if ($subs210 !== []) { + $record->addDataField('210', ' ', ' ', $subs210); + } + + // 215 — Physical description + $subs215 = []; + if (!empty($row['numero_pagine'])) { + $subs215[] = ['a', $row['numero_pagine'] . ' p.']; + } + if (!empty($row['dimensioni'])) { + $subs215[] = ['d', (string) $row['dimensioni']]; + } + if ($subs215 !== []) { + $record->addDataField('215', ' ', ' ', $subs215); + } + + // 225 — Series (UNIMARC equivalent of MARC21 490) + if (!empty($row['collana'])) { + $subs225 = [['a', (string) $row['collana']]]; + // $x — the series ISSN of a monograph (a serial's own ISSN is 011), + // as MARC 21 carries it in 490 $x. + if (!empty($row['issn']) && !$isSerial) { + $subs225[] = ['x', (string) $row['issn']]; + } + if (!empty($row['numero_serie'])) { + $subs225[] = ['v', (string) $row['numero_serie']]; + } + $record->addDataField('225', '0', ' ', $subs225); + } + + // 330 — Summary / abstract + $desc = self::plainDescription($row); + if ($desc !== null) { + $record->addDataField('330', ' ', ' ', [['a', $desc]]); + } + + // 606 — Subject: the keywords. + foreach (self::keywords($row) as $kw) { + $record->addDataField('606', ' ', ' ', [['a', $kw]]); + } + // 608 — the genre (unimarcGenreField()). + $genreField = self::unimarcGenreField($genre !== null ? (string) ($genre['nome'] ?? '') : ''); + if ($genreField !== null) { + $record->addDataField($genreField[0], $genreField[1], $genreField[2], $genreField[3]); + } + + // 676 — Dewey (extended) + if ($extended && !empty($row['classificazione_dewey'])) { + $record->addDataField('676', ' ', ' ', [['a', (string) $row['classificazione_dewey']]]); + } + + // 700/701 — primary and alternative intellectual responsibility. + // 702 — secondary responsibility (translator, illustrator, etc.). + $responsibility = []; + foreach ($authors as $index => $a) { + $role = (string) ($a['ruolo'] ?? ''); + $isCreator = in_array($role, ['principale', 'co-autore'], true); + $tag = $isCreator ? ($index === $primaryCreatorIndex ? '700' : '701') : '702'; + $relCode = match ($role) { + 'traduttore' => '730', + 'curatore' => '340', + 'illustratore' => '440', + 'colorista' => '410', + default => '070', + }; + [$ind2, $nameSubs] = self::unimarcPersonalName((string) $a['nome']); + $nameSubs[] = ['4', $relCode]; + $responsibility[$tag][] = [$ind2, $nameSubs]; + } + // 702 — legacy contributors (libri.traduttore / illustratore / + // curatore) not already represented by an author entity, as the + // MARC 21 700 does. + $entityRoles = array_map(static fn (array $a): string => (string) ($a['ruolo'] ?? ''), $authors); + foreach (['traduttore' => '730', 'illustratore' => '440', 'curatore' => '340'] as $col => $relCode) { + if (!empty($row[$col]) && !in_array($col, $entityRoles, true)) { + [$ind2, $nameSubs] = self::unimarcPersonalName((string) $row[$col]); + $nameSubs[] = ['4', $relCode]; + $responsibility['702'][] = [$ind2, $nameSubs]; + } + } + foreach (['700', '701', '702'] as $tag) { + foreach ($responsibility[$tag] ?? [] as [$ind2, $nameSubs]) { + $record->addDataField($tag, ' ', $ind2, $nameSubs); + } + } + + // 801 — Originating source + $record->addDataField('801', ' ', '0', [ + ['a', 'IT'], + ['b', 'Pinakes'], + ['c', gmdate('Ymd')], + ]); + + // 856 — cover image (extended) + if ($extended) { + $cover = self::coverUrl($row); + if ($cover !== '') { + $record->addDataField('856', '4', ' ', [['u', $cover], ['2', 'http'], ['z', 'Cover image']]); + } + } + + return $record; + } + + /** + * The index of the book's primary creator: the first 'principale', else + * the first 'co-autore'; null when the book has neither. + * + * @param list> $authors + */ + public static function primaryCreatorIndex(array $authors): ?int + { + $coauthorIndex = null; + foreach ($authors as $index => $author) { + $role = (string) ($author['ruolo'] ?? ''); + if ($role === 'principale') { + return $index; + } + if ($role === 'co-autore' && $coauthorIndex === null) { + $coauthorIndex = $index; + } + } + return $coauthorIndex; + } + + /** Whether a libri row describes a serial (MARC bibliographic level 's'). */ + public static function isSerialRecord(array $row): bool + { + return in_array(strtolower((string) ($row['tipo_media'] ?? '')), ['periodical', 'serial', 'periodico', 'rivista'], true); + } + + /** + * MARC 21 008 for books/serials: exactly 40 positions. + * 00-05 date entered (YYMMDD), 06 date type, 07-10 date 1, 11-14 date 2, + * 15-17 place of publication, 35-37 language, 39 cataloguing source. + * + * @param array $row + */ + public static function marc21Field008(array $row): string + { + $field = str_repeat(' ', 40); + $created = strtotime((string) ($row['created_at'] ?? '')) ?: time(); + $field = substr_replace($field, date('ymd', $created), 0, 6); + + $isSerial = self::isSerialRecord($row); + $field = substr_replace($field, $isSerial ? 'c' : 's', 6, 1); + + $year = trim((string) ($row['anno_pubblicazione'] ?? '')); + if ($year !== '' && ctype_digit($year) && strlen($year) <= 4) { + $field = substr_replace($field, str_pad($year, 4, '0', STR_PAD_LEFT), 7, 4); + } + if ($isSerial) { + // Continuing resource still published: date 2 = 9999. + $field = substr_replace($field, '9999', 11, 4); + } + + // Place of publication unknown/unspecified. + $field = substr_replace($field, 'xx ', 15, 3); + + if (trim((string) ($row['lingua'] ?? '')) !== '') { + $field = substr_replace($field, MarcLanguage::marcCodeOf((string) $row['lingua']), 35, 3); + } + + // 39 — cataloguing source: 'd' (other than a national agency). + return substr_replace($field, 'd', 39, 1); + } + + /** + * UNIMARC 100 $a — general processing data, exactly 36 positions: + * 0-7 date entered (YYYYMMDD) · 8 type of publication date (d single + * known date, u unknown) · 9-12 date 1 · 13-16 date 2 · 17-19 target + * audience · 20 government publication (y not a government + * publication) · 21 modified record (0) · 22-24 language of + * cataloguing · 25 transliteration (y none) · 26-29 character set + * (50 = ISO 10646) · 30-33 additional character sets · 34-35 script + * of title (ba = Latin). + * + * @param array $row + */ + public static function unimarcField100a(array $row): string + { + $entered = strtotime((string) ($row['created_at'] ?? '')) ?: time(); + $year = trim((string) ($row['anno_pubblicazione'] ?? '')); + $known = strlen($year) === 4 && ctype_digit($year); + + return date('Ymd', $entered) + . ($known ? 'd' : 'u') + . ($known ? $year : ' ') + . ' ' + . ' ' + . 'y' + . '0' + . self::cataloguingLanguage() + . 'y' + . '50 ' + . ' ' + . 'ba'; + } + + /** ISO 639-2/B language of cataloguing, from the installation locale. */ + public static function cataloguingLanguage(): string + { + try { + $locale = \App\Support\I18n::getInstallationLocale(); + } catch (\Throwable) { + $locale = 'it_IT'; + } + return match (strtolower(substr($locale, 0, 2))) { + 'en' => 'eng', + 'de' => 'ger', + 'fr' => 'fre', + 'da' => 'dan', + default => 'ita', + }; + } + + /** + * UNIMARC personal name (7XX): [ind2, subfields]. A name that can be + * inverted ("Surname, Forename", or a multi-word name whose last word is + * taken as the surname) is entered under the surname — ind2 1, $a surname + * $b forename; a single-word name stays in direct order — ind2 0, $a. + * + * @return array{0: string, 1: list} + */ + public static function unimarcPersonalName(string $name): array + { + $name = trim(preg_replace('/\s+/u', ' ', $name) ?? $name); + if (str_contains($name, ',')) { + [$surname, $forename] = array_map('trim', explode(',', $name, 2)); + if ($surname !== '' && $forename !== '') { + return ['1', [['a', $surname], ['b', $forename]]]; + } + } + $words = $name === '' ? [] : explode(' ', $name); + if (count($words) >= 2) { + $surname = (string) array_pop($words); + return ['1', [['a', $surname], ['b', implode(' ', $words)]]]; + } + return ['0', [['a', $name]]]; + } + + /** + * The summary as plain text: the stored plain projection, else the rich + * description with its markup removed and its entities decoded ("Tom + * & Jerry" is Tom & Jerry); null when the book has none (a + * description that is only markup still yields the empty summary it + * always did). + * + * @param array $row + */ + private static function plainDescription(array $row): ?string + { + if (!empty($row['descrizione_plain'])) { + return (string) $row['descrizione_plain']; + } + $desc = (string) ($row['descrizione'] ?? ''); + return $desc !== '' ? (string) \App\Support\DescriptionText::toPlain($desc) : null; + } + + /** + * @param array $row + * @return list + */ + private static function keywords(array $row): array + { + $out = []; + if (!empty($row['parole_chiave'])) { + foreach (explode(',', (string) $row['parole_chiave']) as $kw) { + $kw = trim($kw); + if ($kw !== '') { + $out[] = $kw; + } + } + } + return $out; + } + + /** + * The absolute URL of the book's cover (stored as a site path such as + * /uploads/copertine/x.jpg, or already absolute). + * + * @param array $row + */ + public static function coverUrl(array $row): string + { + $url = trim((string) ($row['copertina_url'] ?? '')); + if ($url === '' || preg_match('#^https?://#i', $url) === 1) { + return $url; + } + return function_exists('absoluteUrl') ? \absoluteUrl('/' . ltrim($url, '/')) : $url; + } +} diff --git a/app/Support/Marc/ContributorSorter.php b/app/Support/Marc/ContributorSorter.php new file mode 100644 index 000000000..bcc404275 --- /dev/null +++ b/app/Support/Marc/ContributorSorter.php @@ -0,0 +1,68 @@ + $book + * @param array> $contributors + */ + public static function sortContributor(array &$book, array &$contributors, string $name, string $role): void + { + if ($role === 'author') { + if (!in_array($name, $book['authors'], true)) { + $book['authors'][] = $name; + } + } elseif (isset($contributors[$role]) && !in_array($name, $contributors[$role], true)) { + $contributors[$role][] = $name; + } + } + + /** + * Editors as a list, translator, illustrator and colorist as the first + * name: the shape the book form reads. + * + * @param array $book + * @param array{editor: list, translator: list, illustrator: list, colorist: list} $contributors + */ + public static function applyContributors(array &$book, array $contributors): void + { + if ($contributors['editor'] !== []) { + $book['editor'] = $contributors['editor']; + } + foreach (['translator', 'illustrator', 'colorist'] as $role) { + if ($contributors[$role] !== []) { + $book[$role] = $contributors[$role][0]; + } + } + } +} diff --git a/app/Support/Marc/Danmarc2LibriMapper.php b/app/Support/Marc/Danmarc2LibriMapper.php new file mode 100644 index 000000000..fd504f7d8 --- /dev/null +++ b/app/Support/Marc/Danmarc2LibriMapper.php @@ -0,0 +1,34 @@ +format()), 'danmarc')) { + $copy = MarcRecord::fromArray($record->toArray()); + $copy->setFormat('danMARC2'); + $record = $copy; + } + $doc = new \DOMDocument(); + if (!$doc->loadXML(MarcXmlWriter::recordToXml($record, MarcXmlWriter::NS_MARCXCHANGE), LIBXML_NONET) + || !$doc->documentElement instanceof \DOMElement + ) { + return MarcBookData::empty(); + } + $book = Danmarc2Parser::parse( + new \DOMXPath($doc), + $doc->documentElement, + static fn (string $code): string => MarcLanguage::nameFor($code) + ); + $data = MarcBookData::fromScrape($book); + $data['copertina_url'] = MarcBookData::coverUrlOf($record); + return $data; + } +} diff --git a/app/Support/Marc/Danmarc2Parser.php b/app/Support/Marc/Danmarc2Parser.php new file mode 100644 index 000000000..3deb3e550 --- /dev/null +++ b/app/Support/Marc/Danmarc2Parser.php @@ -0,0 +1,250 @@ +`), turned into the book data the form reads. + * + * danMARC2 shares tag numbers with MARC 21 but not its subfields, so neither + * the MARC 21 nor the UNIMARC reader can read it (kat-format.dk, 2nd edition): + * + * 021 *e ISBN-13, *a ISBN-10 + * 008 *a year, *l language (ISO 639-2/B) + * 100 / 700 *a surname, *h forenames, *k forenames in full, *4 function + * codes (appendix J: the Library of Congress list plus dk… codes; "led" + * only marks the main responsibility), *b function term ("red.") + * 720 *o a name in direct order ("Hanna Lützen"), *4 function code + * 245 *a title, *b rest of title, *c subtitle, *e / *f statement of + * responsibility + * 250 *a edition 260 *a place, *b publisher, *c year 300 *a extent + * 440 *a series, *v number 504 *a summary 666 *s / *o subject terms + * + * Shared by the SRU client and the MARC bulk importer; the z39-server plugin + * keeps its class name as a subclass. + */ +class Danmarc2Parser +{ + /** A MARCXchange record that declares itself danMARC2. */ + public static function isDanmarc2(DOMNode $record): bool + { + return $record instanceof \DOMElement && stripos($record->getAttribute('format'), 'danmarc') !== false; + } + + /** + * The danMARC2 record of the response that carries the ISBN searched for, + * else the first one. A Danish catalogue answers an ISBN with every + * manifestation it groups with it (the audiobook, the e-book, other + * printings), not only the edition asked about. + */ + public static function selectRecord(DOMXPath $xpath, string $isbn): ?DOMNode + { + $records = $xpath->query("//*[local-name()='record'][@format]"); + if ($records === false) { + return null; + } + $first = null; + $isbn = strtoupper((string) preg_replace('/[^0-9Xx]/', '', $isbn)); + foreach ($records as $record) { + if (!self::isDanmarc2($record)) { + continue; + } + $first ??= $record; + if ($isbn === '') { + break; + } + foreach (array_merge(self::all($xpath, $record, '021', 'e'), self::all($xpath, $record, '021', 'a')) as $value) { + if (strtoupper((string) preg_replace('/[^0-9Xx]/', '', $value)) === $isbn) { + return $record; + } + } + } + return $first; + } + + /** @return array */ + public static function parse(DOMXPath $xpath, DOMNode $record, callable $languageName): array + { + $get = static fn(string $tag, string $code): ?string => self::all($xpath, $record, $tag, $code)[0] ?? null; + + $book = [ + 'title' => '', + 'subtitle' => '', + 'authors' => [], + 'publisher' => '', + 'pubDate' => '', + 'year' => '', + 'isbn13' => '', + 'isbn10' => '', + 'language' => '', + 'pages' => '', + 'description' => '', + 'classificazione_dewey' => '', + 'source' => 'Z39.50/SRU (danMARC2)', + ]; + + $title = trim(implode(' ', array_filter([$get('245', 'a'), $get('245', 'b')]))); + $book['title'] = self::clean($title); + $book['subtitle'] = self::clean((string) $get('245', 'c')); + + // Names: 100 is the main author; 700 and 720 follow their function + // codes, their term or the statement of responsibility. + $statement = implode(' ; ', array_merge(self::all($xpath, $record, '245', 'e'), self::all($xpath, $record, '245', 'f'))); + $contributors = ['editor' => [], 'translator' => [], 'illustrator' => [], 'colorist' => []]; + foreach (self::fields($xpath, $record, '100') as $field) { + $name = self::invertedName($field); + if ($name !== '') { + ContributorSorter::sortContributor($book, $contributors, $name, 'author'); + } + } + foreach (['700', '720'] as $tag) { + foreach (self::fields($xpath, $record, $tag) as $field) { + $subs = self::subfields($field); + $name = $tag === '720' && isset($subs['o']) ? self::clean($subs['o'][0]) : self::invertedName($field); + if ($name === '' || isset($subs['t'])) { + // *t makes the field a name-title entry: a work, not a person's part + continue; + } + // *b is the function term ("red."); *f, the addition to the + // name, often says it too ("pædagogisk note") + $terms = array_merge($subs['b'] ?? [], $subs['f'] ?? []); + $term = $terms !== [] ? implode(' ', $terms) : null; + $role = RelatorRoles::resolve($subs['4'] ?? [], $term, $statement !== '' ? $statement : null, $name, 'marc21'); + ContributorSorter::sortContributor($book, $contributors, $name, $role); + } + } + ContributorSorter::applyContributors($book, $contributors); + + $edition = \App\Support\EditionStatement::clean(self::clean((string) $get('250', 'a'))); + if ($edition !== '') { + $book['edition'] = $edition; + } + $place = \App\Support\PublicationPlace::clean(self::clean((string) $get('260', 'a'))); + if ($place !== '') { + $book['place'] = $place; + } + $book['publisher'] = self::clean((string) $get('260', 'b')); + + $year = $get('260', 'c') ?? $get('008', 'a'); + if ($year !== null && preg_match('/\d{4}/', $year, $m) === 1) { + $book['year'] = $m[0]; + $book['pubDate'] = $m[0] . '-01-01'; + } + + // Every ISBN of the record, for the client to tell whether the one + // searched for is among them + foreach (array_merge(self::all($xpath, $record, '021', 'e'), self::all($xpath, $record, '021', 'a')) as $value) { + $book['_isbns'][] = strtoupper((string) preg_replace('/[^0-9Xx]/', '', $value)); + } + foreach (self::all($xpath, $record, '021', 'e') as $value) { + $value = (string) preg_replace('/[^0-9Xx]/', '', $value); + if (strlen($value) === 13) { + $book['isbn13'] = $value; + break; + } + } + foreach (self::all($xpath, $record, '021', 'a') as $value) { + $value = strtoupper((string) preg_replace('/[^0-9Xx]/', '', $value)); + if (strlen($value) === 10) { + $book['isbn10'] = $value; + break; + } + } + + // Extent: pages only ("355 sider", "288 s."), never the minutes of an audiobook + $pages = ContributorSorter::pagesOf((string) $get('300', 'a'), false); + if ($pages !== '') { + $book['pages'] = $pages; + } + + $language = $get('008', 'l'); + if ($language !== null && $language !== '') { + $book['language'] = $languageName(strtolower(substr($language, 0, 3))); + } + + $book['description'] = self::clean((string) $get('504', 'a')); + + $series = $get('440', 'a'); + if ($series !== null && trim($series) !== '') { + $book['collana'] = self::clean($series); + $number = $get('440', 'v'); + if ($number !== null && trim($number) !== '') { + $book['numero_serie'] = self::clean($number); + } + } + + $subjects = []; + foreach (['s', 'o'] as $code) { + foreach (self::all($xpath, $record, '666', $code) as $value) { + $value = self::clean($value); + if ($value !== '' && !in_array($value, $subjects, true)) { + $subjects[] = $value; + } + } + } + if ($subjects !== []) { + $book['keywords'] = implode(', ', $subjects); + } + + if ($book['authors'] !== []) { + $book['author'] = implode(', ', $book['authors']); + } + return $book; + } + + /** "Lützen, Hanna" from *a Lützen *h Hanna; the full forenames of *k when given. */ + private static function invertedName(DOMNode $field): string + { + $subs = self::subfields($field); + $surname = self::clean($subs['a'][0] ?? ''); + $forenames = self::clean($subs['k'][0] ?? $subs['h'][0] ?? ''); + if ($surname === '') { + return $forenames; + } + return $forenames !== '' ? $surname . ', ' . $forenames : $surname; + } + + /** @return list */ + private static function fields(DOMXPath $xpath, DOMNode $record, string $tag): array + { + $nodes = $xpath->query("./*[local-name()='datafield'][@tag='$tag']", $record); + return $nodes === false ? [] : iterator_to_array($nodes, false); + } + + /** @return list */ + private static function all(DOMXPath $xpath, DOMNode $record, string $tag, string $code): array + { + $nodes = $xpath->query("./*[local-name()='datafield'][@tag='$tag']/*[local-name()='subfield'][@code='$code']", $record); + $out = []; + foreach ($nodes === false ? [] : $nodes as $node) { + $value = trim((string) $node->nodeValue); + if ($value !== '') { + $out[] = $value; + } + } + return $out; + } + + /** @return array> */ + private static function subfields(DOMNode $field): array + { + $out = []; + foreach ($field->childNodes as $sub) { + if ($sub instanceof \DOMElement && $sub->localName === 'subfield') { + $out[$sub->getAttribute('code')][] = trim((string) $sub->nodeValue); + } + } + return $out; + } + + private static function clean(string $text): string + { + $text = (string) preg_replace('/[\x{0080}-\x{009F}]/u', '', $text); + $text = trim((string) preg_replace('/\s+/u', ' ', $text)); + return rtrim($text, ' /:;=,'); + } +} diff --git a/app/Support/Marc/GeneratorStream.php b/app/Support/Marc/GeneratorStream.php new file mode 100644 index 000000000..8b115e639 --- /dev/null +++ b/app/Support/Marc/GeneratorStream.php @@ -0,0 +1,126 @@ + $generator */ + public function __construct(private \Generator $generator) + { + } + + private function pull(int $length): void + { + while (!$this->finished && strlen($this->buffer) < $length) { + if ($this->started) { + $this->generator->next(); + } + $this->started = true; + if (!$this->generator->valid()) { + $this->finished = true; + break; + } + $this->buffer .= (string) $this->generator->current(); + } + } + + public function read(int $length): string + { + $this->pull($length); + $chunk = substr($this->buffer, 0, $length); + $this->buffer = (string) substr($this->buffer, strlen($chunk)); + $this->position += strlen($chunk); + return $chunk; + } + + public function eof(): bool + { + $this->pull(1); + return $this->finished && $this->buffer === ''; + } + + public function getContents(): string + { + $out = ''; + while (!$this->eof()) { + $out .= $this->read(65536); + } + return $out; + } + + public function __toString(): string + { + return $this->getContents(); + } + + public function close(): void + { + $this->finished = true; + $this->buffer = ''; + } + + public function detach() + { + $this->close(); + return null; + } + + public function getSize(): ?int + { + return null; + } + + public function tell(): int + { + return $this->position; + } + + public function isSeekable(): bool + { + return false; + } + + public function seek(int $offset, int $whence = SEEK_SET): void + { + throw new \RuntimeException('GeneratorStream is not seekable'); + } + + public function rewind(): void + { + throw new \RuntimeException('GeneratorStream is not seekable'); + } + + public function isWritable(): bool + { + return false; + } + + public function write(string $string): int + { + throw new \RuntimeException('GeneratorStream is not writable'); + } + + public function isReadable(): bool + { + return true; + } + + public function getMetadata(?string $key = null) + { + return $key === null ? [] : null; + } +} diff --git a/app/Support/Marc/Iso2709Reader.php b/app/Support/Marc/Iso2709Reader.php new file mode 100644 index 000000000..ddaab107d --- /dev/null +++ b/app/Support/Marc/Iso2709Reader.php @@ -0,0 +1,231 @@ + + */ +final class Iso2709Reader implements \IteratorAggregate +{ + public const ENCODING_AUTO = 'auto'; + public const ENCODING_UTF8 = 'utf8'; + public const ENCODING_MARC8 = 'marc8'; + + /** Longest record ISO 2709 can describe, plus slack for junk between records. */ + private const MAX_RECORD_BYTES = 200000; + + /** @var resource */ + private $handle; + private bool $ownsHandle; + private int $position; + private int $offset; + + /** + * @param resource $handle + * @param int $position 1-based number of the next record, for error reports + */ + private function __construct($handle, bool $ownsHandle, private string $encoding, int $position, int $offset) + { + $this->handle = $handle; + $this->ownsHandle = $ownsHandle; + $this->position = $position; + $this->offset = $offset; + } + + public function __destruct() + { + if ($this->ownsHandle && is_resource($this->handle)) { + fclose($this->handle); + } + } + + /** + * @param int $offset byte offset to start from (one offset() returned) + * @param int $position number of records before that offset + */ + public static function fromPath(string $path, string $encoding = self::ENCODING_AUTO, int $offset = 0, int $position = 0): self + { + $handle = @fopen($path, 'rb'); + if ($handle === false) { + throw new \RuntimeException('Cannot open MARC file'); + } + if ($offset > 0) { + fseek($handle, $offset); + } + return new self($handle, true, $encoding, $position, $offset); + } + + public static function fromString(string $data, string $encoding = self::ENCODING_AUTO): self + { + $handle = fopen('php://memory', 'w+b'); + if ($handle === false) { + throw new \RuntimeException('Cannot open memory stream'); + } + fwrite($handle, $data); + rewind($handle); + return new self($handle, true, $encoding, 0, 0); + } + + /** Byte offset just after the last record read: where a later read can resume. */ + public function offset(): int + { + return $this->offset; + } + + /** @return \Generator */ + public function getIterator(): \Generator + { + while (!feof($this->handle)) { + $raw = stream_get_line($this->handle, self::MAX_RECORD_BYTES, Iso2709Writer::RECORD_TERMINATOR); + if ($raw === false) { + break; + } + $after = ftell($this->handle); + $this->offset = $after === false ? $this->offset + strlen($raw) + 1 : $after; + // Line breaks or padding between records are not part of them. + $raw = ltrim($raw, "\r\n\t \x00\x1A"); + if ($raw === '') { + continue; + } + $this->position++; + try { + yield $this->position => self::parse($raw, $this->encoding); + } catch (MarcParseException $e) { + yield $this->position => new MarcReadError($this->position, $e->getMessage(), MarcReadError::excerptOf($raw)); + } + } + } + + /** + * One record, without its record terminator. + * + * @throws MarcParseException + */ + public static function parse(string $raw, string $encoding = self::ENCODING_AUTO): MarcRecord + { + $length = strlen($raw); + if ($length < 25) { + throw new MarcParseException('Record too short to hold a leader and a directory'); + } + $leader = substr($raw, 0, 24); + $directoryEnd = strpos($raw, Iso2709Writer::FIELD_TERMINATOR, 24); + if ($directoryEnd === false) { + throw new MarcParseException('Record has no directory terminator'); + } + // The data starts after the directory terminator; a leader base + // address (12-16) that disagrees is wrong, the terminator is not. + $base = $directoryEnd + 1; + $directory = substr($raw, 24, $directoryEnd - 24); + if ($directory === '' || strlen($directory) % 12 !== 0) { + throw new MarcParseException('Directory length is not a multiple of 12'); + } + + $validUtf8 = mb_check_encoding($raw, 'UTF-8'); + if ($encoding === self::ENCODING_MARC8) { + // The override exists for MARC-8 records mislabelled leader/09 = a. + $utf8 = false; + } elseif ($encoding === self::ENCODING_UTF8) { + if (!$validUtf8) { + throw new MarcParseException('Record declares UTF-8 but is not valid UTF-8'); + } + $utf8 = true; + } elseif ($leader[9] === 'a') { + if (!$validUtf8) { + throw new MarcParseException('Record declares UTF-8 (leader/09 = a) but is not valid UTF-8'); + } + $utf8 = true; + } else { + // An escape (0x1B) only occurs in MARC-8, where it switches sets. + $utf8 = !str_contains($raw, "\x1B") && $validUtf8; + } + $decode = $utf8 + ? static fn (string $s): string => $s + : static fn (string $s): string => Marc8Decoder::decode($s); + if (!$utf8) { + $leader[9] = 'a'; + } + + $record = new MarcRecord($leader); + $dataLength = $length - $base; + foreach (str_split($directory, 12) as $entry) { + $tag = substr($entry, 0, 3); + $fieldLength = substr($entry, 3, 4); + $start = substr($entry, 7, 5); + if (!ctype_digit($fieldLength) || !ctype_digit($start)) { + throw new MarcParseException("Directory entry for tag {$tag} is not numeric"); + } + if ((int) $start + (int) $fieldLength > $dataLength) { + throw new MarcParseException("Field {$tag} runs past the end of the record"); + } + $data = substr($raw, $base + (int) $start, (int) $fieldLength); + // A field ends at its own terminator. A terminator inside the slice + // means the directory points across a field boundary (a corrupt + // entry would hand the bytes of a neighbour to 245 $a): refuse the + // record. A slice one byte short of its terminator is the known + // legacy off-by-one (length counted without it) and is accepted. + $end = $base + (int) $start + (int) $fieldLength; + if (str_ends_with($data, Iso2709Writer::FIELD_TERMINATOR)) { + $data = substr($data, 0, -1); + } elseif (($raw[$end] ?? '') !== Iso2709Writer::FIELD_TERMINATOR) { + throw new MarcParseException("Field {$tag} does not end with a field terminator"); + } + if (str_contains($data, Iso2709Writer::FIELD_TERMINATOR)) { + throw new MarcParseException("Field {$tag} runs into the next field"); + } + $isControl = ctype_digit($tag) && (int) $tag < 10 && !str_contains($data, Iso2709Writer::SUBFIELD_DELIMITER); + if ($isControl) { + $record->addControlField($tag, $decode($data)); + continue; + } + if (strlen($data) < 2) { + throw new MarcParseException("Data field {$tag} has no indicators"); + } + $ind1 = $data[0]; + $ind2 = $data[1]; + $subfields = []; + $chunks = explode(Iso2709Writer::SUBFIELD_DELIMITER, substr($data, 2)); + array_shift($chunks); // whatever precedes the first delimiter + foreach ($chunks as $chunk) { + if ($chunk === '') { + continue; + } + $subfields[] = [$chunk[0], $decode(substr($chunk, 1))]; + } + $record->addDataField($tag, $ind1 === "\x00" ? ' ' : $ind1, $ind2 === "\x00" ? ' ' : $ind2, $subfields); + } + // Each entry was checked on its own above; two entries must also never + // share bytes: a 245 pointing at the start and length of a 500 would + // otherwise import the note as the title, silently. + $spans = []; + foreach (str_split($directory, 12) as $entry) { + if (ctype_digit(substr($entry, 3, 4)) && ctype_digit(substr($entry, 7, 5))) { + $spans[] = [(int) substr($entry, 7, 5), max(1, (int) substr($entry, 3, 4)), substr($entry, 0, 3)]; + } + } + usort($spans, static fn (array $a, array $b): int => $a[0] <=> $b[0]); + for ($i = 1, $n = count($spans); $i < $n; $i++) { + if ($spans[$i][0] < $spans[$i - 1][0] + $spans[$i - 1][1]) { + throw new MarcParseException("Directory entries for tags {$spans[$i - 1][2]} and {$spans[$i][2]} overlap"); + } + } + return $record; + } +} diff --git a/app/Support/Marc/Iso2709Writer.php b/app/Support/Marc/Iso2709Writer.php new file mode 100644 index 000000000..920cc78dc --- /dev/null +++ b/app/Support/Marc/Iso2709Writer.php @@ -0,0 +1,119 @@ +handle = $handle; + } + + /** @param list|null $warnings collects a note for each field that had to be cut */ + public function write(MarcRecord $record, ?array &$warnings = null): void + { + if (!is_resource($this->handle)) { + throw new \LogicException('Iso2709Writer has no stream to write to'); + } + fwrite($this->handle, self::serialize($record, $warnings)); + } + + /** + * @param list|null $warnings collects a note for each field that had to be cut + * @throws \OverflowException when the record exceeds 99,999 bytes + */ + public static function serialize(MarcRecord $record, ?array &$warnings = null): string + { + $directory = ''; + $fieldData = ''; + $pos = 0; + foreach ($record->fields() as $field) { + if ($field->data !== null) { + $content = $field->data; + } else { + $content = $field->ind1 . $field->ind2; + foreach ($field->subfields as [$code, $value]) { + $content .= self::SUBFIELD_DELIMITER . $code . $value; + } + } + $fitted = self::fitField($content); + if ($fitted !== $content && $warnings !== null) { + $warnings[] = sprintf('field %s of %d bytes cut to %d (ISO 2709 field limit)', $field->tag, strlen($content), strlen($fitted)); + } + $content = $fitted . self::FIELD_TERMINATOR; + $length = strlen($content); + $directory .= str_pad(substr($field->tag, 0, 3), 3, '0', STR_PAD_LEFT) + . sprintf('%04d', $length) + . sprintf('%05d', $pos); + $fieldData .= $content; + $pos += $length; + } + $directory .= self::FIELD_TERMINATOR; + + $baseAddress = 24 + strlen($directory); + $recordLength = $baseAddress + strlen($fieldData) + 1; + if ($recordLength > self::MAX_RECORD_LENGTH) { + throw new \OverflowException(sprintf('ISO 2709 record of %d bytes exceeds the %d-byte limit', $recordLength, self::MAX_RECORD_LENGTH)); + } + + $leader = $record->leader(); + $leader = sprintf('%05d', $recordLength) + . substr($leader, 5, 7) + . sprintf('%05d', $baseAddress) + . substr($leader, 17); + + return $leader . $directory . $fieldData . self::RECORD_TERMINATOR; + } + + /** + * A field longer than the directory can describe is cut, rather than + * producing a record no reader can open: at the last subfield boundary + * that fits, so every subfield kept is whole; when the first subfield + * alone is too long (a long summary is the usual case) inside it, on a + * UTF-8 character boundary. + */ + private static function fitField(string $content): string + { + $limit = self::MAX_FIELD_LENGTH - 1; // the field terminator counts + if (strlen($content) <= $limit) { + return $content; + } + // Subfield starts after the first one (offset 2 holds the first + // delimiter, right after the indicators). + $cut = 0; + $offset = strpos($content, self::SUBFIELD_DELIMITER, 3); + while ($offset !== false && $offset <= $limit) { + $cut = $offset; + $offset = strpos($content, self::SUBFIELD_DELIMITER, $offset + 1); + } + if ($cut > 0) { + return substr($content, 0, $cut); + } + return mb_strcut($content, 0, $limit, 'UTF-8'); + } +} diff --git a/app/Support/Marc/Marc21LibriMapper.php b/app/Support/Marc/Marc21LibriMapper.php new file mode 100644 index 000000000..5dfeb4fde --- /dev/null +++ b/app/Support/Marc/Marc21LibriMapper.php @@ -0,0 +1,280 @@ + 'curatore', + 'translator' => 'traduttore', + 'illustrator' => 'illustratore', + 'colorist' => 'colorista', + ]; + + /** Meeting entries (X11) put the subordinate unit in $e and the relator term in $j. */ + private static function isMeeting(MarcField $field): bool + { + return $field->tag === '111' || $field->tag === '711'; + } + + /** + * The relator terms of a name entry: $e, except in a meeting entry + * (111/711), where $e is the subordinate unit and the term is $j. + * + * @return list + */ + private static function relatorTerms(MarcField $field): array + { + return $field->subfieldValues(self::isMeeting($field) ? 'j' : 'e'); + } + + /** + * The name of a name entry: a person's $a; a corporate body's $a with + * its subordinate units ($b), "United States. Congress"; a meeting's $a + * with its subordinate units ($e). + */ + private static function entryName(MarcField $field): string + { + $name = MarcBookData::cleanName($field->subfield('a')); + if (!in_array($field->tag, ['110', '111', '710', '711'], true)) { + return $name; + } + $units = $field->subfieldValues(self::isMeeting($field) ? 'e' : 'b'); + $joined = ''; + foreach (array_merge([$name], array_map([MarcBookData::class, 'cleanName'], $units)) as $part) { + if ($part === '') { + continue; + } + // A part that keeps its final period (it ends with an initial, + // "Istituto X.") is not followed by a second one. + $joined = $joined === '' ? $part : $joined . (str_ends_with($joined, '.') ? ' ' : '. ') . $part; + } + return $joined; + } + + public function map(MarcRecord $record): array + { + $data = MarcBookData::empty(); + + // 245 $a, with the number and name of the part ($n, $p, in order) + // appended: "Storia d'Italia. Vol. 2. Il Medioevo". + $title = MarcBookData::clean($record->subfield('245', 'a')); + $parts = []; + $titleField = $record->first('245'); + foreach ($titleField !== null ? $titleField->subfields : [] as [$code, $value]) { + if ($code === 'n' || $code === 'p') { + $part = MarcBookData::clean($value, ' /:;=,.'); + if ($part !== '') { + $parts[] = $part; + } + } + } + if ($parts !== []) { + $title = trim(rtrim($title, ' .') . '. ' . implode('. ', $parts), ' .'); + } + $data['titolo'] = $title; + $data['sottotitolo'] = MarcBookData::clean($record->subfield('245', 'b')); + + $statement = $record->subfield('245', 'c'); + $authors = []; + $contributors = array_fill_keys(array_values(self::CONTRIBUTOR_COLUMNS), []); + $file = static function (string $name, string $role) use (&$authors, &$contributors): void { + if ($name === '') { + return; + } + if ($role === 'author') { + if (!in_array($name, $authors, true)) { + $authors[] = $name; + } + return; + } + $column = self::CONTRIBUTOR_COLUMNS[$role] ?? null; + if ($column !== null && !in_array($name, $contributors[$column], true)) { + $contributors[$column][] = $name; + } + }; + // Main entry: a person (100), a corporate body (110) or a meeting + // (111); added entries the same way (700, 710, 711). + foreach (['100', '110', '111'] as $tag) { + foreach ($record->fieldsByTag($tag) as $field) { + $name = self::entryName($field); + $codes = $field->subfieldValues('4'); + $term = self::relatorTerms($field); + // The main entry is the author unless its own relator says otherwise. + $role = ($codes === [] && $term === []) + ? 'author' + : RelatorRoles::resolve($codes, $term === [] ? null : implode(' ', $term), null, $name, 'marc21'); + $file($name, $role === 'other' ? 'author' : $role); + } + } + foreach (['700', '710', '711'] as $tag) { + foreach ($record->fieldsByTag($tag) as $field) { + if ($field->subfield('t') !== null) { + continue; // a name-title entry is a work, not a person's part + } + $name = self::entryName($field); + if ($name === '') { + continue; + } + $term = self::relatorTerms($field); + $role = RelatorRoles::resolve($field->subfieldValues('4'), $term === [] ? null : implode(' ', $term), $statement, $name, 'marc21'); + $file($name, $role); + } + } + $data['autori'] = $authors; + foreach ($contributors as $column => $names) { + $data[$column] = $names; + } + + $data['edizione'] = EditionStatement::clean(MarcBookData::clean($record->subfield('250', 'a'))); + + // Publication: 260, or the 264 that states publication (ind2 1), + // else any 264. + $publication = $record->fieldsByTag('260'); + $published264 = array_values(array_filter($record->fieldsByTag('264'), static fn (MarcField $f): bool => $f->ind2 === '1')); + $publication = array_merge($publication, $published264 !== [] ? $published264 : $record->fieldsByTag('264')); + foreach ($publication as $field) { + if ($data['luogo_pubblicazione'] === '') { + $data['luogo_pubblicazione'] = PublicationPlace::clean(MarcBookData::clean($field->subfield('a'))); + } + foreach ($field->subfieldValues('b') as $publisher) { + $publisher = MarcBookData::clean($publisher); + if ($publisher !== '' && !in_array($publisher, $data['editori'], true)) { + $data['editori'][] = $publisher; + } + } + if ($data['anno_pubblicazione'] === '') { + $data['anno_pubblicazione'] = MarcBookData::yearOf($field->subfield('c')); + } + } + $f008 = (string) $record->controlField('008'); + if ($data['anno_pubblicazione'] === '' && strlen($f008) >= 11 && ctype_digit(substr($f008, 7, 4))) { + $data['anno_pubblicazione'] = MarcBookData::yearOf(substr($f008, 7, 4)); + } + + // Only an extent in pages counts: "2 v." or "24 cm" is no page count. + $data['numero_pagine'] = ContributorSorter::pagesOf((string) $record->subfield('300', 'a'), false); + $data['dimensioni'] = MarcBookData::clean($record->subfield('300', 'c'), ' /:;=,+'); + + $data['collana'] = MarcBookData::clean($record->subfield('490', 'a')); + $data['numero_serie'] = MarcBookData::clean($record->subfield('490', 'v')); + if ($data['collana'] === '') { + $data['collana'] = MarcBookData::clean($record->subfield('830', 'a'), ' /:;=,.'); + $data['numero_serie'] = MarcBookData::clean($record->subfield('830', 'v')); + } + + $data['descrizione'] = trim(implode("\n\n", array_map( + static fn (string $s): string => trim($s), + $record->subfieldValues('520', 'a') + ))); + + $keywords = []; + foreach (['650', '651', '653'] as $tag) { + foreach ($record->subfieldValues($tag, 'a') as $term) { + $term = MarcBookData::clean($term, ' /:;=,.'); + if ($term !== '' && !in_array($term, $keywords, true)) { + $keywords[] = $term; + } + } + } + $data['parole_chiave'] = $keywords; + // The genre: a 655 the exporting catalogue marked as its own term + // ($2 local, as this one writes). Any other 655 (LCGFT, RBMS...) is a + // form heading of a scheme the catalogue has no genre for. + foreach ($record->fieldsByTag('655') as $field) { + if (strtolower(trim((string) $field->subfield('2'))) === 'local') { + $data['genere'] = MarcBookData::clean($field->subfield('a'), ' /:;=,.'); + if ($data['genere'] !== '') { + break; + } + } + } + + // 082 $a with its prime marks removed: "891.73/3" is 891.733, + // "813/.54" is 813.54; "[Fic]" is no number. + $dewey = str_replace(['/', "'"], '', (string) $record->subfield('082', 'a')); + if (preg_match('/(\d{3}(?:\.\d+)?)/', $dewey, $m) === 1) { + $data['classificazione_dewey'] = $m[1]; + } + $lc = trim(MarcBookData::clean($record->subfield('050', 'a')) . ' ' . MarcBookData::clean($record->subfield('050', 'b'))); + $data['lc_classification'] = mb_substr($lc, 0, 100); + + foreach ($record->subfieldValues('020', 'a') as $value) { + $isbn = MarcBookData::isbnOf($value); + if (strlen($isbn) === 13 && $data['isbn13'] === '' && IsbnFormatter::isValid($isbn)) { + $data['isbn13'] = $isbn; + } elseif (strlen($isbn) === 10 && $data['isbn10'] === '' && IsbnFormatter::isValid($isbn)) { + $data['isbn10'] = $isbn; + } + } + foreach ($record->fieldsByTag('024') as $field) { + $ean = (string) preg_replace('/\D/', '', (string) $field->subfield('a')); + if (strlen($ean) === 13 && ($field->ind1 === '3' || $field->ind1 === ' ') && $ean !== $data['isbn13']) { + $data['ean'] = $ean; + break; + } + } + // 022: the ISSN of the resource itself; on a monograph the series + // ISSN travels in 490 $x (or 830 $x), as the export writes it. + $data['issn'] = MarcBookData::clean($record->subfield('022', 'a')); + if ($data['issn'] === '') { + $data['issn'] = MarcBookData::clean($record->subfield('490', 'x') ?? $record->subfield('830', 'x')); + } + + // Every 041 $a (one per language), else 008/35-37. + $languages = []; + foreach ($record->subfieldValues('041', 'a') as $code) { + // A pre-1990 041 may pack several codes in one $a ("engita"). + foreach (str_split(trim($code), 3) as $chunk) { + $name = MarcLanguage::nameFor($chunk); + if ($name !== '' && !in_array($name, $languages, true)) { + $languages[] = $name; + } + } + } + if ($languages === [] && strlen($f008) >= 38) { + $name = MarcLanguage::nameFor(substr($f008, 35, 3)); + if ($name !== '') { + $languages[] = $name; + } + } + $data['lingua'] = implode(', ', $languages); + + $data['copertina_url'] = MarcBookData::coverUrlOf($record); + + // Media type from leader/06, refined by the RDA content type (336); + // RDA codes the media type does not imply are kept as the book's own. + $content = $record->subfield('336', 'b'); + $data['tipo_media'] = MarcMediaType::tipoMediaOf(substr($record->leader() . ' ', 6, 1), $content); + foreach (MarcMediaType::overrides($data['tipo_media'], $content, $record->subfield('337', 'b'), $record->subfield('338', 'b')) as $column => $code) { + $data[$column] = $code; + } + + return $data; + } +} diff --git a/app/Support/Marc/Marc8Decoder.php b/app/Support/Marc/Marc8Decoder.php new file mode 100644 index 000000000..03ccd22a0 --- /dev/null +++ b/app/Support/Marc/Marc8Decoder.php @@ -0,0 +1,126 @@ + 0x0141, 0xA2 => 0x00D8, 0xA3 => 0x0110, 0xA4 => 0x00DE, 0xA5 => 0x00C6, + 0xA6 => 0x0152, 0xA7 => 0x02B9, 0xA8 => 0x00B7, 0xA9 => 0x266D, 0xAA => 0x00AE, + 0xAB => 0x00B1, 0xAC => 0x01A0, 0xAD => 0x01AF, 0xAE => 0x02BC, 0xB0 => 0x02BB, + 0xB1 => 0x0142, 0xB2 => 0x00F8, 0xB3 => 0x0111, 0xB4 => 0x00FE, 0xB5 => 0x00E6, + 0xB6 => 0x0153, 0xB7 => 0x02BA, 0xB8 => 0x0131, 0xB9 => 0x00A3, 0xBA => 0x00F0, + 0xBC => 0x01A1, 0xBD => 0x01B0, 0xC0 => 0x00B0, 0xC1 => 0x2113, 0xC2 => 0x2117, + 0xC3 => 0x00A9, 0xC4 => 0x266F, 0xC5 => 0x00BF, 0xC6 => 0x00A1, 0xC7 => 0x00DF, + 0xC8 => 0x20AC, + ]; + + /** ANSEL combining diacritics, G1 (0xE0-0xFE). */ + private const COMBINING = [ + 0xE0 => 0x0309, 0xE1 => 0x0300, 0xE2 => 0x0301, 0xE3 => 0x0302, 0xE4 => 0x0303, + 0xE5 => 0x0304, 0xE6 => 0x0306, 0xE7 => 0x0307, 0xE8 => 0x0308, 0xE9 => 0x030C, + 0xEA => 0x030A, 0xEB => 0xFE20, 0xEC => 0xFE21, 0xED => 0x0315, 0xEE => 0x030B, + 0xEF => 0x0310, 0xF0 => 0x0327, 0xF1 => 0x0328, 0xF2 => 0x0323, 0xF3 => 0x0324, + 0xF4 => 0x0325, 0xF5 => 0x0333, 0xF6 => 0x0332, 0xF7 => 0x0326, 0xF8 => 0x031C, + 0xF9 => 0x032E, 0xFA => 0xFE22, 0xFB => 0xFE23, 0xFE => 0x0313, + ]; + + /** + * Non-sorting begin/end (0x88/0x89) and the joiners (0x8D/0x8E): markup, + * not text. Dropped. + */ + private const DROPPED = [0x88, 0x89, 0x8D, 0x8E]; + + public static function decode(string $bytes): string + { + $out = ''; + $pending = ''; + $length = strlen($bytes); + for ($i = 0; $i < $length; $i++) { + $byte = ord($bytes[$i]); + if ($byte === 0x1B) { + $i = self::escape($bytes, $i); + continue; + } + if ($byte < 0x80) { + $char = chr($byte); + $out .= $char . $pending; + $pending = ''; + continue; + } + if (in_array($byte, self::DROPPED, true)) { + continue; + } + if (isset(self::COMBINING[$byte])) { + $pending .= mb_chr(self::COMBINING[$byte], 'UTF-8'); + continue; + } + if (isset(self::SPACING[$byte])) { + $out .= mb_chr(self::SPACING[$byte], 'UTF-8') . $pending; + $pending = ''; + continue; + } + throw new MarcParseException(sprintf('MARC-8: byte 0x%02X is not a character of Basic or Extended Latin', $byte)); + } + // A diacritic with nothing after it stays, on its own. + $out .= $pending; + if (class_exists(\Normalizer::class)) { + $normalised = \Normalizer::normalize($out, \Normalizer::FORM_C); + if (is_string($normalised)) { + return $normalised; + } + } + return $out; + } + + /** + * Skip an escape sequence that selects Basic or Extended Latin; any other + * set cannot be decoded here. + * + * @return int index of the last byte of the sequence + */ + private static function escape(string $bytes, int $i): int + { + $next = $bytes[$i + 1] ?? ''; + // ESC s: back to ASCII (after a technique-1 switch) + if ($next === 's') { + return $i + 1; + } + // ESC ( B / ESC , B: ASCII as G0; ESC ) E / ESC - E: ANSEL as G1; + // ESC ( ! E: ANSEL designated with an intermediate + if (in_array($next, ['(', ',', ')', '-', '$'], true)) { + $final = $bytes[$i + 2] ?? ''; + if ($final === '!') { + $final = $bytes[$i + 3] ?? ''; + if ($final === 'E') { + return $i + 3; + } + } elseif ($final === 'B' || $final === 'E') { + return $i + 2; + } + throw new MarcParseException('MARC-8: character set "' . self::printable($final) . '" (Greek, Cyrillic, Hebrew, Arabic or CJK) is not supported'); + } + throw new MarcParseException('MARC-8: character set "' . self::printable($next) . '" is not supported'); + } + + private static function printable(string $char): string + { + return $char !== '' && ctype_print($char) ? $char : sprintf('0x%02X', $char === '' ? 0 : ord($char)); + } +} diff --git a/app/Support/Marc/MarcBookData.php b/app/Support/Marc/MarcBookData.php new file mode 100644 index 000000000..44a17b4c3 --- /dev/null +++ b/app/Support/Marc/MarcBookData.php @@ -0,0 +1,191 @@ +> */ + public static function empty(): array + { + return [ + 'titolo' => '', 'sottotitolo' => '', + 'autori' => [], 'curatore' => [], 'traduttore' => [], 'illustratore' => [], 'colorista' => [], + 'editori' => [], 'anno_pubblicazione' => '', 'lingua' => '', 'edizione' => '', + 'luogo_pubblicazione' => '', 'numero_pagine' => '', 'dimensioni' => '', + 'collana' => '', 'numero_serie' => '', 'descrizione' => '', 'parole_chiave' => [], + 'classificazione_dewey' => '', 'lc_classification' => '', + 'isbn13' => '', 'isbn10' => '', 'ean' => '', 'issn' => '', 'copertina_url' => '', + 'tipo_media' => '', 'genere' => '', + ]; + } + + /** + * Collapse whitespace, drop MARC-8 C1 leftovers and the ISBD punctuation a + * record leaves at the end of a value (" /", " :", " ;", " =", ","). + */ + public static function clean(?string $value, string $trailing = ' /:;=,'): string + { + if ($value === null) { + return ''; + } + $value = (string) preg_replace('/[\x{0080}-\x{009F}]/u', '', $value); + $value = trim((string) preg_replace('/\s+/u', ' ', $value)); + return trim(rtrim($value, $trailing)); + } + + /** A name as the record gives it, without the closing comma or period ("Eco, Umberto,"). */ + public static function cleanName(?string $value): string + { + $name = self::clean($value, ' /:;=,'); + // A final period closes the field, unless it ends an initial ("Tolkien, J.R.R.") + if (str_ends_with($name, '.') && preg_match('/(?:^|[\s.])\p{Lu}\.$/u', $name) !== 1) { + $name = rtrim($name, '.'); + } + return trim($name); + } + + /** The first year a date statement names ("c1998", "[2004?]", "1980-1983"). */ + public static function yearOf(?string $value): string + { + if ($value !== null && preg_match('/(1[0-9]{3}|20[0-9]{2})/', $value, $m) === 1) { + return $m[1]; + } + return ''; + } + + /** + * The ISBN of an 020 $a / 010 $a value: its leading digits (and final X), + * without the qualifier a record may add ("9788845292613 (pbk.)"). + */ + public static function isbnOf(?string $value): string + { + $value = strtoupper(trim((string) $value)); + $value = (string) preg_replace('/^([0-9X\- ]+).*$/', '$1', $value); + return (string) preg_replace('/[^0-9X]/', '', $value); + } + + /** + * Book data from the book shape the catalogue readers return (title, + * subtitle, authors, publisher, year, language, editor, translator, ...). + * + * @param array $book + * @return array> + */ + public static function fromScrape(array $book): array + { + $data = self::empty(); + $string = static fn (string $key): string => is_scalar($book[$key] ?? null) ? trim((string) $book[$key]) : ''; + $list = static function (mixed $value): array { + if (is_string($value)) { + $value = $value === '' ? [] : [$value]; + } + if (!is_array($value)) { + return []; + } + return array_values(array_filter(array_map(static fn ($v): string => is_scalar($v) ? trim((string) $v) : '', $value), static fn (string $v): bool => $v !== '')); + }; + $data['titolo'] = $string('title'); + $data['sottotitolo'] = $string('subtitle'); + $data['autori'] = $list($book['authors'] ?? []); + $data['curatore'] = $list($book['editor'] ?? []); + $data['traduttore'] = $list($book['translator'] ?? []); + $data['illustratore'] = $list($book['illustrator'] ?? []); + $data['colorista'] = $list($book['colorist'] ?? []); + $data['editori'] = $list($book['publisher'] ?? []); + $data['anno_pubblicazione'] = self::yearOf($string('year')); + $data['lingua'] = $string('language'); + $data['edizione'] = $string('edition'); + $data['luogo_pubblicazione'] = $string('place'); + $data['numero_pagine'] = ctype_digit($string('pages')) ? $string('pages') : ''; + $data['descrizione'] = $string('description'); + $data['collana'] = $string('collana'); + $data['numero_serie'] = $string('numero_serie'); + $keywords = $book['keywords'] ?? []; + $data['parole_chiave'] = is_string($keywords) + ? array_values(array_filter(array_map('trim', explode(',', $keywords)), static fn (string $k): bool => $k !== '')) + : $list($keywords); + $data['classificazione_dewey'] = $string('classificazione_dewey'); + $data['isbn13'] = $string('isbn13'); + $data['isbn10'] = $string('isbn10'); + $data['ean'] = $string('ean'); + $data['copertina_url'] = $string('image') !== '' ? $string('image') : $string('cover'); + return $data; + } + + /** + * The canonical import row (the column names of the CSV import) the + * shared row pipeline normalises: names joined with ";", keywords with + * ", ". A contributor column is present only when the record names + * someone in that role, so a record never clears contributors it says + * nothing about. + * + * @param array> $data + * @return array + */ + public static function toImportRow(array $data): array + { + $join = static fn (string $key, string $glue = ';'): string => implode($glue, (array) ($data[$key] ?? [])); + $row = [ + 'titolo' => (string) ($data['titolo'] ?? ''), + 'sottotitolo' => (string) ($data['sottotitolo'] ?? ''), + 'autori' => $join('autori'), + 'editore' => $join('editori'), + 'anno_pubblicazione' => (string) ($data['anno_pubblicazione'] ?? ''), + 'lingua' => (string) ($data['lingua'] ?? ''), + 'edizione' => (string) ($data['edizione'] ?? ''), + 'numero_pagine' => (string) ($data['numero_pagine'] ?? ''), + 'descrizione' => (string) ($data['descrizione'] ?? ''), + 'collana' => (string) ($data['collana'] ?? ''), + 'numero_serie' => (string) ($data['numero_serie'] ?? ''), + 'parole_chiave' => $join('parole_chiave', ', '), + 'classificazione_dewey' => (string) ($data['classificazione_dewey'] ?? ''), + 'isbn10' => (string) ($data['isbn10'] ?? ''), + 'isbn13' => (string) ($data['isbn13'] ?? ''), + 'ean' => (string) ($data['ean'] ?? ''), + 'tipo_media' => (string) ($data['tipo_media'] ?? ''), + 'genere' => (string) ($data['genere'] ?? ''), + ]; + foreach (['traduttore', 'illustratore', 'curatore', 'colorista'] as $role) { + if (((array) ($data[$role] ?? [])) !== []) { + $row[$role] = $join($role); + } + } + return $row; + } + + /** + * The cover image URL among a record's 856 fields: one labelled as a + * cover ($3 / $y / $z), typed as an image ($q), or pointing at an image file. + */ + public static function coverUrlOf(MarcRecord $record): string + { + foreach ($record->fieldsByTag('856') as $field) { + $url = trim((string) $field->subfield('u')); + if ($url === '' || preg_match('#^https?://#i', $url) !== 1) { + continue; + } + $label = implode(' ', array_merge($field->subfieldValues('3'), $field->subfieldValues('y'), $field->subfieldValues('z'))); + $type = (string) $field->subfield('q'); + if (preg_match('/cover|copertin|umschlag|couverture|omslag|portada|thumbnail|image de couverture/iu', $label) === 1 + || str_starts_with(strtolower($type), 'image/') + || preg_match('#\.(jpe?g|png|gif|webp)(\?.*)?$#i', $url) === 1 + ) { + return $url; + } + } + return ''; + } +} diff --git a/app/Support/Marc/MarcBookImporter.php b/app/Support/Marc/MarcBookImporter.php new file mode 100644 index 000000000..b3161c663 --- /dev/null +++ b/app/Support/Marc/MarcBookImporter.php @@ -0,0 +1,618 @@ + ['isbn13', 'isbn10'], + 'ean' => ['ean'], + 'titolo' => ['titolo'], + 'sottotitolo' => ['sottotitolo'], + 'anno' => ['anno_pubblicazione'], + 'lingua' => ['lingua'], + 'edizione' => ['edizione'], + 'pagine' => ['numero_pagine'], + 'collana' => ['collana', 'numero_serie'], + 'dewey' => ['classificazione_dewey'], + 'keywords' => ['parole_chiave'], + 'description' => ['descrizione'], + 'publisher' => ['editore_id'], + 'genre' => ['genere_id'], + 'contributors' => ['traduttore', 'illustratore', 'curatore'], + ]; + + /** CSV update family → the book data keys that carry it. */ + private const FAMILY_DATA = [ + 'isbn' => ['isbn13', 'isbn10'], + 'ean' => ['ean'], + 'titolo' => ['titolo'], + 'sottotitolo' => ['sottotitolo'], + 'anno' => ['anno_pubblicazione'], + 'lingua' => ['lingua'], + 'edizione' => ['edizione'], + 'pagine' => ['numero_pagine'], + // A record with only the number in the series (225/490 $v) still + // carries the family: the book's series name is kept, its number filled. + 'collana' => ['collana', 'numero_serie'], + 'dewey' => ['classificazione_dewey'], + 'keywords' => ['parole_chiave'], + 'description' => ['descrizione'], + 'publisher' => ['editori'], + 'genre' => ['genere'], + 'authors' => ['autori'], + 'contributors' => ['traduttore', 'illustratore', 'curatore', 'colorista'], + ]; + + /** + * Columns written here, not by the CSV pipeline, each only when the + * libri table has it (the RDA columns arrive with a later migration). + */ + private const EXTRA_COLUMNS = [ + 'luogo_pubblicazione' => 255, + 'dimensioni' => 50, + 'lc_classification' => 100, + 'issn' => 20, + 'rda_content_type' => 10, + 'rda_media_type' => 10, + 'rda_carrier_type' => 10, + ]; + + private CsvImportController $pipeline; + + /** @var array|null */ + private ?array $columns = null; + + public function __construct( + private \mysqli $db, + private string $mode = self::MODE_SKIP, + private bool $downloadCovers = true, + private ?MarcCoverFetcher $coverFetcher = null + ) { + if (!in_array($mode, self::MODES, true)) { + throw new \InvalidArgumentException('Unknown duplicate mode: ' . $mode); + } + $this->pipeline = new CsvImportController(); + $this->pipeline->setImportSource('marc'); + $this->coverFetcher ??= new MarcCoverFetcher(); + } + + /** + * Import one record's book data. $counters is advanced (imported, + * updated, skipped, authors_created, publishers_created, covers); a + * cover that cannot be fetched is reported in $warnings, not as a + * failure. Throws when the book itself cannot be written; nothing of it + * is then left in the catalogue. Once the book is committed, a failure + * of the extra MARC columns, the cover or the search index is a warning. + * + * @param array> $data book data (MarcBookData shape) + * @param array $counters + * @param list $warnings + * @return array{action: string, id: int|null, title: string} + */ + public function import(array $data, array &$counters, array &$warnings = []): array + { + foreach (['imported', 'updated', 'skipped', 'authors_created', 'publishers_created', 'covers'] as $key) { + $counters[$key] = (int) ($counters[$key] ?? 0); + } + $parsed = $this->pipeline->normalizeImportRow(MarcBookData::toImportRow($data)); + $title = (string) ($parsed['titolo'] ?? ''); + // Identifiers that belong to two different books (the ISBN of one, + // the EAN or other ISBN form of another) name no single book: fill + // would write one work's data into the other, overwrite would trip + // the unique keys. Skip mode only leaves the first match as it is. + if ($this->mode !== self::MODE_SKIP && count($this->identifierMatches($parsed, 2) ?? []) > 1) { + throw new \RuntimeException(__('Gli identificativi del record (ISBN, EAN) appartengono a libri diversi del catalogo: record non importato.')); + } + $existingId = $this->findExisting($parsed); + + if ($existingId !== null && $this->mode === self::MODE_SKIP) { + $counters['skipped']++; + return ['action' => 'skipped', 'id' => $existingId, 'title' => $title]; + } + if ($existingId === null && $title === '') { + throw new \RuntimeException(__('Titolo mancante')); + } + + $existing = $existingId !== null ? $this->loadBook($existingId) : null; + if ($existingId !== null && $existing === null) { + throw new \RuntimeException('Unable to read the existing book'); + } + $families = []; + if ($existing !== null) { + [$families, $parsed] = self::plan($this->mode, $data, $parsed, $existing); + } + + $result = $this->pipeline->importParsedRow( + $this->db, + $parsed, + $families, + $counters, + $existingId, + false + ); + $bookId = $result['id']; + + // The book is committed and counted at this point: what follows only + // completes it, so a failure here is a warning on an imported record, + // never a failed record (that would count it twice and contradict the + // catalogue, where the book already is). + try { + $this->writeExtraColumns($bookId, $data, $existing); + } catch (\Throwable $e) { + $warnings[] = __('Campi MARC aggiuntivi non salvati') . ': ' . $e->getMessage(); + \App\Support\SecureLogger::warning('[MARC import] extra columns not written', ['libro_id' => $bookId, 'error' => $e->getMessage()]); + } + $this->writeCover($bookId, (string) ($data['copertina_url'] ?? ''), $existing, $counters, $warnings); + try { + \App\Support\SearchIndexBuilder::rebuild($this->db, $bookId); + } catch (\Throwable $e) { + $warnings[] = __('Indice di ricerca non aggiornato') . ': ' . $e->getMessage(); + \App\Support\SecureLogger::warning('[MARC import] search index not rebuilt', ['libro_id' => $bookId, 'error' => $e->getMessage()]); + } + + return ['action' => $result['action'], 'id' => $bookId, 'title' => $title]; + } + + /** + * The book (not deleted) a record matches: by any form of its ISBN or its + * EAN; by exact title and first author when it has neither. + * + * @param array $parsed a row as normalizeImportRow() returns it + */ + public function findExisting(array $parsed): ?int + { + $byIdentifier = $this->identifierMatches($parsed, 1); + if ($byIdentifier !== null) { + return $byIdentifier[0] ?? null; + } + return $this->findByTitleAndAuthor($parsed); + } + + /** + * The books (not deleted, lowest id first, at most $limit) any form of + * the record's ISBN or its EAN matches; null when the record has no + * identifier at all. + * + * @param array $parsed + * @return list|null + */ + private function identifierMatches(array $parsed, int $limit): ?array + { + $thirteen = []; + $ten = []; + foreach (['isbn13', 'isbn10'] as $key) { + $isbn = (string) ($parsed[$key] ?? ''); + if ($isbn === '') { + continue; + } + $variants = \App\Support\IsbnFormatter::getAllVariants($isbn); + if (isset($variants['isbn13'])) { + $thirteen[] = $variants['isbn13']; + } + if (isset($variants['isbn10'])) { + $ten[] = $variants['isbn10']; + } + } + $ean = (string) ($parsed['ean'] ?? ''); + $codes13 = array_values(array_unique(array_filter(array_merge($thirteen, [$ean])))); + $ten = array_values(array_unique($ten)); + if ($codes13 !== [] || $ten !== []) { + $conditions = []; + $params = []; + if ($codes13 !== []) { + $in = implode(',', array_fill(0, count($codes13), '?')); + $conditions[] = "isbn13 IN ({$in})"; + $conditions[] = "ean IN ({$in})"; + $params = array_merge($params, $codes13, $codes13); + } + if ($ten !== []) { + $conditions[] = 'isbn10 IN (' . implode(',', array_fill(0, count($ten), '?')) . ')'; + $params = array_merge($params, $ten); + } + $stmt = $this->db->prepare('SELECT id FROM libri WHERE deleted_at IS NULL AND (' . implode(' OR ', $conditions) . ') ORDER BY id LIMIT ' . max(1, $limit)); + if ($stmt === false) { + throw new \RuntimeException('Unable to prepare the duplicate lookup'); + } + $stmt->bind_param(str_repeat('s', count($params)), ...$params); + $stmt->execute(); + $ids = []; + foreach ($stmt->get_result()->fetch_all(MYSQLI_NUM) as $row) { + $ids[] = (int) $row[0]; + } + $stmt->close(); + return $ids; + } + return null; + } + + /** + * The book (not deleted) with the record's exact title whose principal + * author is the record's first author. + * + * @param array $parsed + */ + private function findByTitleAndAuthor(array $parsed): ?int + { + $title = trim((string) ($parsed['titolo'] ?? '')); + $authors = array_values(array_filter(array_map('trim', explode(';', (string) ($parsed['autori'] ?? ''))))); + if ($title === '' || $authors === []) { + return null; + } + // The first author is resolved exactly as the import will link it + // (CsvImportController::getOrCreateAuthor() -> findByCanonicalName(): + // quotes mean exact spelling, otherwise accents are folded but the + // exact spelling wins when both are on file). Comparing names here with + // another rule made a re-import create the same book again. The title + // compares with accents distinct (#501), so a record never overwrites + // a different book whose title only differs by an accent. Only a + // principal author counts: the record's first author is one, and the + // translator of another book with the same title is not its author. + $authorId = (new \App\Models\AuthorRepository($this->db))->findByCanonicalName($authors[0]); + if ($authorId === null) { + return null; + } + $titleCond = \App\Support\ExactSearch::equalsCondition('l.titolo', $title); + $stmt = $this->db->prepare( + 'SELECT l.id FROM libri l + JOIN libri_autori la ON la.libro_id = l.id + WHERE l.deleted_at IS NULL + AND la.autore_id = ? + AND la.ruolo = \'principale\' + AND ' . $titleCond['sql'] . ' + ORDER BY l.id LIMIT 1' + ); + if ($stmt === false) { + throw new \RuntimeException('Unable to prepare the duplicate lookup'); + } + $params = [$authorId, ...$titleCond['params']]; + $stmt->bind_param('i' . $titleCond['types'], ...$params); + $stmt->execute(); + $row = $stmt->get_result()->fetch_assoc(); + $stmt->close(); + return $row ? (int) $row['id'] : null; + } + + /** + * What an existing book gets from a record: the CSV update families the + * record may write, and the parsed row as the pipeline must receive it. + * + * A family writes several columns at once (ISBN-13 and ISBN-10, series + * and number, the legacy contributor columns). In every mode a column + * the record leaves empty keeps the book's value. In fill mode a family + * is written as soon as one of its columns is empty, and every column + * the book already has is handed back to the pipeline unchanged, so a + * non-empty field is never overwritten: a book with an ISBN-13 and no + * ISBN-10 keeps its ISBN-13 even when the record names another, and + * gains the record's ISBN-10 only when it is the same ISBN. + * + * @param array> $data book data (MarcBookData shape) + * @param array $parsed the row as normalizeImportRow() returns it + * @param array $existing the libri row (with _principal_authors) + * @return array{0: array, 1: array} + */ + public static function plan(string $mode, array $data, array $parsed, array $existing): array + { + $carries = static function (string $family) use ($data): bool { + foreach (self::FAMILY_DATA[$family] ?? [] as $key) { + $value = $data[$key] ?? ''; + if (is_array($value) ? $value !== [] : trim((string) $value) !== '') { + return true; + } + } + return false; + }; + $empty = array_flip(\App\Support\BookDataMerger::getMissingFields($existing, array_keys($existing))); + $families = []; + foreach (array_keys(self::FAMILY_DATA) as $family) { + if (!$carries($family)) { + $families[$family] = false; + continue; + } + if ($mode === self::MODE_OVERWRITE) { + $families[$family] = true; + continue; + } + // fill: a family with at least one column the book has empty + if ($family === 'authors') { + $families[$family] = (int) ($existing['_principal_authors'] ?? 0) === 0; + continue; + } + // The colorist has no legacy column: the family is empty for it + // when the book has no colorist link. + if ($family === 'contributors' && ($data['colorista'] ?? []) !== [] && (int) ($existing['_colorist_links'] ?? 0) === 0) { + $families[$family] = true; + continue; + } + $families[$family] = array_intersect_key(array_flip(self::FAMILY_COLUMNS[$family]), $empty) !== []; + } + // Never from a MARC record: the format and the price. The media type + // is its own family of the pipeline, apart from the format (which the + // CSV import derives from the row): in overwrite mode a record that + // carries one sets it; in fill mode it is left, because a book always + // has a media type (libri.tipo_media is NOT NULL, default 'libro'), + // so the family never has an empty column to fill. + foreach (['formato', 'prezzo'] as $family) { + $families[$family] = false; + } + $families['tipo_media'] = $mode === self::MODE_OVERWRITE + && trim((string) ($data['tipo_media'] ?? '')) !== ''; + foreach (self::FAMILY_COLUMNS as $family => $columns) { + foreach ($columns as $column) { + if ($column === 'editore_id' || $column === 'genere_id') { + continue; // the pipeline resolves the publisher and the genre by name + } + if (isset($empty[$column]) || !array_key_exists($column, $existing)) { + continue; + } + $value = $parsed[$column] ?? null; + $recordEmpty = $value === null || (is_string($value) && trim($value) === ''); + if ($recordEmpty || ($mode === self::MODE_FILL && $families[$family])) { + $parsed[$column] = $existing[$column]; + } + } + } + // fill: the empty half of the ISBN family is filled only with the + // other form of the ISBN the book already has. A record of another + // work would otherwise leave the book with two works' identifiers, + // and the duplicate lookup (which matches either column) would then + // skip the other work as this book. + if ($mode === self::MODE_FILL && $families['isbn']) { + $filled = false; + foreach (['isbn10' => 'isbn13', 'isbn13' => 'isbn10'] as $column => $other) { + $known = trim((string) ($existing[$other] ?? '')); + $value = trim((string) ($parsed[$column] ?? '')); + if ($value === '' || !isset($empty[$column])) { + continue; + } + if ($known !== '') { + $variant = \App\Support\IsbnFormatter::getAllVariants($known)[$column] ?? null; + if ($variant === null || $variant !== \App\Support\IsbnFormatter::clean($value)) { + $parsed[$column] = $existing[$column] ?? null; + continue; + } + } + $filled = true; + } + $families['isbn'] = $filled; + } + // overwrite: a record that carries one form of the ISBN keeps the + // book's other form only when it is the same ISBN; another work's + // ISBN-10 next to the record's ISBN-13 is cleared, not kept. + if ($mode === self::MODE_OVERWRITE && $families['isbn']) { + foreach (['isbn10' => 'isbn13', 'isbn13' => 'isbn10'] as $column => $other) { + $recordValue = trim((string) ($data[$column] ?? '')); + $recordOther = trim((string) ($data[$other] ?? '')); + $kept = trim((string) ($parsed[$column] ?? '')); + if ($recordValue !== '' || $recordOther === '' || $kept === '') { + continue; + } + $variant = \App\Support\IsbnFormatter::getAllVariants($recordOther)[$column] ?? null; + if ($variant === null || $variant !== \App\Support\IsbnFormatter::clean($kept)) { + $parsed[$column] = ''; + } + } + } + return [$families, $parsed]; + } + + /** @return array|null */ + private function loadBook(int $id): ?array + { + $stmt = $this->db->prepare('SELECT * FROM libri WHERE id = ? AND deleted_at IS NULL LIMIT 1'); + if ($stmt === false) { + return null; + } + $stmt->bind_param('i', $id); + $stmt->execute(); + $row = $stmt->get_result()->fetch_assoc() ?: null; + $stmt->close(); + if ($row === null) { + return null; + } + $count = $this->db->prepare("SELECT COUNT(*) FROM libri_autori WHERE libro_id = ? AND ruolo = 'principale'"); + if ($count !== false) { + $count->bind_param('i', $id); + $count->execute(); + $row['_principal_authors'] = (int) ($count->get_result()->fetch_row()[0] ?? 0); + $count->close(); + } + $colorists = $this->db->prepare("SELECT COUNT(*) FROM libri_autori WHERE libro_id = ? AND ruolo = 'colorista'"); + if ($colorists !== false) { + $colorists->bind_param('i', $id); + $colorists->execute(); + $row['_colorist_links'] = (int) ($colorists->get_result()->fetch_row()[0] ?? 0); + $colorists->close(); + } + return $row; + } + + /** + * The extra MARC columns to write for one record, column => value (NULL + * clears it). A record writes the columns it carries (in fill mode only + * where the book has them empty). When an overwrite changes the book's + * media type, the RDA overrides the record does not carry are cleared: + * they described the old material (a printed book's carrier "nb" on what + * is now a DVD), and without them the new media type implies its own + * RDA codes. + * + * @param array> $data + * @param array|null $existing + * @param callable(string): bool $hasColumn + * @return array + */ + public static function extraColumnUpdates(string $mode, array $data, ?array $existing, callable $hasColumn): array + { + $newType = trim((string) ($data['tipo_media'] ?? '')); + $typeChanged = $existing !== null + && $mode === self::MODE_OVERWRITE + && $newType !== '' + && $newType !== trim((string) ($existing['tipo_media'] ?? '')); + $updates = []; + // RDA types module off: imports neither set nor clear the overrides. + $rdaOn = \App\Support\Features::enabled('rda_types'); + foreach (self::EXTRA_COLUMNS as $column => $length) { + if (!$hasColumn($column) || (!$rdaOn && str_starts_with($column, 'rda_'))) { + continue; + } + $value = trim((string) ($data[$column] ?? '')); + if ($value === '') { + if ($typeChanged && str_starts_with($column, 'rda_') && trim((string) ($existing[$column] ?? '')) !== '') { + $updates[$column] = null; + } + continue; + } + if ($existing !== null && $mode === self::MODE_FILL && trim((string) ($existing[$column] ?? '')) !== '') { + continue; + } + $updates[$column] = mb_substr($value, 0, $length); + } + return $updates; + } + + /** + * @param array> $data + * @param array|null $existing + */ + private function writeExtraColumns(int $bookId, array $data, ?array $existing): void + { + $updates = self::extraColumnUpdates($this->mode, $data, $existing, fn (string $column): bool => $this->hasColumn($column)); + if ($updates === []) { + return; + } + $sets = []; + $params = []; + foreach ($updates as $column => $value) { + $sets[] = "{$column} = ?"; + $params[] = $value; + } + $params[] = $bookId; + $stmt = $this->db->prepare('UPDATE libri SET ' . implode(', ', $sets) . ' WHERE id = ? AND deleted_at IS NULL'); + if ($stmt === false) { + throw new \RuntimeException('Unable to prepare the MARC column update'); + } + $stmt->bind_param(str_repeat('s', count($params) - 1) . 'i', ...$params); + $stmt->execute(); + $stmt->close(); + } + + /** + * @param array|null $existing + * @param array $counters + * @param list $warnings + */ + private function writeCover(int $bookId, string $url, ?array $existing, array &$counters, array &$warnings): void + { + if (!$this->downloadCovers || $url === '') { + return; + } + if ($existing !== null && $this->mode === self::MODE_FILL && trim((string) ($existing['copertina_url'] ?? '')) !== '') { + return; + } + try { + $path = $this->coverFetcher?->fetch($url) ?? ''; + } catch (\Throwable $e) { + $warnings[] = __('Copertina non scaricata') . ': ' . $e->getMessage(); + return; + } + if ($path === '') { + return; + } + // The file name is unique: if no book row ends up pointing at it, + // nothing ever will, so it is removed instead of left on disk. + $attached = false; + try { + $stmt = $this->db->prepare('UPDATE libri SET copertina_url = ? WHERE id = ? AND deleted_at IS NULL'); + if ($stmt !== false) { + $stmt->bind_param('si', $path, $bookId); + $stmt->execute(); + $attached = $stmt->affected_rows > 0; + $stmt->close(); + } + } catch (\Throwable $e) { + if (!$attached) { + MarcCoverFetcher::discard($path); + } + $warnings[] = __('Copertina non scaricata') . ': ' . $e->getMessage(); + return; + } + if (!$attached) { + MarcCoverFetcher::discard($path); + return; + } + $counters['covers']++; + // An overwrite replaced the book's own downloaded cover: the old file + // is removed once no book points at it any more (discard() touches + // only a plain name in the covers folder, never an external URL). + $previous = trim((string) ($existing['copertina_url'] ?? '')); + if ($previous !== '' && $previous !== $path) { + try { + $still = $this->db->prepare('SELECT 1 FROM libri WHERE copertina_url = ? AND deleted_at IS NULL LIMIT 1'); + if ($still !== false) { + $still->bind_param('s', $previous); + $still->execute(); + $referenced = $still->get_result()->fetch_row() !== null; + $still->close(); + if (!$referenced) { + MarcCoverFetcher::discard($previous); + } + } + } catch (\Throwable $e) { + \App\Support\SecureLogger::warning('[MARC import] previous cover not removed', ['libro_id' => $bookId, 'error' => $e->getMessage()]); + } + } + } + + private function hasColumn(string $column): bool + { + if ($this->columns === null) { + $this->columns = []; + $res = $this->db->query("SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'libri'"); + if ($res instanceof \mysqli_result) { + while ($r = $res->fetch_row()) { + $this->columns[(string) $r[0]] = true; + } + $res->free(); + } + } + return isset($this->columns[$column]); + } +} diff --git a/app/Support/Marc/MarcCoverFetcher.php b/app/Support/Marc/MarcCoverFetcher.php new file mode 100644 index 000000000..a51f6f2e9 --- /dev/null +++ b/app/Support/Marc/MarcCoverFetcher.php @@ -0,0 +1,129 @@ +localCoverFile($url); + if ($local !== null) { + return $this->copyLocal($local); + } + $saved = (new \App\Controllers\CoverController())->downloadFromUrl($url); + return (string) $saved['file_url']; + } + + /** The file in this installation's covers folder a URL names, or null. */ + private function localCoverFile(string $url): ?string + { + $parts = parse_url($url); + $path = (string) ($parts['path'] ?? ''); + if ($path === '' || !isset($parts['host'])) { + return null; + } + $base = parse_url(function_exists('absoluteUrl') ? \absoluteUrl('/') : ''); + if (!is_array($base) || !isset($base['host']) + || strcasecmp((string) $base['host'], (string) $parts['host']) !== 0 + || self::effectivePort($base) !== self::effectivePort($parts) + ) { + return null; + } + $basePath = rtrim((string) ($base['path'] ?? ''), '/'); + if ($basePath !== '' && str_starts_with($path, $basePath . '/')) { + $path = substr($path, strlen($basePath)); + } + if (!str_starts_with($path, self::COVERS_PATH)) { + return null; + } + $name = basename(rawurldecode($path)); + if (preg_match('/^[A-Za-z0-9._-]+\.(jpe?g|png|webp|gif)$/i', $name) !== 1) { + return null; + } + $file = self::coversDir() . '/' . $name; + return is_file($file) ? $file : null; + } + + /** + * The port a parse_url() result points at: the explicit one, else the + * scheme's default, so https://host and https://host:443 are the same. + * + * @param array $parts + */ + public static function effectivePort(array $parts): int + { + if (isset($parts['port'])) { + return (int) $parts['port']; + } + return match (strtolower((string) ($parts['scheme'] ?? ''))) { + 'https' => 443, + 'http' => 80, + default => 0, + }; + } + + private function copyLocal(string $file): string + { + $info = @getimagesize($file); + if ($info === false) { + throw new \RuntimeException(__('File non valido o corrotto.')); + } + // The stored name follows the bytes, not the source name, so a server + // that types files by extension serves the right Content-Type. + $extension = match ($info[2]) { + IMAGETYPE_JPEG => 'jpg', + IMAGETYPE_PNG => 'png', + IMAGETYPE_WEBP => 'webp', + IMAGETYPE_GIF => 'gif', + default => throw new \RuntimeException(__('File non valido o corrotto.')), + }; + $name = uniqid('copertina_', true) . '.' . $extension; + if (!@copy($file, self::coversDir() . '/' . $name)) { + throw new \RuntimeException(__('Errore nel salvataggio dell\'immagine.')); + } + @chmod(self::coversDir() . '/' . $name, 0644); + return self::COVERS_PATH . $name; + } + + /** + * Remove a cover fetch() just stored but nothing ended up referencing + * (the book row was not updated). Only a plain file name under the covers + * folder is touched, never a path a caller could steer elsewhere. + */ + public static function discard(string $storedPath): void + { + if (!str_starts_with($storedPath, self::COVERS_PATH)) { + return; + } + $name = substr($storedPath, strlen(self::COVERS_PATH)); + if (preg_match('/^[A-Za-z0-9._-]+\.(jpe?g|png|webp|gif)$/i', $name) !== 1) { + return; + } + $file = self::coversDir() . '/' . $name; + if (is_file($file)) { + @unlink($file); + } + } + + private static function coversDir(): string + { + return dirname(__DIR__, 3) . '/public/uploads/copertine'; + } +} diff --git a/app/Support/Marc/MarcField.php b/app/Support/Marc/MarcField.php new file mode 100644 index 000000000..e9ab278a6 --- /dev/null +++ b/app/Support/Marc/MarcField.php @@ -0,0 +1,76 @@ + $subfields + */ + public function __construct( + public readonly string $tag, + public readonly ?string $data = null, + public readonly string $ind1 = ' ', + public readonly string $ind2 = ' ', + public readonly array $subfields = [] + ) { + } + + public function isControl(): bool + { + return $this->data !== null; + } + + /** The first value of a subfield, or null. */ + public function subfield(string $code): ?string + { + foreach ($this->subfields as [$c, $value]) { + if ($c === $code) { + return $value; + } + } + return null; + } + + /** @return list every value of a subfield, in order */ + public function subfieldValues(string $code): array + { + $out = []; + foreach ($this->subfields as [$c, $value]) { + if ($c === $code) { + $out[] = $value; + } + } + return $out; + } + + /** + * Subfields keyed by code, as the catalogue parsers read them. Numeric + * codes become integer keys, as PHP does with any numeric string. + * + * @return array> + */ + public function subfieldMap(): array + { + $out = []; + foreach ($this->subfields as [$code, $value]) { + $out[$code][] = $value; + } + return $out; + } + + /** @return array{tag: string, data?: string, ind1?: string, ind2?: string, subfields?: list} */ + public function toArray(): array + { + if ($this->data !== null) { + return ['tag' => $this->tag, 'data' => $this->data]; + } + return ['tag' => $this->tag, 'ind1' => $this->ind1, 'ind2' => $this->ind2, 'subfields' => $this->subfields]; + } +} diff --git a/app/Support/Marc/MarcFormat.php b/app/Support/Marc/MarcFormat.php new file mode 100644 index 000000000..35888b94a --- /dev/null +++ b/app/Support/Marc/MarcFormat.php @@ -0,0 +1,113 @@ + */ + public const FLAVOURS = [self::MARC21, self::UNIMARC, self::DANMARC2]; + + /** The container of a file from its first bytes; null when it is neither. */ + public static function detectContainer(string $head): ?string + { + $head = (string) preg_replace('/^\xEF\xBB\xBF/', '', $head); + $head = ltrim($head, " \t\r\n"); + if ($head === '') { + return null; + } + if ($head[0] === '<') { + return self::CONTAINER_XML; + } + // An ISO 2709 record opens with its 5-digit length and, at 12-16, + // the base address of its data: the directory ends with a field + // terminator just before it. When the head is shorter than the base + // address, any field terminator in it is taken as the sign. + if (strlen($head) >= 24 && ctype_digit(substr($head, 0, 5))) { + $base = self::baseAddress($head); + if ($base !== null && strlen($head) >= $base) { + return $head[$base - 1] === Iso2709Writer::FIELD_TERMINATOR ? self::CONTAINER_ISO2709 : null; + } + if (str_contains($head, Iso2709Writer::FIELD_TERMINATOR)) { + return self::CONTAINER_ISO2709; + } + } + return null; + } + + /** The base address of data (leader 12-16) of an ISO 2709 head; null when it is not one. */ + private static function baseAddress(string $head): ?int + { + $digits = substr($head, 12, 5); + if (strlen($digits) !== 5 || !ctype_digit($digits) || (int) $digits <= 24) { + return null; + } + return (int) $digits; + } + + public static function containerOfFile(string $path): ?string + { + $handle = @fopen($path, 'rb'); + if ($handle === false) { + return null; + } + $head = (string) fread($handle, 4096); + // A record with some 340 fields or more has a directory longer than + // 4096 bytes: read up to its base address (at most 99999, the largest + // ISO 2709 record), so its terminator is found where it belongs. + $trimmed = ltrim((string) preg_replace('/^\xEF\xBB\xBF/', '', $head), " \t\r\n"); + $base = strlen($trimmed) >= 24 && ctype_digit(substr($trimmed, 0, 5)) ? self::baseAddress($trimmed) : null; + if ($base !== null && $base > strlen($trimmed)) { + $head .= (string) fread($handle, $base - strlen($trimmed)); + } + fclose($handle); + return self::detectContainer($head); + } + + /** + * The flavour of one record. A MARCXchange record that says it is + * danMARC2, or whose 001 is a data field (danMARC2 carries the record + * number in 001 *a), is danMARC2; a record with a 200 title and no 245 + * is UNIMARC; anything else is MARC 21. + */ + public static function detectFlavour(MarcRecord $record): string + { + $declared = strtolower($record->format()); + if (str_contains($declared, 'danmarc')) { + return self::DANMARC2; + } + if (str_contains($declared, 'unimarc')) { + return self::UNIMARC; + } + $first001 = $record->first('001'); + if ($first001 !== null && !$first001->isControl()) { + return self::DANMARC2; + } + if (!$record->hasField('245') && $record->hasField('200')) { + return self::UNIMARC; + } + return self::MARC21; + } + + public static function mapperFor(string $flavour): MarcLibriMapper + { + return match ($flavour) { + self::UNIMARC => new UnimarcLibriMapper(), + self::DANMARC2 => new Danmarc2LibriMapper(), + default => new Marc21LibriMapper(), + }; + } +} diff --git a/app/Support/Marc/MarcLanguage.php b/app/Support/Marc/MarcLanguage.php new file mode 100644 index 000000000..5e0ea4114 --- /dev/null +++ b/app/Support/Marc/MarcLanguage.php @@ -0,0 +1,102 @@ + 'Italiano', 'eng' => 'English', 'fre' => 'Français', 'fra' => 'Français', + 'ger' => 'Deutsch', 'deu' => 'Deutsch', 'spa' => 'Español', 'por' => 'Português', + 'rus' => 'Русский', 'chi' => '中文', 'zho' => '中文', 'jpn' => '日本語', 'ara' => 'العربية', + 'dut' => 'Nederlands', 'nld' => 'Nederlands', 'swe' => 'Svenska', 'nor' => 'Norsk', + 'nob' => 'Norsk', 'nno' => 'Norsk', 'dan' => 'Dansk', 'fin' => 'Suomi', 'pol' => 'Polski', + 'cze' => 'Čeština', 'ces' => 'Čeština', 'hun' => 'Magyar', 'rum' => 'Română', 'ron' => 'Română', + 'gre' => 'Ελληνικά', 'ell' => 'Ελληνικά', 'tur' => 'Türkçe', 'heb' => 'עברית', 'hin' => 'हिन्दी', + 'kor' => '한국어', 'tha' => 'ไทย', 'lat' => 'Latina', 'cat' => 'Català', 'grc' => 'Greco antico', + 'ice' => 'Íslenska', 'isl' => 'Íslenska', + ]; + + /** Codes that name no language. */ + private const NONE = ['und', 'mul', 'zxx', 'mis', '|||', ' ', 'xxx']; + + /** + * The stored name for a code; '' for "undetermined" and for a code this + * table does not know (a raw "swa" in the language field would be no + * language to the catalogue). + */ + public static function nameFor(string $code): string + { + $code = strtolower(trim($code)); + if ($code === '' || in_array($code, self::NONE, true) || strlen($code) !== 3) { + return ''; + } + return self::NAMES[$code] ?? ''; + } + /** + * ISO 639-2/B code for one stored language (Italian/English/native name, + * ISO 639-1 or 639-2 code), or null when it is not recognised. + */ + public static function codeFor(string $lang): ?string + { + static $map = [ + 'italiano' => 'ita', 'italian' => 'ita', 'it' => 'ita', 'ita' => 'ita', + 'inglese' => 'eng', 'english' => 'eng', 'en' => 'eng', 'eng' => 'eng', + 'francese' => 'fre', 'français' => 'fre', 'french' => 'fre', 'fr' => 'fre', 'fre' => 'fre', 'fra' => 'fre', + 'tedesco' => 'ger', 'deutsch' => 'ger', 'german' => 'ger', 'de' => 'ger', 'ger' => 'ger', 'deu' => 'ger', + 'spagnolo' => 'spa', 'español' => 'spa', 'spanish' => 'spa', 'es' => 'spa', 'spa' => 'spa', + 'portoghese' => 'por', 'português' => 'por', 'portuguese' => 'por', 'pt' => 'por', 'por' => 'por', + 'danese' => 'dan', 'dansk' => 'dan', 'danish' => 'dan', 'da' => 'dan', 'dan' => 'dan', + 'olandese' => 'dut', 'nederlands' => 'dut', 'dutch' => 'dut', 'nl' => 'dut', 'dut' => 'dut', 'nld' => 'dut', + 'svedese' => 'swe', 'svenska' => 'swe', 'swedish' => 'swe', 'sv' => 'swe', 'swe' => 'swe', + 'norvegese' => 'nor', 'norsk' => 'nor', 'norwegian' => 'nor', 'no' => 'nor', 'nor' => 'nor', + 'russo' => 'rus', 'russian' => 'rus', 'ru' => 'rus', 'rus' => 'rus', + 'polacco' => 'pol', 'polski' => 'pol', 'polish' => 'pol', 'pl' => 'pol', 'pol' => 'pol', + 'greco' => 'gre', 'greek' => 'gre', 'el' => 'gre', 'gre' => 'gre', 'ell' => 'gre', + 'greco antico' => 'grc', 'grc' => 'grc', + 'latino' => 'lat', 'latin' => 'lat', 'la' => 'lat', 'lat' => 'lat', + 'cinese' => 'chi', 'chinese' => 'chi', 'zh' => 'chi', 'chi' => 'chi', 'zho' => 'chi', + 'giapponese' => 'jpn', 'japanese' => 'jpn', 'ja' => 'jpn', 'jpn' => 'jpn', + 'arabo' => 'ara', 'arabic' => 'ara', 'ar' => 'ara', 'ara' => 'ara', + 'catalano' => 'cat', 'català' => 'cat', 'catalan' => 'cat', 'ca' => 'cat', 'cat' => 'cat', + ]; + $key = mb_strtolower(trim($lang)); + if (isset($map[$key])) { + return $map[$key]; + } + // Every name nameFor() stores (Русский, 中文, Suomi, Latina...) maps + // back: NAMES lists the B form of a B/T pair first. + foreach (self::NAMES as $code => $name) { + if (mb_strtolower($name) === $key) { + return $code; + } + } + return null; + } + + /** + * The individual languages of a stored lingua value ("italiano, inglese"). + * + * @return list + */ + public static function listOf(string $lang): array + { + $parts = preg_split('/\s*[,;\/]\s*/', trim($lang)) ?: []; + return array_values(array_filter(array_map('trim', $parts), static fn (string $p): bool => $p !== '')); + } + + /** + * The MARC code of a stored lingua value ('und' when unknown). A + * multi-language value uses its first language. + */ + public static function marcCodeOf(string $lang): string + { + $first = self::listOf($lang)[0] ?? ''; + return self::codeFor($first) ?? 'und'; + } +} diff --git a/app/Support/Marc/MarcLibriMapper.php b/app/Support/Marc/MarcLibriMapper.php new file mode 100644 index 000000000..d512e25e8 --- /dev/null +++ b/app/Support/Marc/MarcLibriMapper.php @@ -0,0 +1,15 @@ +> */ + public function map(MarcRecord $record): array; +} diff --git a/app/Support/Marc/MarcMediaType.php b/app/Support/Marc/MarcMediaType.php new file mode 100644 index 000000000..d3908564a --- /dev/null +++ b/app/Support/Marc/MarcMediaType.php @@ -0,0 +1,157 @@ + ['a', 'ta', 'txt', 'n', 'nc'], + 'disco' => ['j', 'sd |||||||||||', 'prm', 's', 'sd'], + 'audiolibro' => ['i', 'sd |||||||||||', 'spw', 's', 'sd'], + 'dvd' => ['g', 'vd |v||||', 'tdi', 'v', 'vd'], + 'altro' => ['a', null, 'xxx', 'x', 'zu'], + ]; + + /** RDA content types (rdacontent): code → English term. */ + private const CONTENT_TERMS = [ + 'txt' => 'text', 'prm' => 'performed music', 'spw' => 'spoken word', 'snd' => 'sounds', + 'tdi' => 'two-dimensional moving image', 'tdm' => 'three-dimensional moving image', + 'sti' => 'still image', 'tci' => 'tactile image', 'ntm' => 'notated music', 'tcm' => 'tactile notated music', + 'cri' => 'cartographic image', 'crm' => 'cartographic moving image', 'crd' => 'cartographic dataset', + 'crf' => 'cartographic three-dimensional form', 'crn' => 'cartographic tactile image', + 'cod' => 'computer dataset', 'cop' => 'computer program', 'tdf' => 'three-dimensional form', + 'tct' => 'tactile text', 'xxx' => 'other', 'zzz' => 'unspecified', + ]; + + /** RDA media types (rdamedia): code → English term. */ + private const MEDIA_TERMS = [ + 'n' => 'unmediated', 's' => 'audio', 'v' => 'video', 'c' => 'computer', 'g' => 'projected', + 'h' => 'microform', 'e' => 'stereographic', 'p' => 'microscopic', 'x' => 'other', 'z' => 'unspecified', + ]; + + /** RDA carrier types (rdacarrier): code → English term. */ + private const CARRIER_TERMS = [ + 'nc' => 'volume', 'nb' => 'sheet', 'na' => 'roll', 'no' => 'card', 'nn' => 'flipchart', 'nr' => 'object', 'nz' => 'other', + 'sd' => 'audio disc', 'ss' => 'audiocassette', 'st' => 'audiotape reel', 'sz' => 'other', + 'vd' => 'videodisc', 'vf' => 'videocassette', 'vr' => 'videotape reel', 'vz' => 'other', + 'cr' => 'online resource', 'cd' => 'computer disc', 'ck' => 'computer card', 'cz' => 'other', + 'zu' => 'unspecified', + ]; + + /** The media type as stored, defaulting to a book. */ + public static function normalise(?string $tipoMedia): string + { + $type = \App\Support\MediaLabels::normalizeTipoMedia($tipoMedia); + return $type !== null && isset(self::TYPES[$type]) ? $type : self::DEFAULT; + } + + /** Leader/06 (type of record) for a media type. */ + public static function leaderType(?string $tipoMedia): string + { + return self::TYPES[self::normalise($tipoMedia)][0]; + } + + /** MARC 21 007 for a media type, null when it has none worth claiming. */ + public static function field007(?string $tipoMedia): ?string + { + return self::TYPES[self::normalise($tipoMedia)][1]; + } + + /** + * The RDA codes of a book: those its media type implies, each replaced + * by the row's own (`rda_content_type`, `rda_media_type`, + * `rda_carrier_type`) when present. + * + * @param array $row a libri row + * @return array{content: string, media: string, carrier: string} + */ + public static function rdaCodes(array $row): array + { + [, , $content, $media, $carrier] = self::TYPES[self::normalise(isset($row['tipo_media']) ? (string) $row['tipo_media'] : null)]; + // RDA types module off: the stored overrides are not applied. + $overridesOn = \App\Support\Features::enabled('rda_types'); + $own = static fn (string $key): string => $overridesOn ? strtolower(trim((string) ($row[$key] ?? ''))) : ''; + return [ + 'content' => $own('rda_content_type') !== '' ? $own('rda_content_type') : $content, + 'media' => $own('rda_media_type') !== '' ? $own('rda_media_type') : $media, + 'carrier' => $own('rda_carrier_type') !== '' ? $own('rda_carrier_type') : $carrier, + ]; + } + + /** The English RDA term of a code; null when the code is not known. */ + public static function term(string $kind, string $code): ?string + { + $code = strtolower(trim($code)); + return match ($kind) { + 'content' => self::CONTENT_TERMS[$code] ?? null, + 'media' => self::MEDIA_TERMS[$code] ?? null, + 'carrier' => self::CARRIER_TERMS[$code] ?? null, + default => null, + }; + } + + /** + * The media type a record's leader/06 says, refined by its RDA content + * type (a sound recording of performed music is a disco whatever the + * leader says, of spoken word an audiolibro); '' when the leader names + * a kind the catalogue has no media type for (a map, a computer file). + * Language material with RDA content "other" (xxx) is an altro, the + * media type that exports that way. + */ + public static function tipoMediaOf(string $leaderType, ?string $contentCode = null): string + { + $content = strtolower(trim((string) $contentCode)); + return match (strtolower($leaderType)) { + // 'altro' is exported as language material with RDA content + // "other" (xxx): read it back as such, not as a book. + 'a', 't' => $content === 'xxx' ? 'altro' : self::DEFAULT, + 'g' => 'dvd', + 'i' => $content === 'prm' ? 'disco' : 'audiolibro', + 'j' => $content === 'spw' ? 'audiolibro' : 'disco', + default => '', + }; + } + + /** + * The RDA codes a record carries that its media type does not imply, + * keyed as the libri columns (`rda_content_type`, ...): what an import + * stores as the book's own, so a plain book stores nothing. + * + * @return array + */ + public static function overrides(string $tipoMedia, ?string $contentCode, ?string $mediaCode, ?string $carrierCode): array + { + [, , $content, $media, $carrier] = self::TYPES[self::normalise($tipoMedia)]; + $out = []; + foreach ([ + 'rda_content_type' => [$contentCode, $content, 'content'], + 'rda_media_type' => [$mediaCode, $media, 'media'], + 'rda_carrier_type' => [$carrierCode, $carrier, 'carrier'], + ] as $column => [$given, $implied, $kind]) { + $code = strtolower(trim((string) $given)); + if ($code !== '' && $code !== $implied && self::term($kind, $code) !== null) { + $out[$column] = $code; + } + } + return $out; + } +} diff --git a/app/Support/Marc/MarcParseException.php b/app/Support/Marc/MarcParseException.php new file mode 100644 index 000000000..9289ac012 --- /dev/null +++ b/app/Support/Marc/MarcParseException.php @@ -0,0 +1,9 @@ + */ + private array $fields = []; + + /** The MARCXchange `format` attribute ("danMARC2", "UNIMARC"), when given. */ + private string $format = ''; + + public function __construct(string $leader = self::DEFAULT_LEADER) + { + $this->leader = self::normaliseLeader($leader); + } + + /** A leader of exactly 24 characters: padded with blanks, or cut. */ + public static function normaliseLeader(string $leader): string + { + $leader = str_replace(["\r", "\n", "\t"], ' ', $leader); + if (strlen($leader) < 24) { + return str_pad($leader, 24, ' '); + } + return substr($leader, 0, 24); + } + + public function leader(): string + { + return $this->leader; + } + + public function setLeader(string $leader): void + { + $this->leader = self::normaliseLeader($leader); + } + + public function format(): string + { + return $this->format; + } + + public function setFormat(string $format): void + { + $this->format = $format; + } + + public function addControlField(string $tag, string $data): self + { + $this->fields[] = new MarcField($tag, $data); + return $this; + } + + /** + * @param list $subfields + */ + public function addDataField(string $tag, string $ind1, string $ind2, array $subfields): self + { + $this->fields[] = new MarcField($tag, null, self::indicator($ind1), self::indicator($ind2), $subfields); + return $this; + } + + public function addField(MarcField $field): self + { + $this->fields[] = $field; + return $this; + } + + private static function indicator(string $value): string + { + return $value === '' ? ' ' : substr($value, 0, 1); + } + + /** @return list */ + public function fields(): array + { + return $this->fields; + } + + /** @return list */ + public function fieldsByTag(string $tag): array + { + return array_values(array_filter($this->fields, static fn (MarcField $f): bool => $f->tag === $tag)); + } + + public function first(string $tag): ?MarcField + { + foreach ($this->fields as $field) { + if ($field->tag === $tag) { + return $field; + } + } + return null; + } + + public function hasField(string $tag): bool + { + return $this->first($tag) !== null; + } + + public function controlField(string $tag): ?string + { + foreach ($this->fields as $field) { + if ($field->tag === $tag && $field->data !== null) { + return $field->data; + } + } + return null; + } + + /** The first value of a subfield across every field with the tag. */ + public function subfield(string $tag, string $code): ?string + { + foreach ($this->fields as $field) { + if ($field->tag === $tag && $field->data === null) { + $value = $field->subfield($code); + if ($value !== null) { + return $value; + } + } + } + return null; + } + + /** @return list */ + public function subfieldValues(string $tag, string $code): array + { + $out = []; + foreach ($this->fields as $field) { + if ($field->tag === $tag && $field->data === null) { + foreach ($field->subfieldValues($code) as $value) { + $out[] = $value; + } + } + } + return $out; + } + + /** @return array{leader: string, format: string, fields: list>} */ + public function toArray(): array + { + return [ + 'leader' => $this->leader, + 'format' => $this->format, + 'fields' => array_map(static fn (MarcField $f): array => $f->toArray(), $this->fields), + ]; + } + + /** @param array $data the shape toArray() returns */ + public static function fromArray(array $data): self + { + $record = new self((string) ($data['leader'] ?? self::DEFAULT_LEADER)); + $record->setFormat((string) ($data['format'] ?? '')); + foreach ((array) ($data['fields'] ?? []) as $field) { + if (!is_array($field) || !isset($field['tag'])) { + continue; + } + $tag = (string) $field['tag']; + if (array_key_exists('data', $field)) { + $record->addControlField($tag, (string) $field['data']); + continue; + } + $subfields = []; + foreach ((array) ($field['subfields'] ?? []) as $sub) { + if (is_array($sub) && count($sub) >= 2) { + $subfields[] = [(string) $sub[0], (string) $sub[1]]; + } + } + $record->addDataField($tag, (string) ($field['ind1'] ?? ' '), (string) ($field['ind2'] ?? ' '), $subfields); + } + return $record; + } +} diff --git a/app/Support/Marc/MarcXmlReader.php b/app/Support/Marc/MarcXmlReader.php new file mode 100644 index 000000000..f57a0baa2 --- /dev/null +++ b/app/Support/Marc/MarcXmlReader.php @@ -0,0 +1,166 @@ +, or wrapped in an SRU or OAI-PMH + * response. A `record` element counts as a MARC record when it has a leader, + * controlfield or datafield child, so the SRU/OAI envelope around it is + * walked through, not mistaken for one. + * + * A record the converter cannot read is yielded as a MarcReadError in its + * place. XML that stops being well-formed ends the file with one error. + * + * @implements \IteratorAggregate + */ +final class MarcXmlReader implements \IteratorAggregate +{ + private const MARC_CHILDREN = ['leader', 'controlfield', 'datafield']; + + private function __construct(private \XMLReader $reader) + { + } + + public static function fromPath(string $path): self + { + $reader = new \XMLReader(); + if (!@$reader->open($path, null, LIBXML_NONET | LIBXML_COMPACT | LIBXML_PARSEHUGE)) { + throw new \RuntimeException('Cannot open MARCXML file'); + } + return new self($reader); + } + + public static function fromString(string $xml): self + { + $reader = \XMLReader::XML($xml, null, LIBXML_NONET | LIBXML_COMPACT | LIBXML_PARSEHUGE); + if (!$reader instanceof \XMLReader) { + throw new \RuntimeException('Cannot read MARCXML'); + } + return new self($reader); + } + + /** @return \Generator */ + public function getIterator(): \Generator + { + $previous = libxml_use_internal_errors(true); + $position = 0; + try { + // libxml's error buffer is global: only this reader's errors may + // decide that the file is not well-formed. + libxml_clear_errors(); + /** @var list<\LibXMLError> $own */ + $own = []; + $more = $this->reader->read(); + while ($more) { + if ($this->reader->nodeType === \XMLReader::DOC_TYPE) { + // MARCXML has no DTD. A document type declaration can only + // bring entity definitions, and with LIBXML_PARSEHUGE their + // expansion is not capped ("billion laughs"): stop before + // any of them is used. + yield $position + 1 => new MarcReadError( + $position + 1, + 'XML with a document type declaration (DOCTYPE) is not accepted' + ); + return; + } + if ($this->reader->nodeType === \XMLReader::ELEMENT && $this->reader->localName === 'record') { + $node = @$this->reader->expand(); + if ($node instanceof \DOMElement && self::isMarcRecord($node)) { + $position++; + // Keep what the reader itself reported so far (it parses + // ahead), then let the consumer run between the yields. + $own = array_merge($own, libxml_get_errors()); + try { + yield $position => self::toRecord($node); + } catch (MarcParseException $e) { + yield $position => new MarcReadError($position, $e->getMessage(), MarcReadError::excerptOf((string) $node->textContent)); + } + // The consumer ran between the yields (a mapper may + // parse XML of its own): drop what it left behind. + libxml_clear_errors(); + $more = $this->reader->next(); + continue; + } + } + $more = $this->reader->read(); + } + $error = null; + foreach (array_merge($own, libxml_get_errors()) as $candidate) { + if ($candidate->level >= LIBXML_ERR_ERROR) { + $error = $candidate; + } + } + if ($error !== null) { + yield $position + 1 => new MarcReadError( + $position + 1, + sprintf('XML is not well-formed at line %d: %s', $error->line, trim($error->message)) + ); + } + } finally { + libxml_clear_errors(); + libxml_use_internal_errors($previous); + $this->reader->close(); + } + } + + private static function isMarcRecord(\DOMElement $node): bool + { + foreach ($node->childNodes as $child) { + if ($child instanceof \DOMElement && in_array($child->localName, self::MARC_CHILDREN, true)) { + return true; + } + } + return false; + } + + /** + * A MARCXML / MARCXchange element as a MarcRecord. + * + * @throws MarcParseException + */ + public static function toRecord(\DOMElement $node): MarcRecord + { + $record = new MarcRecord(MarcRecord::DEFAULT_LEADER); + $record->setFormat($node->getAttribute('format')); + $hasLeader = false; + foreach ($node->childNodes as $child) { + if (!$child instanceof \DOMElement) { + continue; + } + switch ($child->localName) { + case 'leader': + $record->setLeader((string) $child->textContent); + $hasLeader = true; + break; + case 'controlfield': + $tag = $child->getAttribute('tag'); + if ($tag === '') { + throw new MarcParseException('controlfield without a tag'); + } + $record->addControlField($tag, (string) $child->textContent); + break; + case 'datafield': + $tag = $child->getAttribute('tag'); + if ($tag === '') { + throw new MarcParseException('datafield without a tag'); + } + $subfields = []; + foreach ($child->childNodes as $sub) { + if ($sub instanceof \DOMElement && $sub->localName === 'subfield') { + $subfields[] = [$sub->getAttribute('code'), (string) $sub->textContent]; + } + } + $record->addDataField($tag, $child->getAttribute('ind1'), $child->getAttribute('ind2'), $subfields); + break; + } + } + if (!$hasLeader && $record->fields() === []) { + throw new MarcParseException('Empty record'); + } + return $record; + } +} diff --git a/app/Support/Marc/MarcXmlWriter.php b/app/Support/Marc/MarcXmlWriter.php new file mode 100644 index 000000000..d785a37ee --- /dev/null +++ b/app/Support/Marc/MarcXmlWriter.php @@ -0,0 +1,137 @@ +handle = $handle; + $this->xw = new \XMLWriter(); + $this->xw->openMemory(); + } + + public function startCollection(): void + { + $this->xw->startDocument('1.0', 'UTF-8'); + $this->xw->startElementNs(null, 'collection', $this->namespace); + $this->xw->writeAttributeNs('xmlns', 'xsi', null, 'http://www.w3.org/2001/XMLSchema-instance'); + $this->xw->writeAttributeNs('xsi', 'schemaLocation', null, $this->namespace . ' ' + . ($this->namespace === self::NS_MARCXCHANGE ? self::SCHEMA_MARCXCHANGE : self::SCHEMA_MARC21)); + $this->flush(); + } + + /** @param string $type MARCXchange record type ("Bibliographic"); ignored for MARCXML */ + public function writeRecord(MarcRecord $record, string $type = 'Bibliographic'): void + { + $this->xw->startElement('record'); + if ($this->namespace === self::NS_MARCXCHANGE) { + $this->xw->writeAttribute('type', $type); + if ($record->format() !== '') { + $this->xw->writeAttribute('format', $record->format()); + } + } + $this->xw->writeElement('leader', $record->leader()); + self::writeFields($this->xw, $record); + $this->xw->endElement(); + $this->flush(); + } + + public function endCollection(): void + { + $this->xw->endElement(); + $this->xw->endDocument(); + $this->flush(); + } + + /** The XML written so far (memory mode), or '' once flushed to a stream. */ + public function output(): string + { + return $this->xw->outputMemory(true); + } + + private function flush(): void + { + if (is_resource($this->handle)) { + fwrite($this->handle, $this->xw->outputMemory(true)); + } + } + + /** One standalone record as an XML document (MARCXchange records carry their format). */ + public static function recordToXml(MarcRecord $record, string $namespace = self::NS_MARC21): string + { + $xw = new \XMLWriter(); + $xw->openMemory(); + $xw->startDocument('1.0', 'UTF-8'); + $xw->startElementNs(null, 'record', $namespace); + if ($namespace === self::NS_MARCXCHANGE) { + $xw->writeAttribute('type', 'Bibliographic'); + if ($record->format() !== '') { + $xw->writeAttribute('format', $record->format()); + } + } + $xw->writeElement('leader', $record->leader()); + self::writeFields($xw, $record); + $xw->endElement(); + $xw->endDocument(); + return $xw->outputMemory(); + } + + /** The control and data fields of a record, in order. */ + public static function writeFields(\XMLWriter $xw, MarcRecord $record): void + { + foreach ($record->fields() as $field) { + if ($field->data !== null) { + self::controlField($xw, $field->tag, $field->data); + } else { + self::dataField($xw, $field->tag, $field->ind1, $field->ind2, $field->subfields); + } + } + } + + public static function controlField(\XMLWriter $xw, string $tag, string $text): void + { + $xw->startElement('controlfield'); + $xw->writeAttribute('tag', $tag); + $xw->text($text); + $xw->endElement(); + } + + /** + * @param list $subfields + */ + public static function dataField(\XMLWriter $xw, string $tag, string $ind1, string $ind2, array $subfields): void + { + $xw->startElement('datafield'); + $xw->writeAttribute('tag', $tag); + $xw->writeAttribute('ind1', $ind1); + $xw->writeAttribute('ind2', $ind2); + foreach ($subfields as [$code, $value]) { + $xw->startElement('subfield'); + $xw->writeAttribute('code', $code); + $xw->text($value); + $xw->endElement(); + } + $xw->endElement(); + } +} diff --git a/app/Support/Marc/RelatorRoles.php b/app/Support/Marc/RelatorRoles.php new file mode 100644 index 000000000..b2d43812a --- /dev/null +++ b/app/Support/Marc/RelatorRoles.php @@ -0,0 +1,206 @@ + 'author', 'cre' => 'author', + 'edt' => 'editor', 'edc' => 'editor', 'dkmdt' => 'editor', + 'trl' => 'translator', + 'ill' => 'illustrator', + 'clr' => 'colorist', 'dkfvl' => 'colorist', + // Part of the book, or not its making at all + 'aui' => 'other', 'aft' => 'other', 'win' => 'other', 'wpr' => 'other', 'wfw' => 'other', + 'wac' => 'other', 'wst' => 'other', 'wat' => 'other', 'cwt' => 'other', 'cmm' => 'other', + 'dktek' => 'other', 'pbl' => 'other', 'isb' => 'other', 'prt' => 'other', 'bnd' => 'other', + 'nrt' => 'other', 'dkind' => 'other', 'cov' => 'other', 'bjd' => 'other', 'bkd' => 'other', + 'bdd' => 'other', 'pfr' => 'other', 'crr' => 'other', 'hnr' => 'other', 'dte' => 'other', + 'fmo' => 'other', 'dnr' => 'other', 'rev' => 'other', 'sad' => 'other', 'ths' => 'other', + ]; + + /** UNIMARC (IFLA) codes. */ + private const NUMERIC_CODES = [ + '070' => 'author', + '340' => 'editor', + '730' => 'translator', + '440' => 'illustrator', + '080' => 'other', '075' => 'other', '650' => 'other', '610' => 'other', + '390' => 'other', '320' => 'other', + ]; + + /** + * Terms, in English, Italian, German, French, Spanish, Danish, Norwegian, + * Swedish, Dutch and Polish; full words and the usual abbreviations. + * Tried in this order, so "translated with an introduction" is a + * translation and "edited and translated" an edition. + */ + private const TERMS = [ + 'editor' => '(editor|editors|eds?|edited|edit|hrsg|herausgeber\w*|herausgegeben|red|redaktør|redaktor|redaktör|redacteur|rédacteur|redactor|redigeret|redigerad|redigert|curatore|curatrice|curat[oa]|a cura|cura di|éditeur scientifique|editeur scientifique|edición|edicion|ed lit)', + 'translator' => '(translat\w*|trans|tr|trad|traduttor\w*|tradott\w*|traduzione|traducteur|traductrice|traduit\w*|traduction|traductor\w*|traducid\w*|traducción|übers\w*|ubers\w*|oversætter\w*|oversat\w*|oversetter\w*|oversatt\w*|översätt\w*|översatt\w*|översättning|vertaler|vertaald|vertaling|tłumacz\w*|przeł\w*)', + 'illustrator' => '(illustrat\w*|illustrazion\w*|ill|illus|ilustrad\w*|ilustraci\w*|illustrateur|illustratrice|illustratore|illustré\w*|illustriert\w*|illustreret|illustrerad|illustrert|illustratør|illustratör|tegninger|tegnet af|drawings)', + 'colorist' => '(colou?rist\w*|colou?red by|colou?rs by|kolorist\w*|colorista|coloriste|farvelægger|farvelagt)', + 'other' => '(introduction|introduced|preface|foreword|afterword|postface|commentary|supplementary|notes?|noter|efterskrift|annotat\w*|publisher|einleitung|vorwort|nachwort|kommentar\w*|anmerkungen|forord|efterord|indledning|kommenteret|förord|inledning|préface|présentation|introducción|prólogo|epílogo|introduzione|prefazione|postfazione|commento|editore|verlag|forlag|förlag|éditeur)', + ]; + + /** + * The role of a name, from its codes, its term and the statement of + * responsibility of the record, in that order. 'author' when none of them + * says otherwise. + * + * @param list $codes + */ + public static function resolve(array $codes, ?string $term, ?string $statement, string $name, string $scheme): string + { + // The first code this list knows decides ("aut" before "trl" keeps a + // self-translating author an author); a code it does not know still + // says the name has a part, which keeps it an author when nothing + // else is recognised. + $unknownCode = false; + foreach ($codes as $code) { + $code = self::normaliseCode($code); + if ($code === '' || in_array($code, self::NEUTRAL_CODES, true)) { + continue; + } + if ($scheme === 'unimarc' && isset(self::NUMERIC_CODES[$code])) { + return self::NUMERIC_CODES[$code]; + } + if (isset(self::LETTER_CODES[$code])) { + return self::LETTER_CODES[$code]; + } + $unknownCode = true; + } + if ($unknownCode) { + return 'author'; + } + $term = self::normaliseText((string) $term); + if ($term !== '') { + return self::roleOfText($term) ?? 'author'; + } + if ($statement !== null && $name !== '') { + return self::roleInStatement($statement, $name) ?? 'author'; + } + return 'author'; + } + + /** + * The role a statement of responsibility gives a name: the role words + * between the name and the punctuation before it ("…; translated with an + * introduction by David McDuff" → translator). Null when the statement + * does not mention the name, or mentions it with no role word, as the + * first author usually is ("Fyodor Dostoyevsky; …"). + */ + public static function roleInStatement(string $statement, string $name): ?string + { + $surname = self::surnameOf($name); + if (mb_strlen($surname) < 2) { + return null; + } + $text = self::normaliseText(self::expandAbbreviations($statement), false); + if (preg_match('/(? match (mb_strtolower($m[1])) { + 'ed', 'eds', 'edit' => 'edited ', + 'tr', 'trans' => 'translated ', + 'red' => 'redigeret ', + default => 'illustrated ', + }, + $statement + ); + } + + /** A relator code as the lists above spell it: "edt" from "Edt." or from a relator URI. */ + private static function normaliseCode(string $code): string + { + $code = (string) preg_replace('#^.*/#', '', trim($code)); + return strtolower(trim($code, " .\t")); + } + + private static function normaliseText(string $text, bool $trim = true): string + { + $text = mb_strtolower(trim((string) preg_replace('/\s+/u', ' ', $text))); + // "hrsg." and "hrsg" alike; brackets and parentheses are not part of a role + $text = (string) preg_replace('/[()\[\].]/u', ' ', $text); + $text = trim((string) preg_replace('/\s+/u', ' ', $text)); + return $trim ? trim($text, ' ,;:') : $text; + } + + private static function roleOfText(string $text): ?string + { + $text = self::normaliseText($text); + if ($text === '') { + return null; + } + foreach (self::TERMS as $role => $pattern) { + if (preg_match('/(? 'traduttore', + 'curatore' => 'curatore', + 'illustratore' => 'illustratore', + 'colorista' => 'colorista', + ]; + + public function map(MarcRecord $record): array + { + $parsed = (new UnimarcLibriParser())->fromUnimarcXml( + MarcXmlWriter::recordToXml($record, MarcXmlWriter::NS_MARCXCHANGE) + ); + $data = MarcBookData::empty(); + $data['titolo'] = MarcBookData::clean((string) ($parsed['titolo'] ?? '')); + $data['sottotitolo'] = MarcBookData::clean((string) ($parsed['sottotitolo'] ?? '')); + $data['isbn13'] = (string) ($parsed['isbn13'] ?? ''); + $data['isbn10'] = (string) ($parsed['isbn10'] ?? ''); + $data['anno_pubblicazione'] = isset($parsed['anno_pubblicazione']) ? (string) $parsed['anno_pubblicazione'] : ''; + $data['edizione'] = \App\Support\EditionStatement::clean(MarcBookData::clean((string) ($parsed['edizione'] ?? ''))); + $data['numero_pagine'] = isset($parsed['numero_pagine']) ? (string) $parsed['numero_pagine'] : ''; + $data['collana'] = MarcBookData::clean((string) ($parsed['collana'] ?? '')); + $data['numero_serie'] = MarcBookData::clean((string) ($parsed['numero_serie'] ?? '')); + $data['classificazione_dewey'] = (string) ($parsed['classificazione_dewey'] ?? ''); + // An unknown code stays empty, as in the MARC 21 mapper: the reader's + // own fallback would store the raw code ("swa") as a language name. + // Every 101 $a, one per language, as the MARC 21 mapper reads 041. + $languages = []; + foreach ($record->subfieldValues('101', 'a') as $code) { + $name = MarcLanguage::nameFor(trim($code)); + if ($name !== '' && !in_array($name, $languages, true)) { + $languages[] = $name; + } + } + $data['lingua'] = implode(', ', $languages); + + // The genre: a 608 form / genre heading the exporting catalogue marked + // as its own term ($2 local, as BookMarcBuilder writes it), like the + // MARC 21 655. Every 606 is a topical subject, so a keyword, whatever + // its $2: another catalogue's local subjects are not taken as a genre. + foreach ($record->fieldsByTag('608') as $field) { + if (strtolower(trim((string) $field->subfield('2'))) === 'local') { + $data['genere'] = MarcBookData::clean($field->subfield('a'), ' /:;=,.'); + if ($data['genere'] !== '') { + break; + } + } + } + $subjects = []; + foreach ($record->fieldsByTag('606') as $field) { + $term = MarcBookData::clean($field->subfield('a'), ' /:;=,.'); + if ($term !== '' && !in_array($term, $subjects, true)) { + $subjects[] = $term; + } + } + $data['parole_chiave'] = $subjects; + + foreach ((array) ($parsed['authors'] ?? []) as $author) { + if (!is_array($author)) { + continue; + } + $name = MarcBookData::cleanName((string) ($author['name'] ?? '')); + if ($name === '') { + continue; + } + $column = self::ROLE_COLUMNS[(string) ($author['ruolo'] ?? '')] ?? 'autori'; + if (!in_array($name, $data[$column], true)) { + $data[$column][] = $name; + } + } + + foreach (array_merge($record->fieldsByTag('210'), $record->fieldsByTag('214')) as $field) { + if ($data['luogo_pubblicazione'] === '') { + $data['luogo_pubblicazione'] = \App\Support\PublicationPlace::clean(MarcBookData::clean($field->subfield('a'))); + } + foreach ($field->subfieldValues('c') as $publisher) { + $publisher = MarcBookData::clean($publisher); + if ($publisher !== '' && !in_array($publisher, $data['editori'], true)) { + $data['editori'][] = $publisher; + } + } + } + $data['dimensioni'] = MarcBookData::clean($record->subfield('215', 'd')); + $data['descrizione'] = trim(implode("\n\n", array_map('trim', $record->subfieldValues('330', 'a')))); + $ean = (string) preg_replace('/\D/', '', (string) $record->subfield('073', 'a')); + if (strlen($ean) === 13 && $ean !== $data['isbn13']) { + $data['ean'] = $ean; + } + // 011: the ISSN of the resource itself; on a monograph the series + // ISSN travels in 225 $x, as the export writes it. + $data['issn'] = MarcBookData::clean($record->subfield('011', 'a')); + if ($data['issn'] === '') { + $data['issn'] = MarcBookData::clean($record->subfield('225', 'x')); + } + $data['copertina_url'] = MarcBookData::coverUrlOf($record); + // Media type from label/06, refined by the RDA content type of a 181 + // $c whose $2 says rdacontent (an "altro" is language material with + // content "other"). + $content = null; + foreach ($record->fieldsByTag('181') as $field) { + if (strtolower(trim((string) $field->subfield('2'))) === 'rdacontent' && trim((string) $field->subfield('c')) !== '') { + $content = trim((string) $field->subfield('c')); + break; + } + } + $data['tipo_media'] = MarcMediaType::tipoMediaOf(substr($record->leader() . ' ', 6, 1), $content); + + return $data; + } +} diff --git a/app/Support/Marc/UnimarcLibriParser.php b/app/Support/Marc/UnimarcLibriParser.php new file mode 100644 index 000000000..30795996a --- /dev/null +++ b/app/Support/Marc/UnimarcLibriParser.php @@ -0,0 +1,310 @@ + Keys: titolo, sottotitolo, isbn13, isbn10, + * anno_pubblicazione, editore, edizione, numero_pagine, collana, + * numero_serie, lingua, classificazione_dewey, parole_chiave, + * soggetti (list), authors (list of [name, ruolo]). + */ + public function fromUnimarcXml(string $xml): array + { + $out = []; + $authors = []; + $subjects = []; + + $doc = new \DOMDocument(); + $prev = libxml_use_internal_errors(true); + $loaded = $doc->loadXML($xml); + libxml_clear_errors(); + libxml_use_internal_errors($prev); + if (!$loaded) { + return $out; + } + + foreach ($this->elementsByLocalName($doc, 'datafield') as $field) { + $tag = $field->getAttribute('tag'); + $subs = $this->subfieldsOf($field); + + switch ($tag) { + case '010': // ISBN + $isbn = preg_replace('/[^0-9X]/i', '', $subs['a'][0] ?? '') ?? ''; + if ($isbn !== '') { + if (strlen($isbn) === 13) { + $out['isbn13'] = $isbn; + } else { + $out['isbn10'] = $isbn; + } + } + break; + case '101': // Language of document + if (isset($subs['a'][0])) { + $out['lingua'] = $this->langName($subs['a'][0]); + } + break; + case '200': // Title + statement of responsibility + if (isset($subs['a'][0])) { + $out['titolo'] = $subs['a'][0]; + // $h number of a part, $i name of a part, in order: + // "Storia d'Italia. Vol. 2. Il Medioevo". + $parts = []; + foreach ($field->childNodes as $child) { + if ($child instanceof \DOMElement && $child->localName === 'subfield' && in_array($child->getAttribute('code'), ['h', 'i'], true)) { + $part = trim(rtrim(trim($child->textContent), ' /:;=,.')); + if ($part !== '') { + $parts[] = $part; + } + } + } + if ($parts !== []) { + $out['titolo'] = trim(rtrim($out['titolo'], ' .') . '. ' . implode('. ', $parts), ' .'); + } + } + if (isset($subs['e'][0])) { + $out['sottotitolo'] = $subs['e'][0]; + } + break; + case '205': // Edition + if (isset($subs['a'][0])) { + $out['edizione'] = $subs['a'][0]; + } + break; + case '210': // Publication (UNIMARC pre-2014) + case '214': // Publication (UNIMARC 2014+) + if (isset($subs['c'][0]) && !isset($out['editore'])) { + $out['editore'] = $subs['c'][0]; + } + if (isset($subs['d'][0]) && !isset($out['anno_pubblicazione'])) { + $year = $this->extractYear($subs['d'][0]); + if ($year !== null) { + $out['anno_pubblicazione'] = $year; + } + } + break; + case '215': // Physical description + if (isset($subs['a'][0])) { + $pages = $this->extractPages($subs['a'][0]); + if ($pages > 0) { + $out['numero_pagine'] = $pages; + } + } + break; + case '225': // Series + if (isset($subs['a'][0])) { + $out['collana'] = $subs['a'][0]; + } + if (isset($subs['v'][0])) { + $out['numero_serie'] = $subs['v'][0]; + } + break; + case '606': // Subject + if (isset($subs['a'][0])) { + $subjects[] = [ + 'termine' => $subs['a'][0], + 'bncf_id' => $subs['3'][0] ?? null, + ]; + } + break; + case '676': // Dewey + if (isset($subs['a'][0])) { + $out['classificazione_dewey'] = trim($subs['a'][0]); + } + break; + case '700': // Primary author + case '701': // Alternative author + case '702': // Secondary intellectual responsibility + $name = $this->composeName($subs); + if ($name !== '') { + $relator = $subs['4'][0] ?? '070'; + $ruolo = $this->roleForRelator($relator); + // 700 is the primary responsibility, unless its relator says + // otherwise: a translator or editor there stays one. + if ($tag === '700' && $ruolo === 'co-autore' + && !in_array('principale', array_column($authors, 'ruolo'), true)) { + $ruolo = 'principale'; + } + $authors[] = ['name' => $name, 'ruolo' => $ruolo]; + } + break; + } + } + + if ($authors !== []) { + $out['authors'] = $authors; + $out['autori'] = implode('; ', array_map(static fn(array $a): string => $a['name'], $authors)); + } + if ($subjects !== []) { + $out['soggetti'] = $subjects; + $out['parole_chiave'] = implode(', ', array_map( + static fn(array $s): string => (string) $s['termine'], + $subjects + )); + } + + return $out; + } + + /** + * Map a core libri_autori.ruolo to its UNIMARC $4 relator code. + */ + public static function relatorForRole(string $ruolo): string + { + return match ($ruolo) { + 'traduttore' => '730', // Translator + 'curatore' => '340', // Editor + 'illustratore' => '440', // Illustrator + 'colorista' => '410', // Graphic technician (includes colourists) + default => '070', // Author (principale / co-autore / unknown) + }; + } + + /** + * Map a UNIMARC $4 relator code to a core libri_autori.ruolo. + */ + private function roleForRelator(string $relator): string + { + return match ($relator) { + '730' => 'traduttore', + '340' => 'curatore', + '440' => 'illustratore', + '410' => 'colorista', + default => 'co-autore', + }; + } + + // ── helpers ──────────────────────────────────────────────────────────── + + /** + * @param list $subfields + */ + protected function datafield(\DOMDocument $doc, string $tag, string $ind1, string $ind2, array $subfields): \DOMElement + { + $field = $doc->createElement('datafield'); + $field->setAttribute('tag', $tag); + $field->setAttribute('ind1', $ind1); + $field->setAttribute('ind2', $ind2); + foreach ($subfields as [$code, $value]) { + $sub = $doc->createElement('subfield'); + $sub->setAttribute('code', $code); + $sub->appendChild($doc->createTextNode($value)); + $field->appendChild($sub); + } + return $field; + } + + /** + * @return list<\DOMElement> + */ + private function elementsByLocalName(\DOMDocument $doc, string $localName): array + { + $out = []; + foreach ($doc->getElementsByTagName('*') as $node) { + if ($node->localName === $localName) { + $out[] = $node; + } + } + return $out; + } + + /** + * Collect subfields of a datafield keyed by code (a code may repeat). + * + * Numeric subfield codes ('3', '4', ...) become int array keys in PHP, so + * the key type is int|string. + * + * @return array> + */ + private function subfieldsOf(\DOMElement $field): array + { + $subs = []; + foreach ($field->childNodes as $child) { + if ($child instanceof \DOMElement && $child->localName === 'subfield') { + $code = $child->getAttribute('code'); + $subs[$code][] = trim($child->textContent); + } + } + return $subs; + } + + /** + * Compose a personal name from $a (entry element) + $b (rest of name). + * + * @param array> $subs + */ + private function composeName(array $subs): string + { + $a = trim($subs['a'][0] ?? ''); + $b = trim($subs['b'][0] ?? ''); + if ($a === '') { + return ''; + } + // UNIMARC entry under surname: $a=surname, $b=forename → "Forename Surname" + if ($b !== '') { + $a = rtrim($a, ', '); + return trim($b . ' ' . $a); + } + // Single $a may itself be "Surname, Forename". + if (str_contains($a, ',')) { + [$surname, $forename] = array_map('trim', explode(',', $a, 2)); + if ($surname !== '' && $forename !== '') { + return $forename . ' ' . $surname; + } + } + return $a; + } + + private function extractYear(string $value): ?int + { + if (preg_match('/\b(1[0-9]{3}|20[0-9]{2})\b/', $value, $m)) { + return (int) $m[1]; + } + return null; + } + + private function extractPages(string $desc): int + { + if (preg_match('/(\d+)\s*p/i', $desc, $m)) { + return (int) $m[1]; + } + return 0; + } + + private function langName(string $code): string + { + return match (strtolower(trim($code))) { + 'ita' => 'italiano', + 'eng' => 'inglese', + 'ger', 'deu' => 'tedesco', + 'fre', 'fra' => 'francese', + 'spa' => 'spagnolo', + 'por' => 'portoghese', + 'rus' => 'russo', + 'lat' => 'latino', + default => $code, + }; + } +} diff --git a/app/Support/NotificationService.php b/app/Support/NotificationService.php index 635d74ea6..d4eace54f 100644 --- a/app/Support/NotificationService.php +++ b/app/Support/NotificationService.php @@ -34,6 +34,8 @@ class NotificationService { // Serialized by a connection-scoped lock and acknowledged through // pickup_reminder_sent_for: an outbox copy would send it twice. 'loan_pickup_reminder', + // Acknowledged through utenti.card_expiry_warning_sent_for. + 'card_expiring', ]; /** @@ -64,15 +66,20 @@ private function pickupExpiryDays(): int * the pickup window to the deadline actually set. A loan shorter than the * window caps the deadline at its end, and the staff may move it: in both * cases the configured number would promise days the reader does not have. + * $copiaId applies that copy's branch closures, as pickupDeadline() does. */ - private function effectivePickupDays(?string $windowStart, ?string $deadline): string + private function effectivePickupDays(?string $windowStart, ?string $deadline, ?int $copiaId = null): string { if ($windowStart !== null && $windowStart !== '' && $deadline !== null && $deadline !== '') { try { $start = new \DateTimeImmutable(substr($windowStart, 0, 10)); $end = new \DateTimeImmutable(substr($deadline, 0, 10)); if ($end >= $start) { - return (string) min($this->pickupExpiryDays(), (int) $start->diff($end)->days); + // Open days only (closures calendar): a closed day inside + // the window is not a day to collect the book; calendar + // days without closures. + $openDays = LibraryCalendar::forDb($this->db)->openDaysBetween($start->format('Y-m-d'), $end->format('Y-m-d'), LibraryCalendar::branchOfCopy($this->db, $copiaId)); + return (string) min($this->pickupExpiryDays(), $openDays); } } catch (\Throwable) { } @@ -95,10 +102,11 @@ private function pickupReminderDays(): int * MaintenanceService::activateScheduledLoans() applies: start + pickup days, * never past the end of the loan. Lets the approval email name it up front. */ - private function expectedPickupDeadline(string $start, string $end): string + private function expectedPickupDeadline(string $start, string $end, ?int $copiaId = null): string { - $deadline = (new \DateTimeImmutable($start))->modify('+' . $this->pickupExpiryDays() . ' days')->format('Y-m-d'); - return ($end !== '' && $end < $deadline) ? $end : $deadline; + // Open days only (closures calendar, with the copy's branch), as + // activateScheduledLoans() computes it. + return LibraryCalendar::pickupDeadline($this->db, $start, $this->pickupExpiryDays(), $end !== '' ? $end : null, $copiaId); } /** @@ -821,6 +829,110 @@ public function sendOverdueLoanNotifications(): int { return $sentCount; } + /** + * Warn patrons whose library card expires within + * loans.card_expiry_warning_days (0 = off, the default). + * + * Only standard/premium accounts (admin and staff have no card) that are + * active and have an email. The expiry date is acknowledged in + * utenti.card_expiry_warning_sent_for only after delivery: it goes out + * once per expiry, and a renewed card (new date) re-arms it. An advisory + * lock per patron serializes concurrent cron/HTTP workers. + */ + public function sendCardExpiryWarnings(): int { + $warningDays = max(0, min(365, (int) ((new \App\Models\SettingsRepository($this->db))->get('loans', 'card_expiry_warning_days', '0') ?? 0))); + if ($warningDays <= 0 || !(new \App\Services\CirculationPolicy($this->db))->schemaReady()) { + return 0; + } + $hasFlag = $this->db->query("SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'utenti' AND COLUMN_NAME = 'card_expiry_warning_sent_for'"); + if (!$hasFlag instanceof \mysqli_result || (int) ($hasFlag->fetch_row()[0] ?? 0) === 0) { + return 0; + } + + $sentCount = 0; + try { + $today = DateHelper::today(); + $lastDay = (new \DateTimeImmutable($today))->modify('+' . $warningDays . ' days')->format('Y-m-d'); + $eligibleSql = " + SELECT id, email, data_scadenza_tessera, codice_tessera, + CONCAT(nome, ' ', cognome) AS utente_nome + FROM utenti + WHERE stato = 'attivo' + AND tipo_utente IN ('standard', 'premium') + AND data_scadenza_tessera IS NOT NULL + AND data_scadenza_tessera >= ? AND data_scadenza_tessera <= ? + AND (card_expiry_warning_sent_for IS NULL OR card_expiry_warning_sent_for <> data_scadenza_tessera) + AND email IS NOT NULL AND TRIM(email) <> '' + "; + $stmt = $this->db->prepare($eligibleSql); + $stmt->bind_param('ss', $today, $lastDay); + $stmt->execute(); + $users = $stmt->get_result()->fetch_all(MYSQLI_ASSOC); + $stmt->close(); + + $database = (string) $this->db->query('SELECT DATABASE()')->fetch_row()[0]; + $lockPrefix = 'pinakes_card_expiry_' . substr(hash('sha256', $database), 0, 24) . '_'; + foreach ($users as $user) { + $userId = (int) $user['id']; + $lockName = $lockPrefix . $userId; + $locked = false; + try { + $claim = $this->db->prepare('SELECT GET_LOCK(?, 0)'); + $claim->bind_param('s', $lockName); + $claim->execute(); + $locked = (int) $claim->get_result()->fetch_row()[0] === 1; + $claim->close(); + if (!$locked) { + continue; + } + // Re-read under the lock: another worker may have sent it, + // or the desk may have renewed the card meanwhile. + $fresh = $this->db->prepare($eligibleSql . ' AND id = ?'); + $fresh->bind_param('ssi', $today, $lastDay, $userId); + $fresh->execute(); + $user = $fresh->get_result()->fetch_assoc(); + $fresh->close(); + if (!$user) { + continue; + } + $expiry = (string) $user['data_scadenza_tessera']; + $recipientLocale = $this->resolveRecipientLocale((string) $user['email']); + $daysLeft = (int) (new \DateTimeImmutable($today))->diff(new \DateTimeImmutable($expiry))->days; + $sent = $this->sendWithRetry((string) $user['email'], 'card_expiring', [ + 'utente_nome' => $user['utente_nome'], + 'codice_tessera' => (string) $user['codice_tessera'], + 'data_scadenza_tessera' => $this->formatEmailDate($expiry, false, $recipientLocale), + 'giorni_rimasti' => $daysLeft === 0 ? $this->translateInLocale('oggi', $recipientLocale) : (string) $daysLeft, + ]); + if ($sent) { + $ack = $this->db->prepare('UPDATE utenti SET card_expiry_warning_sent_for = ? WHERE id = ? AND data_scadenza_tessera = ?'); + $ack->bind_param('sis', $expiry, $userId, $expiry); + $ack->execute(); + $ack->close(); + $sentCount++; + } + } catch (\Throwable $e) { + SecureLogger::error("Failed to send card expiry warning for user {$userId}: " . $e->getMessage()); + } finally { + if ($locked) { + try { + $release = $this->db->prepare('SELECT RELEASE_LOCK(?)'); + $release->bind_param('s', $lockName); + $release->execute(); + $release->close(); + } catch (\Throwable $releaseError) { + SecureLogger::error("Failed to release card expiry lock for user {$userId}: " . $releaseError->getMessage()); + } + } + } + } + } catch (\Throwable $e) { + SecureLogger::error('Failed to send card expiry warnings: ' . $e->getMessage()); + } + + return $sentCount; + } + /** * Reminder to collect a book that is waiting for pickup, sent * loans.pickup_reminder_days before the pickup deadline (0 = off). @@ -855,7 +967,7 @@ public function sendPickupReminders(): int { $openedBefore = (new \DateTimeImmutable($today))->modify('+' . $this->pickupExpiryDays() . ' days')->format('Y-m-d'); $eligibleSql = " - SELECT p.id, p.utente_id, p.pickup_deadline, p.data_prestito, p.data_scadenza, + SELECT p.id, p.utente_id, p.copia_id, p.pickup_deadline, p.data_prestito, p.data_scadenza, l.titolo AS libro_titolo, CONCAT(u.nome, ' ', u.cognome) AS utente_nome, u.email AS utente_email FROM prestiti p @@ -917,7 +1029,7 @@ public function sendPickupReminders(): int { 'libro_titolo' => $loan['libro_titolo'], 'data_inizio' => $this->formatEmailDate((string) $loan['data_prestito'], false, $recipientLocale), 'data_fine' => $this->formatEmailDate((string) $loan['data_scadenza'], false, $recipientLocale), - 'giorni_ritiro' => $this->effectivePickupDays((string) $loan['data_prestito'], $deadline), + 'giorni_ritiro' => $this->effectivePickupDays((string) $loan['data_prestito'], $deadline, !empty($loan['copia_id']) ? (int) $loan['copia_id'] : null), 'giorni_rimasti' => $daysLeft === 0 ? $todayLabels[$recipientLocale] : (string) $daysLeft, 'scadenza_ritiro' => $formattedDeadline, 'pickup_deadline' => $formattedDeadline, @@ -1430,6 +1542,7 @@ public function runAutomaticNotifications(): array { 'overdue_notifications' => 0, 'loan_recalls' => 0, 'pickup_reminders' => 0, + 'card_expiry_warnings' => 0, 'wishlist_notifications' => 0, 'errors' => [] ]; @@ -1445,6 +1558,7 @@ public function runAutomaticNotifications(): array { // recall query requires overdue_notification_sent = 1. $results['loan_recalls'] = $this->sendLoanRecalls(); $results['pickup_reminders'] = $this->sendPickupReminders(); + $results['card_expiry_warnings'] = $this->sendCardExpiryWarnings(); $results['wishlist_notifications'] = $this->checkAndNotifyWishlistAvailability(); } catch (\Throwable $e) { @@ -1814,9 +1928,9 @@ public function sendLoanApprovedNotification(int $loanId): bool { 'giorni_prestito' => $days, // A loan starting later gets its pickup deadline only on the start // day; say now which one it will be and how many days that is. - 'giorni_ritiro' => $this->effectivePickupDays((string) $loan['data_prestito'], $this->expectedPickupDeadline((string) $loan['data_prestito'], (string) $loan['data_scadenza'])), - 'scadenza_ritiro' => $this->formatEmailDate($this->expectedPickupDeadline((string) $loan['data_prestito'], (string) $loan['data_scadenza']), false, $recipientLocale), - 'pickup_deadline' => $this->formatEmailDate($this->expectedPickupDeadline((string) $loan['data_prestito'], (string) $loan['data_scadenza']), false, $recipientLocale), + 'giorni_ritiro' => $this->effectivePickupDays((string) $loan['data_prestito'], $this->expectedPickupDeadline((string) $loan['data_prestito'], (string) $loan['data_scadenza'], !empty($loan['copia_id']) ? (int) $loan['copia_id'] : null), !empty($loan['copia_id']) ? (int) $loan['copia_id'] : null), + 'scadenza_ritiro' => $this->formatEmailDate($this->expectedPickupDeadline((string) $loan['data_prestito'], (string) $loan['data_scadenza'], !empty($loan['copia_id']) ? (int) $loan['copia_id'] : null), false, $recipientLocale), + 'pickup_deadline' => $this->formatEmailDate($this->expectedPickupDeadline((string) $loan['data_prestito'], (string) $loan['data_scadenza'], !empty($loan['copia_id']) ? (int) $loan['copia_id'] : null), false, $recipientLocale), 'pickup_instructions' => $this->translateInLocale('Recati in biblioteca durante gli orari di apertura per ritirare il libro.', $recipientLocale), 'sezione_calendario' => $this->buildCalendarSection((int)$loan['id'], (string)$loan['libro_titolo'], (string)$loan['data_prestito'], (string)$loan['data_scadenza'], $recipientLocale) ]; @@ -1997,7 +2111,7 @@ public function sendPickupReadyNotification(int $loanId): bool { 'data_inizio' => $this->formatEmailDate($loan['data_prestito'], false, $recipientLocale), 'data_fine' => $this->formatEmailDate($loan['data_scadenza'], false, $recipientLocale), 'giorni_prestito' => $days, - 'giorni_ritiro' => $this->effectivePickupDays((string) $loan['data_prestito'], $loan['pickup_deadline'] ? (string) $loan['pickup_deadline'] : null), + 'giorni_ritiro' => $this->effectivePickupDays((string) $loan['data_prestito'], $loan['pickup_deadline'] ? (string) $loan['pickup_deadline'] : null, !empty($loan['copia_id']) ? (int) $loan['copia_id'] : null), 'scadenza_ritiro' => $loan['pickup_deadline'] ? $this->formatEmailDate($loan['pickup_deadline'], false, $recipientLocale) : '', // #304: alias under the DB column name so a customised template using // {{pickup_deadline}} (the natural name a user copies from the schema) @@ -2181,7 +2295,7 @@ public function sendPickupExpiredNotification(int $loanId, ?string $pickupDeadli $variables = [ 'utente_nome' => $loan['utente_nome'], 'libro_titolo' => $loan['libro_titolo'], - 'giorni_ritiro' => $this->effectivePickupDays((string) ($loan['data_prestito'] ?? ''), $deadline ? (string) $deadline : null), + 'giorni_ritiro' => $this->effectivePickupDays((string) ($loan['data_prestito'] ?? ''), $deadline ? (string) $deadline : null, !empty($loan['copia_id']) ? (int) $loan['copia_id'] : null), 'scadenza_ritiro' => $deadline ? $this->formatEmailDate($deadline, false, $recipientLocale) : '', // #304: alias under the DB column name, see sendPickupReadyNotification. 'pickup_deadline' => $deadline ? $this->formatEmailDate($deadline, false, $recipientLocale) : '' @@ -2245,7 +2359,7 @@ public function sendPickupCancelledNotification( return $this->sendWithRetry($loan['utente_email'], 'loan_pickup_expired', [ 'utente_nome' => $loan['utente_nome'], 'libro_titolo' => $loan['libro_titolo'], - 'giorni_ritiro' => $this->effectivePickupDays((string) ($loan['data_prestito'] ?? ''), $effectiveDeadline ? (string) $effectiveDeadline : null), + 'giorni_ritiro' => $this->effectivePickupDays((string) ($loan['data_prestito'] ?? ''), $effectiveDeadline ? (string) $effectiveDeadline : null, !empty($loan['copia_id']) ? (int) $loan['copia_id'] : null), 'scadenza_ritiro' => $formattedDeadline, 'pickup_deadline' => $formattedDeadline, ]); @@ -2285,6 +2399,23 @@ public function sendReservationBookAvailable(string $email, array $variables): b /** * Invia conferma di restituzione all'utente (GAP-1) */ + /** + * {{multa}} of the loan-returned email (plan §7): the late fine left on + * this loan after any waiver. Empty when nothing was charged, so the + * line vanishes from the email of an install without fines, and empty + * while the ledger is off, even for an older charge. + */ + private function returnedLoanFineLine(int $loanId, string $locale): string + { + if (!\App\Services\PatronLedger::active($this->db)) { + return ''; + } + $cents = \App\Services\PatronLedger::forDb($this->db)->lateFineNetCents($loanId); + return $cents > 0 + ? $this->translateInLocale('Multa per ritardo', $locale) . ': ' . \App\Services\FineParameters::format($cents, $locale) + : ''; + } + public function sendLoanReturnedNotification(int $loanId): bool { try { // CI-SOFT-DELETE-EXEMPT: the return-confirmation must reach the @@ -2311,10 +2442,12 @@ public function sendLoanReturnedNotification(int $loanId): bool { return false; } + $recipientLocale = $this->resolveRecipientLocale((string) $loan['utente_email']); $variables = [ 'utente_nome' => $loan['utente_nome'], 'libro_titolo' => $loan['libro_titolo'], - 'data_restituzione' => $this->formatEmailDate($loan['data_restituzione'] ?? DateHelper::today(), false, $this->resolveRecipientLocale((string) $loan['utente_email'])), + 'data_restituzione' => $this->formatEmailDate($loan['data_restituzione'] ?? DateHelper::today(), false, $recipientLocale), + 'multa' => $this->returnedLoanFineLine($loanId, $recipientLocale), ]; return $this->sendWithRetry($loan['utente_email'], 'loan_returned', $variables); @@ -2324,6 +2457,65 @@ public function sendLoanReturnedNotification(int $loanId): bool { } } + /** + * Late fine charged on return (plan §7). Off by default: sent only when + * Settings → Loans → Fines → "email the reader" is on and the loan still + * carries a late fine (a fine waived at the desk sends nothing). + */ + public function sendFineChargedNotification(int $loanId): bool + { + if (!\App\Services\PatronLedger::active($this->db)) { + return false; + } + try { + $enabled = (string) ((new \App\Models\SettingsRepository($this->db))->get('loans', \App\Services\FineParameters::SETTING_FINE_EMAIL, '0') ?? '0'); + if ($enabled !== '1') { + return false; + } + $ledger = \App\Services\PatronLedger::forDb($this->db); + $fineCents = $ledger->lateFineNetCents($loanId); + if ($fineCents <= 0) { + return false; + } + $stmt = $this->db->prepare(" + SELECT p.utente_id, p.data_scadenza, p.data_restituzione, + l.titolo AS libro_titolo, + CONCAT(u.nome, ' ', u.cognome) AS utente_nome, + u.email AS utente_email + FROM prestiti p + -- CI-SOFT-DELETE-EXEMPT: the fine notice must reach the reader even for an archived title. + JOIN libri l ON l.id = p.libro_id + JOIN utenti u ON u.id = p.utente_id + WHERE p.id = ? + "); + $stmt->bind_param('i', $loanId); + $stmt->execute(); + $loan = $stmt->get_result()->fetch_assoc(); + $stmt->close(); + if (!$loan || empty($loan['utente_email'])) { + return false; + } + $email = (string) $loan['utente_email']; + $locale = $this->resolveRecipientLocale($email); + $due = (string) $loan['data_scadenza']; + $returned = (string) ($loan['data_restituzione'] ?: DateHelper::today()); + $variables = [ + 'utente_nome' => $loan['utente_nome'], + 'libro_titolo' => $loan['libro_titolo'], + 'data_scadenza' => $this->formatEmailDate($due, false, $locale), + 'data_restituzione' => $this->formatEmailDate($returned, false, $locale), + 'giorni_ritardo' => (string) $ledger->lateFineDays($loanId), + 'multa' => \App\Services\FineParameters::format($fineCents, $locale), + 'saldo' => \App\Services\FineParameters::format(max(0, $ledger->balanceCents((int) $loan['utente_id'])), $locale), + ]; + + return $this->sendWithRetry($email, 'fine_charged', $variables); + } catch (\Throwable $e) { + SecureLogger::error('Failed to send fine charged notification: ' . $e->getMessage()); + return false; + } + } + /** Notify the borrower when a loan is closed as lost or damaged. */ public function sendLoanCopyOutcomeNotification(int $loanId): bool { diff --git a/app/Support/PatronCardPdf.php b/app/Support/PatronCardPdf.php new file mode 100644 index 000000000..cf61a215d --- /dev/null +++ b/app/Support/PatronCardPdf.php @@ -0,0 +1,424 @@ +settings = $settings; + $this->title = $settings['title'] !== '' ? $settings['title'] : $appName; + $this->logoFile = ($settings['show_logo'] && $logoFile !== null && self::isUsableImage($logoFile)) ? $logoFile : null; + } + + /** + * Card size (mm) for the configured layout, as [width, height]. + * + * @return array{0: float, 1: float} + */ + public function cardSize(): array + { + return $this->settings['layout'] === 'portrait' + ? [self::CARD_H, self::CARD_W] + : [self::CARD_W, self::CARD_H]; + } + + /** + * Slots of one A4 sheet as [columns, rows, left, top] for the layout. + * + * @return array{0:int, 1:int, 2:float, 3:float} + */ + public function sheetGrid(): array + { + [$w, $h] = $this->cardSize(); + $cols = (int) floor(self::A4_W / $w); + $rows = (int) floor((self::A4_H - 20) / $h); + $cols = max(1, min($cols, 3)); + $rows = max(1, $rows); + $left = (self::A4_W - $cols * $w) / 2; + $top = (self::A4_H - $rows * $h) / 2; + return [$cols, $rows, $left, $top]; + } + + /** + * @param list $cards + * @param string $format 'single' or 'sheet' + * @return string the PDF bytes + */ + public function render(array $cards, string $format): string + { + [$w, $h] = $this->cardSize(); + $portrait = $h > $w; + $hasBack = $this->settings['back_text'] !== ''; + + if ($format === 'sheet') { + $pdf = new TCPDF('P', 'mm', 'A4', true, 'UTF-8', false); + } else { + $pdf = new TCPDF($portrait ? 'P' : 'L', 'mm', [$w, $h], true, 'UTF-8', false); + } + $pdf->SetCreator($this->title); + $pdf->SetAuthor($this->title); + $pdf->SetTitle(__('Tessere della biblioteca')); + $pdf->setPrintHeader(false); + $pdf->setPrintFooter(false); + $pdf->SetMargins(0, 0, 0); + $pdf->SetAutoPageBreak(false, 0); + $pdf->setCellPaddings(0, 0, 0, 0); + + if ($format === 'sheet') { + [$cols, $rows, $left, $top] = $this->sheetGrid(); + $perSheet = $cols * $rows; + foreach (array_chunk($cards, $perSheet) as $sheet) { + $pdf->AddPage(); + foreach ($sheet as $i => $card) { + $col = $i % $cols; + $row = intdiv($i, $cols); + $x = $left + $col * $w; + $y = $top + $row * $h; + $this->cutGuide($pdf, $x, $y, $w, $h); + $this->front($pdf, $x, $y, $w, $h, $card); + } + if ($hasBack) { + $pdf->AddPage(); + foreach ($sheet as $i => $card) { + // Mirror the columns: on a long-edge duplex print the + // back of column c lands behind column (cols-1-c). + $col = $cols - 1 - ($i % $cols); + $row = intdiv($i, $cols); + $x = $left + $col * $w; + $y = $top + $row * $h; + $this->cutGuide($pdf, $x, $y, $w, $h); + $this->back($pdf, $x, $y, $w, $h); + } + } + } + } else { + foreach ($cards as $card) { + $pdf->AddPage(); + $this->front($pdf, 0, 0, $w, $h, $card); + if ($hasBack) { + $pdf->AddPage(); + $this->back($pdf, 0, 0, $w, $h); + } + } + } + + if ($cards === []) { + $pdf->AddPage(); + } + + return $pdf->Output('', 'S'); + } + + /** @param array{name:string, code:string, expiry:string, photo:?string} $card */ + private function front(TCPDF $pdf, float $x, float $y, float $w, float $h, array $card): void + { + switch ($this->settings['layout']) { + case 'minimal': + $this->frontMinimal($pdf, $x, $y, $w, $h, $card); + break; + case 'portrait': + $this->frontPortrait($pdf, $x, $y, $w, $h, $card); + break; + default: + $this->frontClassic($pdf, $x, $y, $w, $h, $card); + } + } + + /** + * Header band with logo and title; photo on the left; name, code and expiry + * on the right; Code128 across the bottom of the text column, or a QR code + * at its right. + * + * @param array{name:string, code:string, expiry:string, photo:?string} $card + */ + private function frontClassic(TCPDF $pdf, float $x, float $y, float $w, float $h, array $card): void + { + $band = 11.0; + $this->band($pdf, $x, $y, $w, $band); + + $pad = 3.0; + $top = $y + $band + 3; + $textX = $x + $pad; + if (($this->settings['show_photo'] && Features::enabled('patron_photos'))) { + $this->photo($pdf, $x + $pad, $top, 22, 28, $card); + $textX = $x + $pad + 22 + 3; + } + $textW = $x + $w - $pad - $textX; + $qr = $this->settings['code_type'] === 'qr'; + $qrSize = 20.0; + if ($qr) { + $textW -= $qrSize + 2; + } + + $this->fitText($pdf, $textX, $top, $textW, 6, $card['name'], 'B', 11); + $this->codeLine($pdf, $textX, $top + 7, $textW, $card['code'], 9); + if ($this->settings['show_expiry'] && $card['expiry'] !== '') { + $this->fitText($pdf, $textX, $top + 12, $textW, 4, __('Scadenza') . ': ' . $card['expiry'], '', 7, [80, 80, 80]); + } + + if ($qr) { + $this->qr($pdf, $x + $w - $pad - $qrSize, $top, $qrSize, $card['code']); + } else { + $this->barcode($pdf, $textX, $y + $h - $pad - 11, $textW, 11, $card['code']); + } + } + + /** + * No band and no photo: logo and title small at the top, the name large, + * the code as a wide barcode (or a QR on the right). + * + * @param array{name:string, code:string, expiry:string, photo:?string} $card + */ + private function frontMinimal(TCPDF $pdf, float $x, float $y, float $w, float $h, array $card): void + { + $pad = 4.0; + $titleX = $x + $pad; + if ($this->logoFile !== null) { + $logoW = $this->logoWidth(6, $w * 0.35); + $this->image($pdf, $this->logoFile, $x + $pad, $y + $pad, $logoW, 6, true); + $titleX = $x + $pad + $logoW + 2; + } + $this->fitText($pdf, $titleX, $y + $pad + 0.5, $x + $w - $pad - $titleX, 5, $this->title, 'B', 8, [70, 70, 70]); + + $qr = $this->settings['code_type'] === 'qr'; + $qrSize = 22.0; + $textW = $w - 2 * $pad - ($qr ? $qrSize + 3 : 0); + $this->fitText($pdf, $x + $pad, $y + 15, $textW, 8, $card['name'], 'B', 14); + if ($this->settings['show_expiry'] && $card['expiry'] !== '') { + $this->fitText($pdf, $x + $pad, $y + 24, $textW, 4, __('Scadenza') . ': ' . $card['expiry'], '', 7, [80, 80, 80]); + } + if ($qr) { + $this->qr($pdf, $x + $w - $pad - $qrSize, $y + 14, $qrSize, $card['code']); + $this->codeLine($pdf, $x + $pad, $y + 30, $textW, $card['code'], 10); + } else { + $this->barcode($pdf, $x + $pad, $y + 30, $w - 2 * $pad, 13, $card['code']); + $this->codeLine($pdf, $x + $pad, $y + 44, $w - 2 * $pad, $card['code'], 9, 'C'); + } + } + + /** + * Vertical card: band on top, photo centred, then name, code and expiry. + * + * @param array{name:string, code:string, expiry:string, photo:?string} $card + */ + private function frontPortrait(TCPDF $pdf, float $x, float $y, float $w, float $h, array $card): void + { + $band = 12.0; + $this->band($pdf, $x, $y, $w, $band); + $pad = 3.0; + $cursor = $y + $band + 3; + if (($this->settings['show_photo'] && Features::enabled('patron_photos'))) { + $this->photo($pdf, $x + ($w - 26) / 2, $cursor, 26, 32, $card); + $cursor += 34; + } else { + $cursor += 4; + } + $this->fitText($pdf, $x + $pad, $cursor, $w - 2 * $pad, 6, $card['name'], 'B', 10, [0, 0, 0], 'C'); + $cursor += 7; + if ($this->settings['code_type'] === 'qr') { + $this->qr($pdf, $x + ($w - 17) / 2, $cursor, 17, $card['code']); + $cursor += 18; + } else { + $this->barcode($pdf, $x + $pad + 2, $cursor, $w - 2 * $pad - 4, 10, $card['code']); + $cursor += 11; + } + $this->codeLine($pdf, $x + $pad, $cursor, $w - 2 * $pad, $card['code'], 8, 'C'); + if ($this->settings['show_expiry'] && $card['expiry'] !== '') { + $this->fitText($pdf, $x + $pad, $y + $h - $pad - 4, $w - 2 * $pad, 4, __('Scadenza') . ': ' . $card['expiry'], '', 7, [80, 80, 80], 'C'); + } + } + + private function back(TCPDF $pdf, float $x, float $y, float $w, float $h): void + { + $pad = 4.0; + $this->fitText($pdf, $x + $pad, $y + $pad, $w - 2 * $pad, 5, $this->title, 'B', 8); + $areaY = $y + $pad + 6; + $areaH = $h - 2 * $pad - 6; + $areaW = $w - 2 * $pad; + $text = $this->settings['back_text']; + $size = 7.0; + $pdf->SetFont(self::FONT, '', $size); + while ($size > 4.5 && $pdf->getStringHeight($areaW, $text) > $areaH) { + $size -= 0.25; + $pdf->SetFont(self::FONT, '', $size); + } + $pdf->SetTextColor(40, 40, 40); + $pdf->MultiCell($areaW, $areaH, $text, 0, 'L', false, 1, $x + $pad, $areaY, true, 0, false, true, $areaH, 'T', true); + $pdf->SetTextColor(0, 0, 0); + } + + private function band(TCPDF $pdf, float $x, float $y, float $w, float $hBand): void + { + $pdf->SetFillColor(31, 41, 55); + $pdf->Rect($x, $y, $w, $hBand, 'F'); + $titleX = $x + 3; + if ($this->logoFile !== null) { + $logoW = $this->logoWidth($hBand - 3, $w * 0.35); + $this->image($pdf, $this->logoFile, $x + 3, $y + 1.5, $logoW, $hBand - 3, true); + $titleX = $x + 3 + $logoW + 2; + } + $this->fitText($pdf, $titleX, $y + ($hBand - 5) / 2, $x + $w - 3 - $titleX, 5, $this->title, 'B', 9, [255, 255, 255]); + } + + /** @param array{name:string, code:string, expiry:string, photo:?string} $card */ + private function photo(TCPDF $pdf, float $x, float $y, float $w, float $h, array $card): void + { + $file = $card['photo']; + if ($file !== null && self::isUsableImage($file)) { + // Fit the photo in the box, centred, keeping its proportions. + $this->image($pdf, $file, $x, $y, $w, $h, true); + $pdf->SetDrawColor(200, 200, 200); + $pdf->Rect($x, $y, $w, $h, 'D'); + return; + } + $pdf->SetFillColor(229, 231, 235); + $pdf->Rect($x, $y, $w, $h, 'F'); + $initials = ''; + foreach (preg_split('/\s+/u', trim($card['name'])) ?: [] as $part) { + if ($part !== '' && mb_strlen($initials) < 2) { + $initials .= mb_strtoupper(mb_substr($part, 0, 1)); + } + } + $pdf->SetFont(self::FONT, 'B', 16); + $pdf->SetTextColor(120, 120, 120); + $pdf->MultiCell($w, $h, $initials, 0, 'C', false, 1, $x, $y, true, 0, false, true, $h, 'M'); + $pdf->SetTextColor(0, 0, 0); + } + + private function barcode(TCPDF $pdf, float $x, float $y, float $w, float $h, string $code): void + { + if ($code === '' || preg_match('/^[\x20-\x7E]+$/', $code) !== 1) { + return; // Code128 encodes printable ASCII only; the text line still shows the code. + } + $pdf->write1DBarcode($code, 'C128', $x, $y, $w, $h, 0.4, [ + 'stretch' => true, + 'fitwidth' => false, + 'border' => false, + 'text' => false, + 'fgcolor' => [0, 0, 0], + 'bgcolor' => false, + ], 'N'); + } + + private function qr(TCPDF $pdf, float $x, float $y, float $size, string $code): void + { + if ($code === '') { + return; + } + $pdf->write2DBarcode($code, 'QRCODE,M', $x, $y, $size, $size, [ + 'border' => false, + 'padding' => 0, + 'fgcolor' => [0, 0, 0], + 'bgcolor' => false, + ], 'N'); + } + + /** The card code as text: Helvetica when it is plain ASCII, the Unicode font otherwise. */ + private function codeLine(TCPDF $pdf, float $x, float $y, float $w, string $code, float $size, string $align = 'L'): void + { + $font = preg_match('/^[\x20-\x7E]*$/', $code) === 1 ? self::CODE_FONT : self::FONT; + $pdf->SetFont($font, 'B', $size); + while ($size > 5 && $pdf->GetStringWidth($code) > $w) { + $size -= 0.5; + $pdf->SetFont($font, 'B', $size); + } + $pdf->SetTextColor(0, 0, 0); + $pdf->SetXY($x, $y); + $pdf->Cell($w, 4, $code, 0, 0, $align, false, '', 0, false, 'T', 'T'); + } + + /** @param array{0:int,1:int,2:int} $color */ + private function fitText(TCPDF $pdf, float $x, float $y, float $w, float $h, string $text, string $style, float $size, array $color = [0, 0, 0], string $align = 'L'): void + { + if ($text === '' || $w <= 1) { + return; + } + $pdf->SetFont(self::FONT, $style, $size); + while ($size > 5 && $pdf->GetStringWidth($text) > $w) { + $size -= 0.5; + $pdf->SetFont(self::FONT, $style, $size); + } + $pdf->SetTextColor($color[0], $color[1], $color[2]); + $pdf->SetXY($x, $y); + // Cell with stretch 0 and no fitting beyond the font loop: a name + // longer than the minimum font allows is clipped, never overprinted. + $pdf->Cell($w, $h, $text, 0, 0, $align, false, '', 1, false, 'T', 'T'); + $pdf->SetTextColor(0, 0, 0); + } + + /** Width (mm) the logo takes at height $h, from its pixel proportions, capped at $max. */ + private function logoWidth(float $h, float $max): float + { + $info = $this->logoFile !== null ? @getimagesize($this->logoFile) : false; + if ($info === false || $info[1] <= 0) { + return $h; + } + return min($max, $h * ($info[0] / $info[1])); + } + + private function cutGuide(TCPDF $pdf, float $x, float $y, float $w, float $h): void + { + $pdf->SetLineWidth(0.1); + $pdf->SetDrawColor(190, 190, 190); + $pdf->Rect($x, $y, $w, $h, 'D'); + } + + private function image(TCPDF $pdf, string $file, float $x, float $y, float $w, float $h, bool $fit = false): void + { + try { + $pdf->Image($file, $x, $y, $w, $h, '', '', '', true, 300, '', false, false, 0, $fit ? 'CM' : false); + } catch (\Throwable $e) { + SecureLogger::warning('[PatronCardPdf] image skipped: ' . $e->getMessage()); + } + } + + /** TCPDF stops the request on an unreadable image, so only proven JPEG/PNG files reach it. */ + private static function isUsableImage(string $file): bool + { + if (!is_file($file) || !is_readable($file)) { + return false; + } + $info = @getimagesize($file); + return $info !== false && in_array($info[2], [IMAGETYPE_JPEG, IMAGETYPE_PNG], true); + } +} diff --git a/app/Support/PatronCardSettings.php b/app/Support/PatronCardSettings.php new file mode 100644 index 000000000..19e7cae55 --- /dev/null +++ b/app/Support/PatronCardSettings.php @@ -0,0 +1,111 @@ + 'classic', + 'code_type' => 'code128', + 'format' => 'single', + 'title' => '', + 'back_text' => '', + 'show_photo' => true, + 'show_logo' => true, + 'show_expiry' => true, + ]; + } + + /** + * @return array{layout:string, code_type:string, format:string, title:string, back_text:string, show_photo:bool, show_logo:bool, show_expiry:bool} + */ + public static function resolve(SettingsRepository $repository): array + { + $d = self::defaults(); + $raw = [ + 'card_layout' => (string) $repository->get(self::CATEGORY, 'layout', $d['layout']), + 'card_code_type' => (string) $repository->get(self::CATEGORY, 'code_type', $d['code_type']), + 'card_format' => (string) $repository->get(self::CATEGORY, 'format', $d['format']), + 'card_title' => (string) $repository->get(self::CATEGORY, 'title', $d['title']), + 'card_back_text' => (string) $repository->get(self::CATEGORY, 'back_text', $d['back_text']), + ]; + foreach (['show_photo', 'show_logo', 'show_expiry'] as $flag) { + if ($repository->get(self::CATEGORY, $flag, $d[$flag] ? '1' : '0') === '1') { + $raw['card_' . $flag] = '1'; + } + } + return self::sanitize($raw); + } + + /** + * Normalise a submitted settings form (or stored values) into the settings + * array. Unknown enum values fall back to the default; text is trimmed, + * stripped of tags and length-capped; an absent checkbox means "off". + * + * @param array $post + * @return array{layout:string, code_type:string, format:string, title:string, back_text:string, show_photo:bool, show_logo:bool, show_expiry:bool} + */ + public static function sanitize(array $post): array + { + $d = self::defaults(); + $pick = static function (mixed $value, array $allowed, string $fallback): string { + $value = is_scalar($value) ? strtolower(trim((string) $value)) : ''; + return in_array($value, $allowed, true) ? $value : $fallback; + }; + $text = static function (mixed $value, int $max, bool $multiline): string { + $value = is_scalar($value) ? (string) $value : ''; + $value = strip_tags(str_replace("\r\n", "\n", $value)); + $value = $multiline + ? (string) preg_replace("/\n{3,}/", "\n\n", $value) + : (string) preg_replace('/\s+/u', ' ', $value); + $value = trim($value); + return mb_substr($value, 0, $max, 'UTF-8'); + }; + return [ + 'layout' => $pick($post['card_layout'] ?? null, self::LAYOUTS, $d['layout']), + 'code_type' => $pick($post['card_code_type'] ?? null, self::CODE_TYPES, $d['code_type']), + 'format' => $pick($post['card_format'] ?? null, self::FORMATS, $d['format']), + 'title' => $text($post['card_title'] ?? '', self::TITLE_MAX, false), + 'back_text' => $text($post['card_back_text'] ?? '', self::BACK_TEXT_MAX, true), + 'show_photo' => !empty($post['card_show_photo']), + 'show_logo' => !empty($post['card_show_logo']), + 'show_expiry' => !empty($post['card_show_expiry']), + ]; + } + + /** + * @param array{layout:string, code_type:string, format:string, title:string, back_text:string, show_photo:bool, show_logo:bool, show_expiry:bool} $settings + */ + public static function save(SettingsRepository $repository, array $settings): void + { + foreach (['layout', 'code_type', 'format', 'title', 'back_text'] as $key) { + $repository->set(self::CATEGORY, $key, $settings[$key]); + } + foreach (['show_photo', 'show_logo', 'show_expiry'] as $flag) { + $repository->set(self::CATEGORY, $flag, $settings[$flag] ? '1' : '0'); + } + } +} diff --git a/app/Support/PatronPhoto.php b/app/Support/PatronPhoto.php new file mode 100644 index 000000000..b32dfc069 --- /dev/null +++ b/app/Support/PatronPhoto.php @@ -0,0 +1,400 @@ +-.jpg`); a random part in the name means a new + * photo never reuses a cached URL of the old one. + */ +final class PatronPhoto +{ + public const DIR = 'patron_photos'; + public const MAX_BYTES = 5 * 1024 * 1024; + public const MAX_EDGE = 600; + /** Largest decoded image accepted (16 megapixels, about 64 MB in GD). */ + public const MAX_PIXELS = 16000000; + private const NAME_PATTERN = '/^patron_photos\/(\d+)-[a-f0-9]{16}\.jpg$/D'; + /** + * A file of a reader that no reference points to is swept once it is + * older than this: a younger one may belong to a store() of the same + * reader that has written its file but not yet its reference. + */ + public const ORPHAN_GRACE_SECONDS = 300; + + /** Absolute directory holding the photos (created on first use). */ + public static function directory(): string + { + return dirname(__DIR__, 2) . '/storage/' . self::DIR; + } + + /** + * Absolute path of a stored photo, or null when the stored value is not + * one this class wrote (or the file is gone). The pattern check is what + * keeps a tampered column from pointing the reader at another file. + */ + public static function absolutePath(?string $stored): ?string + { + $stored = (string) $stored; + if ($stored === '' || preg_match(self::NAME_PATTERN, $stored) !== 1) { + return null; + } + $path = dirname(__DIR__, 2) . '/storage/' . $stored; + return is_file($path) ? $path : null; + } + + /** Stored value for a user, or null when none. */ + public static function storedFor(mysqli $db, int $userId): ?string + { + $stmt = $db->prepare('SELECT foto_profilo_url FROM utenti WHERE id = ? LIMIT 1'); + if ($stmt === false) { + return null; + } + $stmt->bind_param('i', $userId); + $stmt->execute(); + $row = $stmt->get_result()->fetch_assoc(); + $stmt->close(); + $value = is_array($row) ? ($row['foto_profilo_url'] ?? null) : null; + return is_string($value) && $value !== '' ? $value : null; + } + + /** + * Decode $sourcePath, re-encode it as a bounded JPEG and make it the + * user's photo. Returns null on success or an error code: + * 'too_large', 'invalid_image', 'storage', 'db'. + */ + public static function store(mysqli $db, int $userId, string $sourcePath): ?string + { + if ($userId <= 0 || !is_file($sourcePath)) { + return 'invalid_image'; + } + $size = filesize($sourcePath); + if ($size === false || $size === 0) { + return 'invalid_image'; + } + if ($size > self::MAX_BYTES) { + return 'too_large'; + } + $jpeg = self::normalise($sourcePath); + if ($jpeg === null) { + return 'invalid_image'; + } + + // Read the reference to replace before writing anything: a failure + // here must not leave a personal photo on disk that nothing points to. + try { + $previous = self::storedFor($db, $userId); + } catch (\Throwable $e) { + SecureLogger::error('[PatronPhoto] cannot read the current photo', ['user_id' => $userId, 'error' => $e->getMessage()]); + return 'db'; + } + + $dir = self::directory(); + if (!is_dir($dir) && !@mkdir($dir, 0750, true) && !is_dir($dir)) { + SecureLogger::error('[PatronPhoto] cannot create the photo directory'); + return 'storage'; + } + $name = self::DIR . '/' . $userId . '-' . bin2hex(random_bytes(8)) . '.jpg'; + $target = dirname(__DIR__, 2) . '/storage/' . $name; + if (!self::writeFile($target, $jpeg)) { + SecureLogger::error('[PatronPhoto] cannot write the photo file', ['user_id' => $userId]); + return 'storage'; + } + @chmod($target, 0640); + + try { + $stmt = $db->prepare('UPDATE utenti SET foto_profilo_url = ? WHERE id = ?'); + if ($stmt === false) { + throw new \RuntimeException($db->error); + } + try { + $stmt->bind_param('si', $name, $userId); + $stmt->execute(); + // The name is new (random part), so a matched row always + // changes: 0 means the account is gone and the file is orphan. + if ($stmt->affected_rows !== 1) { + throw new \RuntimeException('No account to attach the photo to'); + } + } finally { + $stmt->close(); + } + } catch (\Throwable $e) { + @unlink($target); + SecureLogger::error('[PatronPhoto] cannot record the photo', ['user_id' => $userId, 'error' => $e->getMessage()]); + return 'db'; + } + if ($previous !== null && $previous !== $name && !self::deleteFile($previous)) { + // The new photo is in place; the old file is now unreferenced. + // It is logged, and the next store() or delete() of this reader + // retries it through sweepOrphans(). + SecureLogger::error('[PatronPhoto] cannot delete the replaced photo file', ['user_id' => $userId, 'file' => $previous]); + } + self::sweepOrphans($userId, $name); + return null; + } + + /** + * Remove the user's photo: the file, then the reference. True when the + * photo is gone (a replacement stored meanwhile by another request is + * kept). When the file cannot be removed the reference stays, so the photo + * is still known (and deletable) instead of lingering on disk unreferenced. + */ + public static function delete(mysqli $db, int $userId): bool + { + try { + $previous = self::storedFor($db, $userId); + } catch (\Throwable $e) { + SecureLogger::error('[PatronPhoto] cannot read the photo to delete', ['user_id' => $userId, 'error' => $e->getMessage()]); + return false; + } + if ($previous !== null && !self::deleteFile($previous)) { + SecureLogger::error('[PatronPhoto] cannot delete the photo file', ['user_id' => $userId]); + return false; + } + try { + // Not cleared: another request stored a new photo meanwhile, and + // that one stays (it is what the sweep below keeps). + $current = self::clearReference($db, $userId, $previous) ? null : self::storedFor($db, $userId); + } catch (\Throwable $e) { + // The file is already gone: a reference to a missing file shows + // no photo (absolutePath() checks the file), and the next delete + // clears it. + SecureLogger::error('[PatronPhoto] cannot clear the photo', ['user_id' => $userId, 'error' => $e->getMessage()]); + return false; + } + self::sweepOrphans($userId, $current); + return true; + } + + /** + * Clear the reference only while it is still $expected, the value whose + * file was just removed: a photo stored meanwhile by another request is + * a valid replacement and keeps its reference. True when the row changed. + */ + public static function clearReference(mysqli $db, int $userId, ?string $expected): bool + { + $stmt = $db->prepare('UPDATE utenti SET foto_profilo_url = NULL WHERE id = ? AND foto_profilo_url <=> ?'); + if ($stmt === false) { + throw new \RuntimeException($db->error); + } + try { + $stmt->bind_param('is', $userId, $expected); + $stmt->execute(); + return $stmt->affected_rows === 1; + } finally { + $stmt->close(); + } + } + + /** + * Remove the files of a reader other than $keep that are older than + * ORPHAN_GRACE_SECONDS: the leftovers of a replaced or deleted photo whose + * unlink failed earlier. Returns how many were removed. + */ + public static function sweepOrphans(int $userId, ?string $keep, ?int $now = null): int + { + if ($userId <= 0) { + return 0; + } + $now ??= time(); + $keepPath = $keep !== null ? self::absolutePath($keep) : null; + $removed = 0; + foreach (glob(self::directory() . '/' . $userId . '-*.jpg') ?: [] as $file) { + if ($file === $keepPath || preg_match(self::NAME_PATTERN, self::DIR . '/' . basename($file)) !== 1) { + continue; + } + $mtime = @filemtime($file); + if ($mtime === false || $mtime > $now - self::ORPHAN_GRACE_SECONDS) { + continue; + } + if (@unlink($file)) { + $removed++; + } else { + SecureLogger::error('[PatronPhoto] cannot delete an orphaned photo file', ['user_id' => $userId, 'file' => basename($file)]); + } + } + return $removed; + } + + /** + * Write $bytes to $path in full. A failed or short write (the disk + * filling up mid-file) removes what reached the disk at once, instead of + * leaving a partial personal photo for sweepOrphans() to find later. No + * lock: the name is new and random, nothing reads it before store() + * records it. + */ + public static function writeFile(string $path, string $bytes): bool + { + if (@file_put_contents($path, $bytes) === strlen($bytes)) { + return true; + } + @unlink($path); + return false; + } + + /** + * Delete the file behind a stored value (also used when the account + * itself is deleted). True when no file is left: removed, or none there. + */ + public static function deleteFile(?string $stored): bool + { + $path = self::absolutePath($stored); + if ($path === null) { + return true; + } + return @unlink($path) || !is_file($path); + } + + /** Decode any GD-readable JPEG/PNG/WebP and re-encode it as a bounded JPEG. */ + private static function normalise(string $sourcePath): ?string + { + if (!function_exists('imagecreatefromstring')) { + return null; + } + $info = @getimagesize($sourcePath); + if ($info === false || !in_array($info[2], [IMAGETYPE_JPEG, IMAGETYPE_PNG, IMAGETYPE_WEBP], true)) { + return null; + } + [$width, $height] = [(int) $info[0], (int) $info[1]]; + // The pixel count bounds what GD has to allocate (~4 bytes a pixel): + // a tiny flat PNG can declare 8000 x 8000 and exhaust memory_limit. + if ($width < 16 || $height < 16 || $width > 8000 || $height > 8000 || $width * $height > self::MAX_PIXELS) { + return null; + } + $bytes = @file_get_contents($sourcePath); + if ($bytes === false) { + return null; + } + $src = @imagecreatefromstring($bytes); + if ($src === false) { + return null; + } + // Phone photos carry their rotation in EXIF Orientation, which the + // re-encode drops: turn the pixels so the card shows them upright. + // Read now, applied after the downscale, so GD never holds two + // full-resolution copies at once. + $transform = ['flip' => null, 'angle' => 0]; + if ($info[2] === IMAGETYPE_JPEG) { + // Without the exif extension the tag is read from the APP1 block + // directly, so the rotation never depends on an optional module. + $orientation = null; + if (function_exists('exif_read_data')) { + $exif = @exif_read_data($sourcePath); + $orientation = is_array($exif) && isset($exif['Orientation']) ? (int) $exif['Orientation'] : null; + } + $transform = self::orientationTransform($orientation ?? self::jpegOrientation($bytes)); + } + $scale = min(1.0, self::MAX_EDGE / max($width, $height)); + $newW = max(1, (int) round($width * $scale)); + $newH = max(1, (int) round($height * $scale)); + $dst = imagecreatetruecolor($newW, $newH); + // A transparent PNG becomes white, not black. + imagefill($dst, 0, 0, (int) imagecolorallocate($dst, 255, 255, 255)); + imagecopyresampled($dst, $src, 0, 0, 0, 0, $newW, $newH, $width, $height); + unset($src); // free the full-size image before the rotation + if ($transform['flip'] !== null) { + imageflip($dst, $transform['flip']); + } + if ($transform['angle'] !== 0) { + // A quarter turn keeps the long edge within MAX_EDGE. + $rotated = imagerotate($dst, $transform['angle'], 0); + if ($rotated !== false) { + $dst = $rotated; + } + } + ob_start(); + imagejpeg($dst, null, 85); + $out = (string) ob_get_clean(); + return $out !== '' ? $out : null; + } + + /** + * How to turn the pixels of an EXIF Orientation upright: an optional + * mirror (IMG_FLIP_*), applied first, then a GD angle (counter-clockwise). + * 1 or unknown: nothing. 2/4 mirror; 3/6/8 rotate; 5 (transpose) and 7 + * (transverse) mirror horizontally, then rotate. + * + * @return array{flip: ?int, angle: int} + */ + public static function orientationTransform(?int $orientation): array + { + return match ($orientation) { + 2 => ['flip' => IMG_FLIP_HORIZONTAL, 'angle' => 0], + 3 => ['flip' => null, 'angle' => 180], + 4 => ['flip' => IMG_FLIP_VERTICAL, 'angle' => 0], + 5 => ['flip' => IMG_FLIP_HORIZONTAL, 'angle' => 90], + 6 => ['flip' => null, 'angle' => -90], + 7 => ['flip' => IMG_FLIP_HORIZONTAL, 'angle' => -90], + 8 => ['flip' => null, 'angle' => 90], + default => ['flip' => null, 'angle' => 0], + }; + } + + /** + * The EXIF Orientation of a JPEG read from its APP1 segment, without the + * exif extension; null when there is none (or the block is malformed). + */ + public static function jpegOrientation(string $jpeg): ?int + { + $length = strlen($jpeg); + if ($length < 4 || substr($jpeg, 0, 2) !== "\xFF\xD8") { + return null; + } + $pos = 2; + while ($pos + 4 <= $length && $jpeg[$pos] === "\xFF") { + $marker = ord($jpeg[$pos + 1]); + $size = (int) unpack('n', substr($jpeg, $pos + 2, 2))[1]; + if ($marker === 0xDA || $size < 2) { + return null; // image data starts: no APP1 before it + } + if ($marker === 0xE1 && substr($jpeg, $pos + 4, 6) === "Exif\x00\x00") { + return self::tiffOrientation(substr($jpeg, $pos + 10, $size - 8)); + } + $pos += 2 + $size; + } + return null; + } + + /** Orientation tag (0x0112) of IFD0 in a TIFF block. */ + private static function tiffOrientation(string $tiff): ?int + { + $order = substr($tiff, 0, 2); + if ($order !== 'II' && $order !== 'MM' || strlen($tiff) < 8) { + return null; + } + $short = $order === 'II' ? 'v' : 'n'; + $long = $order === 'II' ? 'V' : 'N'; + $ifd = (int) unpack($long, substr($tiff, 4, 4))[1]; + if ($ifd < 8 || $ifd + 2 > strlen($tiff)) { + return null; + } + $entries = (int) unpack($short, substr($tiff, $ifd, 2))[1]; + for ($i = 0; $i < $entries; $i++) { + $entry = $ifd + 2 + $i * 12; + if ($entry + 12 > strlen($tiff)) { + return null; + } + if ((int) unpack($short, substr($tiff, $entry, 2))[1] === 0x0112) { + $value = (int) unpack($short, substr($tiff, $entry + 8, 2))[1]; + return $value >= 1 && $value <= 8 ? $value : null; + } + } + return null; + } +} diff --git a/app/Support/PatronTypeAssignment.php b/app/Support/PatronTypeAssignment.php new file mode 100644 index 000000000..51bc1fcec --- /dev/null +++ b/app/Support/PatronTypeAssignment.php @@ -0,0 +1,246 @@ + $data + */ + public static function fromSubmission(mysqli $db, array $data, ?int $current = null): ?int + { + if (!array_key_exists('tipologia_id', $data)) { + return $current; + } + $raw = $data['tipologia_id']; + if ($raw === null || $raw === '' || $raw === '0') { + return null; + } + if (!is_scalar($raw) || preg_match('/^\d+$/', (string) $raw) !== 1) { + return $current; + } + $id = (int) $raw; + $types = (new CirculationPolicy($db))->types(); + if (!isset($types[$id])) { + return $current; + } + return ((int) ($types[$id]['attiva'] ?? 1) === 1 || $id === $current) ? $id : $current; + } + + /** Store the patron type of $userId (no-op until the 0.9.0 schema exists). */ + public static function save(mysqli $db, int $userId, ?int $typeId): void + { + if (!(new CirculationPolicy($db))->schemaReady()) { + return; + } + $stmt = $db->prepare('UPDATE utenti SET tipologia_id = ? WHERE id = ?'); + $stmt->bind_param('ii', $typeId, $userId); + $stmt->execute(); + $stmt->close(); + } + + /** The stored patron type of $userId (null = default or not migrated). */ + public static function of(mysqli $db, int $userId): ?int + { + if (!(new CirculationPolicy($db))->schemaReady()) { + return null; + } + $stmt = $db->prepare('SELECT tipologia_id FROM utenti WHERE id = ?'); + $stmt->bind_param('i', $userId); + $stmt->execute(); + $row = $stmt->get_result()->fetch_assoc(); + $stmt->close(); + return is_array($row) && $row['tipologia_id'] !== null ? (int) $row['tipologia_id'] : null; + } + + /** + * Card expiry computed from the patron type ($typeId null = default type): + * today + durata_tessera_giorni, or null when the type has no card duration. + */ + public static function automaticCardExpiry(mysqli $db, ?int $typeId): ?string + { + if (!Features::enabled('automatic_card_expiry')) { + return null; + } + $days = (new CirculationPolicy($db))->forType($typeId)->user->cardDurationDays; + return $days !== null ? self::todayPlus($days) : null; + } + + /** + * SQL condition of the user-list "patron type" filter, or null when the + * filter is empty or invalid. $column is the utenti.tipologia_id column + * reference. Users without a type belong to the default one. + * + * @return array{sql: string, types: string, params: list}|null + */ + public static function filterCondition(mysqli $db, mixed $value, string $column = 'tipologia_id'): ?array + { + $policy = new CirculationPolicy($db); + if (!$policy->schemaReady() || !is_scalar($value) || preg_match('/^\d+$/', (string) $value) !== 1) { + return null; + } + $id = (int) $value; + if (!isset($policy->types()[$id])) { + return null; + } + if ($id === $policy->defaultTypeId()) { + return ['sql' => "({$column} = ? OR {$column} IS NULL)", 'types' => 'i', 'params' => [$id]]; + } + return ['sql' => "{$column} = ?", 'types' => 'i', 'params' => [$id]]; + } + + /** + * Display name of a stored patron type (null/unknown = the default type). + */ + public static function nameOf(mysqli $db, mixed $typeId, ?CirculationPolicy $policy = null): string + { + return self::fieldOf($db, $typeId, 'nome', $policy); + } + + /** + * Code of a stored patron type (null/unknown = the default type). The + * users CSV export writes it beside the name: the import resolves a type + * by its code first, so an export re-imports exactly on an installation + * with the same types, even when two of them share a name. + */ + public static function codeOf(mysqli $db, mixed $typeId, ?CirculationPolicy $policy = null): string + { + return self::fieldOf($db, $typeId, 'codice', $policy); + } + + /** + * $policy: pass one instance when resolving many rows (the users export), + * so the patron types are read once instead of once per call. + */ + private static function fieldOf(mysqli $db, mixed $typeId, string $field, ?CirculationPolicy $policy = null): string + { + $policy ??= new CirculationPolicy($db); + $types = $policy->types(); + $id = is_numeric($typeId) ? (int) $typeId : null; + $id = $id !== null && isset($types[$id]) ? $id : $policy->defaultTypeId(); + return $id !== null ? (string) ($types[$id][$field] ?? '') : ''; + } + + /** Card validity used by "Rinnova tessera" when the patron type sets none. */ + public const DEFAULT_RENEWAL_DAYS = 365; + + /** + * Renew the library card of $userId: expiry = today + the card duration + * of the patron type (DEFAULT_RENEWAL_DAYS when the type sets none), an + * expired account becomes active again and the expiry warning re-arms. + * Returns the new expiry date, or null when the account has no card + * (admin/staff) or does not exist. + */ + public static function renewCard(mysqli $db, int $userId): ?string + { + $stmt = $db->prepare('SELECT id, tipo_utente, stato, data_scadenza_tessera FROM utenti WHERE id = ?'); + $stmt->bind_param('i', $userId); + $stmt->execute(); + $user = $stmt->get_result()->fetch_assoc(); + $stmt->close(); + if (!$user || !in_array($user['tipo_utente'], ['standard', 'premium'], true)) { + return null; + } + $policy = new CirculationPolicy($db); + $days = $policy->forUser($userId)->cardDurationDays ?? self::DEFAULT_RENEWAL_DAYS; + $expiry = self::renewalDate($days, $user['data_scadenza_tessera'], + (string) (new \App\Models\SettingsRepository($db))->get('loans', 'card_renewal_base', 'today')); + $state = $user['stato'] === 'scaduto' ? 'attivo' : (string) $user['stato']; + $flagSql = self::hasExpiryWarningFlag($db) ? ', card_expiry_warning_sent_for = NULL' : ''; + $update = $db->prepare('UPDATE utenti SET data_scadenza_tessera = ?, stato = ?' . $flagSql . ' WHERE id = ?'); + $update->bind_param('ssi', $expiry, $state, $userId); + $update->execute(); + $update->close(); + ActivityLog::recordEntityEvent( + $db, + 'utenti', + $userId, + 'user.card_renewed', + ['data_scadenza_tessera' => $user['data_scadenza_tessera'], 'stato' => $user['stato']], + ['data_scadenza_tessera' => $expiry, 'stato' => $state], + source: 'manual' + ); + return $expiry; + } + + /** Shared by the confirmation preview and the actual renewal. */ + public static function renewalDate(int $days, ?string $currentExpiry, string $baseMode = 'today'): string + { + $base = DateHelper::today(); + if ($baseMode === 'expiry' && $currentExpiry !== null + && DateHelper::isISODateFormat($currentExpiry) && $currentExpiry > $base) { + $base = $currentExpiry; + } + $expiry = (new \DateTimeImmutable($base))->modify('+' . max(0, $days) . ' days'); + // MySQL DATE ends at 9999-12-31; lifetime cards must remain renewable. + return (int) $expiry->format('Y') > 9999 ? '9999-12-31' : $expiry->format('Y-m-d'); + } + + /** + * Apply the patron type's card duration on registration approval: only + * when the type sets one (otherwise the stored expiry is kept). + */ + public static function applyCardDurationOnApproval(mysqli $db, int $userId): void + { + if (!Features::enabled('automatic_card_expiry')) { + return; + } + $policy = new CirculationPolicy($db); + $days = $policy->forUser($userId)->cardDurationDays; + if ($days === null) { + return; + } + $expiry = self::todayPlus($days); + $stmt = $db->prepare("UPDATE utenti SET data_scadenza_tessera = ? WHERE id = ? AND tipo_utente IN ('standard', 'premium')"); + $stmt->bind_param('si', $expiry, $userId); + $stmt->execute(); + $stmt->close(); + } + + /** + * today + $days as a calendar date. Card expiries are not loan due dates: + * they deliberately bypass CirculationPolicy::dueDateFor(), which the + * closures calendar shifts to the next open day. + */ + public static function todayPlus(int $days): string + { + return (new \DateTimeImmutable(DateHelper::today()))->modify('+' . max(0, $days) . ' days')->format('Y-m-d'); + } + + private static function hasExpiryWarningFlag(mysqli $db): bool + { + $result = $db->query("SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'utenti' AND COLUMN_NAME = 'card_expiry_warning_sent_for'"); + return $result instanceof \mysqli_result && (int) ($result->fetch_row()[0] ?? 0) === 1; + } + + /** + * Patron types an operator may assign: the active ones plus $currentId + * (kept selectable even after it was deactivated). + * + * @return array> + */ + public static function choices(mysqli $db, ?int $currentId = null): array + { + $out = []; + foreach ((new CirculationPolicy($db))->types() as $id => $type) { + if ((int) $type['attiva'] === 1 || $id === $currentId) { + $out[$id] = $type; + } + } + return $out; + } +} diff --git a/app/Support/PatronTypeImport.php b/app/Support/PatronTypeImport.php new file mode 100644 index 000000000..cc506b9fc --- /dev/null +++ b/app/Support/PatronTypeImport.php @@ -0,0 +1,149 @@ +|null code (lowercase) => id */ + private ?array $codes = null; + /** @var array name (lowercase) => id, null when two active types share the name */ + private array $names = []; + private ?bool $available = null; + + public function __construct( + private readonly mysqli $db, + private readonly string $typesTable = 'tipologie_lettore', + private readonly string $usersTable = 'utenti' + ) { + } + + /** True when the rules module is on and its schema exists (schema cached per instance). */ + public function available(): bool + { + // Check before the schema cache: a long-running import must also + // stop assigning types when the circulation rules are switched off. + if (!Features::enabled('circulation_rules')) { + return false; + } + if ($this->available !== null) { + return $this->available; + } + $this->available = $this->columnExists($this->typesTable, 'id') + && $this->columnExists($this->typesTable, 'codice') + && $this->columnExists($this->usersTable, 'tipologia_id'); + return $this->available; + } + + /** + * Assign the patron type whose code, or else whose name, is $value to the user. + * + * @return 'skipped'|'assigned'|'unknown' skipped = nothing to do (no value, + * or no patron types schema); unknown = the value matches no type + * by code or by name, so the reader keeps the default type + */ + public function assign(int $userId, ?string $value): string + { + $value = trim((string) $value); + if ($value === '' || $userId <= 0 || !$this->available()) { + return 'skipped'; + } + $typeId = $this->idFor($value); + if ($typeId === null) { + return 'unknown'; + } + $stmt = $this->db->prepare("UPDATE `{$this->usersTable}` SET tipologia_id = ? WHERE id = ?"); + if ($stmt === false) { + return 'unknown'; + } + $stmt->bind_param('ii', $typeId, $userId); + $stmt->execute(); + $stmt->close(); + return 'assigned'; + } + + private function idFor(string $value): ?int + { + $this->load(); + $key = self::key($value); + return $this->codes[$key] ?? $this->names[$key] ?? null; + } + + /** Read the active types once: their codes, and their names where the table has them. */ + private function load(): void + { + if ($this->codes !== null) { + return; + } + $this->codes = []; + $this->names = []; + // A deactivated type is not offered in the user form either, so + // neither its code nor its name resolves here: the reader keeps the default. + $active = $this->columnExists($this->typesTable, 'attiva') ? ' WHERE attiva = 1' : ''; + $withName = $this->columnExists($this->typesTable, 'nome'); + $columns = $withName ? 'id, codice, nome' : 'id, codice'; + $result = $this->db->query("SELECT {$columns} FROM `{$this->typesTable}`{$active}"); + if (!($result instanceof \mysqli_result)) { + return; + } + while ($row = $result->fetch_assoc()) { + $id = (int) $row['id']; + $this->codes[self::key((string) $row['codice'])] = $id; + $name = $withName ? self::key((string) $row['nome']) : ''; + if ($name !== '') { + // Two active types with one name: neither is chosen. + $this->names[$name] = array_key_exists($name, $this->names) ? null : $id; + } + } + $result->free(); + } + + /** The lookup key of a code or a name: trimmed, lower case. */ + private static function key(string $value): string + { + return mb_strtolower(trim($value), 'UTF-8'); + } + + private function columnExists(string $table, string $column): bool + { + try { + $stmt = $this->db->prepare( + 'SELECT 1 FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND COLUMN_NAME = ? LIMIT 1' + ); + if ($stmt === false) { + return false; + } + $stmt->bind_param('ss', $table, $column); + $stmt->execute(); + $exists = $stmt->get_result()->fetch_row() !== null; + $stmt->close(); + return $exists; + } catch (\Throwable) { + return false; + } + } +} diff --git a/app/Support/PaymentReceiptPdf.php b/app/Support/PaymentReceiptPdf.php new file mode 100644 index 000000000..cc71342dc --- /dev/null +++ b/app/Support/PaymentReceiptPdf.php @@ -0,0 +1,73 @@ +db); + $movement = $ledger->movement($movementId); + if ($movement === null || (int) $movement['utente_id'] !== $userId || (string) $movement['tipo'] !== 'pagamento') { + throw new \RuntimeException('payment_not_found'); + } + + $stmt = $this->db->prepare('SELECT nome, cognome, codice_tessera, email FROM utenti WHERE id = ?'); + $stmt->bind_param('i', $userId); + $stmt->execute(); + $user = $stmt->get_result()->fetch_assoc() ?: []; + $stmt->close(); + + $appName = (string) ConfigStore::get('app.name', 'Biblioteca'); + $pdf = new TCPDF('P', 'mm', 'A4', true, 'UTF-8', false); + $this->configurePdf($pdf, $appName); + $pdf->SetTitle(__('Ricevuta di pagamento')); + $pdf->AddPage(); + $this->renderHeader($pdf, $appName, $this->resolveLogoPath(), __('Ricevuta di pagamento')); + + $pdf->SetFont('dejavusans', '', 10); + $this->renderSection($pdf, __('Pagamento'), [ + __('Numero ricevuta:') => sprintf('P-%06d', (int) $movement['id']), + __('Data:') => format_date((string) $movement['created_at'], true, '/'), + __('Importo:') => FineParameters::format(-FineParameters::decimalToCents($movement['importo'])), + __('Metodo di pagamento:') => PatronLedger::methodLabel((string) ($movement['metodo_pagamento'] ?? '')) ?: __('Non specificato'), + __('Registrato da:') => trim((string) ($movement['operatore'] ?? '')) !== '' ? (string) $movement['operatore'] : __('Non specificato'), + ]); + $pdf->Ln(3); + + $userFields = [__('Nome:') => trim(((string) ($user['nome'] ?? '')) . ' ' . ((string) ($user['cognome'] ?? '')))]; + if (!empty($user['codice_tessera'])) { + $userFields[__('Codice Tessera:')] = (string) $user['codice_tessera']; + } + if (!empty($user['email'])) { + $userFields[__('Email:')] = (string) $user['email']; + } + $this->renderSection($pdf, __('Dati Utente'), $userFields); + $pdf->Ln(3); + + $this->renderSection($pdf, __('Saldo'), [ + __('Saldo residuo:') => FineParameters::format(max(0, $ledger->balanceAfterCents($userId, $movementId))), + ]); + + $note = trim((string) ($movement['descrizione'] ?? '')); + if ($note !== '') { + $pdf->Ln(3); + $this->renderSection($pdf, __('Note'), ['' => $note]); + } + + $this->renderFooter($pdf); + return $pdf->Output('', 'S'); + } +} diff --git a/app/Support/Permissions.php b/app/Support/Permissions.php index 41f073016..ae7907802 100644 --- a/app/Support/Permissions.php +++ b/app/Support/Permissions.php @@ -6,7 +6,7 @@ /** Capabilities are independent of reader membership (standard/premium). */ final class Permissions { - public const ALL = ['backend.access', 'catalog.manage', 'loans.manage', 'digital.manage', 'digital.access', 'users.manage', 'settings.manage']; + public const ALL = ['backend.access', 'catalog.manage', 'loans.manage', 'digital.manage', 'digital.access', 'users.manage', 'settings.manage', 'inventory.manage']; /** @return array */ public static function labels(): array @@ -19,6 +19,7 @@ public static function labels(): array 'digital.access' => __('Apertura allegati riservati'), 'users.manage' => __('Gestione utenti'), 'settings.manage' => __('Impostazioni'), + 'inventory.manage' => __('Revisione inventariale'), ]; } @@ -118,12 +119,21 @@ public static function permissionForPath(string $path): ?string || preg_match('#^/admin/frbr-lrm/opere/\d+/espressioni$#', $path) === 1) { return 'catalog.manage'; } + // The reader ledger (payment, manual charge, waiver, receipt) is a loan-desk + // action that happens to live under the reader record's URL: it follows the + // loan desk, not the users module, so a users-only role reads the ledger but + // cannot move money. + if (preg_match('#^/admin/users/\d+/ledger/#', $path) === 1) { return 'loans.manage'; } $groups = [ - 'catalog.manage' => ['/admin/books', '/admin/authors', '/admin/publishers', '/admin/genres', '/admin/series', '/admin/placement', '/admin/copies', '/admin/import', '/admin/imports', '/admin/imports-history', '/api/books', '/api/authors', '/api/publishers', '/api/genres', '/api/series', '/api/copies', '/api/dewey', '/api/scrape', '/api/libri', '/api/autori', '/api/editori', '/api/collane', '/api/collocazione', '/api/cover', '/admin/periodicals', '/api/v1/staff/lookups', '/api/v1/staff/isbn', '/api/v1/staff/copies', '/api/v1/staff/books', '/api/v1/staff/authors', '/api/v1/staff/publishers', '/api/v1/staff/imports', '/api/v1/staff/periodicals', '/api/v1/staff/series', '/api/v1/staff/genres', '/api/v1/staff/works', '/api/v1/staff/covers', '/api/v1/staff/enrich', '/api/v1/staff/shelves', '/api/v1/staff/archives', '/api/search/editori', '/api/search/generi', '/api/search/preview', '/api/generi', '/admin/archives', '/admin/opere', '/admin/espressioni'], - 'loans.manage' => ['/admin/loans', '/admin/reservations', '/api/loans', '/api/reservations', '/api/prestiti', '/prestiti', '/api/v1/staff/loans', '/api/v1/staff/reservations'], + 'catalog.manage' => ['/admin/books', '/admin/authors', '/admin/publishers', '/admin/suppliers', '/admin/genres', '/admin/series', '/admin/placement', '/admin/copies', '/admin/import', '/admin/imports', '/admin/imports-history', '/api/books', '/api/authors', '/api/publishers', '/api/genres', '/api/series', '/api/copies', '/api/dewey', '/api/scrape', '/api/libri', '/api/autori', '/api/editori', '/api/collane', '/api/collocazione', '/api/cover', '/admin/periodicals', '/api/v1/staff/lookups', '/api/v1/staff/isbn', '/api/v1/staff/copies', '/api/v1/staff/books', '/api/v1/staff/authors', '/api/v1/staff/publishers', '/api/v1/staff/imports', '/api/v1/staff/periodicals', '/api/v1/staff/series', '/api/v1/staff/genres', '/api/v1/staff/works', '/api/v1/staff/covers', '/api/v1/staff/enrich', '/api/v1/staff/shelves', '/api/v1/staff/archives', '/api/search/editori', '/api/search/generi', '/api/search/preview', '/api/generi', '/admin/archives', '/admin/opere', '/admin/espressioni'], + 'loans.manage' => ['/admin/loans', '/admin/attendance', '/admin/reservations', '/api/loans', '/api/reservations', '/api/prestiti', '/prestiti', '/api/v1/staff/loans', '/api/v1/staff/reservations'], 'users.manage' => ['/admin/users', '/api/users', '/api/utenti', '/api/admin/pending-registrations', '/api/admin/pending-registrations-count', '/api/v1/staff/users', '/api/search/utenti'], 'digital.manage' => ['/admin/plugins/digital-library/upload'], - 'settings.manage' => ['/admin/settings'], + // The closures calendar is a settings page: LibraryCalendarController + // admits Permissions::mayConfigure(). /admin/calendar/rotate-link is + // admin-only inside its route, whatever the map says. + 'settings.manage' => ['/admin/settings', '/admin/calendar'], + 'inventory.manage' => ['/admin/inventory'], ]; foreach ($groups as $permission => $prefixes) { foreach ($prefixes as $prefix) { diff --git a/app/Support/RdaTypes.php b/app/Support/RdaTypes.php new file mode 100644 index 000000000..3370ea3c9 --- /dev/null +++ b/app/Support/RdaTypes.php @@ -0,0 +1,198 @@ + 'rda_content_type', + self::KIND_MEDIA => 'rda_media_type', + self::KIND_CARRIER => 'rda_carrier_type', + ]; + + /** id.loc.gov vocabulary base for each kind. */ + private const URI_BASE = [ + self::KIND_CONTENT => 'http://id.loc.gov/vocabulary/contentTypes/', + self::KIND_MEDIA => 'http://id.loc.gov/vocabulary/mediaTypes/', + self::KIND_CARRIER => 'http://id.loc.gov/vocabulary/carriers/', + ]; + + /** + * code => untranslated Italian label (pass through __() when rendering). + * + * @var array> + */ + private const VOCABULARY = [ + self::KIND_CONTENT => [ + 'txt' => 'Testo', + 'spw' => 'Parola parlata', + 'prm' => 'Musica eseguita', + 'ntm' => 'Musica notata', + 'snd' => 'Suoni', + 'sti' => 'Immagine fissa', + 'tdi' => 'Immagine in movimento bidimensionale', + 'cri' => 'Immagine cartografica', + 'cop' => 'Programma per computer', + 'xxx' => 'Altro', + ], + self::KIND_MEDIA => [ + 'n' => 'Senza mediazione', + 's' => 'Audio', + 'v' => 'Video', + 'c' => 'Computer', + 'h' => 'Microforma', + 'g' => 'Proiettato', + 'x' => 'Altro', + ], + self::KIND_CARRIER => [ + 'nc' => 'Volume', + 'no' => 'Scheda', + 'nb' => 'Foglio', + 'sd' => 'Disco sonoro', + 'ss' => 'Audiocassetta', + 'vd' => 'Videodisco', + 'cd' => 'Disco per computer', + 'cr' => 'Risorsa online', + 'zu' => 'Non specificato', + ], + ]; + + /** + * Derived triple for each tipo_media. + * + * @var array + */ + private const DERIVED = [ + 'libro' => ['content' => 'txt', 'media' => 'n', 'carrier' => 'nc'], + 'disco' => ['content' => 'prm', 'media' => 's', 'carrier' => 'sd'], + 'audiolibro' => ['content' => 'spw', 'media' => 's', 'carrier' => 'sd'], + 'dvd' => ['content' => 'tdi', 'media' => 'v', 'carrier' => 'vd'], + 'altro' => ['content' => 'xxx', 'media' => 'x', 'carrier' => 'zu'], + ]; + + /** @return list */ + public static function kinds(): array + { + return [self::KIND_CONTENT, self::KIND_MEDIA, self::KIND_CARRIER]; + } + + /** + * Code => untranslated label for one kind. + * + * @return array + */ + public static function options(string $kind): array + { + return self::VOCABULARY[$kind] ?? []; + } + + /** Untranslated label of a code, or the code itself when unknown. */ + public static function label(string $kind, string $code): string + { + return self::VOCABULARY[$kind][$code] ?? $code; + } + + /** + * The triple derived from tipo_media (unknown/empty values derive as a book, + * the same default the catalogue applies everywhere else). + * + * @return array{content: string, media: string, carrier: string} + */ + public static function derive(?string $tipoMedia): array + { + $key = MediaLabels::normalizeTipoMedia($tipoMedia) ?? 'libro'; + return self::DERIVED[$key] ?? self::DERIVED['libro']; + } + + /** + * A submitted override, validated against the vocabulary. Anything that + * is not a known code (including '' from the "derived" option) is NULL. + */ + public static function normalize(string $kind, mixed $code): ?string + { + if (!is_string($code)) { + return null; + } + $code = strtolower(trim($code)); + return isset(self::VOCABULARY[$kind][$code]) ? $code : null; + } + + /** + * Overrides explicitly stored on the record (kind => code), only the ones set. + * + * @param array $book + * @return array + */ + public static function overrides(array $book): array + { + $out = []; + // RDA types module off (Settings → Modules): the stored overrides are + // kept but not applied, so every export derives from tipo_media as + // before 0.9.0. + if (!Features::enabled('rda_types')) { + return $out; + } + foreach (self::COLUMNS as $kind => $column) { + $code = self::normalize($kind, $book[$column] ?? null); + if ($code !== null) { + $out[$kind] = $code; + } + } + return $out; + } + + /** + * Effective triple: override when set, derived value otherwise. + * + * @param array $book + * @return array{content: string, media: string, carrier: string} + */ + public static function effective(array $book): array + { + $derived = self::derive(isset($book['tipo_media']) ? (string) $book['tipo_media'] : null); + foreach (self::overrides($book) as $kind => $code) { + $derived[$kind] = $code; + } + return $derived; + } + + /** + * The effective codes under the names the exporters use: the same triple + * as effective(), kept as a stable entry point for BookMarcBuilder + * (336/337/338) and the BIBFRAME plugin. + * + * @param array $book + * @return array{content: string, media: string, carrier: string} + */ + public static function codes(array $book): array + { + return self::effective($book); + } + + /** id.loc.gov URI of a code. */ + public static function uri(string $kind, string $code): string + { + return (self::URI_BASE[$kind] ?? '') . rawurlencode($code); + } +} diff --git a/app/Support/RolePresets.php b/app/Support/RolePresets.php index dff464a3d..f1f73d654 100644 --- a/app/Support/RolePresets.php +++ b/app/Support/RolePresets.php @@ -12,6 +12,7 @@ public static function displayName(string $name): string 'Catalogatore' => __('Catalogatore'), 'Addetto prestiti' => __('Addetto prestiti'), 'Archivista' => __('Archivista'), + 'Revisore inventario' => __('Revisore inventario'), default => $name, }; } @@ -22,6 +23,7 @@ public static function description(string $name): string 'Catalogatore' => __('Profilo iniziale per gestire libri, autori ed editori, senza gestire prestiti, utenti o file riservati.'), 'Addetto prestiti' => __('Profilo iniziale per registrare prestiti, restituzioni e prenotazioni, senza modificare il catalogo o le impostazioni.'), 'Archivista' => __('Profilo iniziale per catalogare e gestire gli allegati digitali, compresa l’apertura dei file riservati.'), + 'Revisore inventario' => __('Profilo iniziale per partecipare alle revisioni inventariali: aprire una sessione e scansionare le copie, senza modificare il catalogo né dichiarare perse le copie.'), default => '', }; } diff --git a/app/Support/SearchIndexBuilder.php b/app/Support/SearchIndexBuilder.php index 1dd41d786..aa90ea3c8 100644 --- a/app/Support/SearchIndexBuilder.php +++ b/app/Support/SearchIndexBuilder.php @@ -286,6 +286,10 @@ public static function rebuildMany(\mysqli $db, array $bookIds): void * match somewhere" behaviour without letting unindexable required terms zero * out otherwise valid results. * + * The column collation folds accents (`haffner` finds `Häffner`). A quoted + * group (`"haffner"`, `"thomas mann"`, unclosed quote = closed) is matched + * with its exact spelling and as a phrase instead — see ExactSearch (#501). + * * UPGRADE-WINDOW SAFETY: when the `search_index` column does not exist yet * (new PHP deployed but the admin has not run the 0.7.31 DB migration), * MATCH/LIKE on it would 500 every catalog search + autocomplete/preview @@ -311,16 +315,27 @@ public static function buildSearchCondition(\mysqli $db, string $column, string return self::buildLegacyCondition($db, $column, $searchQuery); } - $words = preg_split('/\s+/', $searchQuery, -1, PREG_SPLIT_NO_EMPTY) ?: []; - $booleanTerms = []; $parts = []; $params = []; $types = ''; - foreach ($words as $word) { - $wordBase = self::normalizeSearchWord($word); - if ($wordBase === '') { + foreach (self::searchTerms($searchQuery) as ['term' => $wordBase, 'exact' => $exact]) { + if ($exact) { + // Quoted term (#501): FULLTEXT still narrows the rows — every + // indexable word must start a word, accent-folded, the same + // prefix rule an unquoted word follows — and the binary-collation + // LIKE then keeps only the exact spelling/phrase. Without the + // `*`, "haff" would demand the whole word "haff" and find + // nothing while the autocomplete above it lists Haffner. + foreach (self::fulltextTokens($wordBase) as $token) { + if (self::fulltextTermForWord($token) !== null) { + $booleanTerms[] = '+' . $token . '*'; + } + } + $parts[] = ExactSearch::likeSql($column, true); + $params[] = self::likePattern($wordBase); + $types .= 's'; continue; } $fulltextTerm = self::fulltextTermForWord($wordBase); @@ -369,39 +384,50 @@ private static function buildLegacyCondition(\mysqli $db, string $column, string $prefix = $dot === false ? '' : substr($column, 0, $dot + 1); $descExpr = self::descriptionExpr($db, $prefix); - $words = preg_split('/\s+/', $searchQuery, -1, PREG_SPLIT_NO_EMPTY) ?: []; - $parts = []; $params = []; $types = ''; $hasJunction = SchemaInfo::hasLibriEditori($db); - foreach ($words as $word) { - $wordBase = self::normalizeSearchWord($word); - if ($wordBase === '') { - continue; - } + foreach (self::searchTerms($searchQuery) as ['term' => $wordBase, 'exact' => $exact]) { $like = self::likePattern($wordBase); + // A quoted term must also START a word, as on the FULLTEXT path + // (`+token*`): "haffner" in quotes must not answer for Schaffner. + // The exact LIKE keeps the spelling, the REGEXP the word start; + // both compare the lowered value under the binary collation. + // `_` is a word character for the InnoDB FULLTEXT parser too, so + // "haffner" must not start a word after it (x_haffner). + $wordStart = '(^|[^[:alnum:]_])' . preg_quote(mb_strtolower($wordBase, 'UTF-8')); + $exprCount = 0; + $m = static function (string $expr) use ($exact, &$exprCount): string { + $exprCount++; + return $exact + ? '(' . ExactSearch::likeSql($expr, true) . " AND LOWER({$expr}) COLLATE utf8mb4_bin REGEXP ?)" + : ExactSearch::likeSql($expr, false); + }; $bookIdExpr = $prefix . 'id'; $publisherIdExpr = $prefix . 'editore_id'; - $parts[] = "({$prefix}titolo LIKE ? ESCAPE '\\\\'" - . " OR {$prefix}sottotitolo LIKE ? ESCAPE '\\\\'" - . " OR {$descExpr} LIKE ? ESCAPE '\\\\'" - . " OR {$prefix}parole_chiave LIKE ? ESCAPE '\\\\'" - . " OR {$prefix}isbn10 LIKE ? ESCAPE '\\\\'" - . " OR {$prefix}isbn13 LIKE ? ESCAPE '\\\\'" - . " OR {$prefix}ean LIKE ? ESCAPE '\\\\'" - . " OR EXISTS (SELECT 1 FROM libri_autori la JOIN autori a ON la.autore_id = a.id WHERE la.libro_id = {$bookIdExpr} AND CONCAT_WS(' ', a.nome, a.pseudonimo) LIKE ? ESCAPE '\\\\')" - . " OR EXISTS (SELECT 1 FROM editori e WHERE e.id = {$publisherIdExpr} AND e.nome LIKE ? ESCAPE '\\\\')" + $parts[] = '(' . $m("{$prefix}titolo") + . ' OR ' . $m("{$prefix}sottotitolo") + . ' OR ' . $m($descExpr) + . ' OR ' . $m("{$prefix}parole_chiave") + . ' OR ' . $m("{$prefix}isbn10") + . ' OR ' . $m("{$prefix}isbn13") + . ' OR ' . $m("{$prefix}ean") + . " OR EXISTS (SELECT 1 FROM libri_autori la JOIN autori a ON la.autore_id = a.id WHERE la.libro_id = {$bookIdExpr} AND " . $m("CONCAT_WS(' ', a.nome, a.pseudonimo)") . ')' + . " OR EXISTS (SELECT 1 FROM editori e WHERE e.id = {$publisherIdExpr} AND " . $m('e.nome') . ')' . ($hasJunction - ? " OR EXISTS (SELECT 1 FROM libri_editori le JOIN editori e2 ON e2.id = le.editore_id WHERE le.libro_id = {$bookIdExpr} AND e2.nome LIKE ? ESCAPE '\\\\')" + ? " OR EXISTS (SELECT 1 FROM libri_editori le JOIN editori e2 ON e2.id = le.editore_id WHERE le.libro_id = {$bookIdExpr} AND " . $m('e2.nome') . ')' : '') . ')'; - $paramsPerWord = $hasJunction ? 10 : 9; - for ($i = 0; $i < $paramsPerWord; $i++) { + for ($i = 0; $i < $exprCount; $i++) { $params[] = $like; $types .= 's'; + if ($exact) { + $params[] = $wordStart; + $types .= 's'; + } } } @@ -444,14 +470,7 @@ public static function buildRelevanceOrder( ?int $maxWords = null ): array { - $rawWords = preg_split('/\s+/', trim($searchQuery), -1, PREG_SPLIT_NO_EMPTY) ?: []; - $words = []; - foreach ($rawWords as $word) { - $wordBase = self::normalizeSearchWord($word); - if ($wordBase !== '') { - $words[] = $wordBase; - } - } + $words = self::searchTerms($searchQuery); if ($maxWords !== null) { $words = array_slice($words, 0, max(0, $maxWords)); } @@ -468,18 +487,21 @@ public static function buildRelevanceOrder( $params = []; $types = ''; - foreach ($words as $word) { + foreach ($words as ['term' => $word, 'exact' => $exact]) { $like = self::likePattern($word); + // A quoted term (#501) scores only on its exact spelling, matching + // the rows buildSearchCondition admitted. + $m = static fn(string $expr): string => ExactSearch::likeSql($expr, $exact); - $terms[] = "(CASE WHEN ({$prefix}isbn10 LIKE ? ESCAPE '\\\\'" - . " OR {$prefix}isbn13 LIKE ? ESCAPE '\\\\'" - . " OR {$prefix}ean LIKE ? ESCAPE '\\\\') THEN 120 ELSE 0 END)"; + $terms[] = '(CASE WHEN (' . $m("{$prefix}isbn10") + . ' OR ' . $m("{$prefix}isbn13") + . ' OR ' . $m("{$prefix}ean") . ') THEN 120 ELSE 0 END)'; $params[] = $like; $params[] = $like; $params[] = $like; $types .= 'sss'; - $terms[] = "(CASE WHEN {$titleExpr} LIKE ? ESCAPE '\\\\' THEN 100 ELSE 0 END)"; + $terms[] = "(CASE WHEN " . $m($titleExpr) . " THEN 100 ELSE 0 END)"; $params[] = $like; $types .= 's'; @@ -487,25 +509,25 @@ public static function buildRelevanceOrder( $terms[] = "(CASE WHEN EXISTS (SELECT 1 FROM libri_autori la_rel" . " JOIN autori a_rel ON a_rel.id = la_rel.autore_id" . " WHERE la_rel.libro_id = {$prefix}id" - . " AND {$authorExpr} LIKE ? ESCAPE '\\\\') THEN 60 ELSE 0 END)"; + . " AND " . $m($authorExpr) . ") THEN 60 ELSE 0 END)"; $params[] = $like; $types .= 's'; - $terms[] = "(CASE WHEN {$subtitleExpr} LIKE ? ESCAPE '\\\\' THEN 40 ELSE 0 END)"; + $terms[] = "(CASE WHEN " . $m($subtitleExpr) . " THEN 40 ELSE 0 END)"; $params[] = $like; $types .= 's'; $publisherExpr = self::decodedSearchExpr('e_rel.nome'); $publisherMatch = "EXISTS (SELECT 1 FROM editori e_rel" . " WHERE e_rel.id = {$prefix}editore_id" - . " AND {$publisherExpr} LIKE ? ESCAPE '\\\\')"; + . " AND " . $m($publisherExpr) . ")"; $publisherParams = 1; if ($hasJunction) { $publisher2Expr = self::decodedSearchExpr('e2_rel.nome'); $publisherMatch .= " OR EXISTS (SELECT 1 FROM libri_editori le_rel" . " JOIN editori e2_rel ON e2_rel.id = le_rel.editore_id" . " WHERE le_rel.libro_id = {$prefix}id" - . " AND {$publisher2Expr} LIKE ? ESCAPE '\\\\')"; + . " AND " . $m($publisher2Expr) . ")"; $publisherParams++; } $terms[] = "(CASE WHEN ({$publisherMatch}) THEN 25 ELSE 0 END)"; @@ -514,11 +536,11 @@ public static function buildRelevanceOrder( $types .= 's'; } - $terms[] = "(CASE WHEN {$keywordsExpr} LIKE ? ESCAPE '\\\\' THEN 10 ELSE 0 END)"; + $terms[] = "(CASE WHEN " . $m($keywordsExpr) . " THEN 10 ELSE 0 END)"; $params[] = $like; $types .= 's'; - $terms[] = "(CASE WHEN {$descExpr} LIKE ? ESCAPE '\\\\' THEN 3 ELSE 0 END)"; + $terms[] = "(CASE WHEN " . $m($descExpr) . " THEN 3 ELSE 0 END)"; $params[] = $like; $types .= 's'; } @@ -560,7 +582,9 @@ private static function fulltextTermForWord(string $word): ?string */ private static function fulltextTokens(string $word): array { - if (preg_match_all('/[\p{L}\p{N}_]+/u', $word, $matches) !== 1) { + // Every token, not only a lone one: a quoted phrase needs each of its + // words (fulltextTermForWord() still rejects a multi-token word). + if (!preg_match_all('/[\p{L}\p{N}_]+/u', $word, $matches)) { return []; } return array_values(array_filter($matches[0], static fn(string $token): bool => $token !== '')); @@ -585,7 +609,7 @@ private static function isDefaultFulltextStopword(string $token): bool private static function likePattern(string $term): string { - return '%' . str_replace(['\\', '%', '_'], ['\\\\', '\\%', '\\_'], $term) . '%'; + return ExactSearch::likePattern($term); } /** @@ -602,15 +626,25 @@ private static function decodedSearchExpr(string $expression): string } /** - * Normalize a raw user search word for LIKE/FULLTEXT matching: strip a - * trailing '*' (the wildcard the FULLTEXT boolean path adds itself) and - * surrounding whitespace. Shared by buildSearchCondition (the WHERE) and - * buildRelevanceOrder (the ORDER BY) so the two paths tokenize identically + * Split a raw user query into search terms: quoted groups become one exact + * term (#501, see ExactSearch), other words are tolerant terms with a + * trailing '*' stripped (the wildcard the FULLTEXT boolean path adds + * itself). Shared by buildSearchCondition (the WHERE), the legacy fallback + * and buildRelevanceOrder (the ORDER BY) so all paths tokenize identically * and ranking never diverges from the rows the WHERE admitted. + * + * @return list */ - private static function normalizeSearchWord(string $word): string + private static function searchTerms(string $searchQuery): array { - return trim(rtrim($word, '*')); + $terms = []; + foreach (ExactSearch::tokenize(trim($searchQuery)) as $term) { + $value = $term['exact'] ? $term['term'] : trim(rtrim($term['term'], '*')); + if ($value !== '') { + $terms[] = ['term' => $value, 'exact' => $term['exact']]; + } + } + return $terms; } private static ?bool $hasDescrizionePlain = null; diff --git a/app/Support/SettingsMailTemplates.php b/app/Support/SettingsMailTemplates.php index ecb979057..0b138e150 100644 --- a/app/Support/SettingsMailTemplates.php +++ b/app/Support/SettingsMailTemplates.php @@ -430,6 +430,22 @@ public static function all(?string $locale = null): array

Se non ti serve più, puoi annullare il prestito dalla pagina «Prenotazioni» del tuo account: il libro passerà subito a qualcun altro.

Buona lettura!

+HTML, + ], + 'card_expiring' => [ + 'label' => __('Tessera in scadenza'), + 'description' => __("Inviata qualche giorno prima della scadenza della tessera del lettore. I giorni di preavviso si impostano in Impostazioni → Prestiti (0 = disattivata)."), + 'subject' => '🪪 La tua tessera scade il {{data_scadenza_tessera}}', + 'placeholders' => ['utente_nome', 'codice_tessera', 'data_scadenza_tessera', 'giorni_rimasti'], + 'body' => <<<'HTML' +

La tua tessera sta per scadere

+

Ciao {{utente_nome}},

+

la tua tessera della biblioteca (n. {{codice_tessera}}) scade il {{data_scadenza_tessera}} (giorni rimasti: {{giorni_rimasti}}).

+
+

Dopo la scadenza non potrai più chiedere prestiti né prenotare libri finché la tessera non viene rinnovata.

+
+

Per rinnovarla rivolgiti al banco della biblioteca durante gli orari di apertura.

+

A presto!

HTML, ], 'loan_pickup_cancelled' => [ @@ -453,7 +469,7 @@ public static function all(?string $locale = null): array 'label' => __('Restituzione confermata'), 'description' => __("Inviata all'utente quando un prestito viene registrato come restituito."), 'subject' => '✅ Restituzione confermata', - 'placeholders' => ['utente_nome', 'libro_titolo', 'data_restituzione'], + 'placeholders' => ['utente_nome', 'libro_titolo', 'data_restituzione', 'multa'], 'body' => <<<'HTML'

Restituzione confermata

Ciao {{utente_nome}},

@@ -462,6 +478,7 @@ public static function all(?string $locale = null): array

Libro: {{libro_titolo}}

Data restituzione: {{data_restituzione}}

+

{{multa}}

Speriamo che la lettura ti sia piaciuta. A presto in biblioteca!

Cordiali saluti,
Il team della biblioteca

HTML, @@ -483,6 +500,27 @@ public static function all(?string $locale = null): array

Importo registrato: {{sanzione}}

Per qualsiasi chiarimento contatta la biblioteca.

+HTML, + ], + 'fine_charged' => [ + 'label' => __('Multa per ritardo addebitata'), + 'description' => __("Inviata al lettore quando la restituzione in ritardo genera una multa. Si attiva in Impostazioni → Prestiti → Multe."), + 'subject' => '💶 Multa per ritardo: {{multa}}', + 'placeholders' => ['utente_nome', 'libro_titolo', 'data_scadenza', 'data_restituzione', 'giorni_ritardo', 'multa', 'saldo'], + 'body' => <<<'HTML' +

Multa per ritardo nella restituzione

+

Ciao {{utente_nome}},

+

il libro seguente è stato restituito dopo la scadenza e ti è stata addebitata una multa.

+
+

Libro: {{libro_titolo}}

+

Scadenza: {{data_scadenza}}

+

Restituito il: {{data_restituzione}}

+

Giorni di ritardo: {{giorni_ritardo}}

+

Multa: {{multa}}

+

Saldo da pagare: {{saldo}}

+
+

Puoi saldare l'importo al banco della biblioteca.

+

Cordiali saluti,
Il team della biblioteca

HTML, ], 'reservation_expired' => [ @@ -677,6 +715,85 @@ public static function get(string $template, ?string $locale = null): ?array return $all[$template] ?? null; } + /** + * Bodies a template shipped with BEFORE its current default, so an + * upgraded install's stored row can be told apart from a customised one: + * SettingsRepository::ensureEmailTemplates() moves a row that still + * equals one of them to the current default and leaves any other text + * alone. Empty for a template whose default never changed. + * + * loan_returned (0.9.0): the only change was the added `

{{multa}}

` + * line, so the previous in-code/mail_templates default is the current one + * without that line; the English, German and French installer seeds of + * the same template carried their own wording (and the {{user_name}} / + * {{book_title}} aliases), listed as they were shipped. + * + * @return list + */ + public static function retiredBodies(string $template, ?string $locale = null): array + { + if ($template !== 'loan_returned') { + return []; + } + $current = (string) (self::get($template, $locale)['body'] ?? ''); + $bodies = []; + $previous = str_replace("

{{multa}}

\n", '', $current); + if ($previous !== $current) { + $bodies[] = $previous; + } + $seeds = [ + 'en_US' => <<<'HTML' +

Return Confirmed

+

Hello {{user_name}},

+

We confirm the return of the following book. Thank you!

+
+

Book: {{book_title}}

+

Return date: {{data_restituzione}}

+
+

We hope you enjoyed the book. See you soon at the library!

+

Best regards,
The Library Team

+HTML, + 'de_DE' => <<<'HTML' +

Rückgabe bestätigt

+

Guten Tag {{user_name}},

+

wir bestätigen die Rückgabe des folgenden Buches. Vielen Dank!

+
+

Buch: {{book_title}}

+

Rückgabedatum: {{data_restituzione}}

+
+

Wir hoffen, die Lektüre hat Ihnen gefallen. Bis bald in der Bibliothek!

+

Mit freundlichen Grüßen,
Das Bibliotheksteam

+HTML, + 'fr_FR' => <<<'HTML' +

Retour confirmé

+

Bonjour {{user_name}},

+

Nous confirmons le retour du livre suivant. Merci !

+
+

Livre : {{book_title}}

+

Date de retour : {{data_restituzione}}

+
+

Nous espérons que la lecture vous a plu. À bientôt à la bibliothèque !

+

Cordialement,
L'équipe de la bibliothèque

+HTML, + ]; + $key = self::shippedLocaleKey($locale); + if ($key !== null && isset($seeds[$key])) { + $bodies[] = $seeds[$key]; + } + return $bodies; + } + + /** The mail_templates file key ($locale → en_US/de_DE/fr_FR/da_DK), null for Italian or an unknown locale. */ + private static function shippedLocaleKey(?string $locale): ?string + { + $locale = str_replace('-', '_', trim((string) $locale)); + if ($locale === '' || str_starts_with($locale, 'it')) { + return null; + } + $map = ['en' => 'en_US', 'de' => 'de_DE', 'fr' => 'fr_FR', 'da' => 'da_DK', 'en_US' => 'en_US', 'de_DE' => 'de_DE', 'fr_FR' => 'fr_FR', 'da_DK' => 'da_DK']; + return $map[$locale] ?? ($map[substr($locale, 0, 2)] ?? null); + } + /** * #360: whether this locale has SHIPPED template texts — the in-code * Italian base or a mail_templates/.php override. Distinguishes a diff --git a/app/Support/StatisticsSections.php b/app/Support/StatisticsSections.php new file mode 100644 index 000000000..111875f75 --- /dev/null +++ b/app/Support/StatisticsSections.php @@ -0,0 +1,339 @@ +, note?: string} + * + * which the view renders as a two-column table (label, value). Labels are + * already translated by the provider. + */ +final class StatisticsSections +{ + /** Loan states that mean the item actually left the library. */ + public const CIRCULATED_STATES = ['in_corso', 'in_ritardo', 'restituito', 'perso', 'danneggiato']; + + /** + * Visitors from the attendance log over the last $days days. + * + * @return array{days:int, soci:int, ospiti:int, totale:int, daily:list}|null + */ + public static function visitors(mysqli $db, int $days = 30): ?array + { + // Each section follows the module it reports on (Settings -> Modules). + if (!Features::enabled('visitor_log')) { + return null; + } + try { + $repo = new AttendanceRepository($db); + if (!$repo->isAvailable()) { + return null; + } + $daily = $repo->dailyCounts($days); + } catch (\Throwable $e) { + SecureLogger::warning('Statistics visitors section unavailable', ['error' => $e->getMessage()]); + return null; + } + $totals = ['soci' => 0, 'ospiti' => 0, 'totale' => 0]; + foreach ($daily as $row) { + $totals['soci'] += $row['soci']; + $totals['ospiti'] += $row['ospiti']; + $totals['totale'] += $row['totale']; + } + return ['days' => AttendanceRepository::clampDays($days)] + $totals + ['daily' => $daily]; + } + + /** + * Loans in the last 12 months grouped by material (libri.tipo_media), + * every media type listed even when it has no loans. + * + * @return list + */ + public static function loansByMediaType(mysqli $db): array + { + $states = "'" . implode("','", self::CIRCULATED_STATES) . "'"; + $sql = "SELECT COALESCE(NULLIF(l.tipo_media, ''), 'libro') AS tipo_media, COUNT(p.id) AS totale + FROM prestiti p + JOIN libri l ON l.id = p.libro_id AND l.deleted_at IS NULL + WHERE p.stato IN ({$states}) + AND p.data_prestito >= DATE_SUB(CURDATE(), INTERVAL 12 MONTH) + GROUP BY COALESCE(NULLIF(l.tipo_media, ''), 'libro')"; + $counts = []; + $res = $db->query($sql); + if ($res instanceof \mysqli_result) { + while ($row = $res->fetch_assoc()) { + $counts[(string) $row['tipo_media']] = (int) $row['totale']; + } + } + $out = []; + foreach (MediaLabels::allTypes() as $key => $meta) { + $out[] = [ + 'tipo_media' => $key, + 'label' => __($meta['label']), + 'icon' => $meta['icon'], + 'totale' => $counts[$key] ?? 0, + ]; + } + return $out; + } + + /** + * Loans in the last 12 months grouped by patron type (tipologie_lettore). + * A reader without a type counts under the default type, as the + * circulation policy treats them. Every active type is listed, also with + * no loans; a deactivated type only when it has loans in the period. A + * filter on statistics.section.patron_types still wins; no patron types + * schema (upgrade not run yet) hides the section. + * + * @return array{rows: list, note?: string}|null + */ + public static function loansByPatronType(mysqli $db): ?array + { + if (!Features::enabled('circulation_rules')) { + return null; + } + $hooked = self::tableSection(Hooks::apply('statistics.section.patron_types', null, [$db])); + if ($hooked !== null) { + return $hooked; + } + $policy = new \App\Services\CirculationPolicy($db); + if (!$policy->rulesActive()) { + return null; + } + $types = $policy->types(); + if ($types === []) { + return null; + } + $defaultId = $policy->defaultTypeId() ?? 0; + $states = "'" . implode("','", self::CIRCULATED_STATES) . "'"; + $sql = "SELECT COALESCE(u.tipologia_id, {$defaultId}) AS tipologia, COUNT(p.id) AS totale + FROM prestiti p + JOIN libri l ON l.id = p.libro_id AND l.deleted_at IS NULL + LEFT JOIN utenti u ON u.id = p.utente_id + WHERE p.stato IN ({$states}) + AND p.data_prestito >= DATE_SUB(CURDATE(), INTERVAL 12 MONTH) + GROUP BY COALESCE(u.tipologia_id, {$defaultId})"; + $counts = []; + $res = $db->query($sql); + if ($res instanceof \mysqli_result) { + while ($row = $res->fetch_assoc()) { + $counts[(int) $row['tipologia']] = (int) $row['totale']; + } + $res->free(); + } + $rows = []; + foreach ($types as $id => $type) { + $count = $counts[$id] ?? 0; + if ((int) $type['attiva'] !== 1 && $count === 0) { + continue; + } + $rows[] = ['label' => (string) $type['nome'], 'value' => $count]; + } + $defaultName = $defaultId > 0 && isset($types[$defaultId]) ? (string) $types[$defaultId]['nome'] : ''; + return [ + 'rows' => $rows, + 'note' => $defaultName !== '' + ? sprintf(__('Ultimi 12 mesi. I lettori senza tipologia sono contati in «%s», la tipologia predefinita.'), $defaultName) + : __('Ultimi 12 mesi.'), + ]; + } + + /** + * Fines over the last 12 months (charged, collected, waived, reversed) + * and what readers owe today, from the patron ledger (movimenti_lettore). + * A filter on statistics.section.fines still wins. Hidden (NULL) before + * the ledger table exists and on an install that has never configured a + * fine nor written a ledger row: zero configuration, same page as before. + * + * @return array{rows: list, note?: string}|null + */ + public static function fines(mysqli $db): ?array + { + if (!Features::enabled('fines')) { + return null; + } + $hooked = self::tableSection(Hooks::apply('statistics.section.fines', null, [$db])); + if ($hooked !== null) { + return $hooked; + } + $exists = $db->query("SELECT 1 FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'movimenti_lettore' LIMIT 1"); + if (!$exists instanceof \mysqli_result || $exists->num_rows === 0) { + return null; + } + $any = $db->query('SELECT 1 FROM movimenti_lettore LIMIT 1'); + $hasRows = $any instanceof \mysqli_result && $any->num_rows > 0; + if (!$hasRows && !self::anyFineConfigured($db)) { + return null; + } + $to = DateHelper::today(); + $from = (new \DateTimeImmutable($to))->modify('-12 months')->modify('+1 day')->format('Y-m-d'); + $sum = ['addebitato' => 0, 'incassato' => 0, 'condonato' => 0, 'stornato' => 0]; + foreach (\App\Services\PatronLedger::forDb($db)->periodTotals($from, $to) as $month) { + foreach ($sum as $k => $v) { + $sum[$k] = $v + (int) $month[$k]; + } + } + $outstanding = 0; + $debtors = 0; + $res = $db->query('SELECT SUM(saldo), COUNT(*) FROM (SELECT SUM(importo) AS saldo FROM movimenti_lettore GROUP BY utente_id HAVING SUM(importo) > 0) d'); + if ($res instanceof \mysqli_result) { + $row = $res->fetch_row(); + $outstanding = \App\Services\FineParameters::decimalToCents($row[0] ?? null); + $debtors = (int) ($row[1] ?? 0); + } + $fmt = static fn (int $cents): string => \App\Services\FineParameters::format($cents); + return [ + 'rows' => [ + ['label' => __('Addebitato'), 'value' => $fmt($sum['addebitato'])], + ['label' => __('Incassato'), 'value' => $fmt($sum['incassato'])], + ['label' => __('Condonato'), 'value' => $fmt($sum['condonato'])], + ['label' => __('Stornato'), 'value' => $fmt($sum['stornato'])], + ['label' => __('Saldo da incassare'), 'value' => $fmt($outstanding)], + ['label' => __('Lettori con saldo a debito'), 'value' => $debtors], + ], + 'note' => __('Ultimi 12 mesi; il saldo da incassare è quello di oggi. Il dettaglio mese per mese è nel report delle multe più in basso.'), + ]; + } + + /** Whether a daily fine is set anywhere: globally, on a patron type or in the rules matrix. */ + private static function anyFineConfigured(mysqli $db): bool + { + $policy = new \App\Services\CirculationPolicy($db); + if (\App\Services\FineParameters::toCents((string) ($policy->globalLayer()['daily_fine'] ?? '')) > 0) { + return true; + } + foreach ($policy->types() as $type) { + if ((float) ($type['multa_giornaliera'] ?? 0) > 0) { + return true; + } + } + foreach ($policy->rules() as $rule) { + if ((float) ($rule['multa_giornaliera'] ?? 0) > 0) { + return true; + } + } + return false; + } + + /** + * Stocktake outcomes (found / misplaced / on loan / missing) of the last + * closed session. A filter on statistics.section.stocktake still wins; + * NULL (no stocktake table, no closed session) hides the section. + * + * @return array{rows: list, note?: string}|null + */ + public static function stocktakeOutcomes(mysqli $db): ?array + { + if (!Features::enabled('stocktake')) { + return null; + } + $hooked = self::tableSection(Hooks::apply('statistics.section.stocktake', null, [$db])); + if ($hooked !== null) { + return $hooked; + } + // Built-in provider: the outcomes of the most recently closed + // stocktake session. No stocktake table or no closed session hides + // the section. + $exists = $db->query("SELECT 1 FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'inventario_sessioni' LIMIT 1"); + if (!$exists instanceof \mysqli_result || $exists->num_rows === 0) { + return null; + } + $res = $db->query("SELECT id, nome, chiusa_il, (SELECT COUNT(*) FROM inventario_sessioni WHERE stato = 'chiusa') AS chiuse + FROM inventario_sessioni WHERE stato = 'chiusa' ORDER BY chiusa_il DESC, id DESC LIMIT 1"); + $last = $res instanceof \mysqli_result ? $res->fetch_assoc() : null; + if (!is_array($last)) { + return null; + } + $summary = (new \App\Services\StocktakeService($db))->summary((int) $last['id']); + return [ + 'rows' => [ + ['label' => __('Copie attese'), 'value' => $summary['attese']], + ['label' => __('Trovata'), 'value' => $summary['trovate']], + ['label' => __('Fuori posto'), 'value' => $summary['fuori_posto']], + ['label' => __('In prestito'), 'value' => $summary['in_prestito']], + ['label' => __('Mancanti'), 'value' => $summary['mancanti']], + ['label' => __('Codici sconosciuti'), 'value' => $summary['sconosciute']], + ], + 'note' => sprintf( + __('Ultima revisione chiusa: «%s», %s. Revisioni chiuse in totale: %d.'), + (string) $last['nome'], + format_date((string) $last['chiusa_il'], true, '/'), + (int) $last['chiuse'] + ), + ]; + } + + /** + * Validate a provider result against the generic table shape; anything + * else (NULL included) hides the section. + * + * @return array{rows: list, note?: string}|null + */ + public static function tableSection(mixed $data): ?array + { + if (!is_array($data) || !isset($data['rows']) || !is_array($data['rows'])) { + return null; + } + $rows = []; + foreach ($data['rows'] as $row) { + if (!is_array($row) || !isset($row['label'], $row['value']) || !is_scalar($row['label'])) { + continue; + } + $value = $row['value']; + if (!is_int($value) && !is_float($value) && !is_string($value)) { + continue; + } + $rows[] = ['label' => (string) $row['label'], 'value' => $value]; + } + $out = ['rows' => $rows]; + if (isset($data['note']) && is_string($data['note']) && $data['note'] !== '') { + $out['note'] = $data['note']; + } + return $out; + } + + /** + * The extension sections in display order, keyed by a stable id; only the + * ones whose provider returned data. + * + * @return array, note?: string}}> + */ + public static function extensions(mysqli $db): array + { + $providers = [ + 'patron-types' => [__('Prestiti per tipologia di lettore'), 'fa-id-badge', [self::class, 'loansByPatronType']], + 'fines' => [__('Multe'), 'fa-coins', [self::class, 'fines']], + 'stocktake' => [__('Esiti delle revisioni inventariali'), 'fa-clipboard-check', [self::class, 'stocktakeOutcomes']], + ]; + $out = []; + foreach ($providers as $id => [$title, $icon, $callable]) { + try { + $data = $callable($db); + } catch (\Throwable $e) { + SecureLogger::warning('Statistics extension section failed', ['section' => $id, 'error' => $e->getMessage()]); + $data = null; + } + if (is_array($data)) { + $out[$id] = ['title' => $title, 'icon' => $icon, 'data' => $data]; + } + } + return $out; + } +} diff --git a/app/Support/StocktakePdfGenerator.php b/app/Support/StocktakePdfGenerator.php new file mode 100644 index 000000000..1cf25a22e --- /dev/null +++ b/app/Support/StocktakePdfGenerator.php @@ -0,0 +1,186 @@ + $session + * @param array $summary + * @param list> $rows + * @param list $filterLabels + * @param array $labels + * @throws \LengthException with more than MAX_ROWS rows: the caller refuses those before + */ + public function generate(array $session, array $summary, array $rows, array $filterLabels, array $labels): string + { + if (count($rows) > self::MAX_ROWS) { + throw new \LengthException(sprintf('A stocktake PDF holds at most %d rows, %d given', self::MAX_ROWS, count($rows))); + } + $appName = (string) ConfigStore::get('app.name', 'Biblioteca'); + $title = __('Revisione inventariale'); + + $pdf = $this->createDocument(); + $pdf->SetCreator($appName); + $pdf->SetAuthor($appName); + $pdf->SetTitle($title . ' - ' . (string) $session['nome']); + $pdf->SetSubject((string) $session['nome']); + $pdf->setPrintHeader(false); + $pdf->setPrintFooter(false); + $pdf->SetMargins(12, 12, 12); + $pdf->SetAutoPageBreak(true, 12); + $pdf->AddPage(); + + $pdf->SetFont(self::FONT, 'B', 16); + $pdf->Cell(0, 9, $appName, 0, 1, 'C'); + $pdf->SetFont(self::FONT, '', 12); + $pdf->Cell(0, 7, $title . ': ' . (string) $session['nome'], 0, 1, 'C'); + + $pdf->SetFont(self::FONT, '', 9); + $pdf->SetTextColor(90, 90, 90); + $pdf->MultiCell(0, 5, implode(' · ', $this->metaLines($session)), 0, 'C'); + $pdf->MultiCell(0, 5, $filterLabels !== [] ? implode(' · ', $filterLabels) : __('Tutto il catalogo'), 0, 'C'); + $pdf->SetTextColor(0, 0, 0); + $pdf->Ln(3); + + $pdf->SetFont(self::FONT, 'B', 10); + $pdf->Cell(0, 7, __('Riepilogo'), 0, 1, 'L'); + $pdf->SetFont(self::FONT, '', 9); + $pairs = [ + [__('Copie attese'), $summary['attese'] ?? 0], + [$labels['trovata'] ?? 'trovata', $summary['trovate'] ?? 0], + [$labels['fuori_posto'] ?? 'fuori_posto', $summary['fuori_posto'] ?? 0], + [$labels['in_prestito'] ?? 'in_prestito', $summary['in_prestito'] ?? 0], + [$labels['mancante'] ?? 'mancante', $summary['mancanti'] ?? 0], + [$labels['sconosciuta'] ?? 'sconosciuta', $summary['sconosciute'] ?? 0], + [$labels['fuori_ambito'] ?? 'fuori_ambito', $summary['fuori_ambito'] ?? 0], + [$labels['duplicata'] ?? 'duplicata', $summary['duplicate'] ?? 0], + ]; + if (($session['stato'] ?? '') === 'chiusa') { + $pairs[] = [__('Segnate come perse'), $summary['perse'] ?? 0]; + } + $pdf->SetFillColor(245, 245, 245); + foreach ($pairs as $i => [$label, $value]) { + $pdf->Cell(60, 6, (string) $label, 1, 0, 'L', $i % 2 === 0); + $pdf->Cell(25, 6, (string) $value, 1, 1, 'R', $i % 2 === 0); + } + $pdf->Ln(4); + + $cols = [ + ['label' => __('Codice copia'), 'key' => 'codice', 'w' => 32], + ['label' => __('Titolo'), 'key' => 'titolo', 'w' => 70], + ['label' => __('Collocazione'), 'key' => 'collocazione', 'w' => 26], + ['label' => __('Scaffale atteso'), 'key' => 'scaffale_atteso', 'w' => 30], + ['label' => __('Stato copia'), 'key' => 'stato_copia', 'w' => 28], + ]; + foreach (self::SECTIONS as $section) { + $sectionRows = array_values(array_filter($rows, static fn (array $row): bool => $row['categoria'] === $section)); + if ($sectionRows === []) { + continue; + } + $pdf->SetFont(self::FONT, 'B', 10); + $pdf->Cell(0, 7, ($labels[$section] ?? $section) . ' (' . count($sectionRows) . ')', 0, 1, 'L'); + $pdf->SetFont(self::FONT, 'B', 8); + $pdf->SetFillColor(235, 235, 235); + foreach ($cols as $c) { + $pdf->Cell($c['w'], 6, $c['label'], 1, 0, 'L', true); + } + $pdf->Ln(); + $pdf->SetFont(self::FONT, '', 8); + $pdf->SetFillColor(249, 249, 249); + $fill = false; + foreach ($sectionRows as $row) { + // The copy code is what staff look for on the shelf: it wraps + // instead of being cut, so two long codes never print alike. + $code = (string) ($row['codice'] ?? ''); + $height = max(1, $pdf->getNumLines($code, $cols[0]['w'])) * 4.5 + 1; + if ($pdf->GetY() + $height > $pdf->getPageHeight() - $pdf->getBreakMargin()) { + $pdf->AddPage(); + } + foreach ($cols as $c) { + $text = $c['key'] === 'codice' ? $code : $this->fit((string) ($row[$c['key']] ?? ''), $c['w']); + $pdf->MultiCell($c['w'], $height, $text, 1, 'L', $fill, 0, null, null, true, 0, false, true, $height, 'M'); + } + $pdf->Ln($height); + $fill = !$fill; + } + $pdf->Ln(3); + } + + $pdf->Ln(2); + $pdf->SetFont(self::FONT, 'I', 8); + $pdf->SetTextColor(128, 128, 128); + $tz = (string) ConfigStore::get('app.timezone', 'Europe/Rome'); + try { + $timezone = new \DateTimeZone($tz); + } catch (\Throwable) { + $timezone = new \DateTimeZone('Europe/Rome'); + } + $now = new \DateTime('now', $timezone); + $pdf->Cell(0, 5, sprintf(__('Documento generato il %s alle %s'), format_date($now->format('Y-m-d'), false, '/'), $now->format('H:i')), 0, 1, 'C'); + + return $pdf->Output('', 'S'); + } + + /** + * The TCPDF document the report is drawn on. Its text ends up as glyph + * ids of a subset font, so a test that wants to read what was written + * swaps in a document that records the cells it is given. + */ + protected function createDocument(): TCPDF + { + return new TCPDF('P', 'mm', 'A4', true, 'UTF-8', false); + } + + /** + * The header facts: state, when it opened, when it ended, branch. + * chiusa_il is also the moment a cancelled session was cancelled, so its + * label follows the state. + * + * @param array $session + * @return list + */ + public function metaLines(array $session): array + { + $state = (string) ($session['stato'] ?? ''); + $states = ['aperta' => __('Aperta'), 'chiusa' => __('Chiusa'), 'annullata' => __('Annullata')]; + $meta = [ + __('Stato') . ': ' . ($states[$state] ?? $state), + __('Aperta il') . ': ' . format_date((string) $session['aperta_il'], true, '/'), + ]; + if (!empty($session['chiusa_il'])) { + $meta[] = ($state === 'annullata' ? __('Annullata il') : __('Chiusa il')) . ': ' . format_date((string) $session['chiusa_il'], true, '/'); + } + if (!empty($session['sede_nome'])) { + $meta[] = __('Sede') . ': ' . (string) $session['sede_nome']; + } + return $meta; + } + + private function fit(string $value, float $widthMm): string + { + $max = (int) max(4, floor($widthMm / 1.9)); + return mb_strlen($value) <= $max ? $value : mb_substr($value, 0, $max - 1) . '…'; + } +} diff --git a/app/Support/Updater.php b/app/Support/Updater.php index 0ac058a17..49aabde93 100644 --- a/app/Support/Updater.php +++ b/app/Support/Updater.php @@ -111,6 +111,7 @@ class Updater 'storage/cache', 'storage/logs', 'storage/calendar', + 'storage/patron_photos', 'storage/tmp', 'storage/sessions', 'public/uploads', @@ -4012,6 +4013,22 @@ public function runMigrations(string $fromVersion, string $toVersion): array $this->debugLog('WARNING', 'Pickup deadline backfill failed; it will retry on the next upgrade run'); } + // 0.9.0 (plan §7): the migration converts at most 100000 legacy + // prestiti.sanzione amounts, and the debt block reads only the + // ledger. Finish the conversion here, after the triggers, so a + // patron over the threshold is blocked from the first request + // instead of from the next maintenance run (which stays the + // retry). Idempotent (penalty: keys) and non-fatal. + if (version_compare($toVersion, '0.9.0-rc.1', '>=')) { + try { + if (!\App\Services\PatronLedger::completeLegacyPenaltyBackfill($this->db)) { + $this->debugLog('WARNING', 'Legacy penalty backfill deferred; maintenance will retry'); + } + } catch (\Throwable $e) { + $this->debugLog('WARNING', 'Legacy penalty backfill failed; maintenance will retry', ['error' => $e->getMessage()]); + } + } + // 0.7.72: the edge-cache privacy block shipped in 0.7.70/0.7.71 // WITHOUT `CacheLookup on`, so LSWS ignored the app's cache headers // and no page was ever served from edge cache — the admin toggle @@ -4091,6 +4108,12 @@ public function runMigrations(string $fromVersion, string $toVersion): array */ private function recalculateAfterMigrations(): void { + // Schema probes cached earlier in this request (before the migration + // created the circulation tables) must not survive into the recalc. + // This runs from the Updater of the release that ships the classes, + // so they are always there. + \App\Services\CirculationPolicy::resetSchemaCache(); + \App\Services\PatronLedger::resetSchemaCache(); try { $integrity = new \App\Support\DataIntegrity($this->db); $integrity->recalculateAllBookAvailability(); diff --git a/app/Support/UserImport.php b/app/Support/UserImport.php new file mode 100644 index 000000000..4fa4db97c --- /dev/null +++ b/app/Support/UserImport.php @@ -0,0 +1,513 @@ + [max length or 0, required]. */ + private const FIELDS = [ + 'nome' => [100, true], + 'cognome' => [100, false], + 'email' => [255, true], + 'telefono' => [20, false], + 'indirizzo' => [500, false], + 'data_nascita' => [0, false], + 'sesso' => [0, false], + 'cod_fiscale' => [16, false], + 'codice_tessera' => [20, false], + 'data_scadenza_tessera' => [0, false], + 'note_utente' => [2000, false], + 'locale' => [0, false], + 'tipologia' => [40, false], + ]; + + /** Header spellings recognised for each field (normalised with normalizeHeader()). */ + private const ALIASES = [ + 'nome' => ['nome', 'name', 'firstname', 'first', 'givenname', 'vorname', 'prenom', 'fornavn'], + 'cognome' => ['cognome', 'surname', 'lastname', 'last', 'familyname', 'nachname', 'nom', 'nomdefamille', 'efternavn'], + 'email' => ['email', 'mail', 'emailaddress', 'indirizzoemail', 'posta', 'courriel', 'adressemail'], + 'telefono' => ['telefono', 'phone', 'tel', 'telephone', 'telefon', 'cellulare', 'mobile', 'telefonnummer'], + 'indirizzo' => ['indirizzo', 'address', 'adresse', 'indirizzocompleto'], + 'data_nascita' => ['datanascita', 'datadinascita', 'birthdate', 'dateofbirth', 'birthday', 'geburtsdatum', 'datedenaissance', 'fodselsdato'], + 'sesso' => ['sesso', 'gender', 'sex', 'geschlecht', 'sexe', 'kon'], + 'cod_fiscale' => ['codfiscale', 'codicefiscale', 'cf', 'taxcode', 'fiscalcode', 'steuernummer', 'codefiscal'], + 'codice_tessera' => ['codicetessera', 'tessera', 'card', 'cardnumber', 'cardcode', 'librarycard', 'barcode', 'ausweisnummer', 'ausweis', 'numerodecarte', 'ndecarte', 'kortnummer', 'lanerkort'], + 'data_scadenza_tessera' => ['datascadenzatessera', 'scadenzatessera', 'scadenza', 'expiry', 'expirydate', 'cardexpiry', 'expires', 'ablaufdatum', 'gultigbis', 'datedexpiration', 'udlobsdato'], + 'note_utente' => ['noteutente', 'note', 'notes', 'notizen', 'noter', 'noteinterne'], + 'locale' => ['locale', 'lingua', 'language', 'sprache', 'langue', 'sprog'], + 'tipologia' => ['tipologia', 'tipologialettore', 'patrontype', 'membertype', 'readertype', 'lesertyp', 'leserkategorie', 'typedelecteur', 'categoriedelecteur', 'lanertype', 'lanerkategori'], + ]; + + /** + * Header spellings that name the patron type CODE column of the users CSV + * export ("Codice tipologia lettore" in the five languages). They map to + * `tipologia` like the name column, but guessMapping() prefers them: an + * export carries both columns and must re-import by code, which is exact + * even when two types share a name. + */ + private const PREFERRED_ALIASES = [ + 'tipologia' => ['codicetipologia', 'codicetipologialettore', 'tipologiacodice', 'tipologialettorecodice', 'patrontypecode', 'typecode', 'membertypecode', 'readertypecode', 'lesertypcode', 'codederleserkategorie', 'leserkategoriecode', 'codetypedelecteur', 'codedelacategoriedelecteur', 'lanertypekode', 'lanerkategorikode', 'kodeforlanerkategori'], + ]; + + /** + * Target fields shown in the mapping step, in order, as key => label. + * Custom registration fields follow as `custom:`; `tipologia` is offered + * only when the patron types exist (PatronTypeImport::available()). + * + * @param list $customDefinitions + * @return array + */ + public static function targetFields(array $customDefinitions, bool $withPatronType): array + { + $fields = [ + 'nome' => __('Nome'), + 'cognome' => __('Cognome'), + 'email' => __('Email'), + 'telefono' => __('Telefono'), + 'indirizzo' => __('Indirizzo'), + 'data_nascita' => __('Data di nascita'), + 'sesso' => __('Sesso'), + 'cod_fiscale' => __('Codice Fiscale'), + 'codice_tessera' => __('Codice tessera'), + 'data_scadenza_tessera' => __('Scadenza tessera'), + 'note_utente' => __('Note interne'), + 'locale' => __('Lingua dell\'interfaccia'), + ]; + if ($withPatronType) { + $fields['tipologia'] = __('Tipologia lettore (codice o nome)'); + } + foreach ($customDefinitions as $def) { + $fields['custom:' . $def['id']] = $def['etichetta']; + } + return $fields; + } + + /** + * Accented letters of the five languages' headers and gender words, to + * their ASCII base. iconv's ASCII//TRANSLIT depends on LC_CTYPE (under + * the "C" locale glibc gives "?", musl gives nothing) and would turn + * "Prénom" into "prnom"; this table is applied first, iconv only mops up. + */ + private const TRANSLIT = [ + 'à' => 'a', 'á' => 'a', 'â' => 'a', 'ä' => 'a', 'ã' => 'a', 'å' => 'a', 'æ' => 'ae', + 'ç' => 'c', + 'è' => 'e', 'é' => 'e', 'ê' => 'e', 'ë' => 'e', + 'ì' => 'i', 'í' => 'i', 'î' => 'i', 'ï' => 'i', + 'ñ' => 'n', + 'ò' => 'o', 'ó' => 'o', 'ô' => 'o', 'ö' => 'o', 'õ' => 'o', 'ø' => 'o', 'œ' => 'oe', + 'ù' => 'u', 'ú' => 'u', 'û' => 'u', 'ü' => 'u', + 'ý' => 'y', 'ÿ' => 'y', + 'ß' => 'ss', + // Abbreviated card headers ("Nº de carte") must not depend on whether + // iconv transliterates the ordinal indicator as "o" or drops it. + 'º' => '', + 'À' => 'a', 'Á' => 'a', 'Â' => 'a', 'Ä' => 'a', 'Ã' => 'a', 'Å' => 'a', 'Æ' => 'ae', + 'Ç' => 'c', + 'È' => 'e', 'É' => 'e', 'Ê' => 'e', 'Ë' => 'e', + 'Ì' => 'i', 'Í' => 'i', 'Î' => 'i', 'Ï' => 'i', + 'Ñ' => 'n', + 'Ò' => 'o', 'Ó' => 'o', 'Ô' => 'o', 'Ö' => 'o', 'Õ' => 'o', 'Ø' => 'o', 'Œ' => 'oe', + 'Ù' => 'u', 'Ú' => 'u', 'Û' => 'u', 'Ü' => 'u', + 'Ý' => 'y', 'Ÿ' => 'y', + ]; + + /** Lowercase, accents removed, only letters and digits. */ + public static function normalizeHeader(string $header): string + { + $header = preg_replace('/^\xEF\xBB\xBF/', '', $header) ?? $header; + $header = strtr(mb_strtolower(trim($header), 'UTF-8'), self::TRANSLIT); + if (!preg_match('/^[\x00-\x7F]*$/', $header)) { + // Letters outside the table (a Polish ł, a Czech č): iconv's + // locale-dependent best effort, or nothing — never a wrong match. + $ascii = function_exists('iconv') ? @iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $header) : false; + if (is_string($ascii) && $ascii !== '') { + $header = strtolower($ascii); + } + } + return (string) preg_replace('/[^a-z0-9]/', '', $header); + } + + /** + * Suggested mapping: column index => field key ('' = ignore). Each field is + * proposed for one column at most: the first that matches, except that a + * PREFERRED_ALIASES spelling wins over any other wherever it stands (the + * users export has the patron type name and then its code; the code is + * the column that re-imports exactly). + * + * @param list $headers + * @param array $targetFields from targetFields() + * @return array + */ + public static function guessMapping(array $headers, array $targetFields): array + { + $byAlias = []; + foreach (self::ALIASES as $field => $aliases) { + if (!isset($targetFields[$field])) { + continue; + } + foreach ($aliases as $alias) { + $byAlias[$alias] ??= $field; + } + // The label in the current language too (an export from this + // installation carries localized headers). + $byAlias[self::normalizeHeader($targetFields[$field])] ??= $field; + } + $preferred = []; + foreach (self::PREFERRED_ALIASES as $field => $aliases) { + if (!isset($targetFields[$field])) { + continue; + } + foreach ($aliases as $alias) { + $byAlias[$alias] ??= $field; + if ($byAlias[$alias] === $field) { + $preferred[$alias] = true; + } + } + } + foreach ($targetFields as $key => $label) { + if (str_starts_with($key, 'custom:')) { + $byAlias[self::normalizeHeader($label)] ??= $key; + } + } + $mapping = array_fill_keys(array_keys($headers), ''); + $taken = []; + // Two passes: the preferred spellings take their field first, then + // the other headers take what is left, in file order. + foreach ([true, false] as $preferredPass) { + foreach ($headers as $i => $header) { + $norm = self::normalizeHeader((string) $header); + if (isset($preferred[$norm]) !== $preferredPass) { + continue; + } + $field = $byAlias[$norm] ?? ''; + if ($field !== '' && !isset($taken[$field])) { + $mapping[$i] = $field; + $taken[$field] = true; + } + } + } + return $mapping; + } + + /** + * Validate a submitted mapping. Returns the clean mapping, or an error + * message when a field is mapped twice or nome/email are missing. + * + * @param mixed $mapping column index => field key, as sent by the browser + * @param array $targetFields + * @return array{mapping: array, error: ?string} + */ + public static function validateMapping(mixed $mapping, array $targetFields, int $columnCount): array + { + if (!is_array($mapping)) { + return ['mapping' => [], 'error' => __('Mappatura delle colonne non valida.')]; + } + $clean = []; + $used = []; + foreach ($mapping as $index => $field) { + // PHP turns a canonical integer key ("1") into an int already, so + // a string key here is a spelling like "01" or "1x": cast to int + // it would alias another column and drop one of the two fields. + if (!is_int($index)) { + return ['mapping' => [], 'error' => __('Mappatura delle colonne non valida.')]; + } + $field = is_string($field) ? $field : ''; + if ($index < 0 || $index >= $columnCount || $field === '') { + continue; + } + if (!isset($targetFields[$field])) { + return ['mapping' => [], 'error' => __('Mappatura delle colonne non valida.')]; + } + if (isset($used[$field])) { + return ['mapping' => [], 'error' => sprintf(__('Il campo "%s" è associato a più di una colonna.'), $targetFields[$field])]; + } + $used[$field] = true; + $clean[$index] = $field; + } + foreach (['nome', 'email'] as $required) { + if (!isset($used[$required])) { + return ['mapping' => [], 'error' => sprintf(__('Associa una colonna al campo obbligatorio "%s".'), $targetFields[$required])]; + } + } + ksort($clean); + return ['mapping' => $clean, 'error' => null]; + } + + /** + * Turn one CSV row into an account record. + * + * @param list $row raw cells + * @param array $mapping column index => field key + * @param list $customDefinitions + * @param list $locales valid interface locale codes + * @param array{sesso:bool, cod_fiscale:bool} $visible whether the optional profile fields are collected + * @return array{record: array, custom: array, tipologia: ?string, error: ?string} + */ + public static function normalizeRow(array $row, array $mapping, array $customDefinitions, array $locales, array $visible = ['sesso' => true, 'cod_fiscale' => true]): array + { + $fail = static fn (string $message): array => ['record' => [], 'custom' => [], 'tipologia' => null, 'error' => $message]; + $raw = []; + $customRaw = []; + foreach ($mapping as $index => $field) { + $value = self::cell($row[$index] ?? null); + if (str_starts_with($field, 'custom:')) { + $customRaw[(int) substr($field, 7)] = $value; + } else { + $raw[$field] = $value; + } + } + + $record = []; + foreach (self::FIELDS as $field => [$max]) { + $value = $raw[$field] ?? ''; + if ($max > 0 && mb_strlen($value, 'UTF-8') > $max) { + return $fail(sprintf(__('Il valore di "%s" è troppo lungo (massimo %d caratteri).'), $field, $max)); + } + $record[$field] = $value === '' ? null : $value; + } + + if ($record['nome'] === null) { + return $fail(__('Nome mancante.')); + } + if ($record['email'] === null) { + return $fail(__('Email mancante.')); + } + $email = mb_strtolower((string) $record['email'], 'UTF-8'); + if (filter_var($email, FILTER_VALIDATE_EMAIL) === false) { + return $fail(sprintf(__('Email non valida: %s'), (string) $record['email'])); + } + $record['email'] = $email; + $record['cognome'] ??= ''; + + foreach (['data_nascita', 'data_scadenza_tessera'] as $dateField) { + if ($record[$dateField] !== null) { + $date = self::parseDate($record[$dateField]); + if ($date === null) { + return $fail(sprintf(__('Data non valida: %s'), $record[$dateField])); + } + $record[$dateField] = $date; + } + } + + $record['sesso'] = $visible['sesso'] ? self::normalizeGender($record['sesso']) : null; + $record['cod_fiscale'] = $visible['cod_fiscale'] && $record['cod_fiscale'] !== null ? strtoupper($record['cod_fiscale']) : null; + + if ($record['locale'] !== null) { + $code = str_replace('-', '_', $record['locale']); + $match = null; + foreach ($locales as $locale) { + if (strcasecmp($locale, $code) === 0 || strcasecmp(substr($locale, 0, 2), $code) === 0) { + $match = $locale; + break; + } + } + $record['locale'] = $match; // an unknown language falls back to the installation one + } + + $tipologia = $record['tipologia']; + unset($record['tipologia']); + + $custom = []; + if ($customDefinitions !== [] && $customRaw !== []) { + $mapped = array_values(array_filter($customDefinitions, static fn (array $d): bool => array_key_exists($d['id'], $customRaw))); + $result = RegistrationFields::validate($mapped, ['custom_field' => $customRaw], false); + if ($result['error'] !== null) { + return $fail(sprintf(__('Valore non valido per il campo "%s".'), $result['error'])); + } + $custom = array_filter($result['values'], static fn (string $v): bool => $v !== ''); + } + + return ['record' => $record, 'custom' => $custom, 'tipologia' => $tipologia, 'error' => null]; + } + + /** Y-m-d from the spellings a spreadsheet produces, or null. */ + public static function parseDate(string $value): ?string + { + $value = trim($value); + $formats = ['Y-m-d', 'd/m/Y', 'd.m.Y', 'd-m-Y', 'Y/m/d', 'd/m/y', 'd.m.y']; + foreach ($formats as $format) { + $date = \DateTimeImmutable::createFromFormat('!' . $format, $value); + if ($date !== false && $date->format($format) === $value) { + $year = (int) $date->format('Y'); + if ($year >= 1900 && $year <= 2200) { + return $date->format('Y-m-d'); + } + } + } + return null; + } + + public static function normalizeGender(?string $value): ?string + { + if ($value === null) { + return null; + } + $v = self::normalizeHeader($value); + return match (true) { + in_array($v, ['m', 'maschio', 'male', 'man', 'uomo', 'mannlich', 'homme', 'masculin', 'mand'], true) => 'M', + in_array($v, ['f', 'femmina', 'female', 'woman', 'donna', 'w', 'weiblich', 'femme', 'feminin', 'kvinde', 'k'], true) => 'F', + in_array($v, ['altro', 'other', 'x', 'divers', 'autre', 'andet', 'nonbinary', 'nb'], true) => 'Altro', + default => null, + }; + } + + /** + * The uploaded bytes as UTF-8. A BOM is dropped. A file that is not valid + * UTF-8 — a spreadsheet saved as "CSV" on Windows writes the system code + * page — is read as Windows-1252, the superset of ISO-8859-1 such exports + * fall under; "Caffè" then keeps its accent instead of breaking the JSON + * preview. Whatever comes back is valid UTF-8. + * + * @return array{text: string, converted: bool} + */ + public static function toUtf8(string $bytes): array + { + if (str_starts_with($bytes, "\xEF\xBB\xBF")) { + $bytes = substr($bytes, 3); + } + if (mb_check_encoding($bytes, 'UTF-8')) { + return ['text' => $bytes, 'converted' => false]; + } + $text = mb_convert_encoding($bytes, 'UTF-8', 'Windows-1252'); + if (!mb_check_encoding($text, 'UTF-8')) { + $text = mb_scrub($bytes, 'UTF-8'); + } + return ['text' => $text, 'converted' => true]; + } + + /** + * The first $max logical CSV records of $text, for detectDelimiter(): a + * quoted cell may hold line breaks ("Via Roma 1\nint. 3"), so physical + * lines are joined until the quotes balance — enclosure '"', a doubled + * '""' inside a cell keeps the count even. At most MAX_SAMPLE_LINES + * physical lines are read, so an unbalanced quote cannot scan the file. + * + * @return list + */ + public static function sampleRecords(string $text, int $max): array + { + $records = []; + $buffer = null; + $offset = 0; + $length = strlen($text); + for ($read = 0; $read < self::MAX_SAMPLE_LINES && $offset < $length && count($records) < $max; $read++) { + $end = strpos($text, "\n", $offset); + $line = $end === false ? substr($text, $offset) : substr($text, $offset, $end - $offset); + $offset = $end === false ? $length : $end + 1; + $buffer = $buffer === null ? $line : $buffer . "\n" . $line; + if (substr_count($buffer, '"') % 2 === 0) { + $records[] = $buffer; + $buffer = null; + } + } + if ($buffer !== null && count($records) < $max) { + $records[] = $buffer; + } + return $records; + } + + /** + * Delimiter of a CSV sample (the header and the first few records): the one + * of ";", "," and TAB that splits the lines into columns most consistently, + * or null when none splits the header at all. + * + * Each candidate is judged by the structure it yields, not by how often it + * occurs: a ";" file whose addresses hold commas ("Via Roma 1, int. 3, + * 00100 Roma") has more commas than semicolons, yet only ";" gives every + * line the header's column count. Quoted fields are respected as the + * reader respects them (enclosure '"', no escape character), so a comma + * inside "…" never counts. The candidate whose column count is shared by + * the largest share of the data lines wins; the wider one on a tie. + * + * @param list $lines + */ + public static function detectDelimiter(array $lines): ?string + { + $sample = []; + foreach ($lines as $line) { + if (is_string($line) && trim($line) !== '') { + $sample[] = rtrim($line, "\r\n"); + } + } + if ($sample === []) { + return null; + } + $header = array_shift($sample); + $best = null; + $bestScore = [0.0, 1]; + foreach ([';', ',', "\t"] as $delimiter) { + $columns = count(str_getcsv($header, $delimiter, '"', '')); + if ($columns < 2) { + continue; + } + $matching = 0; + foreach ($sample as $line) { + if (count(str_getcsv($line, $delimiter, '"', '')) === $columns) { + $matching++; + } + } + $score = [$sample === [] ? 1.0 : $matching / count($sample), $columns]; + if ($score > $bestScore) { + $best = $delimiter; + $bestScore = $score; + } + } + return $best; + } + + /** A CSV cell as trimmed text, with the export's formula-escape apostrophe undone. */ + private static function cell(mixed $value): string + { + $value = trim(is_scalar($value) ? (string) $value : ''); + if (strlen($value) > 1 && $value[0] === "'" && in_array($value[1], ['=', '+', '-', '@'], true)) { + $value = substr($value, 1); + } + return strip_tags($value); + } + + /** + * Delete user_import_*.csv files in $dir not modified for $maxAge seconds. + * + * @return int number of files removed + */ + public static function sweepStaleUploads(string $dir, int $maxAge = self::STALE_UPLOAD_SECONDS, ?int $now = null): int + { + $now ??= time(); + $removed = 0; + foreach (glob(rtrim($dir, '/') . '/user_import_*.csv') ?: [] as $file) { + $mtime = @filemtime($file); + if ($mtime !== false && $now - $mtime > $maxAge && @unlink($file)) { + $removed++; + } + } + return $removed; + } +} diff --git a/app/Support/mail_templates/da_DK.php b/app/Support/mail_templates/da_DK.php index e0bb8f7d5..1ca3b5d87 100644 --- a/app/Support/mail_templates/da_DK.php +++ b/app/Support/mail_templates/da_DK.php @@ -202,6 +202,17 @@

Har du ikke længere brug for den, kan du annullere lånet på siden »Reservationer« på din konto: så går bogen straks videre til en anden.

God læselyst!

', + ], + 'card_expiring' => [ + 'subject' => '🪪 Dit lånerkort udløber den {{data_scadenza_tessera}}', + 'body' => '

Dit lånerkort udløber snart

+

Hej {{utente_nome}},

+

dit lånerkort (nr. {{codice_tessera}}) udløber den {{data_scadenza_tessera}} (dage tilbage: {{giorni_rimasti}}).

+
+

Når det er udløbet, kan du ikke låne eller reservere bøger, før kortet er fornyet.

+
+

Henvend dig ved bibliotekets skranke i åbningstiden for at forny det.

+

På gensyn!

', ], 'loan_pickup_cancelled' => [ 'subject' => '❌ Afhentning annulleret', @@ -303,6 +314,22 @@

Administrer Anmodning

', ], + 'fine_charged' => [ + 'subject' => '💶 Gebyr for for sen aflevering: {{multa}}', + 'body' => '

Gebyr for for sen aflevering

+

Hej {{utente_nome}},

+

bogen herunder blev afleveret efter afleveringsfristen, og der er pålagt et gebyr på din konto.

+
+

Bog: {{libro_titolo}}

+

Afleveringsfrist: {{data_scadenza}}

+

Afleveret den: {{data_restituzione}}

+

Dage for sent: {{giorni_ritardo}}

+

Gebyr: {{multa}}

+

Saldo til betaling: {{saldo}}

+
+

Du kan betale beløbet ved bibliotekets skranke.

+

Venlig hilsen
Biblioteket

', + ], 'loan_returned' => [ 'subject' => '✅ Aflevering bekræftet', 'body' => '

Aflevering bekræftet

@@ -312,6 +339,7 @@

Bog: {{libro_titolo}}

Afleveringsdato: {{data_restituzione}}

+

{{multa}}

Vi håber, du nød læsningen. Vi ses snart på biblioteket!

Med venlig hilsen,
Bibliotekets team

', ], diff --git a/app/Support/mail_templates/de_DE.php b/app/Support/mail_templates/de_DE.php index 90a58c3a6..abce15a43 100644 --- a/app/Support/mail_templates/de_DE.php +++ b/app/Support/mail_templates/de_DE.php @@ -193,6 +193,17 @@

Wenn du es nicht mehr brauchst, kannst du die Ausleihe auf der Seite „Reservierungen“ deines Kontos stornieren: Das Buch geht dann sofort an jemand anderen.

Viel Freude beim Lesen!

', + ], + 'card_expiring' => [ + 'subject' => '🪪 Dein Bibliotheksausweis läuft am {{data_scadenza_tessera}} ab', + 'body' => '

Dein Bibliotheksausweis läuft bald ab

+

Hallo {{utente_nome}},

+

dein Bibliotheksausweis (Nr. {{codice_tessera}}) läuft am {{data_scadenza_tessera}} ab (verbleibende Tage: {{giorni_rimasti}}).

+
+

Nach dem Ablauf kannst du keine Bücher mehr ausleihen oder vormerken, bis der Ausweis verlängert wird.

+
+

Zum Verlängern wende dich während der Öffnungszeiten an die Theke der Bibliothek.

+

Bis bald!

', ], 'loan_pickup_cancelled' => [ 'subject' => '❌ Abholung storniert', @@ -291,6 +302,22 @@
  • Datum der Anfrage: {{data_richiesta}}
  • Anfrage verwalten

    ', + ], + 'fine_charged' => [ + 'subject' => '💶 Gebühr für verspätete Rückgabe: {{multa}}', + 'body' => '

    Gebühr für verspätete Rückgabe

    +

    Hallo {{utente_nome}},

    +

    das folgende Buch wurde nach Ablauf der Leihfrist zurückgegeben, dafür wurde dir eine Gebühr berechnet.

    +
    +

    Buch: {{libro_titolo}}

    +

    Fällig am: {{data_scadenza}}

    +

    Zurückgegeben am: {{data_restituzione}}

    +

    Tage Verspätung: {{giorni_ritardo}}

    +

    Gebühr: {{multa}}

    +

    Offener Betrag: {{saldo}}

    +
    +

    Du kannst den Betrag an der Ausleihtheke begleichen.

    +

    Viele Grüße
    Dein Bibliotheksteam

    ', ], 'loan_returned' => [ 'subject' => '✅ Rückgabe bestätigt', @@ -301,6 +328,7 @@

    Buch: {{libro_titolo}}

    Rückgabedatum: {{data_restituzione}}

    +

    {{multa}}

    Wir hoffen, die Lektüre hat dir gefallen. Bis bald in der Bibliothek!

    Mit freundlichen Grüßen,
    Dein Bibliotheksteam

    ', ], diff --git a/app/Support/mail_templates/en_US.php b/app/Support/mail_templates/en_US.php index 43d1f63b1..af43852c9 100644 --- a/app/Support/mail_templates/en_US.php +++ b/app/Support/mail_templates/en_US.php @@ -193,6 +193,17 @@

    If you no longer need it, you can cancel the loan from the “Reservations” page of your account: the book will go to someone else right away.

    Happy reading!

    ', + ], + 'card_expiring' => [ + 'subject' => '🪪 Your library card expires on {{data_scadenza_tessera}}', + 'body' => '

    Your library card is about to expire

    +

    Hi {{utente_nome}},

    +

    your library card (no. {{codice_tessera}}) expires on {{data_scadenza_tessera}} (days left: {{giorni_rimasti}}).

    +
    +

    Once it has expired you will not be able to borrow or reserve books until the card is renewed.

    +
    +

    To renew it, ask at the library desk during opening hours.

    +

    See you soon!

    ', ], 'loan_pickup_cancelled' => [ 'subject' => '❌ Pickup Cancelled', @@ -291,6 +302,22 @@
  • Request date: {{data_richiesta}}
  • Manage Request

    ', + ], + 'fine_charged' => [ + 'subject' => '💶 Late return fine: {{multa}}', + 'body' => '

    Late return fine

    +

    Hi {{utente_nome}},

    +

    the book below was returned after its due date and a fine has been charged to your account.

    +
    +

    Book: {{libro_titolo}}

    +

    Due date: {{data_scadenza}}

    +

    Returned on: {{data_restituzione}}

    +

    Days late: {{giorni_ritardo}}

    +

    Fine: {{multa}}

    +

    Balance due: {{saldo}}

    +
    +

    You can settle the amount at the library desk.

    +

    Best regards,
    The library team

    ', ], 'loan_returned' => [ 'subject' => '✅ Return confirmed', @@ -301,6 +328,7 @@

    Book: {{libro_titolo}}

    Return date: {{data_restituzione}}

    +

    {{multa}}

    We hope you enjoyed the read. See you soon at the library!

    Best regards,
    The library team

    ', ], diff --git a/app/Support/mail_templates/fr_FR.php b/app/Support/mail_templates/fr_FR.php index 3b8d15ec5..bae0c116d 100644 --- a/app/Support/mail_templates/fr_FR.php +++ b/app/Support/mail_templates/fr_FR.php @@ -193,6 +193,17 @@

    Si vous n’en avez plus besoin, vous pouvez annuler le prêt depuis la page « Réservations » de votre compte : le livre passera aussitôt à quelqu’un d’autre.

    Bonne lecture !

    ', + ], + 'card_expiring' => [ + 'subject' => '🪪 Votre carte de bibliothèque expire le {{data_scadenza_tessera}}', + 'body' => '

    Votre carte de bibliothèque va expirer

    +

    Bonjour {{utente_nome}},

    +

    votre carte de bibliothèque (n° {{codice_tessera}}) expire le {{data_scadenza_tessera}} (jours restants : {{giorni_rimasti}}).

    +
    +

    Après l\'expiration, vous ne pourrez plus emprunter ni réserver de livres tant que la carte n\'aura pas été renouvelée.

    +
    +

    Pour la renouveler, adressez-vous à l\'accueil de la bibliothèque pendant les heures d\'ouverture.

    +

    À bientôt !

    ', ], 'loan_pickup_cancelled' => [ 'subject' => '❌ Retrait annulé', @@ -291,6 +302,22 @@
  • Date de la demande : {{data_richiesta}}
  • Gérer la demande

    ', + ], + 'fine_charged' => [ + 'subject' => '💶 Pénalité de retard : {{multa}}', + 'body' => '

    Pénalité de retard

    +

    Bonjour {{utente_nome}},

    +

    le livre ci-dessous a été rendu après sa date d’échéance et une pénalité a été portée à votre compte.

    +
    +

    Livre : {{libro_titolo}}

    +

    Échéance : {{data_scadenza}}

    +

    Rendu le : {{data_restituzione}}

    +

    Jours de retard : {{giorni_ritardo}}

    +

    Pénalité : {{multa}}

    +

    Solde à régler : {{saldo}}

    +
    +

    Vous pouvez régler le montant à l’accueil de la bibliothèque.

    +

    Cordialement,
    L’équipe de la bibliothèque

    ', ], 'loan_returned' => [ 'subject' => '✅ Retour confirmé', @@ -301,6 +328,7 @@

    Livre : {{libro_titolo}}

    Date de retour : {{data_restituzione}}

    +

    {{multa}}

    Nous espérons que cette lecture vous a plu. À très bientôt à la bibliothèque !

    Cordialement,
    L\'équipe de la bibliothèque

    ', ], diff --git a/app/Views/admin/calendar.php b/app/Views/admin/calendar.php new file mode 100644 index 000000000..363ac4276 --- /dev/null +++ b/app/Views/admin/calendar.php @@ -0,0 +1,789 @@ + $closedWeekdays + * @var list> $closures + * @var array|null $editing + * @var array $branches + * @var ?int $previewSede + * @var string $month + * @var string $prevMonth + * @var string $nextMonth + * @var array $monthClosed + * @var int $currentYear + * @var string $today + * @var list> $yearlyClosures + * @var list> $customHolidays + * @var array|null $customEditing + * @var bool $customSchema + * @var array{missing: list, missing_country: string, countries: array, country: string, regions: array, region: string, years: array{min: int, max: int}, data_years: array{int, int}|null, year: int, list: list, yearly: bool, regional: bool, optional: bool, name_en: string, category: string, estimated: bool, checked: bool}>, saved: bool, schema: bool, unknown_country: string} $holidays + */ + +$csrf = \App\Support\Csrf::ensureToken(); +// Plain escaping: the stored text is shown exactly as saved. +$e = static fn ($v): string => htmlspecialchars((string) $v, ENT_QUOTES, 'UTF-8'); +$calLocale = \App\Support\I18n::getLocale(); +$fmt = static function (string $pattern, string $ymd) use ($calLocale): string { + if (class_exists('IntlDateFormatter')) { + $f = new \IntlDateFormatter($calLocale, \IntlDateFormatter::NONE, \IntlDateFormatter::NONE, 'UTC', null, $pattern); + $out = $f->format(new \DateTimeImmutable($ymd . ' 12:00:00', new \DateTimeZone('UTC'))); + if (is_string($out) && $out !== '') { + return $out; + } + } + return $ymd; +}; +$displayDate = static function (string $ymd): string { + return \App\Support\LoanWindow::displayDate($ymd); +}; +// 2026-11-01 is a Sunday: index 0 = Sunday … 6 = Saturday, as closed_weekdays. +$weekdayNames = []; +for ($w = 0; $w <= 6; $w++) { + $weekdayNames[$w] = mb_convert_case($fmt('EEEE', \App\Support\LibraryCalendar::shift('2026-11-01', $w)), MB_CASE_TITLE, 'UTF-8'); +} +$editingId = $editing !== null ? (int) $editing['id'] : 0; +$formAction = $editingId > 0 ? url('/admin/calendar/closures/' . $editingId . '/update') : url('/admin/calendar/closures'); +?> +
    +
    +

    + + + + +

    + + +
    + +
    +
    +

    + + +

    +
    +
    +
    + +

    +
    + + + +
    +
    + +
    +
    +
    +
    + + +
    +
    +

    + + 0 ? __("Modifica chiusura") : __("Aggiungi chiusura") ?> +

    + 0): ?> + + +
    +
    +
    + +
    +
    + + +
    +
    + + +
    +
    +
    + + +
    +
    +
    + + +
    +
    + +
    +
    +
    + +
    +
    +
    +
    +
    + + +
    +
    +

    + + +

    +
    +
    +

    +

    + + + + + + +
    +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + +
    + + + +
    + + +
    + +

    + +
    + + + + +
    +
    + +
    +
    +
    +
    + + + $customEditing !== null && ($customEditing[$field] ?? null) !== null ? $customEditing[$field] : $default; + $customKind = (string) $cv('tipo', 'fixed'); + $customAction = $customEditId > 0 ? url('/admin/calendar/custom-holidays/' . $customEditId . '/update') : url('/admin/calendar/custom-holidays'); + $calendarCodes = \App\Support\Holidays\WorldCalendars::codes(); + $customCalendar = (string) $cv('calendario', 'islamic-umalqura'); + $monthName = static fn (int $m): string => mb_convert_case($fmt('LLLL', sprintf('2024-%02d-01', $m)), MB_CASE_TITLE, 'UTF-8'); + ?> +
    +
    +

    + + +

    + 0): ?> + + +
    +
    +

    + +

    + +
    + +
    +
    + + +
    +
    + + +
    +
    + +
    +
    + + +
    +
    + + +
    +
    + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + +
    + + +

    +
    + +
    +
    + + +
    +
    + +
    + + +
    + +
    +
    + + +
    +

    +
    + +
    + + +
    + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    +
    +
    + + +
    +
    + + +
    +
    + +
    +
    +
    + +
    +
    + + +

    + +
    + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + +
    + + + +
    + + +
    +
    + + +
    +
    +
    +
    + + +
    +
    + +
    + +
    +
    +

    + + +

    +
    +
    +
    + +

    + +

    + +
    + + + +
    + +
    + +
    +
    +
    +
    + + +
    +
    +

    + + +

    +
    +
    +
    + +

    +
    + + +
    +
    + + +
    +
    + +
    +
    +
    +
    +
    + + +
    +
    +

    + + +

    +
    + + + + +
    + + +
    + +
    +
    +
    +
    + +
    + +
    +
    + format('N')) - 1; // Monday first + for ($i = 0; $i < $lead; $i++): ?> +
    + +
    + +
    + +
    +
    + + +
    +
    +
    + + +
    +
    +

    + + () +

    +
    +
    + +
    + +

    +

    +
    + +
    + + + + + + + + + + + + + + + + + + + + + +
    + – + + + + + + + + + + + + + + + + + + +
    + + + +
    + + +
    +
    + +
    +
    + +
    +
    +

    +
    + diff --git a/app/Views/admin/csv_import.php b/app/Views/admin/csv_import.php index ec31b3893..0846d63af 100644 --- a/app/Views/admin/csv_import.php +++ b/app/Views/admin/csv_import.php @@ -170,6 +170,22 @@ + +
    + + +

    +
    + +
    diff --git a/app/Views/admin/imports_history.php b/app/Views/admin/imports_history.php index cdd553e90..4570627ce 100644 --- a/app/Views/admin/imports_history.php +++ b/app/Views/admin/imports_history.php @@ -180,6 +180,8 @@ $typeLabels = [ 'csv' => ['label' => 'CSV', 'color' => 'bg-blue-100 text-blue-800'], 'librarything' => ['label' => 'LibraryThing', 'color' => 'bg-purple-100 text-purple-800'], + 'marc' => ['label' => 'MARC', 'color' => 'bg-green-100 text-green-800'], + 'users' => ['label' => __('Utenti'), 'color' => 'bg-amber-100 text-amber-800'], ]; $type = $typeLabels[$import['import_type']] ?? ['label' => 'Unknown', 'color' => 'bg-gray-100 text-gray-800']; diff --git a/app/Views/admin/marc_import.php b/app/Views/admin/marc_import.php new file mode 100644 index 000000000..2e052a6b5 --- /dev/null +++ b/app/Views/admin/marc_import.php @@ -0,0 +1,497 @@ + + +
    +
    + +

    + + + + + + + + + + + + +

    +
    + +
    + + +
    + +
    + +
    +

    + + +

    + +
    + +
    + + + +
    +
    + + +
    +
    + + +
    +
    + +
    +
    +
    + + +
    +
    +
    + [__("Salta"), __("Il libro già in catalogo resta com'è")], + 'fill' => [__("Completa i campi vuoti"), __("Solo i campi vuoti del libro vengono riempiti dal record")], + 'overwrite' => [__("Sovrascrivi"), __("I campi presenti nel record sostituiscono quelli del libro; gli altri restano")], + ] as $modeKey => [$modeLabel, $modeHelp]): ?> + + +
    +
    + +
    + +
    + + +
    + + +

    +
    + + + + +
    +
    +
    + + +
    +
    + +
    +
    +
    + + + + +
    + +
    +
    +

    + + +

    +
      +
    1. + 1 + +
    2. +
    3. + 2 + +
    4. +
    5. + 3 + +
    6. +
    +
    +
    +

    + + +

    +
      +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    +
    +
    +
    +

    +
    + + + + + + + @@ -1007,6 +1010,16 @@ function escapeHtml(str) { syntax: 'marcxml', indexes: { isbn: 'isbn' } }, + // Another Pinakes installation: its SRU server answers at /api/sru + // with MARCXML records that carry the cover in 856. Replace the host. + pinakes: { + name: '[Pinakes] ' + , + url: 'https://biblioteca.example.org/api/sru', + db: '', + version: '1.2', + syntax: 'marcxml', + indexes: { isbn: 'bath.isbn', issn: 'bath.issn', title: 'dc.title', author: 'dc.creator', any: 'cql.anywhere' } + }, // [DK] The Danish union catalogue through DBC OpenSearch: danMARC2 // records in MARCXchange. agency and profile are DBC's public test // pair; a Danish library puts its own in the URL. @@ -1119,6 +1132,9 @@ function addZ39ServerRow(server = null) { const row = document.createElement('div'); row.className = 'z39-server-row bg-gray-50 rounded-xl p-4 border border-gray-200 relative group'; + // Search indexes beyond ISBN (title, author, any: copy cataloguing) + // have no input of their own: they ride on the row and are saved back. + row.dataset.indexes = JSON.stringify(server.indexes && typeof server.indexes === 'object' ? server.indexes : {}); row.innerHTML = `
    @@ -1224,9 +1240,9 @@ function checkEmptyServers() { version: row.querySelector('[name="server_version[]"]')?.value.trim() || '1.1', syntax: row.querySelector('[name="server_syntax[]"]').value, quote_search_terms: row.querySelector('[name="server_quote_terms[]"]')?.checked || false, - indexes: { + indexes: Object.assign((() => { try { return JSON.parse(row.dataset.indexes || '{}') || {}; } catch (e) { return {}; } })(), { isbn: row.querySelector('[name="server_isbn_index[]"]').value || 'isbn' - }, + }), enabled: true }); }); diff --git a/app/Views/admin/stats.php b/app/Views/admin/stats.php index c3e69a28b..16f62b0bd 100644 --- a/app/Views/admin/stats.php +++ b/app/Views/admin/stats.php @@ -146,6 +146,97 @@
    + +
    +
    +
    +

    + + +

    +
    +
    +
    + + + + + + + + + + + + + + + +
    +
    +
    +
    + + +
    +
    +

    + + +

    +
    +
    +
    +
    +

    +

    +
    +
    +

    +

    +
    +
    +

    +

    +
    +
    + + + +
    +
    + +
    + + $section): ?> +
    +
    +

    + + +

    +
    +
    + + + + + + + + + +
    + +

    + +
    +
    + +
    @@ -290,6 +381,87 @@ class="w-10 h-14 object-cover rounded shadow-sm"
    + + + Loans) ?> + 0, 'incassato' => 0, 'condonato' => 0, 'stornato' => 0]; + foreach (($finesByMonth ?? []) as $finesRow) { + foreach ($finesTotals as $finesKey => $finesValue) { + $finesTotals[$finesKey] = $finesValue + (int) $finesRow[$finesKey]; + } + } + $finesCsvUrl = url('/admin/statistics/fines.csv') . '?' . http_build_query(['fines_from' => $finesFrom ?? '', 'fines_to' => $finesTo ?? '']); + ?> +
    +
    +
    +

    + + +

    +
    +
    +
    +
    + + +
    +
    + + +
    + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +

    +
    +
    +
    +
    +
    + diff --git a/app/Views/attendance/index.php b/app/Views/attendance/index.php new file mode 100644 index 000000000..c50666566 --- /dev/null +++ b/app/Views/attendance/index.php @@ -0,0 +1,311 @@ +, daily:list, hourly:array, + * totals:array, today:array, recent:list, branches:array, + * retentionDays:int, isAdmin:bool + * } + */ +use App\Support\Csrf; + +$csrf = Csrf::ensureToken(); +$days = (int) $data['days']; +$branches = $data['branches']; +$selectedBranch = (int) ($_SESSION['attendance_sede_id'] ?? 0); +$hourly = $data['hourly']; +$maxHour = max(1, ...array_values($hourly)); +$branchSelect = static function (string $id) use ($branches, $selectedBranch): void { + if ($branches === []) { + return; + } + ?> +
    + + +
    + +
    +

    +

    + +
    + + + +
    + + + +
    +
    +
    +

    + + +

    +
    +
    +
    + +
    + + +
    + + +
    +
    +
    + +
    +
    +

    + + +

    +
    +
    +
    + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    +
    +
    + + +
    +
    +

    +

    +
    +
    +

    +

    +
    +
    +

    +

    +
    +
    +

    +

    +
    +
    + + +
    + : + + > + + + +
    + +
    + +
    +
    +

    + + +

    +
    +
    +
    + + + + + + + + + $count): if ($count === 0) { continue; } ?> + + + + + + + + + +
    +
    +
    + +
    +
    +
    +
    +
    + + +
    +
    +

    + + +

    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    +
    +
    + +
    + +
    +
    +

    + + +

    +
    +
    +
      + +
    • + + +
    • + + + + + + + + + +
    • + +
    +
    +
    + + +
    +
    +

    + + +

    +
    +
    +

    + 0): ?> + + + + + +

    + +
    + +
    + + +

    +
    + +
    +
    + + +
    + +
    +
    +
    +

    + diff --git a/app/Views/autori/index.php b/app/Views/autori/index.php index c62ceb3a5..bda7b9b1e 100644 --- a/app/Views/autori/index.php +++ b/app/Views/autori/index.php @@ -56,11 +56,12 @@
    -
    @@ -237,7 +238,7 @@ class="form-input" /> url: (window.BASE_PATH || '') + '/api/autori', type: 'GET', data: function(d) { - d.search_nome = document.getElementById('search_nome').value; + d.search_text = document.getElementById('search_nome').value; d.search_pseudonimo = document.getElementById('search_pseudonimo').value; d.search_nazionalita = document.getElementById('search_nazionalita').value; d.search_sito = document.getElementById('search_sito').value; diff --git a/app/Views/dashboard/permissions.php b/app/Views/dashboard/permissions.php index cad89c9bf..f08f79359 100644 --- a/app/Views/dashboard/permissions.php +++ b/app/Views/dashboard/permissions.php @@ -5,8 +5,8 @@

      - ['/admin/books', __('Catalogazione')], 'loans.manage' => ['/admin/loans', __('Prestiti')], 'users.manage' => ['/admin/users', __('Utenti')], 'settings.manage' => ['/admin/settings', __('Impostazioni')]] as $permission => [$path, $label]): ?> -
    • + ['/admin/books', __('Catalogazione')], 'loans.manage' => ['/admin/loans', __('Prestiti')], 'users.manage' => ['/admin/users', __('Utenti')], 'settings.manage' => ['/admin/settings', __('Impostazioni')], 'inventory.manage' => ['/admin/inventory', __('Revisione inventariale')]] as $permission => [$path, $label]): ?> +

    diff --git a/app/Views/editori/index.php b/app/Views/editori/index.php index 1bf0c43cc..f76cc8d28 100644 --- a/app/Views/editori/index.php +++ b/app/Views/editori/index.php @@ -56,11 +56,12 @@
    -
    diff --git a/app/Views/fornitori/form.php b/app/Views/fornitori/form.php new file mode 100644 index 000000000..39eae50a9 --- /dev/null +++ b/app/Views/fornitori/form.php @@ -0,0 +1,130 @@ +|null, old: array|null } + */ +use App\Support\Csrf; + +$csrf = Csrf::ensureToken(); +$fornitore = $data['fornitore']; +$isEdit = is_array($fornitore); +$action = $isEdit ? url('/admin/suppliers/update/' . (int) $fornitore['id']) : url('/admin/suppliers/create'); +// A submission refused by validation comes back as typed (SuppliersController::keepOldInput). +$old = is_array($data['old'] ?? null) ? $data['old'] : null; +$v = static fn (string $key): string => htmlspecialchars($old !== null ? (string) ($old[$key] ?? '') : (string) ($fornitore[$key] ?? ''), ENT_QUOTES, 'UTF-8'); +?> +
    +
    + +

    + + + +

    + + + +
    + + + +
    +
    +

    + + +

    +
    +
    +
    +
    + + " /> +
    +
    + + +
    +
    + +
    +
    + + +
    +
    + + +
    +
    + +
    +
    + + +
    +
    + + +
    +
    + +
    + + +
    + +
    + + +
    +
    +
    + + +
    + + + + + +
    +
    +

    +
    diff --git a/app/Views/fornitori/index.php b/app/Views/fornitori/index.php new file mode 100644 index 000000000..bd82afef9 --- /dev/null +++ b/app/Views/fornitori/index.php @@ -0,0 +1,221 @@ +> } + */ +use App\Support\Csrf; + +$fornitori = $data['fornitori']; +$csrf = Csrf::ensureToken(); +?> +
    +
    + +

    +

    + + +
    + + + +
    + + + +
    +
    +
    +
    + + +

    +
    +
    +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +

    +
    + + + + +
    + +
    +
    +
    +
    + + +
    + + + + + + +
    + + +
    +
    +
    +
    +
    +
    +

    +
    + + diff --git a/app/Views/frontend/book-detail.php b/app/Views/frontend/book-detail.php index 63a793ccd..3234bc428 100644 --- a/app/Views/frontend/book-detail.php +++ b/app/Views/frontend/book-detail.php @@ -624,11 +624,23 @@ class="book-cover-large img-fluid pk-book__img
    + + + + + + + + + + + +
    diff --git a/app/Views/frontend/home-sections/hero.php b/app/Views/frontend/home-sections/hero.php index 59ed44d86..0e0c88d41 100644 --- a/app/Views/frontend/home-sections/hero.php +++ b/app/Views/frontend/home-sections/hero.php @@ -55,6 +55,7 @@ + + +
    + +
    +
    +
    +
    +
    +
    + + @@ -84,9 +98,20 @@ - + + + + + + + + + + + +
    @@ -102,6 +127,16 @@ + + + + + + + + + +
    @@ -138,8 +173,9 @@ - +

    @@ -299,6 +335,21 @@ class="form-input" />
    + + + +
    + + +
    + @@ -360,6 +411,16 @@ class="form-input" /> + + + + + + @@ -419,6 +480,8 @@ class="form-input" /> let genereUrlFilter = initialGenere; let sottogenereUrlFilter = initialSottogenere; const initialKeywords = urlParams.get('keywords') || ''; + // "Libri di questo fornitore" opens the list with ?fornitore=N preselected. + const initialFornitore = ; const initialCollana = urlParams.get('collana') || ''; let collanaFilter = initialCollana; @@ -614,7 +677,7 @@ function renderRecentSearches() { stateLoadCallback: function(settings) { // When URL filters are active, reset saved pagination to page 1 // to avoid "Showing 2501 to 44 of 44" when saved state had a high page offset - const hasUrlFilter = initialKeywords || initialCollana || initialGenere || initialSottogenere; + const hasUrlFilter = initialKeywords || initialCollana || initialGenere || initialSottogenere || initialFornitore > 0; let raw = null; try { raw = localStorage.getItem('DataTables_libri-table_' + window.location.pathname); @@ -655,7 +718,8 @@ function renderRecentSearches() { anno_from: document.getElementById('anno_from').value, anno_to: document.getElementById('anno_to').value, collana: collanaFilter, - tipo_media: document.getElementById('tipo_media_filter').value + tipo_media: document.getElementById('tipo_media_filter').value, + fornitore_filter: document.getElementById('fornitore_filter')?.value || 0 }; }, dataSrc: function(json) { @@ -845,6 +909,7 @@ className: 'text-center align-middle', document.querySelectorAll('.row-select').forEach(cb => { cb.addEventListener('change', handleRowSelect); }); + syncSelectAll(); updateBulkActionsBar(); } }); @@ -873,7 +938,7 @@ className: 'text-center align-middle', updateActiveFilters(); }); - ['search_text', 'search_isbn', 'stato_filter', 'tipo_media_filter', 'acq_from', 'acq_to', 'anno_from', 'anno_to'].forEach(id => { + ['search_text', 'search_isbn', 'stato_filter', 'tipo_media_filter', 'fornitore_filter', 'acq_from', 'acq_to', 'anno_from', 'anno_to'].forEach(id => { const el = document.getElementById(id); if (el) { el.addEventListener('input', reloadDebounced); @@ -881,6 +946,12 @@ className: 'text-center align-middle', } }); + // A supplier preselected from the URL sits in the closed "more filters" + // panel: show it as an active filter chip from the start. + if (initialFornitore > 0) { + updateActiveFilters(); + } + // Active filters display function updateActiveFilters() { const container = document.getElementById('active-filters'); @@ -909,6 +980,11 @@ function updateActiveFilters() { const tipoMedia = document.getElementById('tipo_media_filter').value; if (tipoMedia) filters.push({ key: 'tipo_media_filter', label: `${}: ${tipoMedia}`, icon: 'fa-compact-disc' }); + const fornitoreSel = document.getElementById('fornitore_filter'); + if (fornitoreSel && fornitoreSel.value) { + filters.push({ key: 'fornitore_filter', label: `${}: ${fornitoreSel.options[fornitoreSel.selectedIndex].text}`, icon: 'fa-truck' }); + } + const genere = document.getElementById('filter_genere').value; if (genere && document.getElementById('genere_id').value) { filters.push({ key: 'genere', label: `${}: ${genere}`, icon: 'fa-tags' }); @@ -937,6 +1013,7 @@ function clearFilter(key) { else if (key === 'editore') { document.getElementById('filter_editore').value = ''; document.getElementById('editore_filter').value = ''; } else if (key === 'stato_filter') document.getElementById('stato_filter').value = ''; else if (key === 'tipo_media_filter') document.getElementById('tipo_media_filter').value = ''; + else if (key === 'fornitore_filter') document.getElementById('fornitore_filter').value = ''; else if (key === 'genere') { document.getElementById('filter_genere').value = ''; document.getElementById('genere_id').value = ''; genereUrlFilter = 0; sottogenereUrlFilter = 0; } table.ajax.reload(); updateActiveFilters(); @@ -944,7 +1021,7 @@ function clearFilter(key) { // Clear all filters document.getElementById('clear-filters').addEventListener('click', function() { - ['search_text', 'search_isbn', 'stato_filter', 'tipo_media_filter', 'acq_from', 'acq_to', 'anno_from', 'anno_to', 'filter_autore', 'filter_editore', 'filter_genere', 'filter_posizione'].forEach(id => { + ['search_text', 'search_isbn', 'stato_filter', 'tipo_media_filter', 'fornitore_filter', 'acq_from', 'acq_to', 'anno_from', 'anno_to', 'filter_autore', 'filter_editore', 'filter_genere', 'filter_posizione'].forEach(id => { const el = document.getElementById(id); if (el) el.value = ''; }); @@ -1037,7 +1114,14 @@ function handleRowSelect(e) { if (e.target.checked) selectedBooks.add(id); else selectedBooks.delete(id); updateBulkActionsBar(); - document.getElementById('select-all').checked = selectedBooks.size > 0 && selectedBooks.size === document.querySelectorAll('.row-select').length; + syncSelectAll(); + } + + // "Select all" reflects the rows on screen: the selection survives paging, + // so on a new page the box starts unticked unless every row there is selected. + function syncSelectAll() { + const boxes = Array.from(document.querySelectorAll('.row-select')); + document.getElementById('select-all').checked = boxes.length > 0 && boxes.every(cb => cb.checked); } document.getElementById('select-all').addEventListener('change', function() { @@ -1437,6 +1521,23 @@ function updateBulkActionsBar() { } }); + // Bulk ISBD catalogue cards (PDF, one card per page) + document.getElementById('bulk-catalogue-cards')?.addEventListener('click', function() { + if (selectedBooks.size === 0) return; + const maxCards = ; + if (selectedBooks.size > maxCards) { + // The server prints at most maxCards cards: say so instead of silently dropping the rest. + Swal.fire({ + icon: 'warning', + title: , + text: .replace('%d', maxCards) + }); + return; + } + const ids = Array.from(selectedBooks).join(','); + window.open(`${window.BASE_PATH}/admin/books/catalogue-cards?ids=${encodeURIComponent(ids)}`, '_blank', 'noopener'); + }); + // Bulk export document.getElementById('bulk-export').addEventListener('click', function() { if (selectedBooks.size === 0) return; @@ -1444,6 +1545,23 @@ function updateBulkActionsBar() { window.location.href = `${window.BASE_PATH}/admin/books/export/csv?ids=${ids}`; }); + // Bulk MARC export of the selection: MARC 21, MARCXML or UNIMARC + document.getElementById('bulk-export-marc')?.addEventListener('click', async function() { + if (selectedBooks.size === 0) return; + const choice = await Swal.fire({ + title: __('Esporta in MARC'), + input: 'select', + inputOptions: { marc21: __('MARC 21 (.mrc)'), marcxml: __('MARCXML'), unimarc: __('UNIMARC (.mrc)') }, + inputValue: 'marc21', + showCancelButton: true, + confirmButtonText: __('Esporta'), + cancelButtonText: __('Annulla') + }); + if (!choice.isConfirmed || !choice.value) return; + const ids = Array.from(selectedBooks).join(','); + window.location.href = `${window.BASE_PATH}/admin/books/export/marc?format=${encodeURIComponent(choice.value)}&ids=${ids}`; + }); + // View toggle function setView(view) { currentView = view; @@ -1786,7 +1904,7 @@ function renderGrid() { .books-filter-grid > #clear-filters, .books-recent-searches { flex: 0 0 auto; } .books-recent-searches > button { width: auto; } - .books-bulk-buttons { grid-template-columns: repeat(4, auto); } + .books-bulk-buttons { grid-template-columns: repeat(3, auto); } } .books-scroll-hint { display: none; } diff --git a/app/Views/libri/partials/book_form.php b/app/Views/libri/partials/book_form.php index 832fe82ab..d045819b6 100644 --- a/app/Views/libri/partials/book_form.php +++ b/app/Views/libri/partials/book_form.php @@ -413,6 +413,32 @@ + + name, loaded by the controller. + $supplierOptions = isset($fornitori) && is_array($fornitori) ? $fornitori : []; + $currentSupplier = (int) ($book['fornitore_id'] ?? 0); + // A stored supplier missing from the options (list not loaded) must + // not be posted back as "no supplier": the select is then disabled, + // so the field is not submitted and the stored value is kept. + $supplierLocked = $currentSupplier > 0 && !isset($supplierOptions[$currentSupplier]); + ?> +
    + @@ -465,6 +491,62 @@ + + $rdaCode) { + $rdaDerivedByMedia[$mediaKey][$rdaKind] = __(\App\Support\RdaTypes::label($rdaKind, $rdaCode)) . ' (' . $rdaCode . ')'; + } + } + $rdaKindLabels = [ + \App\Support\RdaTypes::KIND_CONTENT => __('Tipo di contenuto RDA'), + \App\Support\RdaTypes::KIND_MEDIA => __('Tipo di mediazione RDA'), + \App\Support\RdaTypes::KIND_CARRIER => __('Tipo di supporto RDA'), + ]; + ?> +
    +

    +

    +
    + $rdaColumn): $rdaCurrent = \App\Support\RdaTypes::normalize($rdaKind, $book[$rdaColumn] ?? null); ?> +
    + + +
    + +
    +
    + + + + + + + + + + @@ -398,7 +414,7 @@ class="text-gray-900 hover:text-gray-600 hover:underline font-semibold">"> @@ -580,6 +596,30 @@ class="text-gray-700 hover:text-gray-900 hover:underline transition-colors"> getById((int) $libro['fornitore_id']) + : null; + ?> + + + + $rdaCode) { + $rdaParts[] = __(\App\Support\RdaTypes::label($rdaKind, $rdaCode)); + } + ?> + +
    +
    +
    +
    + + + + + @@ -1159,6 +1202,16 @@ class="inline-flex items-center gap-2 px-3 py-1.5 bg-gray-800 text-white text-sm $canEdit = empty($copia['prestito_id']) || $bookAtLibrary; $canDelete = $canEdit && in_array($rawCopiaStatus, ['perso', 'danneggiato', 'manutenzione', 'in_restauro', 'in_trasferimento'], true); ?> + +
    + + + +
    + + + + diff --git a/app/Views/settings/circulation-tab.php b/app/Views/settings/circulation-tab.php new file mode 100644 index 000000000..9b59774e1 --- /dev/null +++ b/app/Views/settings/circulation-tab.php @@ -0,0 +1,204 @@ +>, defaultTypeId: ?int, media: array, rules: array>, inherited: array>, globals: array, userCounts: array, effective: array} $circulationData */ + +$cTypes = $circulationData['types']; +$cMedia = $circulationData['media']; +$cRules = $circulationData['rules']; +$cInherited = $circulationData['inherited']; +$cGlobals = $circulationData['globals']; +$cIntOrEmpty = static fn ($v): string => $v === null || $v === '' ? '' : (string) (int) $v; +$cMoneyOrEmpty = static fn ($v): string => $v === null || $v === '' ? '' : (string) $v; +// Inherited amount in the cell placeholder: "0.50", "—" when none applies. +$cMoneyPlaceholder = static fn (?float $v): string => $v !== null && $v > 0 ? number_format($v, 2, '.', '') : '—'; +?> +
    + +

    + +
    + + +
    +
    +

    + + +

    +

    +
    +
    + +
    + +
    +
    +
    +
    +
    +
    + + + + +
    + + +
    + +
    +
    +
    +
    +
    + +
    +
    + + + + + + + + + + + + + + + + $cType): $cEff = $circulationData['effective'][$cTypeId]; ?> + + + + + + + + +
    + +

    +
    +
    + + + + + + + +
    +
    +
    + maxActiveLoans > 0 ? (string) $cEff->maxActiveLoans : '∞', $cEff->durationDays, $cEff->maxRenewals) ?> +
    + user->reservationsEnabled + ? sprintf(__('Prenotazioni: %s'), $cEff->user->maxReservations > 0 ? (string) $cEff->user->maxReservations : '∞') + : __('Prenotazioni disattivate') ?> + user->cardDurationDays !== null): ?> · user->cardDurationDays) ?> + finesEnabled()): ?> · dailyFineCents)), ENT_QUOTES, 'UTF-8') ?> + debtBlockEnabled()): ?> · debtBlockThresholdCents)), ENT_QUOTES, 'UTF-8') ?> +
    +
    +
    + + ['fa-arrow-up', __('Sposta su'), $cIndex > 0], 'down' => ['fa-arrow-down', __('Sposta giù'), $cIndex < $cLast]] as $cDir => [$cIcon, $cLabel, $cEnabled]): if (!$cEnabled) { continue; } ?> +
    + + + +
    + + +
    + + +
    +
    + + +
    + +
    +
    +
    +
    + + +
    +

    + + +

    +

    +
    + +
    +
    + + + + + + + + + + + ['nome' => __('Tutte le tipologie')]] + $cTypes as $cTypeId => $cType): ?> + + + $cMediaLabel): + $cKey = (int) $cTypeId . '|' . $cM; + $cRule = $cRules[$cKey] ?? []; + $cInh = $cInherited[$cKey]; + $cName = 'rules[' . (int) $cTypeId . '][' . $cM . ']'; + $cId = 'rule-' . (int) $cTypeId . '-' . $cM; + $cLoanable = isset($cRule['prestabile']) ? (string) (int) $cRule['prestabile'] : ''; + ?> + + + + + +
    +
    + + + [__('Max prestiti'), 'max_prestiti', $cInh['max'] > 0 ? (string) $cInh['max'] : '∞'], 'duration' => [__('Giorni'), 'durata_prestito_giorni', (string) $cInh['duration']], 'renewals' => [__('Rinnovi'), 'max_rinnovi', (string) $cInh['renewals']], 'renewal_days' => [__('Gg/rinnovo'), 'giorni_rinnovo', (string) $cInh['renewal_days']], 'grace' => [__('Tolleranza'), 'giorni_tolleranza', (string) $cInh['grace']]] as $cField => [$cLabel, $cColumn, $cPlaceholder]): ?> + + + [__('Multa/giorno'), 'multa_giornaliera'], 'fine_max' => [__('Multa max'), 'multa_massima_prestito']] as $cField => [$cLabel, $cColumn]): ?> + + +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    diff --git a/app/Views/settings/circulation-type-edit.php b/app/Views/settings/circulation-type-edit.php new file mode 100644 index 000000000..f04f80bdf --- /dev/null +++ b/app/Views/settings/circulation-type-edit.php @@ -0,0 +1,36 @@ + $type + * @var array $globals + */ +use App\Support\Csrf; + +$csrfToken = Csrf::ensureToken(); +$typeForm = $type; +$cGlobals = $globals; +?> +
    + +
    + + +
    + +
    +
    +
    diff --git a/app/Views/settings/index.php b/app/Views/settings/index.php index 5ace6cf99..89469491b 100644 --- a/app/Views/settings/index.php +++ b/app/Views/settings/index.php @@ -892,6 +892,9 @@ class="mt-1 w-4 h-4 aspect-square shrink-0 text-gray-900 focus:ring-gray-500"> + + +
    @@ -953,6 +956,11 @@ class="sharing-provider-checkbox w-4 h-4 rounded text-gray-900 focus:ring-gray-5 + + + + + diff --git a/app/Views/settings/loans-tab.php b/app/Views/settings/loans-tab.php index 53fc9edc2..c16662847 100644 --- a/app/Views/settings/loans-tab.php +++ b/app/Views/settings/loans-tab.php @@ -6,6 +6,7 @@
    +
    @@ -86,6 +87,93 @@ class="block w-32 rounded-xl border-gray-300 focus:border-gray-500 focus:ring-gr
    + +
    +
    +

    + + +

    +

    + +
    +
    + +
    +
    +
    + +
    +
    +
    +
    + +

    +
    + +
    + + +
    + + +
    +
    +
    + + +
    +
    +

    + + +

    +

    +
    +
    +
    +
    + +

    +
    + +
    + + + + + + +
    +
    +
    @@ -199,6 +287,38 @@ class="block w-32 rounded-xl border-gray-300 focus:border-gray-500 focus:ring-gr
    + +
    +
    +

    + + +

    +

    +
    +
    +
    + +
    + + +
    +

    + + +

    +
    +
    +
    +
    @@ -387,6 +507,169 @@ class="block w-32 rounded-xl border-gray-300 focus:border-gray-500 focus:ring-gr
    + + +
    +
    +

    + + +

    +

    +
    +
    + +
    + +
    +
    +
    + +
    +
    + +
    +
    +
    + +
    +
    +
    +
    + +

    +
    + +
    + + 0): ?> +

    + +

    +
    + + +
    +
    + +
    + + pattern="[0-9]{1,8}([.,][0-9]{1,2})?" + placeholder="0" + value="" + class="block w-32 rounded-xl border-gray-300 focus:border-gray-500 focus:ring-gray-500 text-sm py-3 px-4 text-center font-semibold text-lg"> + +
    +

    + + +

    +
    +
    + +
    + + min="0" + max="365" + value="" + class="block w-32 rounded-xl border-gray-300 focus:border-gray-500 focus:ring-gray-500 text-sm py-3 px-4 text-center font-semibold text-lg"> + +
    +

    + + +

    +
    +
    + +
    + + pattern="[0-9]{1,8}([.,][0-9]{1,2})?" + placeholder="0" + value="" + class="block w-32 rounded-xl border-gray-300 focus:border-gray-500 focus:ring-gray-500 text-sm py-3 px-4 text-center font-semibold text-lg"> + +
    +

    + + +

    +
    +
    + +
    + + pattern="[0-9]{1,8}([.,][0-9]{1,2})?" + placeholder="0" + value="" + class="block w-32 rounded-xl border-gray-300 focus:border-gray-500 focus:ring-gray-500 text-sm py-3 px-4 text-center font-semibold text-lg"> + +
    +

    + + +

    +
    +
    +
    + +

    +
    + +
    +
    +
    +
    +
    + +
    diff --git a/app/Views/settings/partials/circulation-type-fields.php b/app/Views/settings/partials/circulation-type-fields.php new file mode 100644 index 000000000..f458d0c05 --- /dev/null +++ b/app/Views/settings/partials/circulation-type-fields.php @@ -0,0 +1,83 @@ +|null $typeForm current row, null for a new type + * @var array $cGlobals global layer (placeholders = inherited values) + */ +$tf = $typeForm ?? []; +$tfPrefix = $typeForm === null ? 'new-type' : 'type-' . (int) ($tf['id'] ?? 0); +$tfInt = static fn (string $k): string => isset($tf[$k]) && $tf[$k] !== '' ? (string) (int) $tf[$k] : ''; +$tfGlobal = [ + 'max_prestiti' => (int) ($cGlobals['max_active_loans'] ?? 0) > 0 ? (string) (int) $cGlobals['max_active_loans'] : '∞', + 'durata_prestito_giorni' => (string) (int) ($cGlobals['duration_days'] ?? 30), + 'max_durata_richiesta_giorni' => (string) (int) ($cGlobals['max_request_days'] ?? 90), + 'max_rinnovi' => (string) (int) ($cGlobals['max_renewals'] ?? 3), + 'giorni_rinnovo' => (string) (int) ($cGlobals['duration_days'] ?? 30), + 'max_prenotazioni' => '∞', + 'durata_tessera_giorni' => '—', + 'giorni_tolleranza' => (string) (int) ($cGlobals['grace_days'] ?? 0), +]; +$tfMoney = static fn (string $k): string => isset($tf[$k]) && $tf[$k] !== '' ? (string) $tf[$k] : ''; +$tfGlobalMoney = static function (mixed $v): string { + $cents = \App\Services\FineParameters::toCents((string) ($v ?? '')); + return $cents !== null && $cents > 0 ? \App\Services\FineParameters::format($cents) : '—'; +}; +$tfGlobalMoneyByField = [ + 'multa_giornaliera' => $tfGlobalMoney($cGlobals['daily_fine'] ?? null), + 'multa_massima_prestito' => $tfGlobalMoney($cGlobals['max_fine_per_loan'] ?? null), + 'soglia_blocco_debito' => $tfGlobalMoney($cGlobals['debt_block_threshold'] ?? null), +]; +?> +
    +
    + + +
    +
    + + +
    +
    + + +
    + +
    + + +

    +
    + + +
    + + +

    +
    + +
    +

    + + +

    +

    + + +

    +
    + + + +
    diff --git a/app/Views/settings/partials/feature-switch.php b/app/Views/settings/partials/feature-switch.php new file mode 100644 index 000000000..355094a96 --- /dev/null +++ b/app/Views/settings/partials/feature-switch.php @@ -0,0 +1,20 @@ + +
    +
    + +

    +
    + +
    diff --git a/app/Views/user_dashboard/prenotazioni.php b/app/Views/user_dashboard/prenotazioni.php index 12a3e1c3b..affe72805 100644 --- a/app/Views/user_dashboard/prenotazioni.php +++ b/app/Views/user_dashboard/prenotazioni.php @@ -732,6 +732,71 @@ function resolveCoverUrl(array $item, string $key = 'copertina_url'): string {
    + (int) $a['cents'], $ledgerAccrued)); + if ($ledgerMovements !== [] || $ledgerAccrued !== [] || $ledgerBalanceCents !== 0): + ?> +
    +
    + +
    +
    +

    +

    +
    +
    + +
    +
    + +
    +
    +

    +

    +
    +
    + + +
    + +
    +
    +
    +

    +
    +
    + + +
    +
    + + +
    + +
    + + +
    + +
    +
    +
    +
    + +
    + + +
    + +

    diff --git a/app/Views/utenti/_movimenti.php b/app/Views/utenti/_movimenti.php new file mode 100644 index 000000000..125a3e0a9 --- /dev/null +++ b/app/Views/utenti/_movimenti.php @@ -0,0 +1,220 @@ +> $ledgerMovements + * @var list $ledgerAccrued + * @var \App\Services\FineParameters $ledgerParams + * @var bool $ledgerCanManage loans.manage: the desk forms and the receipts; without it the ledger is read-only + * @var int $ledgerPage + * @var int $ledgerPages + * @var int $ledgerUpTo + * @var int $ledgerPerPage + * @var int $ledgerTotal + */ +use App\Services\FineParameters; +use App\Services\PatronLedger; + +$ledgerE = static fn ($v): string => htmlspecialchars((string) $v, ENT_QUOTES, 'UTF-8'); +$ledgerFmt = static fn (int $cents): string => FineParameters::format($cents); +$ledgerAccruedTotal = array_sum(array_map(static fn (array $a): int => $a['cents'], $ledgerAccrued)); +$ledgerBlocked = \App\Support\Features::enabled('debt_block') && $ledgerParams->debtBlockEnabled() && $ledgerBalanceCents > (int) $ledgerParams->debtBlockThresholdCents; +$ledgerFlash = is_scalar($_GET['ledger'] ?? null) ? (string) $_GET['ledger'] : ''; +$ledgerError = is_scalar($_GET['ledger_error'] ?? null) ? (string) $_GET['ledger_error'] : ''; +$ledgerMovementId = (int) (is_scalar($_GET['movement'] ?? null) ? $_GET['movement'] : 0); +$ledgerCsrf = App\Support\Csrf::ensureToken(); +// The ledger routes (/admin/users/{id}/ledger/*) need loans.manage +// (Permissions::permissionForPath): the forms, the buttons and the receipt +// links are rendered only for whoever may reach them. +$ledgerCanManage = $ledgerCanManage && App\Support\Permissions::can('loans.manage'); +?> + +
    +
    +

    +
    +

    +

    +
    +
    + +
    + +
    + +
    + + + 0 && $ledgerCanManage): ?> + + + + + + + +
    +
    + + + + + +
    + +

    debtBlockThresholdCents))) ?>

    +
    + + +
    +

    +

    +
      + +
    • + +
    +
    + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +

    +
    +
    +
    + +
    + + — +
    + +
    +
    + + + + + +
    +
    + 1): + $ledgerFrom = ($ledgerPage - 1) * $ledgerPerPage + 1; + $ledgerTo = min($ledgerTotal, $ledgerPage * $ledgerPerPage); + $ledgerPageUrl = static fn (int $page): string => url('/admin/users/details/' . $id) . '?ledger_page=' . $page . '&ledger_upto=' . $ledgerUpTo . '#movimenti'; + ?> + + + + +
    +
    + + +

    +
    + + +
    +
    + + +
    +
    + + +
    + +
    + +
    + + +

    +
    + + +
    +
    + + +
    + +
    + +
    + + +

    +
    + + +
    +
    + + +
    + +
    +
    + +
    diff --git a/app/Views/utenti/crea_utente.php b/app/Views/utenti/crea_utente.php index 5e01bb9a5..4f266cb7e 100644 --- a/app/Views/utenti/crea_utente.php +++ b/app/Views/utenti/crea_utente.php @@ -85,6 +85,7 @@
    +
    @@ -172,6 +173,14 @@ + +
    + +
    + @@ -211,6 +220,20 @@ const day = String(fiveYearsLater.getDate()).padStart(2, '0'); dataScadenzaTesseraField.value = `${year}-${month}-${day}`; dataScadenzaTesseraField.dataset.originalValue = dataScadenzaTesseraField.value; + // The 5-year date is a suggestion, not a date the librarian typed: tell + // the server, so "Calcola la scadenza dalla durata tessera" still applies + // the patron type's card duration. Any edit of the field clears the flag. + const userForm = document.getElementById('user-form'); + if (userForm) { + const prefilled = document.createElement('input'); + prefilled.type = 'hidden'; + prefilled.name = 'card_expiry_prefilled'; + prefilled.value = '1'; + userForm.appendChild(prefilled); + const typed = () => { prefilled.value = '0'; }; + dataScadenzaTesseraField.addEventListener('input', typed); + dataScadenzaTesseraField.addEventListener('change', typed); + } } const storeOriginalState = (container) => { diff --git a/app/Views/utenti/dettagli_utente.php b/app/Views/utenti/dettagli_utente.php index ad7e6d70d..2bef6a489 100644 --- a/app/Views/utenti/dettagli_utente.php +++ b/app/Views/utenti/dettagli_utente.php @@ -21,6 +21,9 @@ $creatoIl = $utente['created_at'] ?? ''; $aggiornatoIl = $utente['updated_at'] ?? ''; $note = $utente['note_utente'] ?? ''; +$photoRaw = (string) ($utente['foto_profilo_url'] ?? ''); +$photoStored = \App\Support\PatronPhoto::absolutePath($photoRaw) !== null ? $photoRaw : null; +$photoUrl = $photoStored !== null ? url('/admin/users/' . $id . '/photo') . '?v=' . substr(sha1($photoStored), 0, 10) : null; $statusLabels = [ 'attivo' => __('Attivo'), @@ -122,6 +125,12 @@ ?> + +
    +
    +
    + + () +
    +
    + + + + + +
    +

    +
    +
    +
    +
    maxActiveLoans > 0 ? (int) $patronPolicy->maxActiveLoans : __('Nessun limite') ?>
    +
    +
    +
    +
    durationDays ?>
    +
    +
    +
    +
    maxRequestDays ?>
    +
    +
    +
    +
    maxRenewals ?>
    +
    +
    +
    +
    + user->reservationsEnabled + ? __('Prenotazioni disattivate') + : ($patronPolicy->user->maxReservations > 0 ? sprintf(__('Massimo %d'), $patronPolicy->user->maxReservations) : __('Nessun limite')) ?> +
    +
    +
    +
    +
    user->cardDurationDays !== null ? (int) $patronPolicy->user->cardDurationDays : '—' ?>
    +
    +

    +
    + + +
    + @@ -340,6 +452,8 @@ + +

    diff --git a/app/Views/utenti/import.php b/app/Views/utenti/import.php new file mode 100644 index 000000000..030afceaf --- /dev/null +++ b/app/Views/utenti/import.php @@ -0,0 +1,518 @@ + __('— Ignora colonna —'), + 'uploading' => __('Caricamento del file...'), + 'rows' => __('%d righe da importare'), + 'processing' => __('Importazione in corso...'), + 'progress' => __('%d di %d righe elaborate'), + 'invalidResponse' => __('Risposta non valida dal server'), + 'networkError' => __('Errore di rete. Riprova.'), + 'stale' => __('Questa anteprima è stata sostituita da un altro file caricato: carica di nuovo il file per importarlo.'), + 'line' => __('Riga'), + 'name' => __('Utente'), + 'type' => __('Tipo'), + 'message' => __('Messaggio'), + 'types' => [ + 'duplicate' => __('Duplicato'), + 'validation' => __('Riga non valida'), + 'database' => __('Errore del database'), + 'email' => __('Email'), + 'warning' => __('Avviso'), + 'truncated' => __('Report troncato'), + ], +]; +?> +
    +
    +

    + + + +

    + + + + +
    +
    + + + +
    + + + + + + + + +
    + + + + +
    +

    + + +

    +

    +
    + + +
    +
    + + + +
    +

    + + +

    +
    + + + + + +
    +
    +
    + + ;', ',') ?> +
    +
    + +
    +
    +
    + + + + + + + + +
    +

    + + +

    +

    + + + + +
    +
    + + +
    +
    +

    + + +

    +
      +
    • +
    • +
    • +
    • +
    • + +
    • +
    • + +
    • +
    + +

    + + . +

    + +
    +
    +
    +

    +
    + + diff --git a/app/Views/utenti/index.php b/app/Views/utenti/index.php index fe1edc237..dd5b773b4 100644 --- a/app/Views/utenti/index.php +++ b/app/Views/utenti/index.php @@ -22,11 +22,17 @@

    -
    +
    @@ -223,6 +235,22 @@ class="btn-icon btn-secondary p-2 text-gray-500 hover:text-gray-700 hover:bg-gra
    + types() : []; ?> + +
    + + +
    + +