@jenova-marie/sumerian-date 0.1.0

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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 jenova-marie
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.
package/README.md ADDED
@@ -0,0 +1,199 @@
1
+ # sumerian-date
2
+
3
+ Convert Gregorian dates to the **Nippur Sumerian ritual calendar** as documented by [The Ishtar Gate](https://web.archive.org/web/20250125122100/https://www.theishtargate.com/Calendar/) (archived January 2025).
4
+
5
+ Pure JavaScript, zero runtime dependencies, works in Node.js 14+.
6
+
7
+ ---
8
+
9
+ ## Background
10
+
11
+ The Sumerian calendar is a **lunisolar calendar** rooted in the Nippur tradition, the sacred city of Enlil. Each month begins at the first sunset after the astronomical new moon. The year begins with the month whose new moon falls after the vernal equinox. Short years contain 12 months; long years contain 13, following the 19-year Metonic cycle.
12
+
13
+ Astronomical calculations implement the algorithms of Jean Meeus (*Astronomical Algorithms*, 2nd ed.) for:
14
+ - Vernal equinox (Ch. 27)
15
+ - New moon (Ch. 49)
16
+ - Sunset time for Nippur (32.13°N, 45.23°E by default)
17
+
18
+ The day-start rule (sunset) is verified against the Ishtar Gate app reference:
19
+ > New moon 8 April 2024 21:20 local (UTC+3) → year start **10 April 2024** ✓
20
+
21
+ ---
22
+
23
+ ## Installation
24
+
25
+ ```bash
26
+ npm install sumerian-date
27
+ ```
28
+
29
+ ---
30
+
31
+ ## Quick Start
32
+
33
+ ```javascript
34
+ const { sumerianDate, sumerianYear } = require('sumerian-date');
35
+
36
+ // Today's Sumerian date
37
+ const today = sumerianDate();
38
+ console.log(today.toString());
39
+ // → "Day 15 of Šekinku — Gregorian 5 Mar 2026"
40
+
41
+ console.log(today.monthCuneiform); // 𒌗𒊺𒆥𒋻
42
+ console.log(today.monthMeaning); // "Reaping of Barley"
43
+ console.log(today.observances); // [{ name: 'Ešeš', description: '...' }, ...]
44
+ ```
45
+
46
+ ---
47
+
48
+ ## API
49
+
50
+ ### `sumerianDate([date], [opts])` → `SumerianDate`
51
+
52
+ Convert a JS `Date` to its Sumerian equivalent. Defaults to `new Date()` (now).
53
+
54
+ ```javascript
55
+ const sd = sumerianDate(new Date('2024-06-04T15:37:00Z'));
56
+
57
+ sd.monthName // "Gusisu"
58
+ sd.monthCuneiform // "𒌗𒄞𒋛𒁲"
59
+ sd.monthMeaning // "Marching Forth of Oxen"
60
+ sd.monthDescription // "Beginning of the agricultural season..."
61
+ sd.dayOfMonth // 27
62
+ sd.monthLengthDays // 29
63
+ sd.monthStartDate // Date object
64
+ sd.monthEndDate // Date object
65
+ sd.cyclePosition // 18 (position in the 19-year Metonic cycle)
66
+ sd.isIntercalaryYear // false
67
+ sd.isIntercalaryMonth // false
68
+ sd.observances // Array of { name, description } for today's observances
69
+ sd.toString() // "Day 27 of Gusisu — Gregorian 4 Jun 2024"
70
+ ```
71
+
72
+ **opts:**
73
+ | Option | Default | Description |
74
+ |--------|---------|-------------|
75
+ | `latitude` | `32.13` | Reference latitude for sunset (Nippur) |
76
+ | `longitude` | `45.23` | Reference longitude for sunset (Nippur) |
77
+
78
+ ### `sumerianYear([gregorianYear], [opts])` → `SumerianYear`
79
+
80
+ Get the full year structure including all months with start dates.
81
+
82
+ ```javascript
83
+ const year = sumerianYear(2026);
84
+
85
+ year.springGregorianYear // 2026
86
+ year.cyclePosition // 1 (first year of new 19-year cycle)
87
+ year.isLongYear // false
88
+ year.yearStart // Date: ~late March / early April
89
+ year.yearEnd // Date
90
+ year.nextYearStart // Date
91
+
92
+ year.months.forEach(m => {
93
+ console.log(`${m.number}. ${m.name} — starts ${m.startDate.toDateString()} (${m.lengthDays} days)`);
94
+ });
95
+ // 1. Barazagar — starts Thu Apr 02 2026 (30 days)
96
+ // 2. Gusisu — starts Sat May 02 2026 (29 days)
97
+ // ...
98
+ // 12. Šekinku — starts Mon Feb 22 2027 (29 days)
99
+ ```
100
+
101
+ ### `cyclePosition(springGregorianYear)` → `number` (1–19)
102
+
103
+ Returns the position within the 19-year Metonic cycle.
104
+ Long years (13 months) are at positions: **3, 6, 8, 11, 14, 17, 19**.
105
+
106
+ ```javascript
107
+ cyclePosition(2007) // 1 — first year of cycle
108
+ cyclePosition(2009) // 3 — long year (Diri Šekinku appended)
109
+ cyclePosition(2023) // 17 — long year (Diri Kin Inana inserted after month 6)
110
+ cyclePosition(2026) // 1 — new cycle begins
111
+ ```
112
+
113
+ ### `vernalEquinox(year)` → `Date`
114
+
115
+ ```javascript
116
+ vernalEquinox(2024) // Thu Mar 20 2024 03:06 UTC
117
+ ```
118
+
119
+ ### `nextNewMoon(afterDate)` → `Date`
120
+
121
+ ```javascript
122
+ nextNewMoon(new Date('2024-03-20')) // Mon Apr 08 2024 18:21 UTC
123
+ ```
124
+
125
+ ### `months` — Array of month data
126
+
127
+ The twelve standard months. Each has:
128
+ - `number` (1–12)
129
+ - `name` — transliteration
130
+ - `cuneiform` — Unicode cuneiform
131
+ - `meaning` — English translation
132
+ - `season`
133
+ - `description`
134
+ - `festivals` — array of `{ day, name, description }`
135
+
136
+ ### `intercalaryMonth` / `intercalaryMonthYear17`
137
+
138
+ The two intercalary month variants (Diri Šekinku and Diri Kin Inana).
139
+
140
+ ---
141
+
142
+ ## The Twelve Months
143
+
144
+ | # | Name | 𒀭 | Meaning | Season |
145
+ |---|------|-----|---------|--------|
146
+ | 1 | Barazagar | 𒌗𒁈𒍠𒃻 | Dais of the Sanctuary | Spring |
147
+ | 2 | Gusisu | 𒌗𒄞𒋛𒁲 | Marching Forth of Oxen | Spring |
148
+ | 3 | Sig Ga | 𒌗𒋞𒂵 | Placing of Bricks | Spring |
149
+ | 4 | Šunumun | 𒌗𒋗𒆰𒈾 | Seeding | Summer |
150
+ | 5 | Nenegar | 𒌗𒉈𒉈𒃻 | Lighting of the Braziers | Summer |
151
+ | 6 | Kin Inana | 𒌗𒆥𒀭𒈹 | The Labours of Inana | Summer |
152
+ | 7 | Duku | 𒌗𒇯𒆬 | The Sacred Mound | Autumn |
153
+ | 8 | Apin Dua | 𒌗𒀳𒃮𒀀 | Releasing of the Plough | Autumn |
154
+ | 9 | Gan Gan-e | 𒌗𒃶𒃶𒈾 | Coming of Clouds | Autumn |
155
+ | 10 | Kusu | 𒌗𒆬𒋆 | Lady of the Grain | Autumn |
156
+ | 11 | Uduru | 𒌗𒍩𒀀 | Emmer | Winter |
157
+ | 12 | Šekinku | 𒌗𒊺𒆥𒋻 | Reaping of Barley | Winter |
158
+ | 13* | Diri Šekinku | 𒌗𒋛𒀀𒊺𒆥𒋻 | Second Reaping of Barley | Winter |
159
+
160
+ *Intercalary month, appended after Šekinku in cycle years 3, 6, 8, 11, 14, 19.
161
+ In cycle year 17, *Diri Kin Inana* is inserted after month 6 instead.
162
+
163
+ ---
164
+
165
+ ## Monthly Observances
166
+
167
+ Two observances occur every month:
168
+
169
+ | Day | Name | Description |
170
+ |-----|------|-------------|
171
+ | 7 | **Ešeš** (Quarter Moon) | First-quarter moon; offering to household gods |
172
+ | 15 | **Ešeš** (Full Moon) | Great Offering to all household gods |
173
+ | Last | **Kisiga** (New Moon) | Funerary offerings; libation of fresh water for the dead |
174
+
175
+ These are returned in `sumerianDate().observances` for the current day.
176
+
177
+ ---
178
+
179
+ ## Calendar Rules (Summary)
180
+
181
+ 1. **Month start**: First sunset after the astronomical new moon (day begins at sunset, per Sumerian custom).
182
+ 2. **Year start**: The month whose new moon falls first after the vernal equinox.
183
+ 3. **Intercalation**: Metonic 19-year cycle. Years 3, 6, 8, 11, 14, 17, 19 have 13 months. Cycle began spring 2007.
184
+ 4. **Month 13 placement**: After month 12 in most long years; after month 6 (as *Diri Kin Inana*) in cycle year 17.
185
+ 5. **Reference location**: Nippur (32.13°N, 45.23°E) by default. Override with `opts.latitude` / `opts.longitude`.
186
+
187
+ ---
188
+
189
+ ## Sources
190
+
191
+ - **The Ishtar Gate** — Ritual Calendar (archived): https://web.archive.org/web/20250125122100/https://www.theishtargate.com/Calendar/
192
+ - Mark E. Cohen, *Cultic Calendars of the Ancient Near East* (CDL Press, 1993)
193
+ - Jean Meeus, *Astronomical Algorithms* (2nd ed., Willmann-Bell, 1998) — Chapters 27 & 49
194
+
195
+ ---
196
+
197
+ ## License
198
+
199
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,577 @@
1
+ "use strict";
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __commonJS = (cb, mod) => function __require() {
4
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
5
+ };
6
+
7
+ // src/astronomy.js
8
+ var require_astronomy = __commonJS({
9
+ "src/astronomy.js"(exports2, module2) {
10
+ "use strict";
11
+ var DEG = Math.PI / 180;
12
+ function gregorianToJD(year, month, day) {
13
+ let y = year;
14
+ let m = month;
15
+ if (m <= 2) {
16
+ y -= 1;
17
+ m += 12;
18
+ }
19
+ const A = Math.floor(y / 100);
20
+ const B = 2 - A + Math.floor(A / 4);
21
+ return Math.floor(365.25 * (y + 4716)) + Math.floor(30.6001 * (m + 1)) + day + B - 1524.5;
22
+ }
23
+ function jdToGregorian2(jd) {
24
+ const z = Math.floor(jd + 0.5);
25
+ const f = jd + 0.5 - z;
26
+ let A;
27
+ if (z < 2299161) {
28
+ A = z;
29
+ } else {
30
+ const alpha = Math.floor((z - 186721625e-2) / 36524.25);
31
+ A = z + 1 + alpha - Math.floor(alpha / 4);
32
+ }
33
+ const B = A + 1524;
34
+ const C = Math.floor((B - 122.1) / 365.25);
35
+ const D = Math.floor(365.25 * C);
36
+ const E = Math.floor((B - D) / 30.6001);
37
+ const dayFrac = B - D - Math.floor(30.6001 * E) + f;
38
+ const day = Math.floor(dayFrac);
39
+ const month = E < 14 ? E - 1 : E - 13;
40
+ const year = month > 2 ? C - 4716 : C - 4715;
41
+ const fracDay = dayFrac - day;
42
+ const hour = Math.floor(fracDay * 24);
43
+ const fracHour = fracDay * 24 - hour;
44
+ const minute = Math.floor(fracHour * 60);
45
+ const second = Math.round((fracHour * 60 - minute) * 60);
46
+ return { year, month, day, hour, minute, second };
47
+ }
48
+ function vernalEquinoxJDE2(year) {
49
+ let JDE0;
50
+ if (year >= 1e3) {
51
+ const Y = (year - 2e3) / 1e3;
52
+ JDE0 = 245162380984e-5 + 365242.37404 * Y + 0.05169 * Y * Y - 411e-5 * Y * Y * Y - 57e-5 * Y * Y * Y * Y;
53
+ } else {
54
+ const Y = year / 1e3;
55
+ JDE0 = 17211392855e-4 + 365242.1376 * Y + 0.06679 * Y * Y - 669e-5 * Y * Y * Y;
56
+ }
57
+ const T = (JDE0 - 2451545) / 36525;
58
+ const W = 35999.373 * T - 2.47;
59
+ const dL = 1 + 0.0334 * Math.cos(W * DEG) + 7e-4 * Math.cos(2 * W * DEG);
60
+ const corrections = [
61
+ [485, 324.96, 1934.136],
62
+ [203, 337.23, 32964.467],
63
+ [199, 342.08, 20.186],
64
+ [182, 27.85, 445267.112],
65
+ [156, 73.14, 45036.886],
66
+ [136, 171.52, 22518.443],
67
+ [77, 222.54, 65928.934],
68
+ [74, 296.72, 3034.906],
69
+ [70, 243.58, 9037.513],
70
+ [58, 119.81, 33718.147],
71
+ [52, 297.17, 150.678],
72
+ [50, 21.02, 2281.226],
73
+ [45, 247.54, 29929.562],
74
+ [44, 325.15, 31555.956],
75
+ [29, 60.93, 4443.417],
76
+ [18, 155.12, 67555.328],
77
+ [17, 288.79, 4562.452],
78
+ [16, 198.04, 62894.029],
79
+ [14, 199.76, 31557.381],
80
+ [12, 95.39, 14577.848],
81
+ [10, 49.69, 31557.381],
82
+ [9, 162.03, 31557.381],
83
+ [9, 35.47, 1222.114],
84
+ [9, 32.43, 1191.013],
85
+ [9, 36.4, 1232.124],
86
+ [9, 57.03, 1229.225]
87
+ ];
88
+ const S = corrections.reduce((sum, [a, b, c]) => sum + a * Math.cos(DEG * (b + c * T)), 0);
89
+ return JDE0 + 1e-5 * S / dL;
90
+ }
91
+ function newMoonJDE(k) {
92
+ const T = k / 1236.85;
93
+ const T2 = T * T;
94
+ const T3 = T2 * T;
95
+ const T4 = T3 * T;
96
+ let JDE = 245155009766e-5 + 29.530588861 * k + 15437e-8 * T2 - 15e-8 * T3 + 73e-11 * T4;
97
+ const E = 1 - 2516e-6 * T - 74e-7 * T2;
98
+ const M = (2.5534 + 29.1053567 * k - 14e-7 * T2 - 11e-8 * T3) * DEG;
99
+ const Mp = (201.5643 + 385.81693528 * k + 0.0107582 * T2 + 1238e-8 * T3 - 58e-9 * T4) * DEG;
100
+ const F = (160.7108 + 390.67050284 * k - 16118e-7 * T2 - 227e-8 * T3 + 11e-9 * T4) * DEG;
101
+ const Om = (124.7746 - 1.56375588 * k + 20672e-7 * T2 + 215e-8 * T3) * DEG;
102
+ JDE += -0.4072 * Math.sin(Mp) + 0.17241 * E * Math.sin(M) + 0.01608 * Math.sin(2 * Mp) + 0.01039 * Math.sin(2 * F) + 739e-5 * E * Math.sin(Mp - M) - 514e-5 * E * Math.sin(Mp + M) + 208e-5 * E * E * Math.sin(2 * M) - 111e-5 * Math.sin(Mp - 2 * F) - 57e-5 * Math.sin(Mp + 2 * F) + 56e-5 * E * Math.sin(2 * Mp + M) - 42e-5 * Math.sin(3 * Mp) + 42e-5 * E * Math.sin(M + 2 * F) + 38e-5 * E * Math.sin(M - 2 * F) - 24e-5 * E * Math.sin(2 * Mp - M) - 17e-5 * Math.sin(Om) - 7e-5 * Math.sin(Mp + 2 * M) + 4e-5 * Math.sin(2 * Mp - 2 * F) + 4e-5 * Math.sin(3 * M) + 3e-5 * Math.sin(Mp + M - 2 * F) + 3e-5 * Math.sin(2 * Mp + 2 * F) - 3e-5 * Math.sin(Mp + M + 2 * F) + 3e-5 * Math.sin(Mp - M + 2 * F) - 2e-5 * Math.sin(Mp - M - 2 * F) - 2e-5 * Math.sin(3 * Mp + M) + 2e-5 * Math.sin(4 * Mp);
103
+ return JDE;
104
+ }
105
+ function kNearJDE(jde) {
106
+ return (jde - 245155009766e-5) / 29.530588861;
107
+ }
108
+ function firstNewMoonAfter2(afterJDE) {
109
+ let k = Math.ceil(kNearJDE(afterJDE));
110
+ let nm = newMoonJDE(k);
111
+ while (nm <= afterJDE + 0.01) {
112
+ k++;
113
+ nm = newMoonJDE(k);
114
+ }
115
+ return nm;
116
+ }
117
+ function sunsetUTHour(jdNoon, latitude, longitude) {
118
+ const lngHour = longitude / 15;
119
+ const { year, month, day } = jdToGregorian2(jdNoon);
120
+ const N = Math.floor(275 * month / 9) - 2 * Math.floor((month + 9) / 12) + day - 30;
121
+ const t = N + (18 - lngHour) / 24;
122
+ const M = (0.9856 * t - 3.289) % 360;
123
+ let L = M + 1.916 * Math.sin(M * DEG) + 0.02 * Math.sin(2 * M * DEG) + 282.634;
124
+ L = (L % 360 + 360) % 360;
125
+ let RA = (Math.atan(0.91764 * Math.tan(L * DEG)) / DEG + 360) % 360;
126
+ const Lq = Math.floor(L / 90) * 90;
127
+ const RAq = Math.floor(RA / 90) * 90;
128
+ RA = (RA + Lq - RAq) / 15;
129
+ const sinDec = 0.39782 * Math.sin(L * DEG);
130
+ const cosDec = Math.cos(Math.asin(sinDec));
131
+ const cosH = (Math.cos(90.833 * DEG) - sinDec * Math.sin(latitude * DEG)) / (cosDec * Math.cos(latitude * DEG));
132
+ if (cosH > 1) return null;
133
+ if (cosH < -1) return null;
134
+ const H = Math.acos(cosH) / DEG / 15;
135
+ let T = H + RA - 0.06571 * t - 6.622;
136
+ let UT = T - lngHour;
137
+ return (UT % 24 + 24) % 24;
138
+ }
139
+ module2.exports = {
140
+ gregorianToJD,
141
+ jdToGregorian: jdToGregorian2,
142
+ vernalEquinoxJDE: vernalEquinoxJDE2,
143
+ newMoonJDE,
144
+ firstNewMoonAfter: firstNewMoonAfter2,
145
+ sunsetUTHour,
146
+ kNearJDE
147
+ };
148
+ }
149
+ });
150
+
151
+ // src/months.js
152
+ var require_months = __commonJS({
153
+ "src/months.js"(exports2, module2) {
154
+ "use strict";
155
+ var MONTHS2 = [
156
+ {
157
+ number: 1,
158
+ name: "Barazagar",
159
+ cuneiform: "\u{12317}\u{12048}\u{12360}\u{120FB}",
160
+ meaning: "Dais of the Sanctuary",
161
+ season: "spring",
162
+ description: "New Year. The throne-dais of Enlil at the E-kur temple of Nippur. The zagmuk (akitu) New Year festival begins, commemorating the bond between humanity and the gods.",
163
+ festivals: [
164
+ { day: 1, name: "Zagmuk (New Year / Akitu)", description: "Multi-day New Year festival begins." },
165
+ { day: 15, name: "E\u0161e\u0161 (Full Moon)", description: "Great Offering to all household gods." }
166
+ ]
167
+ },
168
+ {
169
+ number: 2,
170
+ name: "Gusisu",
171
+ cuneiform: "\u{12317}\u{1211E}\u{122DB}\u{12072}",
172
+ meaning: "Marching Forth of Oxen",
173
+ season: "spring",
174
+ description: "Beginning of the agricultural season. Sacred to Ninurta, Lord of the Plough. A time to prepare resources and reflect on intentions set at New Year.",
175
+ festivals: [
176
+ { day: 7, name: "E\u0161e\u0161 (Quarter Moon)", description: "First-quarter moon offering." },
177
+ { day: 15, name: "E\u0161e\u0161 (Full Moon)", description: "Great Offering." },
178
+ { day: 20, name: "Ezem Gusisu (Day 1)", description: "Three-day Festival of Marching Oxen begins." },
179
+ { day: 21, name: "Ezem Gusisu (Day 2)", description: "" },
180
+ { day: 22, name: "Ezem Gusisu (Day 3)", description: "Ninurta receives highest honours." }
181
+ ]
182
+ },
183
+ {
184
+ number: 3,
185
+ name: "Sig Ga",
186
+ cuneiform: "\u{12317}\u{122DE}\u{120B5}",
187
+ meaning: "Placing of Bricks",
188
+ season: "spring",
189
+ description: "Labour of the cities and making of mud bricks. A time to lay firm foundations. Holy days sacred to the brick god Kulla.",
190
+ festivals: [
191
+ { day: 7, name: "E\u0161e\u0161 (Quarter Moon)", description: "" },
192
+ { day: 15, name: "E\u0161e\u0161 (Full Moon)", description: "Great Offering." }
193
+ ]
194
+ },
195
+ {
196
+ number: 4,
197
+ name: "\u0160unumun",
198
+ cuneiform: "\u{12317}\u{122D7}\u{121B0}\u{1223E}",
199
+ meaning: "Seeding",
200
+ season: "summer",
201
+ description: "Ceremonial onset of the seeding season; fields prepared by weeding. Month of ritual mourning for Dumuzi (Tammuz), consort of Inana, who descends to the Underworld for six months.",
202
+ festivals: [
203
+ { day: 7, name: "E\u0161e\u0161 (Quarter Moon)", description: "" },
204
+ { day: 15, name: "E\u0161e\u0161 (Full Moon)", description: "Great Offering." },
205
+ { day: null, name: "Mourning of Dumuzi", description: "Ritual mourning for Dumuzi throughout the month." }
206
+ ]
207
+ },
208
+ {
209
+ number: 5,
210
+ name: "Nenegar",
211
+ cuneiform: "\u{12317}\u{12248}\u{12248}\u{120FB}",
212
+ meaning: "Lighting of the Braziers",
213
+ season: "summer",
214
+ description: "Height of the Mesopotamian summer. The worlds of the living and dead are most closely connected. Fires lit as invitation to and protection from spirits of the dead.",
215
+ festivals: [
216
+ { day: 7, name: "E\u0161e\u0161 (Quarter Moon)", description: "" },
217
+ { day: 15, name: "E\u0161e\u0161 (Full Moon)", description: "Great Offering." },
218
+ { day: 27, name: "Festival of Spirits", description: "Braziers lit; ceremonial meal with the dead; offerings to chthonic deities and gods of the Underworld." }
219
+ ]
220
+ },
221
+ {
222
+ number: 6,
223
+ name: "Kin Inana",
224
+ cuneiform: "\u{12317}\u{121A5}\u{1202D}\u{12239}",
225
+ meaning: "The Labours of Inana",
226
+ season: "summer",
227
+ description: "Month-long celebration and adoration of Inana. Her statue is adorned and processed to the temples of Enlil and Nanna. The second half recreates Inana's Descent to the Underworld and triumphant return.",
228
+ festivals: [
229
+ { day: 1, name: "Adornment of Inana", description: "Inana's statue robed in jewellery and divine icons." },
230
+ { day: 7, name: "E\u0161e\u0161 (Quarter Moon)", description: "" },
231
+ { day: 15, name: "E\u0161e\u0161 (Full Moon) / Great Offering of Inana", description: "Peak of the month's festivities." },
232
+ { day: 21, name: "Return of Inana", description: "Statues ceremonially bathed and re-invested." },
233
+ { day: 26, name: "Inana's Glory", description: "Further offerings; recitations of myths of Inana's power." }
234
+ ]
235
+ },
236
+ {
237
+ number: 7,
238
+ name: "Duku",
239
+ cuneiform: "\u{12317}\u{121EF}\u{121AC}",
240
+ meaning: "The Sacred Mound",
241
+ season: "autumn",
242
+ description: "Second half of the year begins with its own akitu festival. The Sacred Mound is where earth first emerged from the primordial waters and life was first created. The seventh day of the seventh month is a day to petition the gods for peace and vitality.",
243
+ festivals: [
244
+ { day: 1, name: "Akitu (Autumnal)", description: "Second half-year festival; honouring primordial ancestors." },
245
+ { day: 7, name: "Petition Day / E\u0161e\u0161", description: "Petition the gods for peace, health, and vitality." },
246
+ { day: 15, name: "E\u0161e\u0161 (Full Moon)", description: "Great Offering." }
247
+ ]
248
+ },
249
+ {
250
+ number: 8,
251
+ name: "Apin Dua",
252
+ cuneiform: "\u{12317}\u{12033}\u{120EE}\u{12000}",
253
+ meaning: "Releasing of the Plough",
254
+ season: "autumn",
255
+ description: "The plough is put away; seeding is complete and the harvest awaited. A time to check milestones and tie off loose ends. Cities of Ur, Isin, Larsa, and Adab also celebrate a Festival of Spirits this month.",
256
+ festivals: [
257
+ { day: 7, name: "E\u0161e\u0161 (Quarter Moon)", description: "" },
258
+ { day: 15, name: "E\u0161e\u0161 (Full Moon)", description: "Great Offering." }
259
+ ]
260
+ },
261
+ {
262
+ number: 9,
263
+ name: "Gan Gan-e",
264
+ cuneiform: "\u{12317}\u{120F6}\u{120F6}\u{1223E}",
265
+ meaning: "Coming of Clouds",
266
+ season: "autumn",
267
+ description: "Start of the growing season; clouds appear over the plain of Sumer. Sacred to the storm god I\u0161kur. A time of anticipation and perseverance.",
268
+ festivals: [
269
+ { day: 7, name: "E\u0161e\u0161 (Quarter Moon)", description: "" },
270
+ { day: 15, name: "E\u0161e\u0161 (Full Moon)", description: "Great Offering." },
271
+ { day: 28, name: "Festival of Clouds", description: "Offerings to I\u0161kur, god of storms." }
272
+ ]
273
+ },
274
+ {
275
+ number: 10,
276
+ name: "Kusu",
277
+ cuneiform: "\u{12317}\u{121AC}\u{122C6}",
278
+ meaning: "Lady of the Grain",
279
+ season: "autumn",
280
+ description: "Named for Ezina-Kusu, grain goddess of Nippur, associated with Nisaba. The amber waves of grain approach full height. A time of preparation for the imminent harvest. (Also known as Abbe after c. 2000 BCE at Nippur.)",
281
+ festivals: [
282
+ { day: 7, name: "E\u0161e\u0161 (Quarter Moon)", description: "" },
283
+ { day: 15, name: "E\u0161e\u0161 (Full Moon)", description: "Great Offering." }
284
+ ]
285
+ },
286
+ {
287
+ number: 11,
288
+ name: "Uduru",
289
+ cuneiform: "\u{12317}\u{12369}\u{12000}",
290
+ meaning: "Emmer",
291
+ season: "winter",
292
+ description: "The harvest season begins in earnest. Emmer and barley \u2014 principal crops of Sumer \u2014 are reaped. The work of the year comes to a close.",
293
+ festivals: [
294
+ { day: 7, name: "E\u0161e\u0161 (Quarter Moon)", description: "" },
295
+ { day: 15, name: "E\u0161e\u0161 (Full Moon)", description: "Great Offering." }
296
+ ]
297
+ },
298
+ {
299
+ number: 12,
300
+ name: "\u0160ekinku",
301
+ cuneiform: "\u{12317}\u{122BA}\u{121A5}\u{122FB}",
302
+ meaning: "Reaping of Barley",
303
+ season: "winter",
304
+ description: "Culmination of the year. The barley harvest \u2014 high point of the Sumerian agricultural cycle \u2014 is celebrated with great hope and joy. Contains the vernal equinox, after which the new year begins.",
305
+ festivals: [
306
+ { day: 7, name: "E\u0161e\u0161 (Quarter Moon)", description: "" },
307
+ { day: 15, name: "Festival of Barley / E\u0161e\u0161 (Full Moon)", description: "Honour, offerings, and praise given to Enlil and Ezina." }
308
+ ]
309
+ }
310
+ ];
311
+ var DIRI_\u0160EKINKU2 = {
312
+ number: 13,
313
+ name: "Diri \u0160ekinku",
314
+ cuneiform: "\u{12317}\u{122DB}\u{12000}\u{122BA}\u{121A5}\u{122FB}",
315
+ meaning: "Second Reaping of Barley",
316
+ season: "winter",
317
+ intercalary: true,
318
+ description: "Intercalary month; observations mirror those of \u0160ekinku.",
319
+ festivals: [
320
+ { day: 7, name: "E\u0161e\u0161 (Quarter Moon)", description: "" },
321
+ { day: 15, name: "E\u0161e\u0161 (Full Moon)", description: "Great Offering." }
322
+ ]
323
+ };
324
+ var DIRI_KIN_INANA2 = {
325
+ number: 7,
326
+ // inserted before standard month 7 (Duku)
327
+ name: "Diri Kin Inana",
328
+ cuneiform: null,
329
+ // not attested in the source document
330
+ meaning: "Second Labours of Inana",
331
+ season: "summer",
332
+ intercalary: true,
333
+ description: "Intercalary month in cycle year 17 only; inserted after Kin Inana. Observations likely mirror those of Kin Inana.",
334
+ festivals: [
335
+ { day: 7, name: "E\u0161e\u0161 (Quarter Moon)", description: "" },
336
+ { day: 15, name: "E\u0161e\u0161 (Full Moon)", description: "Great Offering." }
337
+ ]
338
+ };
339
+ module2.exports = { MONTHS: MONTHS2, DIRI_\u0160EKINKU: DIRI_\u0160EKINKU2, DIRI_KIN_INANA: DIRI_KIN_INANA2 };
340
+ }
341
+ });
342
+
343
+ // src/calendar.js
344
+ var require_calendar = __commonJS({
345
+ "src/calendar.js"(exports2, module2) {
346
+ "use strict";
347
+ var {
348
+ gregorianToJD,
349
+ jdToGregorian: jdToGregorian2,
350
+ vernalEquinoxJDE: vernalEquinoxJDE2,
351
+ firstNewMoonAfter: firstNewMoonAfter2,
352
+ sunsetUTHour
353
+ } = require_astronomy();
354
+ var { MONTHS: MONTHS2, DIRI_\u0160EKINKU: DIRI_\u0160EKINKU2, DIRI_KIN_INANA: DIRI_KIN_INANA2 } = require_months();
355
+ var LONG_YEAR_POSITIONS = /* @__PURE__ */ new Set([3, 6, 8, 11, 14, 17, 19]);
356
+ var CYCLE_EPOCH_GREGORIAN_YEAR = 2007;
357
+ function getCyclePosition2(springYear) {
358
+ const offset = ((springYear - CYCLE_EPOCH_GREGORIAN_YEAR) % 19 + 19) % 19;
359
+ return offset + 1;
360
+ }
361
+ function buildMonthSequence(cyclePosition2) {
362
+ const isLong = LONG_YEAR_POSITIONS.has(cyclePosition2);
363
+ if (!isLong) {
364
+ return MONTHS2.slice(0, 12).map((m) => ({ ...m }));
365
+ }
366
+ if (cyclePosition2 === 17) {
367
+ const seq = [];
368
+ for (let i = 0; i < 12; i++) {
369
+ const m = { ...MONTHS2[i] };
370
+ if (i >= 6) m.number = i + 2;
371
+ seq.push(m);
372
+ if (i === 5) {
373
+ seq.push({ ...DIRI_KIN_INANA2, number: 7 });
374
+ }
375
+ }
376
+ return seq;
377
+ }
378
+ return [
379
+ ...MONTHS2.slice(0, 12).map((m) => ({ ...m })),
380
+ { ...DIRI_\u0160EKINKU2 }
381
+ ];
382
+ }
383
+ var NIPPUR_LAT = 32.13;
384
+ var NIPPUR_LON = 45.23;
385
+ function monthStartDate(nmJDE, latitude = NIPPUR_LAT, longitude = NIPPUR_LON) {
386
+ const g = jdToGregorian2(nmJDE);
387
+ const jdNoon = gregorianToJD(g.year, g.month, g.day);
388
+ const sunsetUT = sunsetUTHour(jdNoon, latitude, longitude);
389
+ const nmUTHour = (nmJDE - jdNoon) * 24 + 12;
390
+ const effectiveSunset = sunsetUT !== null ? sunsetUT : 18;
391
+ const daysToAdd = nmUTHour < effectiveSunset ? 1 : 2;
392
+ const start = new Date(Date.UTC(g.year, g.month - 1, g.day));
393
+ start.setUTCDate(start.getUTCDate() + daysToAdd);
394
+ return start;
395
+ }
396
+ function computeSumerianYear2(springGregorianYear, opts = {}) {
397
+ const lat = opts.latitude ?? NIPPUR_LAT;
398
+ const lon = opts.longitude ?? NIPPUR_LON;
399
+ const cyclePosition2 = getCyclePosition2(springGregorianYear);
400
+ const monthSequence = buildMonthSequence(cyclePosition2);
401
+ const isLongYear = LONG_YEAR_POSITIONS.has(cyclePosition2);
402
+ const veJDE = vernalEquinoxJDE2(springGregorianYear);
403
+ const yearNmJDE = firstNewMoonAfter2(veJDE);
404
+ const yearStart = monthStartDate(yearNmJDE, lat, lon);
405
+ let searchAfterJDE = yearNmJDE - 0.5;
406
+ const months2 = [];
407
+ for (let i = 0; i < monthSequence.length; i++) {
408
+ const nm = i === 0 ? yearNmJDE : firstNewMoonAfter2(searchAfterJDE);
409
+ const start = i === 0 ? yearStart : monthStartDate(nm, lat, lon);
410
+ months2.push({
411
+ ...monthSequence[i],
412
+ startDate: start,
413
+ newMoonJDE: nm
414
+ });
415
+ searchAfterJDE = nm;
416
+ }
417
+ const nextYearVeJDE = vernalEquinoxJDE2(springGregorianYear + 1);
418
+ const nextYearNmJDE = firstNewMoonAfter2(nextYearVeJDE);
419
+ const nextYearStart = monthStartDate(nextYearNmJDE, lat, lon);
420
+ for (let i = 0; i < months2.length; i++) {
421
+ const nextStart = i + 1 < months2.length ? months2[i + 1].startDate : nextYearStart;
422
+ const msMs = months2[i].startDate.getTime();
423
+ const nxMs = nextStart.getTime();
424
+ months2[i].endDate = new Date(nxMs - 864e5);
425
+ months2[i].lengthDays = Math.round((nxMs - msMs) / 864e5);
426
+ }
427
+ return {
428
+ springGregorianYear,
429
+ cyclePosition: cyclePosition2,
430
+ isLongYear,
431
+ yearStart,
432
+ yearEnd: new Date(nextYearStart.getTime() - 864e5),
433
+ nextYearStart,
434
+ months: months2,
435
+ vernalEquinoxJDE: veJDE,
436
+ /** Gregorian date of the vernal equinox */
437
+ get vernalEquinoxDate() {
438
+ const g = jdToGregorian2(veJDE);
439
+ return new Date(Date.UTC(g.year, g.month - 1, g.day));
440
+ }
441
+ };
442
+ }
443
+ function toSumerianDate2(date, opts = {}) {
444
+ if (!(date instanceof Date)) {
445
+ opts = date || {};
446
+ date = /* @__PURE__ */ new Date();
447
+ }
448
+ const gregorianYear = date.getUTCFullYear();
449
+ const candidates = [gregorianYear, gregorianYear - 1, gregorianYear + 1];
450
+ let sYear = null;
451
+ for (const candidate of candidates) {
452
+ const sy = computeSumerianYear2(candidate, opts);
453
+ if (date >= sy.yearStart && date < sy.nextYearStart) {
454
+ sYear = sy;
455
+ break;
456
+ }
457
+ }
458
+ if (!sYear) {
459
+ throw new Error(`Could not determine Sumerian year for ${date.toISOString()}`);
460
+ }
461
+ let monthObj = null;
462
+ let dayOfMonth = null;
463
+ for (let i = 0; i < sYear.months.length; i++) {
464
+ const m = sYear.months[i];
465
+ const next = i + 1 < sYear.months.length ? sYear.months[i + 1].startDate : sYear.nextYearStart;
466
+ if (date >= m.startDate && date < next) {
467
+ monthObj = m;
468
+ dayOfMonth = Math.floor((date - m.startDate) / 864e5) + 1;
469
+ break;
470
+ }
471
+ }
472
+ if (!monthObj) {
473
+ throw new Error(`Could not determine Sumerian month for ${date.toISOString()}`);
474
+ }
475
+ const observances = [];
476
+ if (dayOfMonth === 7) {
477
+ observances.push({ name: "E\u0161e\u0161", description: "First-quarter moon offering to all household gods." });
478
+ }
479
+ if (dayOfMonth === 15) {
480
+ observances.push({ name: "E\u0161e\u0161", description: "Full moon \u2014 Great Offering to all household gods." });
481
+ }
482
+ if (dayOfMonth === monthObj.lengthDays) {
483
+ observances.push({ name: "Kisiga", description: "New moon \u2014 Funerary offerings and libations for the beloved dead." });
484
+ }
485
+ (monthObj.festivals || []).forEach((f) => {
486
+ if (f.day === dayOfMonth) observances.push(f);
487
+ });
488
+ return {
489
+ gregorianDate: date,
490
+ springGregorianYear: sYear.springGregorianYear,
491
+ cyclePosition: sYear.cyclePosition,
492
+ monthNumber: monthObj.number,
493
+ monthName: monthObj.name,
494
+ monthCuneiform: monthObj.cuneiform,
495
+ monthMeaning: monthObj.meaning,
496
+ monthDescription: monthObj.description,
497
+ dayOfMonth,
498
+ monthLengthDays: monthObj.lengthDays,
499
+ monthStartDate: monthObj.startDate,
500
+ monthEndDate: monthObj.endDate,
501
+ isIntercalaryYear: sYear.isLongYear,
502
+ isIntercalaryMonth: !!monthObj.intercalary,
503
+ observances,
504
+ sumerianYear: sYear,
505
+ toString() {
506
+ const g = date;
507
+ const months2 = [
508
+ "Jan",
509
+ "Feb",
510
+ "Mar",
511
+ "Apr",
512
+ "May",
513
+ "Jun",
514
+ "Jul",
515
+ "Aug",
516
+ "Sep",
517
+ "Oct",
518
+ "Nov",
519
+ "Dec"
520
+ ];
521
+ return `Day ${dayOfMonth} of ${monthObj.name} \u2014 Gregorian ${g.getUTCDate()} ${months2[g.getUTCMonth()]} ${g.getUTCFullYear()}`;
522
+ }
523
+ };
524
+ }
525
+ module2.exports = {
526
+ computeSumerianYear: computeSumerianYear2,
527
+ toSumerianDate: toSumerianDate2,
528
+ getCyclePosition: getCyclePosition2,
529
+ LONG_YEAR_POSITIONS,
530
+ NIPPUR_LAT,
531
+ NIPPUR_LON
532
+ };
533
+ }
534
+ });
535
+
536
+ // src/index.js
537
+ var { toSumerianDate, computeSumerianYear, getCyclePosition } = require_calendar();
538
+ var { MONTHS, DIRI_\u0160EKINKU, DIRI_KIN_INANA } = require_months();
539
+ var { vernalEquinoxJDE, firstNewMoonAfter, jdToGregorian } = require_astronomy();
540
+ function sumerianDate(date, opts) {
541
+ return toSumerianDate(date, opts);
542
+ }
543
+ function sumerianYear(gregorianYear, opts) {
544
+ if (typeof gregorianYear !== "number") {
545
+ opts = gregorianYear;
546
+ gregorianYear = (/* @__PURE__ */ new Date()).getUTCFullYear();
547
+ }
548
+ return computeSumerianYear(gregorianYear, opts || {});
549
+ }
550
+ function cyclePosition(springGregorianYear) {
551
+ return getCyclePosition(springGregorianYear);
552
+ }
553
+ function vernalEquinox(year) {
554
+ const jde = vernalEquinoxJDE(year);
555
+ const g = jdToGregorian(jde);
556
+ return new Date(Date.UTC(g.year, g.month - 1, g.day, g.hour, g.minute));
557
+ }
558
+ function nextNewMoon(afterDate) {
559
+ const jdEpoch = 24405875e-1;
560
+ const afterJDE = jdEpoch + afterDate.getTime() / 864e5;
561
+ const nmJDE = firstNewMoonAfter(afterJDE);
562
+ const g = jdToGregorian(nmJDE);
563
+ return new Date(Date.UTC(g.year, g.month - 1, g.day, g.hour, g.minute));
564
+ }
565
+ var months = MONTHS;
566
+ var intercalaryMonth = DIRI_\u0160EKINKU;
567
+ var intercalaryMonthYear17 = DIRI_KIN_INANA;
568
+ module.exports = {
569
+ sumerianDate,
570
+ sumerianYear,
571
+ cyclePosition,
572
+ vernalEquinox,
573
+ nextNewMoon,
574
+ months,
575
+ intercalaryMonth,
576
+ intercalaryMonthYear17
577
+ };
package/dist/index.mjs ADDED
@@ -0,0 +1,10 @@
1
+ import mod from './index.cjs';
2
+ export const sumerianDate = mod.sumerianDate;
3
+ export const sumerianYear = mod.sumerianYear;
4
+ export const cyclePosition = mod.cyclePosition;
5
+ export const vernalEquinox = mod.vernalEquinox;
6
+ export const nextNewMoon = mod.nextNewMoon;
7
+ export const months = mod.months;
8
+ export const intercalaryMonth = mod.intercalaryMonth;
9
+ export const intercalaryMonthYear17 = mod.intercalaryMonthYear17;
10
+ export default mod;
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@jenova-marie/sumerian-date",
3
+ "version": "0.1.0",
4
+ "description": "Convert Gregorian dates to the Nippur Sumerian ritual calendar",
5
+ "main": "dist/index.cjs",
6
+ "module": "dist/index.mjs",
7
+ "exports": {
8
+ ".": {
9
+ "import": "./dist/index.mjs",
10
+ "require": "./dist/index.cjs"
11
+ }
12
+ },
13
+ "files": [
14
+ "dist/"
15
+ ],
16
+ "scripts": {
17
+ "build": "node build.js",
18
+ "test": "node test/index.test.js",
19
+ "prepublishOnly": "npm test && npm run build"
20
+ },
21
+ "keywords": [
22
+ "sumerian",
23
+ "calendar",
24
+ "nippur",
25
+ "lunisolar",
26
+ "metonic",
27
+ "ancient",
28
+ "mesopotamia",
29
+ "date",
30
+ "astronomy"
31
+ ],
32
+ "author": "jenova-marie",
33
+ "license": "MIT",
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "git+https://github.com/jenova-marie/sumerian-date.git"
37
+ },
38
+ "engines": {
39
+ "node": ">=14"
40
+ },
41
+ "devDependencies": {
42
+ "esbuild": "^0.27.3"
43
+ }
44
+ }