hapilibur 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Aditya Purnama
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,162 @@
1
+ Metadata-Version: 2.4
2
+ Name: hapilibur
3
+ Version: 0.2.0
4
+ Summary: Dataset & pustaka hari libur nasional, cuti bersama, dan jadwal imsakiyah Indonesia (2026). Data murni JSON, bebas dipakai lintas bahasa.
5
+ Author-email: Aditya Purnama <306090472+kiraadityaa@users.noreply.github.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/kiraadityaa/data-libur-nasional-indonesia
8
+ Project-URL: Repository, https://github.com/kiraadityaa/data-libur-nasional-indonesia
9
+ Keywords: indonesia,libur,holiday,cuti-bersama,imsakiyah,ramadhan,api,json
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
12
+ Classifier: Topic :: Utilities
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Requires-Python: >=3.9
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Dynamic: license-file
18
+
19
+ # data-libur-nasional-indonesia
20
+
21
+ Dataset dan REST API hari libur nasional, cuti bersama, dan jadwal imsakiyah Indonesia — 17 libur, 8 cuti bersama, 2 kota (2026). JSON murni, gratis, tanpa kunci API.
22
+
23
+ [![Python](https://img.shields.io/badge/Python-3.9%2B-blue)](#pustaka-python)
24
+ [![CI](https://img.shields.io/github/actions/workflow/status/kiraadityaa/data-libur-nasional-indonesia/ci.yml?label=CI)](https://github.com/kiraadityaa/data-libur-nasional-indonesia/actions)
25
+ [![License](https://img.shields.io/badge/License-MIT-green)](#lisensi)
26
+
27
+ Live demo dan playground: <https://data-libur-nasional-indonesia.vercel.app> · Spesifikasi mesin-terbaca: [`/openapi.json`](https://data-libur-nasional-indonesia.vercel.app/openapi.json) · Contoh mentah: [`data/libur-nasional.json`](data/libur-nasional.json)
28
+
29
+ ## Mulai dalam 30 detik
30
+
31
+ ```bash
32
+ # 1. Cek satu tanggal via API
33
+ curl "https://data-libur-nasional-indonesia.vercel.app/api/libur?date=2026-08-17"
34
+
35
+ # 2. Sama via pustaka Python (dari source repo ini)
36
+ pip install .
37
+ python -c "from hapilibur import is_libur; print(is_libur('2026-08-17'))" # True
38
+
39
+ # 3. Subscribe kalender tim ke Google/Apple Calendar
40
+ # https://data-libur-nasional-indonesia.vercel.app/api/libur.ics?year=2026
41
+ ```
42
+
43
+ ## Data mentah
44
+
45
+ Semua data adalah JSON statis tanpa framework — unduh dan parse dari bahasa apa pun.
46
+
47
+ <details>
48
+ <summary>Daftar file dataset</summary>
49
+
50
+ | File | Isi |
51
+ |------|-----|
52
+ | [`data/libur-nasional.json`](data/libur-nasional.json) | Hari libur nasional per tahun |
53
+ | [`data/cuti-bersama.json`](data/cuti-bersama.json) | Cuti bersama per tahun |
54
+ | [`data/imsak/jakarta-2026.json`](data/imsak/jakarta-2026.json) | Imsakiyah Jakarta (30 hari Ramadan 1447 H) |
55
+ | [`data/imsak/surabaya-2026.json`](data/imsak/surabaya-2026.json) | Imsakiyah Surabaya (30 hari Ramadan 1447 H) |
56
+
57
+ ```bash
58
+ curl https://raw.githubusercontent.com/kiraadityaa/data-libur-nasional-indonesia/main/data/libur-nasional.json
59
+ ```
60
+
61
+ </details>
62
+
63
+ ## REST API
64
+
65
+ Base URL: `https://data-libur-nasional-indonesia.vercel.app`. Semua endpoint `GET`, cache publik, mendukung preflight `OPTIONS`, dan mengembalikan error terstandar `{error, hint}`.
66
+
67
+ | Endpoint | Guna | Contoh |
68
+ |----------|------|--------|
69
+ | `/api/libur?date=YYYY-MM-DD` | Cek satu tanggal | `?date=2026-08-17` |
70
+ | `/api/libur?year=YYYY` | Rekap setahun (`libur_nasional`, `cuti_bersama`, total) | `?year=2026`, `&format=csv` untuk CSV |
71
+ | `/api/libur?year=YYYY&month=M` | Libur satu bulan | `?year=2026&month=3` |
72
+ | `/api/libur?next=1` | N libur berikutnya (`count` 1–30, default hari ini WIB) | `?next=1&date=2026-06-11&count=3` |
73
+ | `/api/libur?from=&to=` | Rentang tanggal inklusif | `?from=2026-03-20&to=2026-03-24` |
74
+ | `/api/imsak?city=` | Imsakiyah 30 hari (`imsak, subuh, zuhur, ashar, magrib, isya`) | `?city=jakarta&year=2026`, `&date=2026-03-01`, `&format=csv` |
75
+ | `/api/kota` | Daftar kota, provinsi, zona waktu, tahun tersedia | — |
76
+ | `/api/libur.ics` | Kalender iCalendar RFC 5545 | `?year=2026`, `?type=libur`, `?next=1&count=5` |
77
+ | `/api/health` | Health check | — |
78
+
79
+ Parameter `type=all|libur|cuti` berlaku untuk `/api/libur` dan `/api/libur.ics`. Dokumentasi lengkap ada di [`/openapi.json`](https://data-libur-nasional-indonesia.vercel.app/openapi.json) (live: `/api/openapi.json`).
80
+
81
+ Contoh respons:
82
+
83
+ ```json
84
+ // GET /api/libur?date=2026-08-17
85
+ {
86
+ "date": "2026-08-17",
87
+ "is_holiday": true,
88
+ "libur_nasional": { "date": "2026-08-17", "name": "Hari Proklamasi Kemerdekaan RI" },
89
+ "cuti_bersama": null
90
+ }
91
+ ```
92
+
93
+ ```bash
94
+ # Google Calendar → Settings → Import & export
95
+ curl -o libur-indonesia.ics "https://data-libur-nasional-indonesia.vercel.app/api/libur.ics?year=2026"
96
+ ```
97
+
98
+ ## Pustaka Python
99
+
100
+ ```bash
101
+ pip install .
102
+ ```
103
+
104
+ ```python
105
+ from hapilibur import check_detail, imsak, is_libur, month, upcoming
106
+
107
+ is_libur("2026-08-17") # True
108
+ is_libur("2026-03-23", include_cuti=False) # False — itu cuti bersama
109
+ check_detail("2026-08-17")
110
+ # {'date': '2026-08-17', 'name': 'Hari Proklamasi Kemerdekaan RI', 'jenis': ['libur_nasional']}
111
+ month(2026, 3) # 7 tanggal (libur + cuti)
112
+ upcoming("2026-06-11", n=3) # 3 libur terdekat
113
+ imsak("jakarta", 2026)["schedule"][0]
114
+ # {'day': 1, 'date': '2026-02-19', 'imsak': '04:31', 'subuh': '04:41', ...}
115
+ ```
116
+
117
+ <details>
118
+ <summary>Referensi CLI lengkap</summary>
119
+
120
+ ```bash
121
+ hapilibur check 2026-08-17 # cek satu tanggal (tambah --json untuk detail)
122
+ hapilibur tahun 2026 [--json|--csv] # libur nasional setahun
123
+ hapilibur cuti 2026 [--json|--csv] # cuti bersama setahun
124
+ hapilibur bulan 2026 3 [--tanpa-cuti] # libur satu bulan
125
+ hapilibur selang 2026-01-01 2026-12-31 # rentang inklusif (alias: range)
126
+ hapilibur upcoming 2026-06-11 -n 3 # libur berikutnya (alias: next)
127
+ hapilibur imsak jakarta 2026 [--json] # imsakiyah satu kota
128
+ hapilibur kota # kota yang tersedia
129
+ hapilibur --version
130
+ ```
131
+
132
+ Error input (tanggal salah, kota tak dikenal) keluar sebagai satu baris `Error: ...` dengan exit code 2.
133
+
134
+ </details>
135
+
136
+ ## English summary
137
+
138
+ Static JSON dataset and free REST API for Indonesian national holidays, collective leave (*cuti bersama*), and Ramadan prayer times (*imsakiyah*) for Jakarta and Surabaya. Sources: the tri-ministerial decree (SKB 3 Menteri) and the Ministry of Religious Affairs. No API key. Endpoints return JSON, CSV, or iCalendar; a Python library and CLI (`hapilibur`) ship in the same repo. See [Quickstart](#mulai-dalam-30-detik) for copy-paste examples and [`/openapi.json`](https://data-libur-nasional-indonesia.vercel.app/openapi.json) for the full spec.
139
+
140
+ ## Pengembangan dan kontribusi
141
+
142
+ ```bash
143
+ npm run typecheck # tipe API TypeScript
144
+ npm run build:site # generate public/index.html + openapi.json + sitemap.xml + manifest
145
+ npm run validate # validasi data JSON
146
+ npm test # pytest
147
+ npm run verify # semua di atas, sekali jalan
148
+ ```
149
+
150
+ Menambah tahun baru:
151
+
152
+ 1. Tambahkan data ke [`data/libur-nasional.json`](data/libur-nasional.json) dan [`data/cuti-bersama.json`](data/cuti-bersama.json).
153
+ 2. `npm run validate` — wajib lolos.
154
+ 3. `npm run build:site` agar halaman statis tersinkron.
155
+ 4. `python3 -m pytest -q` dan `python3 scripts/generate.py` agar salinan di package tersinkron.
156
+ 5. Buat pull request.
157
+
158
+ Sumber resmi: [SKB 3 Menteri 2026 (PDF)](https://www.kemenkopmk.go.id/sites/default/files/pengumuman/2025-09/SKB%20Libur%20Nasional%20dan%20Cuti%20Bersama%20Tahun%202026.pdf) · [Bimas Islam Kemenag](https://bimasislam.kemenag.go.id). Data mengikuti penetapan pemerintah dan bisa direvisi; awal Ramadan bisa bergeser ±1 hari karena Rukyatul Hilal; data 2027 ditambahkan setelah SKB resmi terbit.
159
+
160
+ ## Lisensi
161
+
162
+ MIT — lihat [LICENSE](LICENSE). Data publik bersumber dari instansi pemerintah; atribusi tetap dicantumkan.
@@ -0,0 +1,144 @@
1
+ # data-libur-nasional-indonesia
2
+
3
+ Dataset dan REST API hari libur nasional, cuti bersama, dan jadwal imsakiyah Indonesia — 17 libur, 8 cuti bersama, 2 kota (2026). JSON murni, gratis, tanpa kunci API.
4
+
5
+ [![Python](https://img.shields.io/badge/Python-3.9%2B-blue)](#pustaka-python)
6
+ [![CI](https://img.shields.io/github/actions/workflow/status/kiraadityaa/data-libur-nasional-indonesia/ci.yml?label=CI)](https://github.com/kiraadityaa/data-libur-nasional-indonesia/actions)
7
+ [![License](https://img.shields.io/badge/License-MIT-green)](#lisensi)
8
+
9
+ Live demo dan playground: <https://data-libur-nasional-indonesia.vercel.app> · Spesifikasi mesin-terbaca: [`/openapi.json`](https://data-libur-nasional-indonesia.vercel.app/openapi.json) · Contoh mentah: [`data/libur-nasional.json`](data/libur-nasional.json)
10
+
11
+ ## Mulai dalam 30 detik
12
+
13
+ ```bash
14
+ # 1. Cek satu tanggal via API
15
+ curl "https://data-libur-nasional-indonesia.vercel.app/api/libur?date=2026-08-17"
16
+
17
+ # 2. Sama via pustaka Python (dari source repo ini)
18
+ pip install .
19
+ python -c "from hapilibur import is_libur; print(is_libur('2026-08-17'))" # True
20
+
21
+ # 3. Subscribe kalender tim ke Google/Apple Calendar
22
+ # https://data-libur-nasional-indonesia.vercel.app/api/libur.ics?year=2026
23
+ ```
24
+
25
+ ## Data mentah
26
+
27
+ Semua data adalah JSON statis tanpa framework — unduh dan parse dari bahasa apa pun.
28
+
29
+ <details>
30
+ <summary>Daftar file dataset</summary>
31
+
32
+ | File | Isi |
33
+ |------|-----|
34
+ | [`data/libur-nasional.json`](data/libur-nasional.json) | Hari libur nasional per tahun |
35
+ | [`data/cuti-bersama.json`](data/cuti-bersama.json) | Cuti bersama per tahun |
36
+ | [`data/imsak/jakarta-2026.json`](data/imsak/jakarta-2026.json) | Imsakiyah Jakarta (30 hari Ramadan 1447 H) |
37
+ | [`data/imsak/surabaya-2026.json`](data/imsak/surabaya-2026.json) | Imsakiyah Surabaya (30 hari Ramadan 1447 H) |
38
+
39
+ ```bash
40
+ curl https://raw.githubusercontent.com/kiraadityaa/data-libur-nasional-indonesia/main/data/libur-nasional.json
41
+ ```
42
+
43
+ </details>
44
+
45
+ ## REST API
46
+
47
+ Base URL: `https://data-libur-nasional-indonesia.vercel.app`. Semua endpoint `GET`, cache publik, mendukung preflight `OPTIONS`, dan mengembalikan error terstandar `{error, hint}`.
48
+
49
+ | Endpoint | Guna | Contoh |
50
+ |----------|------|--------|
51
+ | `/api/libur?date=YYYY-MM-DD` | Cek satu tanggal | `?date=2026-08-17` |
52
+ | `/api/libur?year=YYYY` | Rekap setahun (`libur_nasional`, `cuti_bersama`, total) | `?year=2026`, `&format=csv` untuk CSV |
53
+ | `/api/libur?year=YYYY&month=M` | Libur satu bulan | `?year=2026&month=3` |
54
+ | `/api/libur?next=1` | N libur berikutnya (`count` 1–30, default hari ini WIB) | `?next=1&date=2026-06-11&count=3` |
55
+ | `/api/libur?from=&to=` | Rentang tanggal inklusif | `?from=2026-03-20&to=2026-03-24` |
56
+ | `/api/imsak?city=` | Imsakiyah 30 hari (`imsak, subuh, zuhur, ashar, magrib, isya`) | `?city=jakarta&year=2026`, `&date=2026-03-01`, `&format=csv` |
57
+ | `/api/kota` | Daftar kota, provinsi, zona waktu, tahun tersedia | — |
58
+ | `/api/libur.ics` | Kalender iCalendar RFC 5545 | `?year=2026`, `?type=libur`, `?next=1&count=5` |
59
+ | `/api/health` | Health check | — |
60
+
61
+ Parameter `type=all|libur|cuti` berlaku untuk `/api/libur` dan `/api/libur.ics`. Dokumentasi lengkap ada di [`/openapi.json`](https://data-libur-nasional-indonesia.vercel.app/openapi.json) (live: `/api/openapi.json`).
62
+
63
+ Contoh respons:
64
+
65
+ ```json
66
+ // GET /api/libur?date=2026-08-17
67
+ {
68
+ "date": "2026-08-17",
69
+ "is_holiday": true,
70
+ "libur_nasional": { "date": "2026-08-17", "name": "Hari Proklamasi Kemerdekaan RI" },
71
+ "cuti_bersama": null
72
+ }
73
+ ```
74
+
75
+ ```bash
76
+ # Google Calendar → Settings → Import & export
77
+ curl -o libur-indonesia.ics "https://data-libur-nasional-indonesia.vercel.app/api/libur.ics?year=2026"
78
+ ```
79
+
80
+ ## Pustaka Python
81
+
82
+ ```bash
83
+ pip install .
84
+ ```
85
+
86
+ ```python
87
+ from hapilibur import check_detail, imsak, is_libur, month, upcoming
88
+
89
+ is_libur("2026-08-17") # True
90
+ is_libur("2026-03-23", include_cuti=False) # False — itu cuti bersama
91
+ check_detail("2026-08-17")
92
+ # {'date': '2026-08-17', 'name': 'Hari Proklamasi Kemerdekaan RI', 'jenis': ['libur_nasional']}
93
+ month(2026, 3) # 7 tanggal (libur + cuti)
94
+ upcoming("2026-06-11", n=3) # 3 libur terdekat
95
+ imsak("jakarta", 2026)["schedule"][0]
96
+ # {'day': 1, 'date': '2026-02-19', 'imsak': '04:31', 'subuh': '04:41', ...}
97
+ ```
98
+
99
+ <details>
100
+ <summary>Referensi CLI lengkap</summary>
101
+
102
+ ```bash
103
+ hapilibur check 2026-08-17 # cek satu tanggal (tambah --json untuk detail)
104
+ hapilibur tahun 2026 [--json|--csv] # libur nasional setahun
105
+ hapilibur cuti 2026 [--json|--csv] # cuti bersama setahun
106
+ hapilibur bulan 2026 3 [--tanpa-cuti] # libur satu bulan
107
+ hapilibur selang 2026-01-01 2026-12-31 # rentang inklusif (alias: range)
108
+ hapilibur upcoming 2026-06-11 -n 3 # libur berikutnya (alias: next)
109
+ hapilibur imsak jakarta 2026 [--json] # imsakiyah satu kota
110
+ hapilibur kota # kota yang tersedia
111
+ hapilibur --version
112
+ ```
113
+
114
+ Error input (tanggal salah, kota tak dikenal) keluar sebagai satu baris `Error: ...` dengan exit code 2.
115
+
116
+ </details>
117
+
118
+ ## English summary
119
+
120
+ Static JSON dataset and free REST API for Indonesian national holidays, collective leave (*cuti bersama*), and Ramadan prayer times (*imsakiyah*) for Jakarta and Surabaya. Sources: the tri-ministerial decree (SKB 3 Menteri) and the Ministry of Religious Affairs. No API key. Endpoints return JSON, CSV, or iCalendar; a Python library and CLI (`hapilibur`) ship in the same repo. See [Quickstart](#mulai-dalam-30-detik) for copy-paste examples and [`/openapi.json`](https://data-libur-nasional-indonesia.vercel.app/openapi.json) for the full spec.
121
+
122
+ ## Pengembangan dan kontribusi
123
+
124
+ ```bash
125
+ npm run typecheck # tipe API TypeScript
126
+ npm run build:site # generate public/index.html + openapi.json + sitemap.xml + manifest
127
+ npm run validate # validasi data JSON
128
+ npm test # pytest
129
+ npm run verify # semua di atas, sekali jalan
130
+ ```
131
+
132
+ Menambah tahun baru:
133
+
134
+ 1. Tambahkan data ke [`data/libur-nasional.json`](data/libur-nasional.json) dan [`data/cuti-bersama.json`](data/cuti-bersama.json).
135
+ 2. `npm run validate` — wajib lolos.
136
+ 3. `npm run build:site` agar halaman statis tersinkron.
137
+ 4. `python3 -m pytest -q` dan `python3 scripts/generate.py` agar salinan di package tersinkron.
138
+ 5. Buat pull request.
139
+
140
+ Sumber resmi: [SKB 3 Menteri 2026 (PDF)](https://www.kemenkopmk.go.id/sites/default/files/pengumuman/2025-09/SKB%20Libur%20Nasional%20dan%20Cuti%20Bersama%20Tahun%202026.pdf) · [Bimas Islam Kemenag](https://bimasislam.kemenag.go.id). Data mengikuti penetapan pemerintah dan bisa direvisi; awal Ramadan bisa bergeser ±1 hari karena Rukyatul Hilal; data 2027 ditambahkan setelah SKB resmi terbit.
141
+
142
+ ## Lisensi
143
+
144
+ MIT — lihat [LICENSE](LICENSE). Data publik bersumber dari instansi pemerintah; atribusi tetap dicantumkan.
@@ -0,0 +1,34 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "hapilibur"
7
+ version = "0.2.0"
8
+ description = "Dataset & pustaka hari libur nasional, cuti bersama, dan jadwal imsakiyah Indonesia (2026). Data murni JSON, bebas dipakai lintas bahasa."
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "Aditya Purnama", email = "306090472+kiraadityaa@users.noreply.github.com" }]
13
+ keywords = ["indonesia", "libur", "holiday", "cuti-bersama", "imsakiyah", "ramadhan", "api", "json"]
14
+ classifiers = [
15
+ "Programming Language :: Python :: 3",
16
+ "Topic :: Software Development :: Libraries :: Python Modules",
17
+ "Topic :: Utilities",
18
+ "License :: OSI Approved :: MIT License",
19
+ ]
20
+ dependencies = []
21
+
22
+ [project.urls]
23
+ Homepage = "https://github.com/kiraadityaa/data-libur-nasional-indonesia"
24
+ Repository = "https://github.com/kiraadityaa/data-libur-nasional-indonesia"
25
+
26
+ [project.scripts]
27
+ hapilibur = "hapilibur.__main__:main"
28
+
29
+ [tool.setuptools]
30
+ package-dir = { "" = "src" }
31
+ packages = ["hapilibur"]
32
+
33
+ [tool.setuptools.package-data]
34
+ hapilibur = ["data/*.json", "py.typed"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,25 @@
1
+ """hapilibur — dataset & bantu hitung hari libur nasional dan cuti bersama Indonesia.
2
+
3
+ Sumber: SKB 3 Menteri (Hari Libur Nasional & Cuti Bersama) dan Imsakiyah Bimas Islam Kemenag.
4
+ """
5
+
6
+ from .core import ( # noqa: F401
7
+ CUTI_FILE,
8
+ LIBUR_FILE,
9
+ available_cities,
10
+ between,
11
+ check,
12
+ check_detail,
13
+ cuti,
14
+ holiday_range,
15
+ imsak,
16
+ imsak_cities_available,
17
+ is_holiday,
18
+ is_libur,
19
+ libur,
20
+ month,
21
+ to_csv,
22
+ upcoming,
23
+ )
24
+
25
+ __version__ = "0.2.0"
@@ -0,0 +1,151 @@
1
+ """CLI hapilibur: cek hari libur, cuti bersama, dan jadwal imsakiyah dari terminal."""
2
+ from __future__ import annotations
3
+
4
+ import argparse
5
+ import datetime
6
+ import json
7
+ import sys
8
+
9
+ from . import __version__, core
10
+
11
+
12
+ def _emit(entries: list[dict] | dict, as_json: bool, as_csv: bool) -> None:
13
+ if as_json:
14
+ print(json.dumps(entries, ensure_ascii=False, indent=2))
15
+ return
16
+ if as_csv:
17
+ items = entries if isinstance(entries, list) else [entries]
18
+ print(core.to_csv([e for e in items if e.get("date")]), end="")
19
+ return
20
+ if isinstance(entries, dict):
21
+ print(f"{entries.get('date')} {entries.get('name')}")
22
+ return
23
+ for entry in entries:
24
+ print(f"{entry['date']} {entry['name']}")
25
+
26
+
27
+ def _fmt_date(value: str, as_json: bool) -> None:
28
+ detail = core.check_detail(value)
29
+ if as_json:
30
+ print(json.dumps(detail or {"date": value, "is_holiday": False}, ensure_ascii=False, indent=2))
31
+ return
32
+ print(f"{value}: {detail['name'] if detail else 'Bukan hari libur nasional / cuti bersama'}")
33
+
34
+
35
+ def _print_holidays(entries: list[dict], year: int | None, as_json: bool, as_csv: bool) -> None:
36
+ if not entries:
37
+ label = f"tahun {year}" if year else "rentang tersebut"
38
+ if as_json:
39
+ print("[]")
40
+ elif not as_csv:
41
+ print(f"Tidak ada data untuk {label}.")
42
+ else:
43
+ print("tanggal,nama")
44
+ return
45
+ _emit(entries, as_json, as_csv)
46
+
47
+
48
+ def _print_imsak(payload: dict, as_json: bool) -> None:
49
+ if as_json:
50
+ print(json.dumps(payload, ensure_ascii=False, indent=2))
51
+ return
52
+ print(f"{payload['title']} ({payload['timezone']})")
53
+ print(f"{'Hari':<5}{'Tanggal':<12}{'Imsak':<7}{'Subuh':<7}{'Zuhur':<7}{'Ashar':<7}{'Magrib':<7}{'Isya':<7}")
54
+ for item in payload["schedule"]:
55
+ print(
56
+ f"{item['day']:<5}{item['date']:<12}{item['imsak']:<7}{item['subuh']:<7}"
57
+ f"{item['zuhur']:<7}{item['ashar']:<7}{item['magrib']:<7}{item['isya']:<7}"
58
+ )
59
+
60
+
61
+ def _add_output_flags(parser: argparse.ArgumentParser) -> None:
62
+ parser.add_argument("--json", action="store_true", help="keluarkan sebagai JSON")
63
+ parser.add_argument("--csv", action="store_true", help="keluarkan sebagai CSV")
64
+
65
+
66
+ def _build_parser() -> argparse.ArgumentParser:
67
+ parser = argparse.ArgumentParser(
68
+ prog="hapilibur",
69
+ description="Cek hari libur nasional, cuti bersama, dan jadwal imsakiyah Indonesia.",
70
+ )
71
+ parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
72
+ sub = parser.add_subparsers(dest="command", required=True)
73
+
74
+ p_check = sub.add_parser("check", help="cek apakah suatu tanggal hari libur")
75
+ p_check.add_argument("tanggal", help="tanggal dalam format YYYY-MM-DD, misal 2026-08-17")
76
+ p_check.add_argument("--json", action="store_true", help="keluarkan sebagai JSON")
77
+
78
+ p_tahun = sub.add_parser("tahun", help="daftar hari libur nasional satu tahun")
79
+ p_tahun.add_argument("tahun", nargs="?", type=int, help="tahun (default: tahun berjalan)")
80
+ _add_output_flags(p_tahun)
81
+
82
+ p_cuti = sub.add_parser("cuti", help="daftar cuti bersama satu tahun")
83
+ p_cuti.add_argument("tahun", nargs="?", type=int, help="tahun (default: tahun berjalan)")
84
+ _add_output_flags(p_cuti)
85
+
86
+ p_bulan = sub.add_parser("bulan", help="daftar tanggal libur satu bulan")
87
+ p_bulan.add_argument("tahun", type=int, help="tahun, misal 2026")
88
+ p_bulan.add_argument("bulan", type=int, help="bulan 1-12, misal 3")
89
+ p_bulan.add_argument("--tanpa-cuti", action="store_true", help="kecualikan cuti bersama")
90
+ _add_output_flags(p_bulan)
91
+
92
+ p_selang = sub.add_parser("selang", help="daftar tanggal libur dalam rentang tanggal", aliases=["range"])
93
+ p_selang.add_argument("dari", help="tanggal awal YYYY-MM-DD")
94
+ p_selang.add_argument("sampai", help="tanggal akhir YYYY-MM-DD")
95
+ _add_output_flags(p_selang)
96
+
97
+ p_next = sub.add_parser("upcoming", help="hari libur berikutnya", aliases=["next"])
98
+ p_next.add_argument("tanggal", nargs="?", help="awal pencarian (default: hari ini)")
99
+ p_next.add_argument("-n", "--count", type=int, default=1, help="jumlah hari libur (default: 1)")
100
+ p_next.add_argument("--json", action="store_true", help="keluarkan sebagai JSON")
101
+
102
+ p_imsak = sub.add_parser("imsak", help="jadwal imsakiyah sebuah kota")
103
+ p_imsak.add_argument("kota", help="nama kota, misal jakarta atau surabaya")
104
+ p_imsak.add_argument("tahun", nargs="?", type=int, help="tahun (default: tahun berjalan)")
105
+ p_imsak.add_argument("--json", action="store_true", help="keluarkan sebagai JSON")
106
+
107
+ sub.add_parser("kota", help="daftar kota yang tersedia")
108
+
109
+ return parser
110
+
111
+
112
+ def main(argv: list[str] | None = None) -> int:
113
+ args = _build_parser().parse_args(argv)
114
+
115
+ try:
116
+ if args.command == "check":
117
+ _fmt_date(args.tanggal, as_json=args.json)
118
+ elif args.command == "tahun":
119
+ year = args.tahun or datetime.date.today().year
120
+ _print_holidays(core.libur(year), year, args.json, args.csv)
121
+ elif args.command == "cuti":
122
+ year = args.tahun or datetime.date.today().year
123
+ _print_holidays(core.cuti(year), year, args.json, args.csv)
124
+ elif args.command == "bulan":
125
+ entries = core.month(args.tahun, args.bulan, include_cuti=not args.tanpa_cuti)
126
+ _print_holidays(entries, args.tahun, args.json, args.csv)
127
+ elif args.command in ("selang", "range"):
128
+ _print_holidays(core.between(args.dari, args.sampai), None, args.json, args.csv)
129
+ elif args.command in ("upcoming", "next"):
130
+ result = core.upcoming(args.tanggal, n=args.count)
131
+ if args.json:
132
+ print(json.dumps(result, ensure_ascii=False, indent=2))
133
+ elif args.count == 1:
134
+ assert isinstance(result, dict)
135
+ print(f"{result['date']} {result['name']}")
136
+ else:
137
+ assert isinstance(result, list)
138
+ for item in result:
139
+ print(f"{item['date']} {item['name']}")
140
+ elif args.command == "imsak":
141
+ _print_imsak(core.imsak(args.kota, args.tahun), as_json=args.json)
142
+ elif args.command == "kota":
143
+ print(", ".join(core.available_cities()))
144
+ except ValueError as exc:
145
+ print(f"Error: {exc}", file=sys.stderr)
146
+ return 2
147
+ return 0
148
+
149
+
150
+ if __name__ == "__main__":
151
+ sys.exit(main())