dayjskh 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/.editorconfig ADDED
@@ -0,0 +1,20 @@
1
+ root = true
2
+
3
+ [*]
4
+ charset = utf-8
5
+ end_of_line = lf
6
+ insert_final_newline = true
7
+ indent_style = space
8
+ indent_size = 4
9
+ trim_trailing_whitespace = true
10
+
11
+ [*.{json,vue,js,jsx,ts,tsx,css,scss,html}]
12
+ indent_size = 2
13
+ [*.md]
14
+ trim_trailing_whitespace = false
15
+
16
+ [*.{yml,yaml}]
17
+ indent_size = 2
18
+
19
+ [docker-compose.yml]
20
+ indent_size = 4
@@ -0,0 +1 @@
1
+ {}
package/README.md ADDED
@@ -0,0 +1,37 @@
1
+ # Khmer Lunar Date plugin for [Day.js](https://day.js.org/)
2
+
3
+ Convert Date to Khmer Lunar Date [see more...](https://web.archive.org/web/20190714153528/http://www.cam-cc.org/calendar/).
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install dayjs
9
+ npm install dayjskh
10
+ ```
11
+
12
+ ## Usage
13
+
14
+ ```js
15
+ import dayjs from "dayjs";
16
+ import toKhDate from "dayjskh";
17
+
18
+ dayjs.extend(toKhDate);
19
+
20
+ const date = dayjs();
21
+ console.log(date.toKhDate(format?)); // ថ្ងៃច័ន្ទ ១២កើត ខែស្រាពណ៍ ឆ្នាំថោះ បញ្ចស័ក ពុទ្ធសករាជ ២៥៦៧
22
+ // find khmer new year dateTime
23
+ console.log(date.khNewYear());
24
+ // type
25
+ // {
26
+ // date: Dayjs;
27
+ // days: number;
28
+ // dates: {
29
+ // date: Dayjs;
30
+ // dayName: string;
31
+ // }[]
32
+ // }
33
+ ```
34
+
35
+ # Author
36
+
37
+ This library is Ported from [momentkh](https://github.com/ThyrithSor/momentkh) by [Thyrith Sor](https://github.com/ThyrithSor) in to [Day.js Plugin](https://day.js.org/).
package/index.html ADDED
@@ -0,0 +1,62 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>DayjsKh</title>
7
+ </head>
8
+ <body>
9
+ <div id="app">
10
+ <h1></h1>
11
+ <h2></h2>
12
+ <h3></h3>
13
+ <pre></pre>
14
+ <div
15
+ style="
16
+ display: flex;
17
+ flex-direction: row;
18
+ /* justify-content: space-between; */
19
+ "
20
+ >
21
+ <ul></ul>
22
+ <ul id="ul2"></ul>
23
+ </div>
24
+ </div>
25
+ <script type="module">
26
+ import dayjs from "dayjs";
27
+ import toKhDate from "./src/main.ts";
28
+ dayjs.extend(toKhDate);
29
+ const date = dayjs();
30
+ console.log(date.khNewYear());
31
+ document.querySelector("h1").innerHTML = date.toKhDate();
32
+ // document.querySelector("h2").innerHTML = date.format("DD/MM/YYYY HH:mm");
33
+ document.querySelector("pre").innerHTML = JSON.stringify(
34
+ date.year(2024).khNewYear(),
35
+ );
36
+ // .format("DD/MM/YYYY HH:mm");
37
+ // console.log(date.khNewYear(2024).format("DD/MM/YYYY HH:mm"));
38
+ // loop 5 days
39
+ const ul = document.querySelector("ul");
40
+ for (let i = 0; i < 30; i++) {
41
+ const li = document.createElement("li");
42
+ li.innerHTML = `${date
43
+ .add(i, "day")
44
+ .toKhDate("ថ្ងៃ W DN ខែ m")} - ${date
45
+ .add(i, "day")
46
+ .format("DD/MM/YYYY")}`;
47
+ ul.appendChild(li);
48
+ }
49
+ const date2 = dayjs("2050-01-01T00:00:00+07:00");
50
+ const ul2 = document.querySelector("#ul2");
51
+ for (let i = 0; i < 30; i++) {
52
+ const li = document.createElement("li");
53
+ li.innerHTML = `${date2
54
+ .add(i, "day")
55
+ .toKhDate("ថ្ងៃ W DN ខែ m")} - ${date2
56
+ .add(i, "day")
57
+ .format("DD/MM/YYYY")}`;
58
+ ul2.appendChild(li);
59
+ }
60
+ </script>
61
+ </body>
62
+ </html>
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "dayjskh",
3
+ "version": "0.1.0",
4
+ "description": "Khmer Lunar date Plugin for Day.js",
5
+ "main": "./src/main.ts",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "tsc && vite build",
9
+ "preview": "vite preview"
10
+ },
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "https://github.com/ChanthornAcademy/dayjskh.git"
14
+ },
15
+ "author": "chanthorn",
16
+ "license": "MIT",
17
+ "devDependencies": {
18
+ "@types/node": "^20.5.7",
19
+ "prettier": "3.0.2",
20
+ "typescript": "^5.2.2",
21
+ "vite": "^4.4.9"
22
+ },
23
+ "dependencies": {
24
+ "dayjs": "^1.11.9"
25
+ }
26
+ }
@@ -0,0 +1,104 @@
1
+ const lunarMonths = {};
2
+ "មិគសិរ_បុស្ស_មាឃ_ផល្គុន_ចេត្រ_ពិសាខ_ជេស្ឋ_អាសាឍ_ស្រាពណ៍_ភទ្របទ_អស្សុជ_កក្ដិក_បឋមាសាឍ_ទុតិយាសាឍ"
3
+ .split("_")
4
+ .forEach((month, index) => {
5
+ lunarMonths[month] = index;
6
+ });
7
+
8
+ const solarMonths: { [key: string]: number } = {};
9
+ "មករា_កុម្ភៈ_មីនា_មេសា_ឧសភា_មិថុនា_កក្កដា_សីហា_កញ្ញា_តុលា_វិច្ឆិកា_ធ្នូ"
10
+ .split("_")
11
+ .forEach((month, index) => (solarMonths[month] = index));
12
+
13
+ const animalYears: { [key: string]: number } = {};
14
+ "ជូត_ឆ្លូវ_ខាល_ថោះ_រោង_ម្សាញ់_មមីរ_មមែ_វក_រកា_ច_កុរ"
15
+ .split("_")
16
+ .forEach((year, index) => (animalYears[year] = index));
17
+
18
+ const eraYears: { [key: string]: number } = {};
19
+ "សំរឹទ្ធិស័ក_ឯកស័ក_ទោស័ក_ត្រីស័ក_ចត្វាស័ក_បញ្ចស័ក_ឆស័ក_សប្តស័ក_អដ្ឋស័ក_នព្វស័ក"
20
+ .split("_")
21
+ .forEach((year, index) => (eraYears[year] = index));
22
+
23
+ const moonStatus: { [key: string]: number } = {};
24
+ "កើត_រោច".split("_").forEach((status, index) => (moonStatus[status] = index));
25
+
26
+ const khNewYear: { [key: string]: string } = {
27
+ "1879": "12-04-1879 11:36",
28
+ "1897": "13-04-1897 02:00",
29
+ "2011": "14-04-2011 13:12",
30
+ "2012": "14-04-2012 19:11",
31
+ "2013": "14-04-2013 02:12",
32
+ "2014": "14-04-2014 08:07",
33
+ "2015": "14-04-2015 14:02",
34
+ };
35
+
36
+ const kh = () => {
37
+ let symbolMap = {
38
+ 1: "១",
39
+ 2: "២",
40
+ 3: "៣",
41
+ 4: "៤",
42
+ 5: "៥",
43
+ 6: "៦",
44
+ 7: "៧",
45
+ 8: "៨",
46
+ 9: "៩",
47
+ 0: "០",
48
+ },
49
+ numberMap = {
50
+ "១": 1,
51
+ "២": 2,
52
+ "៣": 3,
53
+ "៤": 4,
54
+ "៥": 5,
55
+ "៦": 6,
56
+ "៧": 7,
57
+ "៨": 8,
58
+ "៩": 9,
59
+ "០": 0,
60
+ };
61
+
62
+ return {
63
+ months:
64
+ "មករា_កុម្ភៈ_មីនា_មេសា_ឧសភា_មិថុនា_កក្កដា_សីហា_កញ្ញា_តុលា_វិច្ឆិកា_ធ្នូ".split(
65
+ "_",
66
+ ),
67
+ monthsShort:
68
+ "មករា_កុម្ភៈ_មីនា_មេសា_ឧសភា_មិថុនា_កក្កដា_សីហា_កញ្ញា_តុលា_វិច្ឆិកា_ធ្នូ".split(
69
+ "_",
70
+ ),
71
+ moonDays:
72
+ "᧡_᧢_᧣_᧤_᧥_᧦_᧧_᧨_᧩_᧪_᧫_᧬_᧭_᧮_᧯_᧱_᧲_᧳_᧴_᧵_᧶_᧷_᧸_᧹_᧺_᧻_᧼_᧽_᧾_᧿".split("_"),
73
+ moonStatus: "កើត_រោច".split("_"),
74
+ moonStatusShort: "ក_រ".split("_"),
75
+ weekdays: "អាទិត្យ_ច័ន្ទ_អង្គារ_ពុធ_ព្រហស្បតិ៍_សុក្រ_សៅរ៍".split("_"),
76
+ weekdaysShort: "អា_ច_អ_ព_ព្រ_សុ_ស".split("_"),
77
+ weekdaysMin: "អា_ច_អ_ព_ព្រ_សុ_ស".split("_"),
78
+ lunarMonths:
79
+ "មិគសិរ_បុស្ស_មាឃ_ផល្គុន_ចេត្រ_ពិសាខ_ជេស្ឋ_អាសាឍ_ស្រាពណ៍_ភទ្របទ_អស្សុជ_កក្ដិក_បឋមាសាឍ_ទុតិយាសាឍ".split(
80
+ "_",
81
+ ),
82
+ animalYear: "ជូត_ឆ្លូវ_ខាល_ថោះ_រោង_ម្សាញ់_មមីរ_មមែ_វក_រកា_ច_កុរ".split("_"),
83
+ eraYear:
84
+ "សំរឹទ្ធិស័ក_ឯកស័ក_ទោស័ក_ត្រីស័ក_ចត្វាស័ក_បញ្ចស័ក_ឆស័ក_សប្តស័ក_អដ្ឋស័ក_នព្វស័ក".split(
85
+ "_",
86
+ ),
87
+ preparse: function (number: any) {
88
+ return number.toString().replace(/[០១២៣៤៥៦៧៨៩]/g, (m) => numberMap[m]);
89
+ },
90
+ postformat: function (number: any) {
91
+ return number.toString().replace(/\d/g, (m) => symbolMap[m]);
92
+ },
93
+ };
94
+ };
95
+
96
+ export const constant = {
97
+ lunarMonths,
98
+ solarMonths,
99
+ animalYears,
100
+ eraYears,
101
+ moonStatus,
102
+ khNewYear,
103
+ kh: kh(),
104
+ };
package/src/dayjskh.ts ADDED
@@ -0,0 +1,522 @@
1
+ // Khmer Chhankitek Calendar
2
+ // Ref.: https://khmer-calendar.tovnah.com/calendar/toc.php
3
+
4
+ import { constant } from "./constant";
5
+ import learnSak from "./lerngSak";
6
+ import dayjs from "dayjs/esm/index.js";
7
+ import duration from "dayjs/esm/plugin/duration";
8
+ import customParseFormat from "dayjs/esm/plugin/customParseFormat";
9
+ import badMutable from "dayjs/esm/plugin/badMutable";
10
+
11
+ dayjs.extend(customParseFormat);
12
+ dayjs.extend(duration);
13
+
14
+ const { lunarMonths } = constant;
15
+
16
+ export default function dayjskh(date?: dayjs.Dayjs | string) {
17
+ // check if date is valid
18
+ const globalDate = dayjs(date); // if date is undefined, it will return current date
19
+ if (!globalDate.isValid()) {
20
+ throw new Error("Invalid Date");
21
+ }
22
+
23
+ const { floor } = Math;
24
+ // Aharkun: អាហារគុណ ឬ ហារគុណ
25
+ // Aharkun is used for Avoman and Bodithey calculation below. Given adYear as a target year in Buddhist Era
26
+ function getAharkun(beYear: number): number {
27
+ let t = beYear * 292207 + 499;
28
+ return floor(t / 800) + 4;
29
+ }
30
+
31
+ function getAharkunMod(beYear: number): number {
32
+ let t = beYear * 292207 + 499;
33
+ return t % 800;
34
+ }
35
+
36
+ // Bodithey: បូតិថី
37
+ // Bodithey determines if a given beYear is a leap-month year. Given year target year in Buddhist Era
38
+ function getBodithey(beYear: number): number {
39
+ const { avml, aharkun } = {
40
+ avml: floor((11 * getAharkun(beYear) + 25) / 692),
41
+ aharkun: getAharkun(beYear),
42
+ };
43
+ return (avml + aharkun + 29) % 30;
44
+ }
45
+
46
+ // Avoman: អាវមាន
47
+ // Avoman determines if a given year is a leap-day year. Given a year in Buddhist Era as denoted as adYear
48
+ function getAvoman(beYear: number): number {
49
+ const aharkun = getAharkun(beYear);
50
+ return (11 * aharkun + 25) % 692;
51
+ }
52
+
53
+ // Kromathupul
54
+ function khromathupul(beYear: number): number {
55
+ const aharkunMod = getAharkunMod(beYear);
56
+ return 800 - aharkunMod;
57
+ }
58
+
59
+ // isKhmerSolarLeap
60
+ function isKhmerSolarLeap(beYear: number): boolean {
61
+ return khromathupul(beYear) <= 207;
62
+ }
63
+
64
+ // Regular if year has 30 day
65
+ // leap month if year has 13 months
66
+ // leap day if Jesth month of the year has 1 extra day
67
+ // leap day and month: both of them
68
+ function getBoditheyLeap(beYear: number): number {
69
+ let result = 0;
70
+ const avoman = getAvoman(beYear);
71
+ const bodithey = getBodithey(beYear);
72
+ let boditheyLeap = 0;
73
+ if (bodithey >= 25 || bodithey <= 5) {
74
+ boditheyLeap = 1;
75
+ }
76
+ // check for avoman leap-day based on gregorian leap
77
+ let avomanLeap = 0;
78
+ if (isKhmerSolarLeap(beYear)) {
79
+ if (avoman <= 126) avomanLeap = 1;
80
+ } else {
81
+ if (avoman <= 137) {
82
+ // check for avoman case 137/0, 137 must be normal year (p.26)
83
+ if (getAvoman(beYear + 1) === 0) {
84
+ avomanLeap = 0;
85
+ } else {
86
+ avomanLeap = 1;
87
+ }
88
+ }
89
+ }
90
+
91
+ // case of 25/5 consecutively
92
+ // only bodithey 5 can be leap-month, so set bodithey 25 to none
93
+ if (bodithey === 25) {
94
+ let nextBodithey = getBodithey(beYear + 1);
95
+ if (nextBodithey === 5) {
96
+ boditheyLeap = 0;
97
+ }
98
+ }
99
+
100
+ // case of 24/6 consecutively, 24 must be leap-month
101
+ if (bodithey == 24) {
102
+ let nextBodithey = getBodithey(beYear + 1);
103
+ if (nextBodithey == 6) {
104
+ boditheyLeap = 1;
105
+ }
106
+ }
107
+
108
+ // format leap result (0:regular, 1:month, 2:day, 3:both)
109
+ if (boditheyLeap === 1 && avomanLeap === 1) {
110
+ result = 3;
111
+ } else if (boditheyLeap === 1) {
112
+ result = 1;
113
+ } else if (avomanLeap === 1) {
114
+ result = 2;
115
+ } else {
116
+ result = 0;
117
+ }
118
+
119
+ return result;
120
+ }
121
+
122
+ // bodithey leap can be both leap-day and leap-month but following the khmer calendar rule, they can't be together on the same year, so leap day must be delayed to next year
123
+ function getProtetinLeap(beYear: number): number {
124
+ const b = getBoditheyLeap(beYear);
125
+ if (b === 3) {
126
+ return 1;
127
+ }
128
+ if (b === 2 || b === 1) {
129
+ return b;
130
+ }
131
+ // case of previous year is 3
132
+ if (getBoditheyLeap(beYear - 1) === 3) {
133
+ return 2;
134
+ }
135
+ return 0;
136
+ }
137
+
138
+ // A year with an extra day is called Chhantrea Thimeas (ចន្ទ្រាធិមាស) or Adhikavereak (អធិកវារៈ). This year has 355 days.
139
+ function isKhmerLunarLeapDay(beYear: number): boolean {
140
+ return getProtetinLeap(beYear) === 2;
141
+ }
142
+ // A year with an extra month is called Adhikameas (អធិកមាស). This year has 384 days.
143
+ function isKhmerLeapMonth(beYear: number): boolean {
144
+ return getProtetinLeap(beYear) === 1;
145
+ }
146
+
147
+ // is Gregorian Leap Year
148
+ // function isGregorianLeapYear(beYear: number): boolean {
149
+ // return (beYear % 4 === 0 && beYear % 100 !== 0) || beYear % 400 === 0;
150
+ // }
151
+
152
+ // get Max day of Khmer month
153
+ function getMaxDayOfKhmerMonth(beMonth: number, beYear: number): number {
154
+ // 'ជេស្ឋ': 6,
155
+ if (beMonth === 6 && isKhmerLunarLeapDay(beYear)) {
156
+ return 30;
157
+ }
158
+ if (
159
+ // 'បឋមាសាឍ': 12, 'ទុតិយាសាឍ': 13
160
+ beMonth === 12 ||
161
+ beMonth === 13
162
+ ) {
163
+ return 30;
164
+ }
165
+ return beMonth % 2 === 0 ? 29 : 30;
166
+ }
167
+
168
+ // Get number of day in Khmer year
169
+ function getNumDayOfKhmerYear(beYear: number): number {
170
+ if (isKhmerLeapMonth(beYear)) {
171
+ return 384;
172
+ } else if (isKhmerLunarLeapDay(beYear)) {
173
+ return 355;
174
+ } else {
175
+ return 354;
176
+ }
177
+ }
178
+
179
+ // Buddhist Era
180
+ // ថ្ងៃឆ្លងឆ្នាំ គឺ ១ រោច ខែពិសាខ
181
+ // @ref http://news.sabay.com.kh/article/1039620
182
+ // @summary: ឯកឧត្តម សេង សុមុនី អ្នកនាំពាក្យ​ក្រ​សួង​ធម្មការ និង​សាសនា​ឲ្យ​Sabay ដឹង​ថា​នៅ​ប្រ​ទេស​កម្ពុជា​ការ​ឆ្លង​ចូល​ពុទ្ធសករាជថ្មី​គឺ​កំណត់​យក​នៅ​ថ្ងៃព្រះ​ពុទ្ធយាងចូល​និព្វាន ពោល​គឺ​នៅ​ថ្ងៃ​១រោច ខែពិសាខ។
183
+ // @param dayjs
184
+
185
+ function getBEYear(date: dayjs.Dayjs): number {
186
+ if (date.diff(getVisakBochea(date.year())) > 0) {
187
+ return date.year() + 544;
188
+ } else {
189
+ return date.year() + 543;
190
+ }
191
+ }
192
+
193
+ function getMaybeBEYear(date: dayjs.Dayjs): number {
194
+ if (date.month() <= 4) {
195
+ return date.year() + 543;
196
+ } else {
197
+ return date.year() + 544;
198
+ }
199
+ }
200
+
201
+ // Next month of Khmer month
202
+
203
+ function nextMonthOf(khmerMonth: number, beYear: number): number {
204
+ switch (khmerMonth) {
205
+ case lunarMonths["មិគសិរ"]:
206
+ return lunarMonths["បុស្ស"];
207
+ case lunarMonths["បុស្ស"]:
208
+ return lunarMonths["មាឃ"];
209
+ case lunarMonths["មាឃ"]:
210
+ return lunarMonths["ផល្គុន"];
211
+ case lunarMonths["ផល្គុន"]:
212
+ return lunarMonths["ចេត្រ"];
213
+ case lunarMonths["ចេត្រ"]:
214
+ return lunarMonths["ពិសាខ"];
215
+ case lunarMonths["ពិសាខ"]:
216
+ return lunarMonths["ជេស្ឋ"];
217
+ case lunarMonths["ជេស្ឋ"]: {
218
+ if (isKhmerLeapMonth(beYear)) {
219
+ return lunarMonths["បឋមាសាឍ"];
220
+ } else {
221
+ return lunarMonths["អាសាឍ"];
222
+ }
223
+ }
224
+ case lunarMonths["អាសាឍ"]:
225
+ return lunarMonths["ស្រាពណ៍"];
226
+ case lunarMonths["ស្រាពណ៍"]:
227
+ return lunarMonths["ភទ្របទ"];
228
+ case lunarMonths["ភទ្របទ"]:
229
+ return lunarMonths["អស្សុជ"];
230
+ case lunarMonths["អស្សុជ"]:
231
+ return lunarMonths["កក្ដិក"];
232
+ case lunarMonths["កក្ដិក"]:
233
+ return lunarMonths["មិគសិរ"];
234
+ case lunarMonths["បឋមាសាឍ"]:
235
+ return lunarMonths["ទុតិយាសាឍ"];
236
+ case lunarMonths["ទុតិយាសាឍ"]:
237
+ return lunarMonths["ស្រាពណ៍"];
238
+ default:
239
+ throw Error("Plugin is facing wrong calculation (Invalid month)");
240
+ }
241
+ }
242
+
243
+ // calculate date from dayjs to Khmer date
244
+ function getLunarDate(targetDate: dayjs.Dayjs): {
245
+ day: number;
246
+ month: number;
247
+ epochMoved: dayjs.Dayjs;
248
+ } {
249
+ dayjs.extend(badMutable);
250
+ // Epoch Date: January 1, 1900
251
+ let epochDayjs = dayjs("1900-01-01");
252
+ let khmerMonth = 1;
253
+ let khmerDay = 0; // 0 - 29 ១កើត ... ១៥កើត ១រោច ...១៤រោច (១៥រោច)
254
+ // Find nearest year epoch
255
+ if (targetDate.diff(epochDayjs) > 0) {
256
+ while (
257
+ dayjs.duration(targetDate.diff(epochDayjs), "milliseconds").asDays() > // ok
258
+ getNumDayOfKhmerYear(getMaybeBEYear(epochDayjs.clone().add(1, "year")))
259
+ ) {
260
+ epochDayjs.add(
261
+ getNumDayOfKhmerYear(
262
+ getMaybeBEYear(epochDayjs.clone().add(1, "year")),
263
+ ),
264
+ "day",
265
+ );
266
+ }
267
+ } else {
268
+ do {
269
+ epochDayjs.subtract(
270
+ getNumDayOfKhmerYear(getMaybeBEYear(epochDayjs)),
271
+ "day",
272
+ );
273
+ } while (
274
+ dayjs.duration(epochDayjs.diff(targetDate), "milliseconds").asDays() > 0
275
+ );
276
+ }
277
+ // Move epoch month
278
+ while (
279
+ dayjs.duration(targetDate.diff(epochDayjs), "milliseconds").asDays() >
280
+ getMaxDayOfKhmerMonth(khmerMonth, getMaybeBEYear(epochDayjs))
281
+ ) {
282
+ epochDayjs.add(
283
+ getMaxDayOfKhmerMonth(khmerMonth, getMaybeBEYear(epochDayjs)),
284
+ "day",
285
+ );
286
+ khmerMonth = nextMonthOf(khmerMonth, getMaybeBEYear(epochDayjs));
287
+ }
288
+
289
+ khmerDay += floor(
290
+ dayjs.duration(targetDate.diff(epochDayjs), "milliseconds").asDays(),
291
+ );
292
+ // fix result display 15 រោច ខែ ជេស្ឋ នៅថ្ងៃ ១ កើតខែបឋមាសាធ
293
+ // ករណី ខែជេស្ឋមានតែ ២៩ ថ្ងៃ តែលទ្ធផលបង្ហាញ ១៥រោច ខែជេស្ឋ
294
+ const totalDaysOfTheMonth = getMaxDayOfKhmerMonth(
295
+ khmerMonth,
296
+ getMaybeBEYear(targetDate),
297
+ );
298
+ if (totalDaysOfTheMonth <= khmerDay) {
299
+ khmerDay = khmerDay % totalDaysOfTheMonth;
300
+ khmerMonth = nextMonthOf(khmerMonth, getMaybeBEYear(epochDayjs));
301
+ }
302
+
303
+ epochDayjs.add(
304
+ dayjs.duration(targetDate.diff(epochDayjs), "milliseconds").asDays(),
305
+ "day",
306
+ );
307
+ return {
308
+ day: khmerDay,
309
+ month: khmerMonth,
310
+ epochMoved: epochDayjs,
311
+ };
312
+ }
313
+
314
+ // រកថ្ងៃវិសាខបូជា
315
+ // ថ្ងៃដាច់ឆ្នាំពុទ្ធសករាជ
316
+ function getVisakBochea(
317
+ gregorianYear: number,
318
+ ): string | number | dayjs.Dayjs | Date | null | undefined | any {
319
+ const date = dayjs(`${gregorianYear}-01-01`);
320
+ for (var i = 0; i < 365; i++) {
321
+ const lunarDate = getLunarDate(date);
322
+ if (lunarDate.month === lunarMonths["ពិសាខ"] && lunarDate.day === 14) {
323
+ return date;
324
+ }
325
+ date.add(1, "day");
326
+ }
327
+ }
328
+
329
+ function datesOfKhmerNewYear(
330
+ startDate: dayjs.Dayjs,
331
+ numberOfNewYearDays: number,
332
+ ) {
333
+ let dates = [];
334
+ for (let i = 0; i < numberOfNewYearDays; i++) {
335
+ dates.push(startDate.clone().add(i, "day"));
336
+ }
337
+ return dates.map((date, index) => {
338
+ // find first day
339
+ let dayName = "Moha Sangkranta"; // មហាសង្រ្កាន្ត
340
+ if (index === 0) {
341
+ dayName = "Moha Sangkranta"; // មហាសង្រ្កាន្ត
342
+ } // last day
343
+ else if (index === numberOfNewYearDays - 1) {
344
+ dayName = "Veareak Laeung Sak"; // ថ្ងៃឡើងស័ក
345
+ } else {
346
+ dayName = "Veareak Vanabat"; // វារៈវ័នបត
347
+ }
348
+ return {
349
+ date,
350
+ dayName,
351
+ };
352
+ });
353
+ }
354
+
355
+ // get khmer new year day
356
+ function getKhmerNewYear(gregorianYear: number): {
357
+ date: dayjs.Dayjs;
358
+ days: number;
359
+ dates: { date: dayjs.Dayjs; dayName: string }[];
360
+ } {
361
+ // ពីគ្រិស្ដសករាជ ទៅ ចុល្លសករាជ
362
+ let jsYear = gregorianYear + 544 - 1182;
363
+ let info = learnSak(jsYear);
364
+ let numberOfNewYearDay;
365
+ if (info.newYearsDaySotins[0].angsar === 0) {
366
+ numberOfNewYearDay = 4;
367
+ } else {
368
+ numberOfNewYearDay = 3;
369
+ }
370
+ let epochLerngSak = dayjs(
371
+ `${gregorianYear}-04-17 ${info.timeOfNewYear.hour}:${info.timeOfNewYear.minute}`,
372
+ "YYYY-MM-DD H:m",
373
+ );
374
+ let KhEpoch = getLunarDate(epochLerngSak);
375
+ let diffFromEpoch =
376
+ (KhEpoch.month - 4) * 30 +
377
+ KhEpoch.day -
378
+ ((info.lunarDateLerngSak.month - 4) * 30 + info.lunarDateLerngSak.day);
379
+
380
+ const result = epochLerngSak.subtract(
381
+ diffFromEpoch + numberOfNewYearDay - 1,
382
+ "day",
383
+ );
384
+
385
+ return {
386
+ date: result,
387
+ days: numberOfNewYearDay,
388
+ dates: datesOfKhmerNewYear(result, numberOfNewYearDay),
389
+ };
390
+ }
391
+ // find animal year
392
+ function getAnimalYear(date: dayjs.Dayjs): number {
393
+ const year = date.year();
394
+ const KhmerNewYear = getKhmerNewYear(year).date;
395
+ if (date.diff(KhmerNewYear) < 0) {
396
+ return (year + 543 + 4) % 12;
397
+ } else {
398
+ return (year + 544 + 4) % 12;
399
+ }
400
+ }
401
+
402
+ // Jolak Sakaraj
403
+ function getJolakSakarajYear(date: dayjs.Dayjs) {
404
+ const year = date.year();
405
+ const KhmerNewYear = getKhmerNewYear(year).date;
406
+ if (date.diff(KhmerNewYear) < 0) {
407
+ return year + 543 - 1182;
408
+ } else {
409
+ return year + 544 - 1182;
410
+ }
411
+ }
412
+
413
+ function getKhmerLunarDayName(day: number): {
414
+ count: number;
415
+ moonStatus: number;
416
+ } {
417
+ return {
418
+ count: (day % 15) + 1,
419
+ moonStatus:
420
+ day > 14 ? constant.moonStatus["រោច"] : constant.moonStatus["កើត"],
421
+ };
422
+ }
423
+
424
+ // Khmer date format handler
425
+ function formatKhmerDate(
426
+ day: number,
427
+ month: number,
428
+ dayjs: dayjs.Dayjs,
429
+ format: string,
430
+ ): string {
431
+ if (format === null || format === undefined || format === "") {
432
+ // Default date format
433
+ const dayOfWeek = dayjs.day();
434
+ const moonDay = getKhmerLunarDayName(day);
435
+ const beYear = getBEYear(dayjs);
436
+ const animalYear = getAnimalYear(dayjs);
437
+ const eraYear = getJolakSakarajYear(dayjs) % 10;
438
+ return constant.kh.postformat(
439
+ `ថ្ងៃ${constant.kh.weekdays[dayOfWeek]} ${moonDay.count}${
440
+ constant.kh.moonStatus[moonDay.moonStatus]
441
+ } ខែ${constant.kh.lunarMonths[month]} ឆ្នាំ${
442
+ constant.kh.animalYear[animalYear]
443
+ } ${constant.kh.eraYear[eraYear]} ពុទ្ធសករាជ ${beYear}`,
444
+ );
445
+ } else if (typeof format === "string") {
446
+ const formatRules = {
447
+ W: () => {
448
+ return constant.kh.weekdays[dayjs.day()];
449
+ },
450
+ w: () => {
451
+ return constant.kh.weekdaysShort[dayjs.day()];
452
+ },
453
+ d: () => {
454
+ const moonDay = getKhmerLunarDayName(day);
455
+ return moonDay.count;
456
+ },
457
+ D: () => {
458
+ const moonDay = getKhmerLunarDayName(day);
459
+ return ("" + moonDay.count).length === 1
460
+ ? `០${moonDay.count}`
461
+ : moonDay.count;
462
+ },
463
+ N: () => {
464
+ const moonDay = getKhmerLunarDayName(day);
465
+ return constant.kh.moonStatus[moonDay.moonStatus];
466
+ },
467
+ n: () => {
468
+ const moonDay = getKhmerLunarDayName(day);
469
+ return constant.kh.moonStatusShort[moonDay.moonStatus];
470
+ },
471
+ o: () => {
472
+ return constant.kh.moonDays[day];
473
+ },
474
+ m: () => {
475
+ return constant.kh.lunarMonths[month];
476
+ },
477
+ M: () => {
478
+ return constant.kh.months[dayjs.month()];
479
+ },
480
+ a: () => {
481
+ return constant.kh.animalYear[getAnimalYear(dayjs)];
482
+ },
483
+ e: () => {
484
+ return constant.kh.eraYear[getJolakSakarajYear(dayjs) % 10];
485
+ },
486
+ b: () => {
487
+ return getBEYear(dayjs);
488
+ },
489
+ c: () => {
490
+ return dayjs.year();
491
+ },
492
+ j: () => {
493
+ return getJolakSakarajYear(dayjs);
494
+ },
495
+ };
496
+ return constant.kh.postformat(
497
+ format.replace(
498
+ new RegExp(Object.keys(formatRules).join("|"), "g"),
499
+ (m) => formatRules[m](),
500
+ ),
501
+ );
502
+ }
503
+ }
504
+
505
+ // format date to Khmer date
506
+ function format(format?: string): string {
507
+ const date = globalDate.clone();
508
+ const lunarDate = getLunarDate(date);
509
+ const result = formatKhmerDate(
510
+ lunarDate.day,
511
+ lunarDate.month,
512
+ date,
513
+ format,
514
+ );
515
+ return result;
516
+ }
517
+
518
+ return {
519
+ format,
520
+ khmerNewYearDate: getKhmerNewYear,
521
+ };
522
+ }
@@ -0,0 +1,288 @@
1
+ import dayjs from "dayjs";
2
+ import { constant } from "./constant";
3
+
4
+ // calculate Khmer New Year
5
+ // based on: https://www.dahlina.com/education/khmer_new_year_time.html?fbclid=IwAR0Eq6US-F0LfplMjKzmiRn7rvPgi31i74Wpv4mNhU034mzdyj-3hYrCA8w
6
+ export default function learnSak(jsYear: number = dayjs().year()) {
7
+ if (arguments.length === 0) {
8
+ jsYear = dayjs().year();
9
+ } else {
10
+ jsYear = arguments[0];
11
+ }
12
+
13
+ const getInfo = (
14
+ jsYear: number,
15
+ ): {
16
+ harkun: number;
17
+ kromathopol: number;
18
+ avaman: number;
19
+ bodithey: number;
20
+ } => {
21
+ let h = 292207 * jsYear + 373;
22
+ let harkun = Math.floor(h / 800) + 1;
23
+ let kromathopol = 800 - (h % 800);
24
+
25
+ let a = 11 * harkun + 650;
26
+ let avaman = a % 692;
27
+ let bodithey = (harkun + Math.floor(a / 692)) % 30;
28
+ return {
29
+ harkun,
30
+ kromathopol,
31
+ avaman,
32
+ bodithey,
33
+ };
34
+ };
35
+
36
+ const getHas366Days = (jsYear: number): boolean => {
37
+ const { kromathopol } = getInfo(jsYear);
38
+ return kromathopol <= 207;
39
+ };
40
+
41
+ const getIsAdhikameas = (jsYear: number): boolean => {
42
+ const { bodithey: infoOfYear } = getInfo(jsYear);
43
+ const { bodithey: infoOfNextYear } = getInfo(jsYear + 1);
44
+ return (
45
+ !(infoOfYear === 25 && infoOfNextYear === 5) &&
46
+ (infoOfYear > 24 ||
47
+ infoOfNextYear < 6 ||
48
+ (infoOfYear === 24 && infoOfNextYear === 6))
49
+ );
50
+ };
51
+
52
+ const getIsChantreathimeas = (jsYear: number): boolean => {
53
+ const { avaman: infoOfYear } = getInfo(jsYear);
54
+ const { avaman: infoOfNextYear } = getInfo(jsYear + 1);
55
+ const { avaman: infoOfPrevYear } = getInfo(jsYear - 1);
56
+ const has366Days = getHas366Days(jsYear);
57
+ return (
58
+ (has366Days && infoOfYear < 127) ||
59
+ (!(infoOfYear === 137 && infoOfNextYear === 0) &&
60
+ ((!has366Days && infoOfYear < 138) ||
61
+ (infoOfPrevYear === 137 && infoOfYear === 0)))
62
+ );
63
+ };
64
+
65
+ const has366Days = getHas366Days(jsYear);
66
+ const isAdhikameas = getIsAdhikameas(jsYear);
67
+ const isChantreathimeas = getIsChantreathimeas(jsYear);
68
+
69
+ const jesthHas30 = (): boolean => {
70
+ const isAthikameas = isAdhikameas;
71
+ let tmp = isChantreathimeas;
72
+ if (isAthikameas && isChantreathimeas) {
73
+ tmp = false;
74
+ }
75
+ if (
76
+ !isChantreathimeas &&
77
+ getIsAdhikameas(jsYear - 1) &&
78
+ getIsChantreathimeas(jsYear - 1)
79
+ ) {
80
+ tmp = true;
81
+ }
82
+ return tmp;
83
+ };
84
+
85
+ const dayLerngSak = (getInfo(jsYear).harkun - 2) % 7;
86
+
87
+ const lunarDateLerngSak = (): {
88
+ day: number;
89
+ month: number;
90
+ } => {
91
+ let { bodithey: bodithey } = getInfo(jsYear);
92
+ if (getIsAdhikameas(jsYear - 1) && getIsChantreathimeas(jsYear - 1)) {
93
+ bodithey = (bodithey + 1) % 30;
94
+ }
95
+ return {
96
+ day: bodithey >= 6 ? bodithey - 1 : bodithey,
97
+ month:
98
+ bodithey >= 6
99
+ ? constant.lunarMonths["ចេត្រ"]
100
+ : constant.lunarMonths["ពិសាខ"],
101
+ };
102
+ };
103
+
104
+ const getSunInfo = (sotin: number) => {
105
+ const infoOfPreviousYear = getInfo(jsYear - 1);
106
+
107
+ const sunAverageAsLibda = (): number => {
108
+ let r2 = 800 * sotin + infoOfPreviousYear.kromathopol;
109
+ let reasey = Math.floor(r2 / 24350); // រាសី
110
+ let r3 = r2 % 24350;
111
+ let angsar = Math.floor(r3 / 811); // អង្សា
112
+ let r4 = r3 % 811;
113
+ let l1 = Math.floor(r4 / 14);
114
+ let libda = l1 - 3; // លិប្ដា
115
+ return 30 * 60 * reasey + 60 * angsar + libda;
116
+ };
117
+
118
+ const leftOver = (): number => {
119
+ let s1 = 30 * 60 * 2 + 60 * 20;
120
+ let leftOver = sunAverageAsLibda() - s1; // មធ្យមព្រះអាទិត្យ - R2.A20.L0
121
+ if (sunAverageAsLibda() < s1) {
122
+ // បើតូចជាង ខ្ចី ១២ រាសី
123
+ leftOver += 30 * 60 * 12;
124
+ }
125
+ return leftOver;
126
+ };
127
+
128
+ const kaen = (): number => {
129
+ return Math.floor(leftOver() / (30 * 60));
130
+ };
131
+
132
+ const lastLeftOver = (): {
133
+ reasey: number;
134
+ angsar: number;
135
+ libda: number;
136
+ } => {
137
+ let rs = -1;
138
+ if ([0, 1, 2].includes(kaen())) {
139
+ rs = kaen();
140
+ } else if ([3, 4, 5].includes(kaen())) {
141
+ rs = 30 * 60 * 6 - leftOver(); // R6.A0.L0 - leftover
142
+ } else if ([6, 7, 8].includes(kaen())) {
143
+ rs = leftOver() - 30 * 60 * 6; // leftover - R6.A0.L0
144
+ } else if ([9, 10, 11].includes(kaen())) {
145
+ rs = 30 * 60 * 11 + 60 * 29 + 60 - leftOver(); // R11.A29.L60 - leftover
146
+ }
147
+ return {
148
+ reasey: Math.floor(rs / (30 * 60)),
149
+ angsar: Math.floor((rs % (30 * 60)) / 60),
150
+ libda: rs % 60,
151
+ };
152
+ };
153
+
154
+ const khan = (): number => {
155
+ if (lastLeftOver().angsar >= 15) {
156
+ return 2 * lastLeftOver().reasey + 1;
157
+ } else {
158
+ return 2 * lastLeftOver().reasey;
159
+ }
160
+ };
161
+
162
+ const pouichalip = (): number => {
163
+ if (lastLeftOver().angsar >= 15) {
164
+ return 60 * (lastLeftOver().angsar - 15) + lastLeftOver().libda;
165
+ } else {
166
+ return 60 * lastLeftOver().angsar + lastLeftOver().libda;
167
+ }
168
+ };
169
+
170
+ const phol = (): {
171
+ reasey: number;
172
+ angsar: number;
173
+ libda: number;
174
+ } => {
175
+ const chhayaSun = (
176
+ khan: number,
177
+ ): {
178
+ multiplicity: number;
179
+ chhaya: number;
180
+ } => {
181
+ let multiplicities = [35, 32, 27, 22, 13, 5];
182
+ let chhayas = [0, 35, 67, 94, 116, 129];
183
+ switch (khan) {
184
+ case 0:
185
+ case 1:
186
+ case 2:
187
+ case 3:
188
+ case 4:
189
+ case 5:
190
+ return {
191
+ multiplicity: multiplicities[khan],
192
+ chhaya: chhayas[khan],
193
+ };
194
+ default:
195
+ return {
196
+ multiplicity: 0,
197
+ chhaya: 134,
198
+ };
199
+ }
200
+ };
201
+
202
+ let val = chhayaSun(khan());
203
+ let q = Math.floor((pouichalip() * val.multiplicity) / 900);
204
+ return {
205
+ reasey: 0,
206
+ angsar: Math.floor((q + val.chhaya) / 60),
207
+ libda: (q + val.chhaya) % 60,
208
+ };
209
+ };
210
+
211
+ const sunInaugurationAsLibda = (): number => {
212
+ let pholAsLibda =
213
+ 30 * 60 * phol().reasey + 60 * phol().angsar + phol().libda;
214
+ if (kaen() <= 5) {
215
+ return sunAverageAsLibda() - pholAsLibda;
216
+ } else {
217
+ return sunAverageAsLibda() + pholAsLibda;
218
+ }
219
+ };
220
+
221
+ return {
222
+ sunAverageAsLibda,
223
+ khan,
224
+ pouichalip,
225
+ phol,
226
+ sunInaugurationAsLibda,
227
+ };
228
+ };
229
+
230
+ const newYearsDaySotins = (): {
231
+ sotin: number;
232
+ reasey: number;
233
+ angsar: number;
234
+ libda: number;
235
+ }[] => {
236
+ let sotins = getHas366Days(jsYear - 1)
237
+ ? [363, 364, 365, 366]
238
+ : [362, 363, 364, 365]; // សុទិន
239
+ return sotins.map((sotin) => {
240
+ let sunInfo = getSunInfo(sotin);
241
+ return {
242
+ sotin: sotin,
243
+ reasey: Math.floor(sunInfo.sunInaugurationAsLibda() / (30 * 60)),
244
+ angsar: Math.floor((sunInfo.sunInaugurationAsLibda() % (30 * 60)) / 60), // អង្សាស្មើសូន្យ គីជាថ្ងៃចូលឆ្នាំ, មួយ ឬ ពីរ ថ្ងៃបន្ទាប់ជាថ្ងៃវ័នបត ហើយ ថ្ងៃចុងក្រោយគីឡើងស័ក
245
+ libda: sunInfo.sunInaugurationAsLibda() % 60,
246
+ };
247
+ });
248
+ };
249
+
250
+ const timeOfNewYear = (): {
251
+ hour: number;
252
+ minute: number;
253
+ } => {
254
+ let sotinNewYear = newYearsDaySotins().filter(function (sotin) {
255
+ return sotin.angsar === 0;
256
+ });
257
+ if (sotinNewYear.length > 0) {
258
+ let libda = sotinNewYear[0].libda; // ២៤ ម៉ោង មាន ៦០លិប្ដា
259
+ let minutes = 24 * 60 - libda * 24;
260
+ return {
261
+ hour: Math.floor(minutes / 60),
262
+ minute: minutes % 60,
263
+ };
264
+ } else {
265
+ throw Error(
266
+ "Plugin is facing wrong calculation on new years hour. No sotin with angsar = 0",
267
+ );
268
+ }
269
+ };
270
+
271
+ const info = getInfo(jsYear);
272
+
273
+ return {
274
+ jsYear: jsYear,
275
+ harkun: info.harkun,
276
+ kromathopol: info.kromathopol,
277
+ avaman: info.avaman,
278
+ bodithey: info.bodithey,
279
+ has366Days: has366Days,
280
+ isAdhikameas: isAdhikameas,
281
+ isChantreathimeas: isChantreathimeas,
282
+ jesthHas30: jesthHas30(),
283
+ dayLerngSak: dayLerngSak,
284
+ lunarDateLerngSak: lunarDateLerngSak(),
285
+ newYearsDaySotins: newYearsDaySotins(),
286
+ timeOfNewYear: timeOfNewYear(),
287
+ };
288
+ }
package/src/main.ts ADDED
@@ -0,0 +1,17 @@
1
+ import dayjskh from "./dayjskh";
2
+ import type { PluginFunc } from "dayjs";
3
+ import type plugin from "../types";
4
+ import { constant } from "./constant";
5
+
6
+ const toKhDate: PluginFunc<plugin.toKhDate> = (o, c, d) => {
7
+ const proto = c.prototype;
8
+ proto.toKhDate = function (format?: string) {
9
+ const date = constant.kh.preparse(this.format());
10
+ return dayjskh(date).format(format);
11
+ };
12
+ proto.khNewYear = function () {
13
+ return dayjskh(this.year()).khmerNewYearDate(this.year());
14
+ };
15
+ };
16
+
17
+ export default toKhDate;
@@ -0,0 +1 @@
1
+ /// <reference types="vite/client" />
package/tsconfig.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "useDefineForClassFields": true,
5
+ "module": "ESNext",
6
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
7
+ "skipLibCheck": true,
8
+
9
+ /* Bundler mode */
10
+ "moduleResolution": "bundler",
11
+ "allowImportingTsExtensions": true,
12
+ "resolveJsonModule": true,
13
+ "isolatedModules": true,
14
+ "noEmit": true,
15
+
16
+ /* Linting */
17
+ "noUnusedLocals": true,
18
+ "noUnusedParameters": false,
19
+ "noFallthroughCasesInSwitch": true
20
+ },
21
+ "include": ["src", "types"]
22
+ }
@@ -0,0 +1,19 @@
1
+ // add type to support dayjs
2
+ import type { Dayjs, PluginFunc } from "dayjs/esm/index";
3
+
4
+ declare const plugin: PluginFunc;
5
+ export = plugin;
6
+ declare namespace plugin {
7
+ export interface toKhDate {
8
+ toKhDate(format?: string): string;
9
+ khNewYear(): {
10
+ date: dayjs.Dayjs;
11
+ days: number;
12
+ dates: { date: dayjs.Dayjs; dayName: string }[];
13
+ };
14
+ }
15
+ }
16
+
17
+ declare module "dayjs" {
18
+ interface Dayjs extends plugin.toKhDate {}
19
+ }