@thyrith/momentkh 2.5.5 → 3.0.1
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/README.md +1298 -93
- package/dist/momentkh.d.ts +196 -0
- package/dist/momentkh.d.ts.map +1 -0
- package/dist/momentkh.js +1063 -0
- package/momentkh.js +1053 -637
- package/momentkh.ts +1341 -0
- package/package.json +25 -2
- package/constant.js +0 -58
- package/example/newYearMoment.js +0 -4
- package/example/toKhDate.js +0 -6
- package/getSoriyatraLerngSak.js +0 -301
- package/index.html +0 -169
- package/locale/km.js +0 -70
package/momentkh.js
CHANGED
|
@@ -1,673 +1,1089 @@
|
|
|
1
|
+
(function (root, factory) {
|
|
2
|
+
if (typeof define === 'function' && define.amd) {
|
|
3
|
+
// AMD
|
|
4
|
+
define([], factory);
|
|
5
|
+
} else if (typeof module === 'object' && module.exports) {
|
|
6
|
+
// Node/CommonJS
|
|
7
|
+
module.exports = factory();
|
|
8
|
+
} else {
|
|
9
|
+
// Browser globals
|
|
10
|
+
var exp = factory();
|
|
11
|
+
root.momentkh = exp;
|
|
12
|
+
root.MoonPhase = exp.MoonPhase;
|
|
13
|
+
root.MonthIndex = exp.MonthIndex;
|
|
14
|
+
root.AnimalYear = exp.AnimalYear;
|
|
15
|
+
root.EraYear = exp.EraYear;
|
|
16
|
+
root.DayOfWeek = exp.DayOfWeek;
|
|
17
|
+
}
|
|
18
|
+
}(typeof self !== 'undefined' ? self : this, function () {
|
|
19
|
+
// CommonJS module code
|
|
20
|
+
var module = { exports: {} };
|
|
21
|
+
var exports = module.exports;
|
|
22
|
+
|
|
23
|
+
"use strict";
|
|
1
24
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
25
|
+
* MomentKH - Standalone Khmer Calendar Library (TypeScript)
|
|
26
|
+
*
|
|
27
|
+
* A simplified, standalone library for Khmer calendar conversion
|
|
28
|
+
* No dependencies required
|
|
4
29
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* Here we choose to use only Buddhist Era.
|
|
30
|
+
* Based on:
|
|
31
|
+
* - khmer_calendar.cpp implementation
|
|
32
|
+
* - Original momentkh library
|
|
9
33
|
*
|
|
10
|
-
* @
|
|
11
|
-
* @
|
|
34
|
+
* @version 2.0.0
|
|
35
|
+
* @license MIT
|
|
12
36
|
*/
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
37
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
38
|
+
exports.constants = exports.DayOfWeek = exports.EraYear = exports.AnimalYear = exports.MonthIndex = exports.MoonPhase = void 0;
|
|
39
|
+
exports.fromGregorian = fromGregorian;
|
|
40
|
+
exports.fromKhmer = fromKhmer;
|
|
41
|
+
exports.getNewYear = getNewYear;
|
|
42
|
+
exports.format = format;
|
|
43
|
+
exports.fromDate = fromDate;
|
|
44
|
+
exports.toDate = toDate;
|
|
45
|
+
// ============================================================================
|
|
46
|
+
// Type Definitions
|
|
47
|
+
// ============================================================================
|
|
48
|
+
// Enums for better type safety and ease of use
|
|
49
|
+
var MoonPhase;
|
|
50
|
+
(function (MoonPhase) {
|
|
51
|
+
MoonPhase[MoonPhase["Waxing"] = 0] = "Waxing";
|
|
52
|
+
MoonPhase[MoonPhase["Waning"] = 1] = "Waning"; // រោច
|
|
53
|
+
})(MoonPhase || (exports.MoonPhase = MoonPhase = {}));
|
|
54
|
+
var MonthIndex;
|
|
55
|
+
(function (MonthIndex) {
|
|
56
|
+
MonthIndex[MonthIndex["Migasir"] = 0] = "Migasir";
|
|
57
|
+
MonthIndex[MonthIndex["Boss"] = 1] = "Boss";
|
|
58
|
+
MonthIndex[MonthIndex["Meak"] = 2] = "Meak";
|
|
59
|
+
MonthIndex[MonthIndex["Phalkun"] = 3] = "Phalkun";
|
|
60
|
+
MonthIndex[MonthIndex["Cheit"] = 4] = "Cheit";
|
|
61
|
+
MonthIndex[MonthIndex["Pisakh"] = 5] = "Pisakh";
|
|
62
|
+
MonthIndex[MonthIndex["Jesth"] = 6] = "Jesth";
|
|
63
|
+
MonthIndex[MonthIndex["Asadh"] = 7] = "Asadh";
|
|
64
|
+
MonthIndex[MonthIndex["Srap"] = 8] = "Srap";
|
|
65
|
+
MonthIndex[MonthIndex["Phatrabot"] = 9] = "Phatrabot";
|
|
66
|
+
MonthIndex[MonthIndex["Assoch"] = 10] = "Assoch";
|
|
67
|
+
MonthIndex[MonthIndex["Kadeuk"] = 11] = "Kadeuk";
|
|
68
|
+
MonthIndex[MonthIndex["Pathamasadh"] = 12] = "Pathamasadh";
|
|
69
|
+
MonthIndex[MonthIndex["Tutiyasadh"] = 13] = "Tutiyasadh"; // ទុតិយាសាឍ
|
|
70
|
+
})(MonthIndex || (exports.MonthIndex = MonthIndex = {}));
|
|
71
|
+
var AnimalYear;
|
|
72
|
+
(function (AnimalYear) {
|
|
73
|
+
AnimalYear[AnimalYear["Chhut"] = 0] = "Chhut";
|
|
74
|
+
AnimalYear[AnimalYear["Chlov"] = 1] = "Chlov";
|
|
75
|
+
AnimalYear[AnimalYear["Khal"] = 2] = "Khal";
|
|
76
|
+
AnimalYear[AnimalYear["Thos"] = 3] = "Thos";
|
|
77
|
+
AnimalYear[AnimalYear["Rong"] = 4] = "Rong";
|
|
78
|
+
AnimalYear[AnimalYear["Masagn"] = 5] = "Masagn";
|
|
79
|
+
AnimalYear[AnimalYear["Momee"] = 6] = "Momee";
|
|
80
|
+
AnimalYear[AnimalYear["Momae"] = 7] = "Momae";
|
|
81
|
+
AnimalYear[AnimalYear["Vok"] = 8] = "Vok";
|
|
82
|
+
AnimalYear[AnimalYear["Roka"] = 9] = "Roka";
|
|
83
|
+
AnimalYear[AnimalYear["Cho"] = 10] = "Cho";
|
|
84
|
+
AnimalYear[AnimalYear["Kor"] = 11] = "Kor"; // កុរ - Pig
|
|
85
|
+
})(AnimalYear || (exports.AnimalYear = AnimalYear = {}));
|
|
86
|
+
var EraYear;
|
|
87
|
+
(function (EraYear) {
|
|
88
|
+
EraYear[EraYear["SamridhiSak"] = 0] = "SamridhiSak";
|
|
89
|
+
EraYear[EraYear["AekSak"] = 1] = "AekSak";
|
|
90
|
+
EraYear[EraYear["ToSak"] = 2] = "ToSak";
|
|
91
|
+
EraYear[EraYear["TreiSak"] = 3] = "TreiSak";
|
|
92
|
+
EraYear[EraYear["ChattvaSak"] = 4] = "ChattvaSak";
|
|
93
|
+
EraYear[EraYear["PanchaSak"] = 5] = "PanchaSak";
|
|
94
|
+
EraYear[EraYear["ChhaSak"] = 6] = "ChhaSak";
|
|
95
|
+
EraYear[EraYear["SappaSak"] = 7] = "SappaSak";
|
|
96
|
+
EraYear[EraYear["AtthaSak"] = 8] = "AtthaSak";
|
|
97
|
+
EraYear[EraYear["NappaSak"] = 9] = "NappaSak"; // នព្វស័ក
|
|
98
|
+
})(EraYear || (exports.EraYear = EraYear = {}));
|
|
99
|
+
var DayOfWeek;
|
|
100
|
+
(function (DayOfWeek) {
|
|
101
|
+
DayOfWeek[DayOfWeek["Sunday"] = 0] = "Sunday";
|
|
102
|
+
DayOfWeek[DayOfWeek["Monday"] = 1] = "Monday";
|
|
103
|
+
DayOfWeek[DayOfWeek["Tuesday"] = 2] = "Tuesday";
|
|
104
|
+
DayOfWeek[DayOfWeek["Wednesday"] = 3] = "Wednesday";
|
|
105
|
+
DayOfWeek[DayOfWeek["Thursday"] = 4] = "Thursday";
|
|
106
|
+
DayOfWeek[DayOfWeek["Friday"] = 5] = "Friday";
|
|
107
|
+
DayOfWeek[DayOfWeek["Saturday"] = 6] = "Saturday"; // សៅរ៍
|
|
108
|
+
})(DayOfWeek || (exports.DayOfWeek = DayOfWeek = {}));
|
|
109
|
+
// ============================================================================
|
|
110
|
+
// Constants and Locale Data
|
|
111
|
+
// ============================================================================
|
|
112
|
+
const LunarMonths = {
|
|
113
|
+
'មិគសិរ': 0, 'បុស្ស': 1, 'មាឃ': 2, 'ផល្គុន': 3,
|
|
114
|
+
'ចេត្រ': 4, 'ពិសាខ': 5, 'ជេស្ឋ': 6, 'អាសាឍ': 7,
|
|
115
|
+
'ស្រាពណ៍': 8, 'ភទ្របទ': 9, 'អស្សុជ': 10, 'កត្ដិក': 11,
|
|
116
|
+
'បឋមាសាឍ': 12, 'ទុតិយាសាឍ': 13
|
|
117
|
+
};
|
|
118
|
+
const LunarMonthNames = [
|
|
119
|
+
'មិគសិរ', 'បុស្ស', 'មាឃ', 'ផល្គុន', 'ចេត្រ', 'ពិសាខ',
|
|
120
|
+
'ជេស្ឋ', 'អាសាឍ', 'ស្រាពណ៍', 'ភទ្របទ', 'អស្សុជ', 'កត្ដិក',
|
|
121
|
+
'បឋមាសាឍ', 'ទុតិយាសាឍ'
|
|
122
|
+
];
|
|
123
|
+
const SolarMonthNames = [
|
|
124
|
+
'មករា', 'កុម្ភៈ', 'មីនា', 'មេសា', 'ឧសភា', 'មិថុនា',
|
|
125
|
+
'កក្កដា', 'សីហា', 'កញ្ញា', 'តុលា', 'វិច្ឆិកា', 'ធ្នូ'
|
|
126
|
+
];
|
|
127
|
+
const AnimalYearNames = [
|
|
128
|
+
'ជូត', 'ឆ្លូវ', 'ខាល', 'ថោះ', 'រោង', 'ម្សាញ់',
|
|
129
|
+
'មមី', 'មមែ', 'វក', 'រកា', 'ច', 'កុរ'
|
|
130
|
+
];
|
|
131
|
+
const EraYearNames = [
|
|
132
|
+
'សំរឹទ្ធិស័ក', 'ឯកស័ក', 'ទោស័ក', 'ត្រីស័ក', 'ចត្វាស័ក',
|
|
133
|
+
'បញ្ចស័ក', 'ឆស័ក', 'សប្តស័ក', 'អដ្ឋស័ក', 'នព្វស័ក'
|
|
134
|
+
];
|
|
135
|
+
const WeekdayNames = [
|
|
136
|
+
'អាទិត្យ', 'ចន្ទ', 'អង្គារ', 'ពុធ', 'ព្រហស្បតិ៍', 'សុក្រ', 'សៅរ៍'
|
|
137
|
+
];
|
|
138
|
+
const WeekdayNamesShort = ['អា', 'ច', 'អ', 'ព', 'ព្រ', 'សុ', 'ស'];
|
|
139
|
+
const MoonStatusNames = ['កើត', 'រោច'];
|
|
140
|
+
const MoonStatusShort = ['ក', 'រ'];
|
|
141
|
+
const MoonDaySymbols = [
|
|
142
|
+
'᧡', '᧢', '᧣', '᧤', '᧥', '᧦', '᧧', '᧨', '᧩', '᧪',
|
|
143
|
+
'᧫', '᧬', '᧭', '᧮', '᧯', '᧱', '᧲', '᧳', '᧴', '᧵',
|
|
144
|
+
'᧶', '᧷', '᧸', '᧹', '᧺', '᧻', '᧼', '᧽', '᧾', '᧿'
|
|
145
|
+
];
|
|
146
|
+
const KhmerNumerals = {
|
|
147
|
+
'0': '០', '1': '១', '2': '២', '3': '៣', '4': '៤',
|
|
148
|
+
'5': '៥', '6': '៦', '7': '៧', '8': '៨', '9': '៩'
|
|
149
|
+
};
|
|
150
|
+
// Exceptional New Year moments (cached values for specific years)
|
|
151
|
+
const khNewYearMoments = {
|
|
152
|
+
'1879': '12-04-1879 11:36',
|
|
153
|
+
'1897': '13-04-1897 02:00',
|
|
154
|
+
'2011': '14-04-2011 13:12',
|
|
155
|
+
'2012': '14-04-2012 19:11',
|
|
156
|
+
'2013': '14-04-2013 02:12',
|
|
157
|
+
'2014': '14-04-2014 08:07',
|
|
158
|
+
'2015': '14-04-2015 14:02',
|
|
159
|
+
'2024': '13-04-2024 22:17',
|
|
160
|
+
};
|
|
161
|
+
// ============================================================================
|
|
162
|
+
// Utility Functions
|
|
163
|
+
// ============================================================================
|
|
164
|
+
function toKhmerNumeral(num) {
|
|
165
|
+
return String(num).replace(/\d/g, d => KhmerNumerals[d]);
|
|
166
|
+
}
|
|
167
|
+
function isGregorianLeapYear(year) {
|
|
168
|
+
return (year % 4 === 0 && year % 100 !== 0) || (year % 400 === 0);
|
|
169
|
+
}
|
|
170
|
+
function getDaysInGregorianMonth(year, month) {
|
|
171
|
+
const daysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
172
|
+
if (month === 2 && isGregorianLeapYear(year)) {
|
|
173
|
+
return 29;
|
|
23
174
|
}
|
|
24
|
-
|
|
175
|
+
return daysInMonth[month - 1];
|
|
25
176
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
if (!this.momentkhConstant) {
|
|
34
|
-
throw "Please import [MOMENTKH]/constant.js to your project"
|
|
35
|
-
} else {
|
|
36
|
-
constant = this.momentkhConstant
|
|
37
|
-
}
|
|
177
|
+
// Julian Day Number conversion
|
|
178
|
+
function gregorianToJulianDay(year, month, day) {
|
|
179
|
+
const a = Math.floor((14 - month) / 12);
|
|
180
|
+
const y = year + 4800 - a;
|
|
181
|
+
const m = month + 12 * a - 3;
|
|
182
|
+
return day + Math.floor((153 * m + 2) / 5) + 365 * y +
|
|
183
|
+
Math.floor(y / 4) - Math.floor(y / 100) + Math.floor(y / 400) - 32045;
|
|
38
184
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
185
|
+
function julianDayToGregorian(jdn) {
|
|
186
|
+
const a = jdn + 32044;
|
|
187
|
+
const b = Math.floor((4 * a + 3) / 146097);
|
|
188
|
+
const c = a - Math.floor((146097 * b) / 4);
|
|
189
|
+
const d = Math.floor((4 * c + 3) / 1461);
|
|
190
|
+
const e = c - Math.floor((1461 * d) / 4);
|
|
191
|
+
const m = Math.floor((5 * e + 2) / 153);
|
|
192
|
+
const day = e - Math.floor((153 * m + 2) / 5) + 1;
|
|
193
|
+
const month = m + 3 - 12 * Math.floor(m / 10);
|
|
194
|
+
const year = 100 * b + d - 4800 + Math.floor(m / 10);
|
|
195
|
+
return { year, month, day };
|
|
196
|
+
}
|
|
197
|
+
function getDayOfWeek(year, month, day) {
|
|
198
|
+
const jdn = gregorianToJulianDay(year, month, day);
|
|
199
|
+
// JDN % 7: where 0=Monday, 1=Tuesday, ..., 6=Sunday
|
|
200
|
+
// We want: 0=Sunday, 1=Monday, ..., 6=Saturday
|
|
201
|
+
// So we need to convert: (jdn + 1) % 7
|
|
202
|
+
return (jdn + 1) % 7;
|
|
203
|
+
}
|
|
204
|
+
// ============================================================================
|
|
205
|
+
// Input Validation Functions
|
|
206
|
+
// ============================================================================
|
|
207
|
+
/**
|
|
208
|
+
* Validates Gregorian date parameters
|
|
209
|
+
* @throws {Error} If any parameter is invalid
|
|
210
|
+
*/
|
|
211
|
+
function validateGregorianDate(year, month, day, hour = 0, minute = 0, second = 0) {
|
|
212
|
+
// Validate types
|
|
213
|
+
if (typeof year !== 'number' || isNaN(year)) {
|
|
214
|
+
throw new Error(`Invalid year: ${year}. Year must be a valid number.`);
|
|
52
215
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
'use strict';
|
|
56
|
-
|
|
57
|
-
Moment.khNewYearMoments = khNewYearMoments
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Bodithey: បូតិថី
|
|
61
|
-
* Bodithey determines if a given beYear is a leap-month year. Given year target year in Buddhist Era
|
|
62
|
-
* @return Number (0-29)
|
|
63
|
-
*/
|
|
64
|
-
function getBodithey(beYear) {
|
|
65
|
-
let ahk = getAharkun(beYear);
|
|
66
|
-
let avml = Math.floor((11 * ahk + 25) / 692);
|
|
67
|
-
let m = avml + ahk + 29;
|
|
68
|
-
return (m % 30);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Avoman: អាវមាន
|
|
73
|
-
* Avoman determines if a given year is a leap-day year. Given a year in Buddhist Era as denoted as adYear
|
|
74
|
-
* @param beYear (0 - 691)
|
|
75
|
-
*/
|
|
76
|
-
function getAvoman(beYear) {
|
|
77
|
-
let ahk = getAharkun(beYear);
|
|
78
|
-
let avm = (11 * ahk + 25) % 692;
|
|
79
|
-
return avm;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Aharkun: អាហារគុណ ឬ ហារគុណ
|
|
84
|
-
* Aharkun is used for Avoman and Bodithey calculation below. Given adYear as a target year in Buddhist Era
|
|
85
|
-
* @param beYear
|
|
86
|
-
* @returns {number}
|
|
87
|
-
*/
|
|
88
|
-
function getAharkun(beYear) {
|
|
89
|
-
let t = beYear * 292207 + 499;
|
|
90
|
-
let ahk = Math.floor(t / 800) + 4;
|
|
91
|
-
return ahk;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Kromathupul
|
|
96
|
-
* @param beYear
|
|
97
|
-
* @returns {number} (1-800)
|
|
98
|
-
*/
|
|
99
|
-
function kromthupul(beYear) {
|
|
100
|
-
let ah = getAharkunMod(beYear);
|
|
101
|
-
let krom = 800 - ah;
|
|
102
|
-
return krom;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* isKhmerSolarLeap
|
|
107
|
-
* @param beYear
|
|
108
|
-
* @returns {number}
|
|
109
|
-
*/
|
|
110
|
-
function isKhmerSolarLeap(beYear) {
|
|
111
|
-
let krom = kromthupul(beYear);
|
|
112
|
-
if (krom <= 207)
|
|
113
|
-
return 1;
|
|
114
|
-
else
|
|
115
|
-
return 0;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* getAkhakunMod
|
|
120
|
-
* @param beYear
|
|
121
|
-
* @returns {number}
|
|
122
|
-
*/
|
|
123
|
-
function getAharkunMod(beYear) {
|
|
124
|
-
let t = beYear * 292207 + 499;
|
|
125
|
-
let ahkmod = t % 800;
|
|
126
|
-
return ahkmod;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* * Regular if year has 30 day
|
|
131
|
-
* * leap month if year has 13 months
|
|
132
|
-
* * leap day if Jesth month of the year has 1 extra day
|
|
133
|
-
* * leap day and month: both of them
|
|
134
|
-
* @param beYear
|
|
135
|
-
* @returns {number} return 0:regular, 1:leap month, 2:leap day, 3:leap day and month
|
|
136
|
-
*/
|
|
137
|
-
function getBoditheyLeap(beYear) {
|
|
138
|
-
let result = 0;
|
|
139
|
-
let avoman = getAvoman(beYear);
|
|
140
|
-
let bodithey = getBodithey(beYear);
|
|
141
|
-
|
|
142
|
-
// check bodithey leap month
|
|
143
|
-
let boditheyLeap = 0;
|
|
144
|
-
if (bodithey >= 25 || bodithey <= 5) {
|
|
145
|
-
boditheyLeap = 1;
|
|
146
|
-
}
|
|
147
|
-
// check for avoman leap-day based on gregorian leap
|
|
148
|
-
let avomanLeap = 0;
|
|
149
|
-
if (isKhmerSolarLeap(beYear)) {
|
|
150
|
-
if (avoman <= 126)
|
|
151
|
-
avomanLeap = 1;
|
|
152
|
-
} else {
|
|
153
|
-
if (avoman <= 137) {
|
|
154
|
-
// check for avoman case 137/0, 137 must be normal year (p.26)
|
|
155
|
-
if (getAvoman(beYear + 1) === 0) {
|
|
156
|
-
avomanLeap = 0;
|
|
157
|
-
} else {
|
|
158
|
-
avomanLeap = 1;
|
|
159
|
-
}
|
|
160
|
-
}
|
|
216
|
+
if (typeof month !== 'number' || isNaN(month)) {
|
|
217
|
+
throw new Error(`Invalid month: ${month}. Month must be a valid number.`);
|
|
161
218
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
// only bodithey 5 can be leap-month, so set bodithey 25 to none
|
|
165
|
-
if (bodithey === 25) {
|
|
166
|
-
let nextBodithey = getBodithey(beYear + 1);
|
|
167
|
-
if (nextBodithey === 5) {
|
|
168
|
-
boditheyLeap = 0;
|
|
169
|
-
}
|
|
219
|
+
if (typeof day !== 'number' || isNaN(day)) {
|
|
220
|
+
throw new Error(`Invalid day: ${day}. Day must be a valid number.`);
|
|
170
221
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
if (bodithey == 24) {
|
|
174
|
-
let nextBodithey = getBodithey(beYear + 1);
|
|
175
|
-
if (nextBodithey == 6) {
|
|
176
|
-
boditheyLeap = 1;
|
|
177
|
-
}
|
|
222
|
+
if (typeof hour !== 'number' || isNaN(hour)) {
|
|
223
|
+
throw new Error(`Invalid hour: ${hour}. Hour must be a valid number.`);
|
|
178
224
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
if (boditheyLeap === 1 && avomanLeap === 1) {
|
|
182
|
-
result = 3;
|
|
183
|
-
} else if (boditheyLeap === 1) {
|
|
184
|
-
result = 1;
|
|
185
|
-
} else if (avomanLeap === 1) {
|
|
186
|
-
result = 2;
|
|
187
|
-
} else {
|
|
188
|
-
result = 0;
|
|
225
|
+
if (typeof minute !== 'number' || isNaN(minute)) {
|
|
226
|
+
throw new Error(`Invalid minute: ${minute}. Minute must be a valid number.`);
|
|
189
227
|
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
if (
|
|
206
|
-
|
|
207
|
-
}
|
|
208
|
-
//
|
|
209
|
-
if (
|
|
210
|
-
|
|
211
|
-
}
|
|
212
|
-
//
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
if (
|
|
224
|
-
|
|
225
|
-
}
|
|
226
|
-
if (
|
|
227
|
-
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
228
|
+
if (typeof second !== 'number' || isNaN(second)) {
|
|
229
|
+
throw new Error(`Invalid second: ${second}. Second must be a valid number.`);
|
|
230
|
+
}
|
|
231
|
+
// Validate month range (1-12)
|
|
232
|
+
if (month < 1 || month > 12) {
|
|
233
|
+
throw new Error(`Invalid month: ${month}. Month must be between 1 and 12.`);
|
|
234
|
+
}
|
|
235
|
+
// Validate day range for the specific month/year
|
|
236
|
+
const daysInMonth = getDaysInGregorianMonth(year, month);
|
|
237
|
+
if (day < 1 || day > daysInMonth) {
|
|
238
|
+
const monthNames = ['January', 'February', 'March', 'April', 'May', 'June',
|
|
239
|
+
'July', 'August', 'September', 'October', 'November', 'December'];
|
|
240
|
+
throw new Error(`Invalid day: ${day}. ${monthNames[month - 1]} ${year} has ${daysInMonth} days.`);
|
|
241
|
+
}
|
|
242
|
+
// Validate hour (0-23)
|
|
243
|
+
if (hour < 0 || hour > 23) {
|
|
244
|
+
throw new Error(`Invalid hour: ${hour}. Hour must be between 0 and 23.`);
|
|
245
|
+
}
|
|
246
|
+
// Validate minute (0-59)
|
|
247
|
+
if (minute < 0 || minute > 59) {
|
|
248
|
+
throw new Error(`Invalid minute: ${minute}. Minute must be between 0 and 59.`);
|
|
249
|
+
}
|
|
250
|
+
// Validate second (0-59)
|
|
251
|
+
if (second < 0 || second > 59) {
|
|
252
|
+
throw new Error(`Invalid second: ${second}. Second must be between 0 and 59.`);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Validates Khmer date parameters
|
|
257
|
+
* @throws {Error} If any parameter is invalid
|
|
258
|
+
*/
|
|
259
|
+
function validateKhmerDate(day, moonPhase, monthIndex, beYear) {
|
|
260
|
+
// Validate types
|
|
261
|
+
if (typeof day !== 'number' || isNaN(day)) {
|
|
262
|
+
throw new Error(`Invalid day: ${day}. Day must be a valid number.`);
|
|
263
|
+
}
|
|
264
|
+
if (typeof moonPhase !== 'number' || isNaN(moonPhase)) {
|
|
265
|
+
throw new Error(`Invalid moonPhase: ${moonPhase}. moonPhase must be a valid number.`);
|
|
266
|
+
}
|
|
267
|
+
if (typeof monthIndex !== 'number' || isNaN(monthIndex)) {
|
|
268
|
+
throw new Error(`Invalid monthIndex: ${monthIndex}. monthIndex must be a valid number.`);
|
|
269
|
+
}
|
|
270
|
+
if (typeof beYear !== 'number' || isNaN(beYear)) {
|
|
271
|
+
throw new Error(`Invalid beYear: ${beYear}. beYear must be a valid number.`);
|
|
272
|
+
}
|
|
273
|
+
// Validate day (1-15)
|
|
274
|
+
if (day < 1 || day > 15) {
|
|
275
|
+
throw new Error(`Invalid day: ${day}. Lunar day must be between 1 and 15.`);
|
|
276
|
+
}
|
|
277
|
+
// Validate moonPhase (0 = Waxing, 1 = Waning)
|
|
278
|
+
const moonPhaseNum = typeof moonPhase === 'number' ? moonPhase : moonPhase;
|
|
279
|
+
if (moonPhaseNum !== 0 && moonPhaseNum !== 1) {
|
|
280
|
+
throw new Error(`Invalid moonPhase: ${moonPhase}. moonPhase must be 0 (Waxing/កើត) or 1 (Waning/រោច).`);
|
|
281
|
+
}
|
|
282
|
+
// Validate monthIndex (0-13)
|
|
283
|
+
const monthIndexNum = typeof monthIndex === 'number' ? monthIndex : monthIndex;
|
|
284
|
+
if (monthIndexNum < 0 || monthIndexNum > 13) {
|
|
285
|
+
throw new Error(`Invalid monthIndex: ${monthIndex}. monthIndex must be between 0 and 13.`);
|
|
286
|
+
}
|
|
287
|
+
// Validate beYear (reasonable range: 2000-3000)
|
|
288
|
+
if (beYear < 2000 || beYear > 3000) {
|
|
289
|
+
throw new Error(`Invalid beYear: ${beYear}. beYear must be between 2000 and 3000.`);
|
|
290
|
+
}
|
|
291
|
+
// Additional validation: check if leap months (12, 13) are used in non-leap years
|
|
292
|
+
// This is done in the conversion function since it requires more complex logic
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Validates JavaScript Date object
|
|
296
|
+
* @throws {Error} If Date object is invalid
|
|
297
|
+
*/
|
|
298
|
+
function validateDateObject(date) {
|
|
299
|
+
if (!(date instanceof Date)) {
|
|
300
|
+
throw new Error('Invalid input: Expected a Date object.');
|
|
301
|
+
}
|
|
302
|
+
if (isNaN(date.getTime())) {
|
|
303
|
+
throw new Error('Invalid Date object: Date is not a valid date.');
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
// ============================================================================
|
|
307
|
+
// Era Conversions
|
|
308
|
+
// ============================================================================
|
|
309
|
+
function adToJs(adYear) {
|
|
310
|
+
return adYear - 638;
|
|
311
|
+
}
|
|
312
|
+
function adToBe(adYear) {
|
|
313
|
+
return adYear + 544;
|
|
314
|
+
}
|
|
315
|
+
function beToAd(beYear) {
|
|
316
|
+
return beYear - 544;
|
|
317
|
+
}
|
|
318
|
+
function jsToAd(jsYear) {
|
|
319
|
+
return jsYear + 638;
|
|
320
|
+
}
|
|
321
|
+
function beToJs(beYear) {
|
|
322
|
+
return beYear - 1182;
|
|
323
|
+
}
|
|
324
|
+
function jsToBe(jsYear) {
|
|
325
|
+
return jsYear + 1182;
|
|
326
|
+
}
|
|
327
|
+
// ============================================================================
|
|
328
|
+
// Calendar Calculation Functions
|
|
329
|
+
// ============================================================================
|
|
330
|
+
function getAharkun(beYear) {
|
|
331
|
+
return Math.floor((beYear * 292207 + 499) / 800) + 4;
|
|
332
|
+
}
|
|
333
|
+
function getAharkunMod(beYear) {
|
|
334
|
+
return (beYear * 292207 + 499) % 800;
|
|
335
|
+
}
|
|
336
|
+
function getKromthupul(beYear) {
|
|
337
|
+
return 800 - getAharkunMod(beYear);
|
|
338
|
+
}
|
|
339
|
+
function getAvoman(beYear) {
|
|
340
|
+
return (getAharkun(beYear) * 11 + 25) % 692;
|
|
341
|
+
}
|
|
342
|
+
function getBodithey(beYear) {
|
|
343
|
+
const aharkun = getAharkun(beYear);
|
|
344
|
+
return (Math.floor((aharkun * 11 + 25) / 692) + aharkun + 29) % 30;
|
|
345
|
+
}
|
|
346
|
+
function isKhmerSolarLeap(beYear) {
|
|
347
|
+
return getKromthupul(beYear) <= 207;
|
|
348
|
+
}
|
|
349
|
+
function isKhmerLeapDayByCalculation(beYear) {
|
|
350
|
+
const avoman = getAvoman(beYear);
|
|
351
|
+
const isSolarLeap = isKhmerSolarLeap(beYear);
|
|
352
|
+
if (avoman === 0 && getAvoman(beYear - 1) === 137) {
|
|
353
|
+
return true;
|
|
354
|
+
}
|
|
355
|
+
else if (isSolarLeap) {
|
|
356
|
+
return avoman < 127;
|
|
357
|
+
}
|
|
358
|
+
else if (avoman === 137 && getAvoman(beYear + 1) === 0) {
|
|
359
|
+
return false;
|
|
360
|
+
}
|
|
361
|
+
else if (avoman < 138) {
|
|
362
|
+
return true;
|
|
363
|
+
}
|
|
364
|
+
return false;
|
|
365
|
+
}
|
|
366
|
+
function isKhmerLeapMonth(beYear) {
|
|
367
|
+
const bodithey = getBodithey(beYear);
|
|
368
|
+
const boditheyNextYear = getBodithey(beYear + 1);
|
|
369
|
+
if (bodithey === 25 && boditheyNextYear === 5) {
|
|
370
|
+
return false;
|
|
371
|
+
}
|
|
372
|
+
return (bodithey === 24 && boditheyNextYear === 6) ||
|
|
373
|
+
(bodithey >= 25) ||
|
|
374
|
+
(bodithey < 6);
|
|
375
|
+
}
|
|
376
|
+
function getLeapType(beYear) {
|
|
239
377
|
if (isKhmerLeapMonth(beYear)) {
|
|
240
|
-
|
|
241
|
-
} else if (isKhmerLeapDay(beYear)) {
|
|
242
|
-
return 355;
|
|
243
|
-
} else {
|
|
244
|
-
return 354;
|
|
378
|
+
return 1; // Leap month (អធិកមាស)
|
|
245
379
|
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
/**
|
|
249
|
-
* Get number of day in Gregorian year
|
|
250
|
-
* @param adYear
|
|
251
|
-
* @returns {number}
|
|
252
|
-
*/
|
|
253
|
-
function getNumberOfDayInGregorianYear(adYear) {
|
|
254
|
-
if (isGregorianLeap(adYear)) {
|
|
255
|
-
return 366;
|
|
256
|
-
} else {
|
|
257
|
-
return 365;
|
|
380
|
+
else if (isKhmerLeapDayByCalculation(beYear)) {
|
|
381
|
+
return 2; // Leap day (ចន្ទ្រាធិមាស)
|
|
258
382
|
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
/**
|
|
271
|
-
* A year with an extra day is called Chhantrea Thimeas (ចន្ទ្រាធិមាស) or Adhikavereak (អធិកវារៈ). This year has 355 days.
|
|
272
|
-
* @param beYear
|
|
273
|
-
* @returns {boolean}
|
|
274
|
-
*/
|
|
275
|
-
function isKhmerLeapDay(beYear) {
|
|
276
|
-
return getProtetinLeap(beYear) === 2
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
/**
|
|
280
|
-
* Gregorian Leap
|
|
281
|
-
* @param adYear
|
|
282
|
-
* @returns {boolean}
|
|
283
|
-
*/
|
|
284
|
-
function isGregorianLeap(adYear) {
|
|
285
|
-
if (adYear % 4 === 0 && adYear % 100 !== 0 || adYear % 400 === 0) {
|
|
286
|
-
return true;
|
|
287
|
-
} else {
|
|
288
|
-
return false;
|
|
383
|
+
else if (isKhmerLeapMonth(beYear - 1)) {
|
|
384
|
+
let previousYear = beYear - 1;
|
|
385
|
+
while (true) {
|
|
386
|
+
if (isKhmerLeapDayByCalculation(previousYear)) {
|
|
387
|
+
return 2;
|
|
388
|
+
}
|
|
389
|
+
previousYear -= 1;
|
|
390
|
+
if (!isKhmerLeapMonth(previousYear)) {
|
|
391
|
+
return 0;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
289
394
|
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
var date = Moment('1/1/' + gregorianYear, 'D/M/YYYY')
|
|
298
|
-
for (var i = 0; i < 365; i++) {
|
|
299
|
-
var lunarDate = findLunarDate(date);
|
|
300
|
-
if (lunarDate.month == LunarMonths['ពិសាខ'] && lunarDate.day == 14) {
|
|
301
|
-
return date
|
|
302
|
-
}
|
|
303
|
-
date.add(1, 'day')
|
|
304
|
-
}
|
|
305
|
-
throw 'Cannot find Visakhabochea day. Please report this bug.';
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
/**
|
|
309
|
-
* Buddhist Era
|
|
310
|
-
* ថ្ងៃឆ្លងឆ្នាំ គឺ ១ រោច ខែពិសាខ
|
|
311
|
-
* @ref http://news.sabay.com.kh/article/1039620
|
|
312
|
-
* @summary: ឯកឧត្តម សេង សុមុនី អ្នកនាំពាក្យក្រសួងធម្មការ និងសាសនាឲ្យSabay ដឹងថានៅប្រទេសកម្ពុជាការឆ្លងចូលពុទ្ធសករាជថ្មីគឺកំណត់យកនៅថ្ងៃព្រះពុទ្ធយាងចូលនិព្វាន ពោលគឺនៅថ្ងៃ១រោច ខែពិសាខ។
|
|
313
|
-
* @param moment
|
|
314
|
-
* @returns {*}
|
|
315
|
-
*/
|
|
316
|
-
function getBEYear(moment) {
|
|
317
|
-
if (moment.diff(getVisakhaBochea(moment.year())) > 0) {
|
|
318
|
-
return moment.year() + 544;
|
|
319
|
-
} else {
|
|
320
|
-
return moment.year() + 543;
|
|
395
|
+
return 0; // Regular year
|
|
396
|
+
}
|
|
397
|
+
function getNumberOfDaysInKhmerMonth(monthIndex, beYear) {
|
|
398
|
+
const leapType = getLeapType(beYear);
|
|
399
|
+
const idx = typeof monthIndex === 'number' ? monthIndex : monthIndex;
|
|
400
|
+
if (idx === MonthIndex.Jesth && leapType === 2) { // ជេស្ឋ with leap day
|
|
401
|
+
return 30;
|
|
321
402
|
}
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
/**
|
|
325
|
-
* Due to recursive problem, I need to calculate the BE based on new year's day
|
|
326
|
-
* This won't be displayed on final result, it is used to find number of day in year,
|
|
327
|
-
* It won't affect the result because on ខែចេត្រ និង ខែពិសាខ, number of days is the same every year
|
|
328
|
-
* ពីព្រោះចូលឆ្នាំតែងតែចំខែចេត្រ ឬ ពិសាខ
|
|
329
|
-
* @param moment
|
|
330
|
-
* @returns {*}
|
|
331
|
-
*/
|
|
332
|
-
function getMaybeBEYear(moment) {
|
|
333
|
-
if (moment.month() + 1 <= SolarMonth['មេសា'] + 1) {
|
|
334
|
-
return moment.year() + 543;
|
|
335
|
-
} else {
|
|
336
|
-
return moment.year() + 544;
|
|
403
|
+
if (idx === MonthIndex.Pathamasadh || idx === MonthIndex.Tutiyasadh) { // បឋមាសាឍ, ទុតិយាសាឍ
|
|
404
|
+
return leapType === 1 ? 30 : 0;
|
|
337
405
|
}
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
let gregorianYear = moment.year();
|
|
356
|
-
let newYearMoment = getKhNewYearMoment(gregorianYear);
|
|
357
|
-
if (moment.diff(newYearMoment) < 0) {
|
|
358
|
-
return gregorianYear + 543 - 1182
|
|
359
|
-
} else {
|
|
360
|
-
return gregorianYear + 544 - 1182
|
|
406
|
+
// Alternating pattern: even months = 29 days, odd months = 30 days
|
|
407
|
+
// មិគសិរ:29, បុស្ស:30, មាឃ:29, ផល្គុន:30, ចេត្រ:29, ពិសាខ:30, ជេស្ឋ:29, អាសាឍ:30, etc.
|
|
408
|
+
return idx % 2 === 0 ? 29 : 30;
|
|
409
|
+
}
|
|
410
|
+
function getNumberOfDaysInKhmerYear(beYear) {
|
|
411
|
+
const leapType = getLeapType(beYear);
|
|
412
|
+
if (leapType === 1)
|
|
413
|
+
return 384; // Leap month
|
|
414
|
+
if (leapType === 2)
|
|
415
|
+
return 355; // Leap day
|
|
416
|
+
return 354; // Regular
|
|
417
|
+
}
|
|
418
|
+
function nextMonthOf(monthIndex, beYear) {
|
|
419
|
+
const leapType = getLeapType(beYear);
|
|
420
|
+
const idx = typeof monthIndex === 'number' ? monthIndex : monthIndex;
|
|
421
|
+
if (idx === MonthIndex.Jesth && leapType === 1) { // ជេស្ឋ in leap month year
|
|
422
|
+
return MonthIndex.Pathamasadh; // បឋមាសាឍ
|
|
361
423
|
}
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
424
|
+
if (idx === MonthIndex.Kadeuk)
|
|
425
|
+
return MonthIndex.Migasir; // កត្ដិក -> មិគសិរ
|
|
426
|
+
if (idx === MonthIndex.Pathamasadh)
|
|
427
|
+
return MonthIndex.Tutiyasadh; // បឋមាសាឍ -> ទុតិយាសាឍ
|
|
428
|
+
if (idx === MonthIndex.Tutiyasadh)
|
|
429
|
+
return MonthIndex.Srap; // ទុតិយាសាឍ -> ស្រាពណ៍
|
|
430
|
+
return (idx + 1);
|
|
431
|
+
}
|
|
432
|
+
function previousMonthOf(monthIndex, beYear) {
|
|
433
|
+
const leapType = getLeapType(beYear);
|
|
434
|
+
const idx = typeof monthIndex === 'number' ? monthIndex : monthIndex;
|
|
435
|
+
if (idx === MonthIndex.Migasir)
|
|
436
|
+
return MonthIndex.Kadeuk; // មិគសិរ -> កត្ដិក
|
|
437
|
+
if (idx === MonthIndex.Srap && leapType === 1)
|
|
438
|
+
return MonthIndex.Tutiyasadh; // ស្រាពណ៍ -> ទុតិយាសាឍ (leap)
|
|
439
|
+
if (idx === MonthIndex.Tutiyasadh)
|
|
440
|
+
return MonthIndex.Pathamasadh; // ទុតិយាសាឍ -> បឋមាសាឍ
|
|
441
|
+
if (idx === MonthIndex.Pathamasadh)
|
|
442
|
+
return MonthIndex.Jesth; // បឋមាសាឍ -> ជេស្ឋ
|
|
443
|
+
return (idx - 1);
|
|
444
|
+
}
|
|
445
|
+
// ============================================================================
|
|
446
|
+
// Khmer New Year Calculation (JS Year based)
|
|
447
|
+
// ============================================================================
|
|
448
|
+
function getAharkunJs(jsYear) {
|
|
449
|
+
const h = jsYear * 292207 + 373;
|
|
450
|
+
return Math.floor(h / 800) + 1;
|
|
451
|
+
}
|
|
452
|
+
function getAvomanJs(jsYear) {
|
|
453
|
+
return (getAharkunJs(jsYear) * 11 + 650) % 692;
|
|
454
|
+
}
|
|
455
|
+
function getKromthupulJs(jsYear) {
|
|
456
|
+
return 800 - ((292207 * jsYear + 373) % 800);
|
|
457
|
+
}
|
|
458
|
+
function getBoditheyJs(jsYear) {
|
|
459
|
+
const aharkun = getAharkunJs(jsYear);
|
|
460
|
+
const a = 11 * aharkun + 650;
|
|
461
|
+
return (aharkun + Math.floor(a / 692)) % 30;
|
|
462
|
+
}
|
|
463
|
+
function isAdhikameas(jsYear) {
|
|
464
|
+
const bodithey = getBoditheyJs(jsYear);
|
|
465
|
+
const boditheyNext = getBoditheyJs(jsYear + 1);
|
|
466
|
+
if (bodithey === 24 && boditheyNext === 6)
|
|
467
|
+
return true;
|
|
468
|
+
if (bodithey === 25 && boditheyNext === 5)
|
|
469
|
+
return false;
|
|
470
|
+
return bodithey > 24 || bodithey < 6;
|
|
471
|
+
}
|
|
472
|
+
function isChantrathimeas(jsYear) {
|
|
473
|
+
const avoman = getAvomanJs(jsYear);
|
|
474
|
+
const avomanNext = getAvomanJs(jsYear + 1);
|
|
475
|
+
const avomanPrev = getAvomanJs(jsYear - 1);
|
|
476
|
+
const isSolarLeap = getKromthupulJs(jsYear) <= 207;
|
|
477
|
+
if (avoman === 0 && avomanPrev === 137)
|
|
478
|
+
return true;
|
|
479
|
+
if (isSolarLeap)
|
|
480
|
+
return avoman < 127;
|
|
481
|
+
if (avoman === 137 && avomanNext === 0)
|
|
482
|
+
return false;
|
|
483
|
+
if (!isSolarLeap && avoman < 138)
|
|
484
|
+
return true;
|
|
485
|
+
if (avomanPrev === 137 && avoman === 0)
|
|
486
|
+
return true;
|
|
487
|
+
return false;
|
|
488
|
+
}
|
|
489
|
+
function has366Days(jsYear) {
|
|
490
|
+
return getKromthupulJs(jsYear) <= 207;
|
|
491
|
+
}
|
|
492
|
+
function getSunInfo(jsYear, sotin) {
|
|
493
|
+
const infoOfPrevYear = {
|
|
494
|
+
kromathopol: getKromthupulJs(jsYear - 1)
|
|
495
|
+
};
|
|
496
|
+
// Sun average as Libda
|
|
497
|
+
const r2 = 800 * sotin + infoOfPrevYear.kromathopol;
|
|
498
|
+
const reasey = Math.floor(r2 / 24350);
|
|
499
|
+
const r3 = r2 % 24350;
|
|
500
|
+
const angsar = Math.floor(r3 / 811);
|
|
501
|
+
const r4 = r3 % 811;
|
|
502
|
+
const l1 = Math.floor(r4 / 14);
|
|
503
|
+
const libda = l1 - 3;
|
|
504
|
+
const sunAverageAsLibda = (30 * 60 * reasey) + (60 * angsar) + libda;
|
|
505
|
+
// Left over
|
|
506
|
+
const s1 = ((30 * 60 * 2) + (60 * 20));
|
|
507
|
+
let leftOver = sunAverageAsLibda - s1;
|
|
508
|
+
if (sunAverageAsLibda < s1) {
|
|
509
|
+
leftOver += (30 * 60 * 12);
|
|
510
|
+
}
|
|
511
|
+
const kaen = Math.floor(leftOver / (30 * 60));
|
|
512
|
+
// Last left over
|
|
513
|
+
let rs = -1;
|
|
514
|
+
if ([0, 1, 2].includes(kaen)) {
|
|
515
|
+
rs = kaen;
|
|
516
|
+
}
|
|
517
|
+
else if ([3, 4, 5].includes(kaen)) {
|
|
518
|
+
rs = (30 * 60 * 6) - leftOver;
|
|
519
|
+
}
|
|
520
|
+
else if ([6, 7, 8].includes(kaen)) {
|
|
521
|
+
rs = leftOver - (30 * 60 * 6);
|
|
522
|
+
}
|
|
523
|
+
else if ([9, 10, 11].includes(kaen)) {
|
|
524
|
+
rs = ((30 * 60 * 11) + (60 * 29) + 60) - leftOver;
|
|
525
|
+
}
|
|
526
|
+
const lastLeftOver = {
|
|
527
|
+
reasey: Math.floor(rs / (30 * 60)),
|
|
528
|
+
angsar: Math.floor((rs % (30 * 60)) / 60),
|
|
529
|
+
libda: rs % 60
|
|
530
|
+
};
|
|
531
|
+
// Khan and pouichalip
|
|
532
|
+
let khan, pouichalip;
|
|
533
|
+
if (lastLeftOver.angsar >= 15) {
|
|
534
|
+
khan = 2 * lastLeftOver.reasey + 1;
|
|
535
|
+
pouichalip = 60 * (lastLeftOver.angsar - 15) + lastLeftOver.libda;
|
|
536
|
+
}
|
|
537
|
+
else {
|
|
538
|
+
khan = 2 * lastLeftOver.reasey;
|
|
539
|
+
pouichalip = 60 * lastLeftOver.angsar + lastLeftOver.libda;
|
|
540
|
+
}
|
|
541
|
+
// Chhaya sun
|
|
542
|
+
const chhayaSunMap = [
|
|
543
|
+
{ multiplicity: 35, chhaya: 0 },
|
|
544
|
+
{ multiplicity: 32, chhaya: 35 },
|
|
545
|
+
{ multiplicity: 27, chhaya: 67 },
|
|
546
|
+
{ multiplicity: 22, chhaya: 94 },
|
|
547
|
+
{ multiplicity: 13, chhaya: 116 },
|
|
548
|
+
{ multiplicity: 5, chhaya: 129 }
|
|
549
|
+
];
|
|
550
|
+
const chhayaSun = khan <= 5 ? chhayaSunMap[khan] : { multiplicity: 0, chhaya: 134 };
|
|
551
|
+
const q = Math.floor((pouichalip * chhayaSun.multiplicity) / 900);
|
|
552
|
+
const pholAsLibda = q + chhayaSun.chhaya;
|
|
553
|
+
// Sun inauguration
|
|
554
|
+
const sunInaugurationAsLibda = kaen <= 5
|
|
555
|
+
? sunAverageAsLibda - pholAsLibda
|
|
556
|
+
: sunAverageAsLibda + pholAsLibda;
|
|
370
557
|
return {
|
|
371
|
-
|
|
372
|
-
|
|
558
|
+
sunInaugurationAsLibda,
|
|
559
|
+
reasey: Math.floor(sunInaugurationAsLibda / (30 * 60)),
|
|
560
|
+
angsar: Math.floor((sunInaugurationAsLibda % (30 * 60)) / 60),
|
|
561
|
+
libda: sunInaugurationAsLibda % 60
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
function getNewYearInfo(jsYear) {
|
|
565
|
+
const sotins = has366Days(jsYear - 1)
|
|
566
|
+
? [363, 364, 365, 366]
|
|
567
|
+
: [362, 363, 364, 365];
|
|
568
|
+
const newYearsDaySotins = sotins.map(sotin => getSunInfo(jsYear, sotin));
|
|
569
|
+
// Find time of new year
|
|
570
|
+
let timeOfNewYear = { hour: 0, minute: 0 };
|
|
571
|
+
for (const sotin of newYearsDaySotins) {
|
|
572
|
+
if (sotin.angsar === 0) {
|
|
573
|
+
const minutes = (24 * 60) - (sotin.libda * 24);
|
|
574
|
+
timeOfNewYear = {
|
|
575
|
+
hour: Math.floor(minutes / 60) % 24,
|
|
576
|
+
minute: minutes % 60
|
|
577
|
+
};
|
|
578
|
+
break;
|
|
579
|
+
}
|
|
373
580
|
}
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
581
|
+
// Number of Vanabat days
|
|
582
|
+
const numberOfVanabatDays = (newYearsDaySotins[0].angsar === 0) ? 2 : 1;
|
|
583
|
+
return {
|
|
584
|
+
timeOfNewYear,
|
|
585
|
+
numberOfVanabatDays,
|
|
586
|
+
newYearsDaySotins
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
// ============================================================================
|
|
590
|
+
// Khmer Date Class
|
|
591
|
+
// ============================================================================
|
|
592
|
+
class KhmerDate {
|
|
593
|
+
constructor(day, moonPhase, monthIndex, beYear) {
|
|
594
|
+
this.day = day;
|
|
595
|
+
this.moonPhase = moonPhase;
|
|
596
|
+
this.monthIndex = monthIndex;
|
|
597
|
+
this.beYear = beYear;
|
|
388
598
|
}
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
* @param format
|
|
397
|
-
* @returns {*}
|
|
398
|
-
*/
|
|
399
|
-
function formatKhmerDate({day, month, moment}, format) {
|
|
400
|
-
if (format === null || format === undefined) {
|
|
401
|
-
// Default date format
|
|
402
|
-
let dayOfWeek = moment.day();
|
|
403
|
-
let moonDay = getKhmerLunarDay(day);
|
|
404
|
-
let beYear = getBEYear(moment);
|
|
405
|
-
let animalYear = getAnimalYear(moment);
|
|
406
|
-
let eraYear = getJolakSakarajYear(moment) % 10;
|
|
407
|
-
return config.postformat(`ថ្ងៃ${config.weekdays[dayOfWeek]} ${moonDay.count}${config.moonStatus[moonDay.moonStatus]} ខែ${config.lunarMonths[month]} ឆ្នាំ${config.animalYear[animalYear]} ${config.eraYear[eraYear]} ពុទ្ធសករាជ ${beYear}`);
|
|
408
|
-
} else if (typeof format === 'string') {
|
|
409
|
-
// Follow the format
|
|
410
|
-
let formatRule = {
|
|
411
|
-
'W': function () { // Day of week
|
|
412
|
-
let dayOfWeek = moment.day();
|
|
413
|
-
return config.weekdays[dayOfWeek]
|
|
414
|
-
},
|
|
415
|
-
'w': function () { // Day of week
|
|
416
|
-
let dayOfWeek = moment.day();
|
|
417
|
-
return config.weekdaysShort[dayOfWeek]
|
|
418
|
-
},
|
|
419
|
-
'd': function () { // no determine digit
|
|
420
|
-
let moonDay = getKhmerLunarDay(day);
|
|
421
|
-
return moonDay.count;
|
|
422
|
-
},
|
|
423
|
-
'D': function () { // minimum 2 digits
|
|
424
|
-
let moonDay = getKhmerLunarDay(day);
|
|
425
|
-
return ('' + moonDay.count).length === 1 ? '0' + moonDay.count : moonDay.count;
|
|
426
|
-
},
|
|
427
|
-
'n': function () {
|
|
428
|
-
let moonDay = getKhmerLunarDay(day);
|
|
429
|
-
return config.moonStatusShort[moonDay.moonStatus]
|
|
430
|
-
},
|
|
431
|
-
'N': function () {
|
|
432
|
-
let moonDay = getKhmerLunarDay(day);
|
|
433
|
-
return config.moonStatus[moonDay.moonStatus]
|
|
434
|
-
},
|
|
435
|
-
'o': function () {
|
|
436
|
-
return config.moonDays[day];
|
|
437
|
-
},
|
|
438
|
-
'm': function () {
|
|
439
|
-
return config.lunarMonths[month];
|
|
440
|
-
},
|
|
441
|
-
'M': function () {
|
|
442
|
-
return config.months[moment.month()];
|
|
443
|
-
},
|
|
444
|
-
'a': function () {
|
|
445
|
-
let animalYear = getAnimalYear(moment);
|
|
446
|
-
return config.animalYear[animalYear];
|
|
447
|
-
},
|
|
448
|
-
'e': function () {
|
|
449
|
-
let eraYear = getJolakSakarajYear(moment) % 10;
|
|
450
|
-
return config.eraYear[eraYear];
|
|
451
|
-
},
|
|
452
|
-
'b': function () {
|
|
453
|
-
return getBEYear(moment);
|
|
454
|
-
},
|
|
455
|
-
'c': function () {
|
|
456
|
-
return moment.year();
|
|
457
|
-
},
|
|
458
|
-
'j': function () {
|
|
459
|
-
return getJolakSakarajYear(moment);
|
|
599
|
+
// Get day number (0-29) - converts from 1-based internal to 0-based external
|
|
600
|
+
getDayNumber() {
|
|
601
|
+
if (this.moonPhase === MoonPhase.Waxing) { // កើត
|
|
602
|
+
return this.day - 1; // day 1-15 → dayNum 0-14
|
|
603
|
+
}
|
|
604
|
+
else { // រោច
|
|
605
|
+
return 15 + (this.day - 1); // day 1-15 → dayNum 15-29
|
|
460
606
|
}
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
return config.postformat(format.replace(new RegExp(Object.keys(formatRule).join('|'), 'g'), function (matched) {
|
|
464
|
-
return formatRule[matched]();
|
|
465
|
-
}));
|
|
466
|
-
|
|
467
607
|
}
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
*/
|
|
477
|
-
function readLunarDate(...params) {
|
|
478
|
-
console.log('Now working yet')
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
/**
|
|
482
|
-
* Next month of the month
|
|
483
|
-
*/
|
|
484
|
-
function nextMonthOf(khmerMonth, BEYear) {
|
|
485
|
-
switch (khmerMonth) {
|
|
486
|
-
case LunarMonths['មិគសិរ']:
|
|
487
|
-
return LunarMonths['បុស្ស'];
|
|
488
|
-
case LunarMonths['បុស្ស']:
|
|
489
|
-
return LunarMonths['មាឃ'];
|
|
490
|
-
case LunarMonths['មាឃ']:
|
|
491
|
-
return LunarMonths['ផល្គុន'];
|
|
492
|
-
case LunarMonths['ផល្គុន']:
|
|
493
|
-
return LunarMonths['ចេត្រ'];
|
|
494
|
-
case LunarMonths['ចេត្រ']:
|
|
495
|
-
return LunarMonths['ពិសាខ'];
|
|
496
|
-
case LunarMonths['ពិសាខ']:
|
|
497
|
-
return LunarMonths['ជេស្ឋ'];
|
|
498
|
-
case LunarMonths['ជេស្ឋ']: {
|
|
499
|
-
if (isKhmerLeapMonth(BEYear)) {
|
|
500
|
-
return LunarMonths['បឋមាសាឍ']
|
|
501
|
-
} else {
|
|
502
|
-
return LunarMonths['អាសាឍ']
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
case LunarMonths['អាសាឍ']:
|
|
506
|
-
return LunarMonths['ស្រាពណ៍'];
|
|
507
|
-
case LunarMonths['ស្រាពណ៍']:
|
|
508
|
-
return LunarMonths['ភទ្របទ'];
|
|
509
|
-
case LunarMonths['ភទ្របទ']:
|
|
510
|
-
return LunarMonths['អស្សុជ'];
|
|
511
|
-
case LunarMonths['អស្សុជ']:
|
|
512
|
-
return LunarMonths['កក្ដិក'];
|
|
513
|
-
case LunarMonths['កក្ដិក']:
|
|
514
|
-
return LunarMonths['មិគសិរ'];
|
|
515
|
-
case LunarMonths['បឋមាសាឍ']:
|
|
516
|
-
return LunarMonths['ទុតិយាសាឍ'];
|
|
517
|
-
case LunarMonths['ទុតិយាសាឍ']:
|
|
518
|
-
return LunarMonths['ស្រាពណ៍'];
|
|
519
|
-
default:
|
|
520
|
-
throw Error('Plugin is facing wrong calculation (Invalid month)');
|
|
608
|
+
static fromDayNumber(dayNum) {
|
|
609
|
+
// Converts from 0-based dayNum to 1-based day
|
|
610
|
+
if (dayNum < 15) {
|
|
611
|
+
return { day: dayNum + 1, moonPhase: MoonPhase.Waxing }; // dayNum 0-14 → day 1-15
|
|
612
|
+
}
|
|
613
|
+
else {
|
|
614
|
+
return { day: (dayNum - 15) + 1, moonPhase: MoonPhase.Waning }; // dayNum 15-29 → day 1-15
|
|
615
|
+
}
|
|
521
616
|
}
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
617
|
+
addDays(count) {
|
|
618
|
+
if (count === 0)
|
|
619
|
+
return this;
|
|
620
|
+
if (count < 0)
|
|
621
|
+
return this.subtractDays(-count);
|
|
622
|
+
let result = new KhmerDate(this.day, this.moonPhase, this.monthIndex, this.beYear);
|
|
623
|
+
let remaining = count;
|
|
624
|
+
while (remaining > 0) {
|
|
625
|
+
const daysInMonth = getNumberOfDaysInKhmerMonth(result.monthIndex, result.beYear);
|
|
626
|
+
const currentDayNum = result.getDayNumber();
|
|
627
|
+
const daysLeftInMonth = (daysInMonth - 1) - currentDayNum;
|
|
628
|
+
if (remaining <= daysLeftInMonth) {
|
|
629
|
+
const newDayNum = currentDayNum + remaining;
|
|
630
|
+
const newDay = KhmerDate.fromDayNumber(newDayNum);
|
|
631
|
+
let newBeYear = result.beYear;
|
|
632
|
+
if (result.monthIndex === MonthIndex.Pisakh) { // ពិសាខ
|
|
633
|
+
if (result.moonPhase === MoonPhase.Waxing && newDay.moonPhase === MoonPhase.Waning) {
|
|
634
|
+
newBeYear++;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
result = new KhmerDate(newDay.day, newDay.moonPhase, result.monthIndex, newBeYear);
|
|
638
|
+
remaining = 0;
|
|
639
|
+
}
|
|
640
|
+
else {
|
|
641
|
+
remaining -= (daysLeftInMonth + 1);
|
|
642
|
+
const nextMonth = nextMonthOf(result.monthIndex, result.beYear);
|
|
643
|
+
const newBeYear = (result.monthIndex === MonthIndex.Cheit) ? result.beYear + 1 : result.beYear;
|
|
644
|
+
result = new KhmerDate(1, MoonPhase.Waxing, nextMonth, newBeYear); // Start at 1កើត
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
return result;
|
|
548
648
|
}
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
649
|
+
subtractDays(count) {
|
|
650
|
+
if (count === 0)
|
|
651
|
+
return this;
|
|
652
|
+
let result = new KhmerDate(this.day, this.moonPhase, this.monthIndex, this.beYear);
|
|
653
|
+
let remaining = count;
|
|
654
|
+
while (remaining > 0) {
|
|
655
|
+
const currentDayNum = result.getDayNumber();
|
|
656
|
+
if (remaining <= currentDayNum) {
|
|
657
|
+
const newDayNum = currentDayNum - remaining;
|
|
658
|
+
const newDay = KhmerDate.fromDayNumber(newDayNum);
|
|
659
|
+
let newBeYear = result.beYear;
|
|
660
|
+
if (result.monthIndex === MonthIndex.Pisakh) { // ពិសាខ
|
|
661
|
+
if (result.moonPhase === MoonPhase.Waning && newDay.moonPhase === MoonPhase.Waxing) {
|
|
662
|
+
newBeYear--;
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
result = new KhmerDate(newDay.day, newDay.moonPhase, result.monthIndex, newBeYear);
|
|
666
|
+
remaining = 0;
|
|
667
|
+
}
|
|
668
|
+
else {
|
|
669
|
+
remaining -= (currentDayNum + 1);
|
|
670
|
+
const prevMonth = previousMonthOf(result.monthIndex, result.beYear);
|
|
671
|
+
const newBeYear = (result.monthIndex === MonthIndex.Pisakh) ? result.beYear - 1 : result.beYear;
|
|
672
|
+
const daysInPrevMonth = getNumberOfDaysInKhmerMonth(prevMonth, newBeYear);
|
|
673
|
+
const newDay = KhmerDate.fromDayNumber(daysInPrevMonth - 1);
|
|
674
|
+
result = new KhmerDate(newDay.day, newDay.moonPhase, prevMonth, newBeYear);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
return result;
|
|
554
678
|
}
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
679
|
+
toString() {
|
|
680
|
+
return `${this.day}${MoonStatusNames[this.moonPhase]} ខែ${LunarMonthNames[this.monthIndex]} ព.ស.${this.beYear}`;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
// ============================================================================
|
|
684
|
+
// Main Conversion Functions
|
|
685
|
+
// ============================================================================
|
|
686
|
+
// Helper function to get approximate BE year (like original getMaybeBEYear)
|
|
687
|
+
function getMaybeBEYear(year, month) {
|
|
688
|
+
// SolarMonth['មេសា'] = 3 (0-based), so month <= 4 (1-based)
|
|
689
|
+
if (month <= 4) {
|
|
690
|
+
return year + 543;
|
|
691
|
+
}
|
|
692
|
+
else {
|
|
693
|
+
return year + 544;
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
// Cache for Pisakha Bochea dates by year
|
|
697
|
+
const visakhaBocheaCache = {};
|
|
698
|
+
// Cache for New Year Full Info
|
|
699
|
+
const newYearInfoCache = {};
|
|
700
|
+
/**
|
|
701
|
+
* Find BE Year transition datetime for a given Gregorian year
|
|
702
|
+
* BE year increases on ១រោច ខែពិសាខ (1st waning day of Pisakh = dayNumber 15 of month 5)
|
|
703
|
+
* Returns timestamp in milliseconds at midnight of that day
|
|
704
|
+
*/
|
|
705
|
+
function getPisakhaBochea(year, isSearching = false) {
|
|
706
|
+
if (visakhaBocheaCache[year]) {
|
|
707
|
+
return visakhaBocheaCache[year];
|
|
708
|
+
}
|
|
709
|
+
// Search for 1រោច Pisakh (when BE year changes) - start from April since it typically occurs then
|
|
710
|
+
for (let searchMonth = 4; searchMonth <= 6; searchMonth++) {
|
|
711
|
+
const daysInMonth = new Date(year, searchMonth, 0).getDate();
|
|
712
|
+
for (let searchDay = 1; searchDay <= daysInMonth; searchDay++) {
|
|
713
|
+
// Avoid infinite recursion by using simplified BE year during search
|
|
714
|
+
const result = gregorianToKhmerInternal(year, searchMonth, searchDay, 12, 0, 0, true);
|
|
715
|
+
if (result.khmer.monthIndex === MonthIndex.Pisakh && result._khmerDateObj.getDayNumber() === 15) {
|
|
716
|
+
// Found 1រោច Pisakh - return timestamp at midnight (start of BE year change day)
|
|
717
|
+
// BE year changes at 00:00 on this day
|
|
718
|
+
const timestamp = new Date(year, searchMonth - 1, searchDay, 0, 0, 0, 0).getTime();
|
|
719
|
+
visakhaBocheaCache[year] = timestamp;
|
|
720
|
+
return timestamp;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
// Fallback if not found
|
|
725
|
+
const fallback = new Date(year, 3, 15, 0, 0, 0, 0).getTime();
|
|
726
|
+
visakhaBocheaCache[year] = fallback;
|
|
727
|
+
return fallback;
|
|
728
|
+
}
|
|
729
|
+
function gregorianToKhmerInternal(year, month, day, hour = 0, minute = 0, second = 0, isSearching = false) {
|
|
558
730
|
/**
|
|
559
|
-
*
|
|
560
|
-
* ករណី ខែជេស្ឋមានតែ ២៩ ថ្ងៃ តែលទ្ធផលបង្ហាញ ១៥រោច ខែជេស្ឋ
|
|
731
|
+
* This follows the original momentkh algorithm exactly using JDN for tracking
|
|
561
732
|
*/
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
733
|
+
// Epoch: January 1, 1900 = dayNumber 0 (១កើត), month index 1 (បុស្ស)
|
|
734
|
+
let epochJdn = gregorianToJulianDay(1900, 1, 1);
|
|
735
|
+
const targetJdn = gregorianToJulianDay(year, month, day);
|
|
736
|
+
let khmerMonth = 1; // បុស្ស
|
|
737
|
+
let khmerDayNumber = 0; // 0-29 format
|
|
738
|
+
let diffDays = targetJdn - epochJdn;
|
|
739
|
+
// Move epoch by full Khmer years
|
|
740
|
+
if (diffDays > 0) {
|
|
741
|
+
while (true) {
|
|
742
|
+
// Get Gregorian date of current epoch to calculate BE year
|
|
743
|
+
const epochGreg = julianDayToGregorian(epochJdn);
|
|
744
|
+
// Match original: use epochMoment.clone().add(1, 'year')
|
|
745
|
+
const nextYearBE = getMaybeBEYear(epochGreg.year + 1, epochGreg.month);
|
|
746
|
+
const daysInNextYear = getNumberOfDaysInKhmerYear(nextYearBE);
|
|
747
|
+
if (diffDays > daysInNextYear) {
|
|
748
|
+
diffDays -= daysInNextYear;
|
|
749
|
+
epochJdn += daysInNextYear;
|
|
750
|
+
}
|
|
751
|
+
else {
|
|
752
|
+
break;
|
|
753
|
+
}
|
|
754
|
+
}
|
|
566
755
|
}
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
756
|
+
else if (diffDays < 0) {
|
|
757
|
+
while (diffDays < 0) {
|
|
758
|
+
const epochGreg = julianDayToGregorian(epochJdn);
|
|
759
|
+
const currentYearBE = getMaybeBEYear(epochGreg.year, epochGreg.month);
|
|
760
|
+
const daysInCurrentYear = getNumberOfDaysInKhmerYear(currentYearBE);
|
|
761
|
+
diffDays += daysInCurrentYear;
|
|
762
|
+
epochJdn -= daysInCurrentYear;
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
// Move epoch by full Khmer months
|
|
766
|
+
while (diffDays > 0) {
|
|
767
|
+
const epochGreg = julianDayToGregorian(epochJdn);
|
|
768
|
+
const currentBE = getMaybeBEYear(epochGreg.year, epochGreg.month);
|
|
769
|
+
const daysInMonth = getNumberOfDaysInKhmerMonth(khmerMonth, currentBE);
|
|
770
|
+
if (diffDays > daysInMonth) {
|
|
771
|
+
diffDays -= daysInMonth;
|
|
772
|
+
epochJdn += daysInMonth;
|
|
773
|
+
khmerMonth = nextMonthOf(khmerMonth, currentBE);
|
|
774
|
+
}
|
|
775
|
+
else {
|
|
776
|
+
break;
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
// Add remaining days
|
|
780
|
+
khmerDayNumber = diffDays;
|
|
781
|
+
// Fix overflow (e.g., if month has only 29 days but we calculated 30)
|
|
782
|
+
const finalBE = getMaybeBEYear(year, month);
|
|
783
|
+
const totalDaysInMonth = getNumberOfDaysInKhmerMonth(khmerMonth, finalBE);
|
|
784
|
+
if (khmerDayNumber >= totalDaysInMonth) {
|
|
785
|
+
khmerDayNumber = khmerDayNumber % totalDaysInMonth;
|
|
786
|
+
khmerMonth = nextMonthOf(khmerMonth, finalBE);
|
|
787
|
+
}
|
|
788
|
+
// Convert dayNumber to day/moonPhase format
|
|
789
|
+
const khmerDayInfo = KhmerDate.fromDayNumber(khmerDayNumber);
|
|
790
|
+
// Calculate actual BE year
|
|
791
|
+
// The BE year changes on ១រោច ខែពិសាខ (1st waning day of Pisakh = dayNumber 15)
|
|
792
|
+
// Compare datetime (including hour/minute) against BE year transition datetime
|
|
793
|
+
let beYear;
|
|
794
|
+
if (isSearching) {
|
|
795
|
+
// During search, use simple approximation to avoid recursion
|
|
796
|
+
beYear = month <= 4 ? year + 543 : year + 544;
|
|
797
|
+
}
|
|
798
|
+
else {
|
|
799
|
+
// Normal mode: compare against exact BE year transition datetime (1រោច Pisakh at 00:00)
|
|
800
|
+
const inputTimestamp = new Date(year, month - 1, day, hour, minute, second).getTime();
|
|
801
|
+
const beYearTransitionTimestamp = getPisakhaBochea(year);
|
|
802
|
+
if (inputTimestamp >= beYearTransitionTimestamp) {
|
|
803
|
+
// On or after 1រោច Pisakh (new BE year)
|
|
804
|
+
beYear = year + 544;
|
|
805
|
+
}
|
|
806
|
+
else {
|
|
807
|
+
// Before 1រោច Pisakh (old BE year)
|
|
808
|
+
beYear = year + 543;
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
// Calculate additional info
|
|
812
|
+
let jsYear = beToJs(beYear);
|
|
813
|
+
let animalYearIndex = ((beYear + 4) % 12 + 12) % 12;
|
|
814
|
+
// Adjust Era and Animal Year based on Khmer New Year logic
|
|
815
|
+
// They should change at New Year, not wait for Pisakha Bochea (which changes BE)
|
|
816
|
+
if (!isSearching) {
|
|
817
|
+
const newYearInfo = getNewYearFullInfo(year);
|
|
818
|
+
const inputTimestamp = new Date(year, month - 1, day, hour, minute, second).getTime();
|
|
819
|
+
const visakhaBocheaTimestamp = getPisakhaBochea(year);
|
|
820
|
+
// Animal Year changes at Moha Songkran (exact New Year time)
|
|
821
|
+
// Only apply manual increment if we are in the gap between New Year and Pisakha Bochea
|
|
822
|
+
// (After Pisakha Bochea, the BE year increments, so the formula based on BE automatically gives the new Animal Year)
|
|
823
|
+
if (inputTimestamp >= newYearInfo.newYearMoment.getTime() && inputTimestamp <= visakhaBocheaTimestamp) {
|
|
824
|
+
animalYearIndex = (animalYearIndex + 1) % 12;
|
|
825
|
+
}
|
|
826
|
+
// Era changes at Midnight of Date Lerng Sak (3rd or 4th day of NY)
|
|
827
|
+
if (inputTimestamp >= newYearInfo.lerngSakMoment.getTime() && inputTimestamp <= visakhaBocheaTimestamp) {
|
|
828
|
+
jsYear++;
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
const eraYearIndex = ((jsYear % 10) + 10) % 10;
|
|
832
|
+
const dayOfWeek = getDayOfWeek(year, month, day);
|
|
833
|
+
const khmerDate = new KhmerDate(khmerDayInfo.day, khmerDayInfo.moonPhase, khmerMonth, beYear);
|
|
570
834
|
return {
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
835
|
+
gregorian: { year, month, day, hour, minute, second, dayOfWeek },
|
|
836
|
+
khmer: {
|
|
837
|
+
day: khmerDayInfo.day,
|
|
838
|
+
moonPhase: khmerDayInfo.moonPhase,
|
|
839
|
+
moonPhaseName: MoonStatusNames[khmerDayInfo.moonPhase],
|
|
840
|
+
monthIndex: khmerMonth,
|
|
841
|
+
monthName: LunarMonthNames[khmerMonth],
|
|
842
|
+
beYear: beYear,
|
|
843
|
+
jsYear: jsYear,
|
|
844
|
+
animalYear: animalYearIndex,
|
|
845
|
+
animalYearName: AnimalYearNames[animalYearIndex],
|
|
846
|
+
eraYear: eraYearIndex,
|
|
847
|
+
eraYearName: EraYearNames[eraYearIndex],
|
|
848
|
+
dayOfWeek: dayOfWeek,
|
|
849
|
+
dayOfWeekName: WeekdayNames[dayOfWeek]
|
|
850
|
+
},
|
|
851
|
+
_khmerDateObj: khmerDate
|
|
852
|
+
};
|
|
853
|
+
}
|
|
854
|
+
function khmerToGregorian(day, moonPhase, monthIndex, beYear) {
|
|
855
|
+
// Validate input parameters
|
|
856
|
+
validateKhmerDate(day, moonPhase, monthIndex, beYear);
|
|
857
|
+
// Convert enums to numbers if needed
|
|
858
|
+
const moonPhaseNum = typeof moonPhase === 'number' ? moonPhase : moonPhase;
|
|
859
|
+
const monthIndexNum = typeof monthIndex === 'number' ? monthIndex : monthIndex;
|
|
860
|
+
// Convert BE year to approximate Gregorian year
|
|
861
|
+
const approxYear = beYear - 544;
|
|
862
|
+
// Search within a range around the approximate year
|
|
863
|
+
// Start from 2 years before to 2 years after to account for calendar differences
|
|
864
|
+
const startYear = approxYear - 2;
|
|
865
|
+
const endYear = approxYear + 2;
|
|
866
|
+
let candidates = [];
|
|
867
|
+
// Iterate through Gregorian dates to find all matches
|
|
868
|
+
for (let year = startYear; year <= endYear; year++) {
|
|
869
|
+
for (let month = 1; month <= 12; month++) {
|
|
870
|
+
const daysInMonth = getDaysInGregorianMonth(year, month);
|
|
871
|
+
for (let gDay = 1; gDay <= daysInMonth; gDay++) {
|
|
872
|
+
// For BE year transition day (1រោច Pisakh) and the day before (15កើត Pisakh),
|
|
873
|
+
// check multiple times during the day because BE year can change during this period
|
|
874
|
+
const isAroundBEYearChange = monthIndexNum === MonthIndex.Pisakh &&
|
|
875
|
+
((day === 15 && moonPhaseNum === MoonPhase.Waxing) || (day === 1 && moonPhaseNum === MoonPhase.Waning));
|
|
876
|
+
const timesToCheck = isAroundBEYearChange
|
|
877
|
+
? [0, 6, 12, 18, 23] // Check at different hours
|
|
878
|
+
: [0]; // Normal case: just check at midnight
|
|
879
|
+
for (const hour of timesToCheck) {
|
|
880
|
+
const khmerResult = gregorianToKhmerInternal(year, month, gDay, hour, 0, 0, false);
|
|
881
|
+
// Check if it matches our target
|
|
882
|
+
if (khmerResult.khmer.beYear === beYear &&
|
|
883
|
+
khmerResult.khmer.monthIndex === monthIndexNum &&
|
|
884
|
+
khmerResult.khmer.day === day &&
|
|
885
|
+
khmerResult.khmer.moonPhase === moonPhaseNum) {
|
|
886
|
+
candidates.push({ year, month, day: gDay });
|
|
887
|
+
break; // Found a match for this date, no need to check other times
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
}
|
|
574
892
|
}
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
let target = this.clone();
|
|
587
|
-
|
|
588
|
-
let result = findLunarDate(target);
|
|
589
|
-
|
|
590
|
-
return formatKhmerDate({
|
|
591
|
-
day: result.day,
|
|
592
|
-
month: result.month,
|
|
593
|
-
moment: target
|
|
594
|
-
}, format)
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
function getKhNewYearMoment(gregorianYear) {
|
|
598
|
-
if (Moment.khNewYearMoments[gregorianYear] !== undefined) {
|
|
599
|
-
// console.log('cache')
|
|
600
|
-
return Moment(Moment.khNewYearMoments[gregorianYear], 'DD-MM-YYYY H:m')
|
|
601
|
-
} else {
|
|
602
|
-
// console.log('calculate')
|
|
603
|
-
let getSoriyatraLerngSak;
|
|
604
|
-
if (typeof require === 'function') {
|
|
605
|
-
getSoriyatraLerngSak = require('./getSoriyatraLerngSak')
|
|
606
|
-
} else {
|
|
607
|
-
if (window) {
|
|
608
|
-
if (!window.getSoriyatraLerngSak) {
|
|
609
|
-
throw 'Please import [MOMENTKH]/getSoriyatraLerngSak.js to your project'
|
|
610
|
-
} else {
|
|
611
|
-
getSoriyatraLerngSak = window.getSoriyatraLerngSak
|
|
612
|
-
}
|
|
613
|
-
} else {
|
|
614
|
-
throw 'Cannot import getSoriyatraLerngSak. This is might not a nodejs environment or a browser'
|
|
893
|
+
if (candidates.length === 0) {
|
|
894
|
+
throw new Error(`Could not find Gregorian date for Khmer date: ${day} ${moonPhaseNum === MoonPhase.Waxing ? 'កើត' : 'រោច'} month ${monthIndexNum} BE ${beYear}`);
|
|
895
|
+
}
|
|
896
|
+
// If multiple candidates found, prefer closest to approximate year
|
|
897
|
+
if (candidates.length > 1) {
|
|
898
|
+
// First, try to filter by year distance
|
|
899
|
+
const minDistance = Math.min(...candidates.map(c => Math.abs(c.year - approxYear)));
|
|
900
|
+
const closestCandidates = candidates.filter(c => Math.abs(c.year - approxYear) === minDistance);
|
|
901
|
+
// If we have a unique closest candidate, return it
|
|
902
|
+
if (closestCandidates.length === 1) {
|
|
903
|
+
return closestCandidates[0];
|
|
615
904
|
}
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
//
|
|
628
|
-
|
|
629
|
-
// return Moment(`14-04-${gregorianYear} ${info.timeOfNewYear.hour}:${info.timeOfNewYear.minute}`, 'DD-MM-YYYY H:m')
|
|
630
|
-
}
|
|
631
|
-
let epochLerngSak = Moment(`17-04-${gregorianYear} ${info.timeOfNewYear.hour}:${info.timeOfNewYear.minute}`, 'DD-MM-YYYY H:m')
|
|
632
|
-
let khEpoch = findLunarDate(epochLerngSak)
|
|
633
|
-
let diffFromEpoch = (((khEpoch.month - 4) * 29) + khEpoch.day) -
|
|
634
|
-
(((info.lunarDateLerngSak.month - 4) * 29) + info.lunarDateLerngSak.day)
|
|
635
|
-
let result = epochLerngSak.subtract(diffFromEpoch + numberNewYearDay - 1, 'day')
|
|
636
|
-
// Caching
|
|
637
|
-
Moment.khNewYearMoments[gregorianYear] = result.format('DD-MM-YYYY H:m')
|
|
638
|
-
return result
|
|
905
|
+
// If there are ties, prefer the one that matches at noon
|
|
906
|
+
const noonMatches = closestCandidates.filter(c => {
|
|
907
|
+
const noonCheck = gregorianToKhmerInternal(c.year, c.month, c.day, 12, 0, 0, false);
|
|
908
|
+
return noonCheck.khmer.beYear === beYear &&
|
|
909
|
+
noonCheck.khmer.monthIndex === monthIndexNum &&
|
|
910
|
+
noonCheck.khmer.day === day &&
|
|
911
|
+
noonCheck.khmer.moonPhase === moonPhaseNum;
|
|
912
|
+
});
|
|
913
|
+
if (noonMatches.length > 0) {
|
|
914
|
+
return noonMatches[0];
|
|
915
|
+
}
|
|
916
|
+
// Fall back to first closest candidate
|
|
917
|
+
return closestCandidates[0];
|
|
639
918
|
}
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
let
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
919
|
+
return candidates[0];
|
|
920
|
+
}
|
|
921
|
+
function getNewYearFullInfo(ceYear) {
|
|
922
|
+
if (newYearInfoCache[ceYear]) {
|
|
923
|
+
return newYearInfoCache[ceYear];
|
|
924
|
+
}
|
|
925
|
+
// Calculate using the standard algorithm first to get necessary info (like angsar for numberNewYearDay)
|
|
926
|
+
const jsYear = adToJs(ceYear);
|
|
927
|
+
let newYearInfo = getNewYearInfo(jsYear);
|
|
928
|
+
// Get Lerng Sak info
|
|
929
|
+
let bodithey = getBoditheyJs(jsYear);
|
|
930
|
+
const isAthikameasPrev = isAdhikameas(jsYear - 1);
|
|
931
|
+
const isChantrathimeasPrev = isChantrathimeas(jsYear - 1);
|
|
932
|
+
if (isAthikameasPrev && isChantrathimeasPrev) {
|
|
933
|
+
bodithey = (bodithey + 1) % 30;
|
|
934
|
+
}
|
|
935
|
+
// lunar DateLerngSak
|
|
936
|
+
const lunarDateLerngSak = {
|
|
937
|
+
day: bodithey >= 6 ? bodithey - 1 : bodithey,
|
|
938
|
+
month: bodithey >= 6 ? 4 : 5 // ចេត្រ or ពិសាខ
|
|
939
|
+
};
|
|
940
|
+
// Number of new year days
|
|
941
|
+
const numberNewYearDay = newYearInfo.newYearsDaySotins[0].angsar === 0 ? 4 : 3;
|
|
942
|
+
// Use April 17 as epoch and work backwards
|
|
943
|
+
const epochLerngSakGreg = { year: ceYear, month: 4, day: 17 };
|
|
944
|
+
// IMPORTANT: prevent recursion by passing isSearching=true (or any flag that skips Era check)
|
|
945
|
+
// gregorianToKhmerInternal(..., isSearching=true) uses simplified BE calc and skips Era check
|
|
946
|
+
const khEpoch = gregorianToKhmerInternal(ceYear, 4, 17, 12, 0, 0, true)._khmerDateObj;
|
|
947
|
+
// Calculate difference
|
|
948
|
+
const diffFromEpoch = ((khEpoch.monthIndex - 4) * 29 + khEpoch.getDayNumber()) -
|
|
949
|
+
((lunarDateLerngSak.month - 4) * 29 + lunarDateLerngSak.day);
|
|
950
|
+
// Calculate days to subtract
|
|
951
|
+
const daysToSubtract = diffFromEpoch + numberNewYearDay - 1;
|
|
952
|
+
// Calculate new year date (Moha Songkran)
|
|
953
|
+
const epochJdn = gregorianToJulianDay(epochLerngSakGreg.year, epochLerngSakGreg.month, epochLerngSakGreg.day);
|
|
954
|
+
let newYearJdn = epochJdn - daysToSubtract;
|
|
955
|
+
// Override with cache if available
|
|
956
|
+
if (khNewYearMoments[ceYear]) {
|
|
957
|
+
const [datePart, timePart] = khNewYearMoments[ceYear].split(' ');
|
|
958
|
+
const [d, m, y] = datePart.split('-').map(Number);
|
|
959
|
+
const [hr, min] = timePart.split(':').map(Number);
|
|
960
|
+
// Update newYearInfo time
|
|
961
|
+
newYearInfo.timeOfNewYear = { hour: hr, minute: min };
|
|
962
|
+
// Update JDN based on cached date
|
|
963
|
+
newYearJdn = gregorianToJulianDay(y, m, d);
|
|
964
|
+
}
|
|
965
|
+
const newYearDate = julianDayToGregorian(newYearJdn);
|
|
966
|
+
const newYearMoment = new Date(newYearDate.year, newYearDate.month - 1, newYearDate.day, newYearInfo.timeOfNewYear.hour, newYearInfo.timeOfNewYear.minute);
|
|
967
|
+
// Calculate Lerng Sak Date (Midnight)
|
|
968
|
+
// Lerng Sak is the last day of NY celebration.
|
|
969
|
+
// Jdn = newYearJdn + (numberNewYearDay - 1)
|
|
970
|
+
const lerngSakJdn = newYearJdn + numberNewYearDay - 1;
|
|
971
|
+
const lerngSakDate = julianDayToGregorian(lerngSakJdn);
|
|
972
|
+
const lerngSakMoment = new Date(lerngSakDate.year, lerngSakDate.month - 1, lerngSakDate.day, 0, 0, 0); // Midnight
|
|
973
|
+
const result = {
|
|
974
|
+
newYearMoment,
|
|
975
|
+
lerngSakMoment,
|
|
976
|
+
newYearInfo: {
|
|
977
|
+
year: newYearDate.year,
|
|
978
|
+
month: newYearDate.month,
|
|
979
|
+
day: newYearDate.day,
|
|
980
|
+
hour: newYearInfo.timeOfNewYear.hour,
|
|
981
|
+
minute: newYearInfo.timeOfNewYear.minute
|
|
982
|
+
}
|
|
983
|
+
};
|
|
984
|
+
newYearInfoCache[ceYear] = result;
|
|
985
|
+
return result;
|
|
986
|
+
}
|
|
987
|
+
function getKhmerNewYear(ceYear) {
|
|
988
|
+
const info = getNewYearFullInfo(ceYear);
|
|
989
|
+
return info.newYearInfo;
|
|
990
|
+
}
|
|
991
|
+
// ============================================================================
|
|
992
|
+
// Formatting Functions
|
|
993
|
+
// ============================================================================
|
|
994
|
+
function formatKhmer(khmerData, formatString) {
|
|
995
|
+
if (!formatString) {
|
|
996
|
+
// Default format
|
|
997
|
+
const { khmer } = khmerData;
|
|
998
|
+
const moonDay = `${khmer.day}${khmer.moonPhaseName}`;
|
|
999
|
+
return toKhmerNumeral(`ថ្ងៃ${khmer.dayOfWeekName} ${moonDay} ខែ${khmer.monthName} ឆ្នាំ${khmer.animalYearName} ${khmer.eraYearName} ពុទ្ធសករាជ ${khmer.beYear}`);
|
|
1000
|
+
}
|
|
1001
|
+
// Custom format
|
|
1002
|
+
const formatRules = {
|
|
1003
|
+
'W': () => khmerData.khmer.dayOfWeekName,
|
|
1004
|
+
'w': () => WeekdayNamesShort[khmerData.gregorian.dayOfWeek],
|
|
1005
|
+
'd': () => khmerData.khmer.day,
|
|
1006
|
+
'D': () => (khmerData.khmer.day < 10 ? '0' : '') + khmerData.khmer.day,
|
|
1007
|
+
'n': () => MoonStatusShort[khmerData.khmer.moonPhase],
|
|
1008
|
+
'N': () => khmerData.khmer.moonPhaseName,
|
|
1009
|
+
'o': () => MoonDaySymbols[khmerData._khmerDateObj.getDayNumber()],
|
|
1010
|
+
'm': () => khmerData.khmer.monthName,
|
|
1011
|
+
'M': () => SolarMonthNames[khmerData.gregorian.month - 1],
|
|
1012
|
+
'a': () => khmerData.khmer.animalYearName,
|
|
1013
|
+
'e': () => khmerData.khmer.eraYearName,
|
|
1014
|
+
'b': () => khmerData.khmer.beYear,
|
|
1015
|
+
'c': () => khmerData.gregorian.year,
|
|
1016
|
+
'j': () => khmerData.khmer.jsYear
|
|
1017
|
+
};
|
|
1018
|
+
const regex = new RegExp(Object.keys(formatRules).join('|'), 'g');
|
|
1019
|
+
const result = formatString.replace(regex, match => {
|
|
1020
|
+
const value = formatRules[match]();
|
|
1021
|
+
return toKhmerNumeral(String(value));
|
|
1022
|
+
});
|
|
1023
|
+
return result;
|
|
1024
|
+
}
|
|
1025
|
+
// ============================================================================
|
|
1026
|
+
// Wrapper function for public API
|
|
1027
|
+
function gregorianToKhmer(year, month, day, hour = 0, minute = 0, second = 0) {
|
|
1028
|
+
// Validate input parameters
|
|
1029
|
+
validateGregorianDate(year, month, day, hour, minute, second);
|
|
1030
|
+
return gregorianToKhmerInternal(year, month, day, hour, minute, second, false);
|
|
1031
|
+
}
|
|
1032
|
+
// ============================================================================
|
|
1033
|
+
// Public API
|
|
1034
|
+
// ============================================================================
|
|
1035
|
+
// Conversion functions
|
|
1036
|
+
function fromGregorian(year, month, day, hour = 0, minute = 0, second = 0) {
|
|
1037
|
+
return gregorianToKhmer(year, month, day, hour, minute, second);
|
|
1038
|
+
}
|
|
1039
|
+
function fromKhmer(day, moonPhase, monthIndex, beYear) {
|
|
1040
|
+
return khmerToGregorian(day, moonPhase, monthIndex, beYear);
|
|
1041
|
+
}
|
|
1042
|
+
// New Year function
|
|
1043
|
+
function getNewYear(ceYear) {
|
|
1044
|
+
return getKhmerNewYear(ceYear);
|
|
1045
|
+
}
|
|
1046
|
+
// Format function
|
|
1047
|
+
function format(khmerData, formatString) {
|
|
1048
|
+
return formatKhmer(khmerData, formatString);
|
|
1049
|
+
}
|
|
1050
|
+
// Utility for creating date from Date object
|
|
1051
|
+
function fromDate(date) {
|
|
1052
|
+
// Validate Date object
|
|
1053
|
+
validateDateObject(date);
|
|
1054
|
+
return gregorianToKhmer(date.getFullYear(), date.getMonth() + 1, date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds());
|
|
1055
|
+
}
|
|
1056
|
+
// Convert Khmer to Date object
|
|
1057
|
+
function toDate(day, moonPhase, monthIndex, beYear) {
|
|
1058
|
+
const greg = khmerToGregorian(day, moonPhase, monthIndex, beYear);
|
|
1059
|
+
return new Date(greg.year, greg.month - 1, greg.day);
|
|
1060
|
+
}
|
|
1061
|
+
// Constants export
|
|
1062
|
+
exports.constants = {
|
|
1063
|
+
LunarMonths,
|
|
1064
|
+
LunarMonthNames,
|
|
1065
|
+
SolarMonthNames,
|
|
1066
|
+
AnimalYearNames,
|
|
1067
|
+
EraYearNames,
|
|
1068
|
+
WeekdayNames,
|
|
1069
|
+
MoonStatusNames
|
|
1070
|
+
};
|
|
1071
|
+
// Default export - aggregate all exports for convenience
|
|
1072
|
+
exports.default = {
|
|
1073
|
+
fromGregorian,
|
|
1074
|
+
fromKhmer,
|
|
1075
|
+
getNewYear,
|
|
1076
|
+
format,
|
|
1077
|
+
fromDate,
|
|
1078
|
+
toDate,
|
|
1079
|
+
constants: exports.constants,
|
|
1080
|
+
MoonPhase,
|
|
1081
|
+
MonthIndex,
|
|
1082
|
+
AnimalYear,
|
|
1083
|
+
EraYear,
|
|
1084
|
+
DayOfWeek
|
|
1085
|
+
};
|
|
667
1086
|
|
|
668
|
-
Moment.fn.khDay = khDay;
|
|
669
|
-
Moment.fn.khMonth = khMonth;
|
|
670
|
-
Moment.fn.khYear = khYear;
|
|
671
1087
|
|
|
672
|
-
return
|
|
673
|
-
}))
|
|
1088
|
+
return module.exports;
|
|
1089
|
+
}));
|