badiDate.js 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773
  1. /**
  2. * @license BadiDate v3.0.2
  3. * (c) 2018 Jan Greis
  4. * licensed under MIT
  5. */
  6. (function (global, factory) {
  7. typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('luxon')) :
  8. typeof define === 'function' && define.amd ? define(['exports', 'luxon'], factory) :
  9. (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.window = global.window || {}, global.luxon));
  10. }(this, (function (exports, luxon) { 'use strict';
  11. const month = {
  12. 1: 'Bahá',
  13. 2: 'Jalál',
  14. 3: 'Jamál',
  15. 4: '‘Aẓamat',
  16. 5: 'Núr',
  17. 6: 'Raḥmat',
  18. 7: 'Kalimát',
  19. 8: 'Kamál',
  20. 9: 'Asmá’',
  21. 10: '‘Izzat',
  22. 11: 'Ma_sh_íyyat',
  23. 12: '‘Ilm',
  24. 13: 'Qudrat',
  25. 14: 'Qawl',
  26. 15: 'Masá’il',
  27. 16: '_Sh_araf',
  28. 17: 'Sulṭán',
  29. 18: 'Mulk',
  30. 19: '‘Alá’',
  31. 20: 'Ayyám-i-Há',
  32. };
  33. const monthL = {
  34. 1: 'Splendour',
  35. 2: 'Glory',
  36. 3: 'Beauty',
  37. 4: 'Grandeur',
  38. 5: 'Light',
  39. 6: 'Mercy',
  40. 7: 'Words',
  41. 8: 'Perfection',
  42. 9: 'Names',
  43. 10: 'Might',
  44. 11: 'Will',
  45. 12: 'Knowledge',
  46. 13: 'Power',
  47. 14: 'Speech',
  48. 15: 'Questions',
  49. 16: 'Honour',
  50. 17: 'Sovereignty',
  51. 18: 'Dominion',
  52. 19: 'Loftiness',
  53. 20: 'Ayyám-i-Há',
  54. };
  55. const holyDay = {
  56. 1: 'Naw-Rúz',
  57. 2: 'First day of Riḍván',
  58. 3: 'Ninth day of Riḍván',
  59. 4: 'Twelfth day of Riḍván',
  60. 5: 'Declaration of the Báb',
  61. 6: 'Ascension of Bahá’u’lláh',
  62. 7: 'Martyrdom of the Báb',
  63. 8: 'Birth of the Báb',
  64. 9: 'Birth of Bahá’u’lláh',
  65. 10: 'Day of the Covenant',
  66. 11: 'Ascension of ‘Abdu’l-Bahá',
  67. };
  68. // CAREFUL: Numbering corresponds to Badí' week, i.e. 1 is Jalál (-> Saturday)
  69. const weekday = {
  70. 1: 'Jalál',
  71. 2: 'Jamál',
  72. 3: 'Kamál',
  73. 4: 'Fiḍál',
  74. 5: '‘Idál',
  75. 6: 'Istijlál',
  76. 7: 'Istiqlál',
  77. };
  78. const weekdayAbbr3 = {
  79. 1: 'Jal',
  80. 2: 'Jam',
  81. 3: 'Kam',
  82. 4: 'Fiḍ',
  83. 5: '‘Idá',
  84. 6: 'Isj',
  85. 7: 'Isq',
  86. };
  87. const weekdayAbbr2 = {
  88. 1: 'Jl',
  89. 2: 'Jm',
  90. 3: 'Ka',
  91. 4: 'Fi',
  92. 5: '‘Id',
  93. 6: 'Ij',
  94. 7: 'Iq',
  95. };
  96. const weekdayL = {
  97. 1: 'Glory',
  98. 2: 'Beauty',
  99. 3: 'Perfection',
  100. 4: 'Grace',
  101. 5: 'Justice',
  102. 6: 'Majesty',
  103. 7: 'Independence',
  104. };
  105. const yearInVahid = {
  106. 1: 'Alif',
  107. 2: 'Bá’',
  108. 3: 'Ab',
  109. 4: 'Dál',
  110. 5: 'Báb',
  111. 6: 'Váv',
  112. 7: 'Abad',
  113. 8: 'Jád',
  114. 9: 'Bahá',
  115. 10: 'Ḥubb',
  116. 11: 'Bahháj',
  117. 12: 'Javáb',
  118. 13: 'Aḥad',
  119. 14: 'Vahháb',
  120. 15: 'Vidád',
  121. 16: 'Badí‘',
  122. 17: 'Bahí',
  123. 18: 'Abhá',
  124. 19: 'Váḥid',
  125. };
  126. const vahid = 'Váḥid';
  127. const kulliShay = 'Kull-i-_Sh_ay’';
  128. const BE = 'B.E.';
  129. const badiCalendar = 'Badí‘ Calendar';
  130. const unicodeCharForZero = '0';
  131. const defaultFormat = 'd MM+ y BE';
  132. var en = /*#__PURE__*/Object.freeze({
  133. __proto__: null,
  134. month: month,
  135. monthL: monthL,
  136. holyDay: holyDay,
  137. weekday: weekday,
  138. weekdayAbbr3: weekdayAbbr3,
  139. weekdayAbbr2: weekdayAbbr2,
  140. weekdayL: weekdayL,
  141. yearInVahid: yearInVahid,
  142. vahid: vahid,
  143. kulliShay: kulliShay,
  144. BE: BE,
  145. badiCalendar: badiCalendar,
  146. unicodeCharForZero: unicodeCharForZero,
  147. defaultFormat: defaultFormat
  148. });
  149. /* eslint-disable dot-notation, line-comment-position, camelcase, sort-imports */
  150. const badiLocale = { en, default: en };
  151. const setDefaultLanguage = (language) => {
  152. if (badiLocale[language] === undefined) {
  153. // eslint-disable-next-line no-console
  154. console.log('Chosen language does not exist. Setting has not been changed.');
  155. }
  156. else {
  157. badiLocale['default'] = badiLocale[language];
  158. }
  159. };
  160. let underlineFormat = 'css';
  161. const setUnderlineFormat = (format) => {
  162. if (['css', 'u', 'diacritic', 'none'].includes(format)) {
  163. underlineFormat = format;
  164. }
  165. else {
  166. // eslint-disable-next-line no-console
  167. console.log('Invalid underline format. Choose one of ["css", "u", "diacritic", "none"]. ' +
  168. 'Setting has not been changed.');
  169. }
  170. };
  171. const formatTokens = [
  172. ['DDL', 'DD+', 'MML', 'MM+', 'WWL', 'yyv', 'KiS'],
  173. ['dd', 'DD', 'mm', 'MM', 'ww', 'WW', 'yv', 'YV', 'vv', 'kk', 'yy', 'BE', 'BC', 'Va'],
  174. ['d', 'D', 'm', 'M', 'W', 'v', 'k', 'y']
  175. ];
  176. // eslint-disable-next-line complexity
  177. const formatBadiDate = (badiDate, formatString, language) => {
  178. if (!badiDate.isValid) {
  179. return 'Not a valid Badí‘ date';
  180. }
  181. if (typeof language === 'string' && badiLocale[language] === undefined && language.includes('-')) {
  182. language = language.split('-')[0];
  183. }
  184. if (language === undefined || badiLocale[language] === undefined) {
  185. language = 'default';
  186. }
  187. formatString = formatString !== null && formatString !== void 0 ? formatString : formatItemFallback(language, 'defaultFormat');
  188. let formattedDate = '';
  189. const length = formatString.length;
  190. for (let i = 0; i < length; i++) {
  191. // Text wrapped in {} is output as-is. A '{' without a matching '}'
  192. // results in invalid input
  193. if (formatString[i] === '{' && i < length - 1) {
  194. for (let j = i + 1; j <= length; j++) {
  195. if (j === length) {
  196. return 'Invalid formatting string.';
  197. }
  198. if (formatString[j] === '}') {
  199. i = j;
  200. break;
  201. }
  202. formattedDate += formatString[j];
  203. }
  204. }
  205. else {
  206. const next1 = formatString[i];
  207. const next2 = next1 + formatString[i + 1];
  208. const next3 = next2 + formatString[i + 2];
  209. if (formatTokens[0].includes(next3)) {
  210. formattedDate += getFormatItem(badiDate, next3, language);
  211. i += 2;
  212. }
  213. else if (formatTokens[1].includes(next2)) {
  214. formattedDate += getFormatItem(badiDate, next2, language);
  215. i += 1;
  216. }
  217. else if (formatTokens[2].includes(next1)) {
  218. formattedDate += getFormatItem(badiDate, next1, language);
  219. }
  220. else {
  221. formattedDate += next1;
  222. }
  223. }
  224. }
  225. return formattedDate;
  226. };
  227. // eslint-disable-next-line complexity
  228. const getFormatItem = (badiDate, token, language) => {
  229. switch (token) {
  230. // Single character tokens
  231. case 'd':
  232. return digitRewrite(badiDate.day, language);
  233. case 'D':
  234. return postProcessLocaleItem(formatItemFallback(language, 'month', badiDate.day), 3);
  235. case 'm':
  236. return digitRewrite(badiDate.month, language);
  237. case 'M':
  238. return postProcessLocaleItem(formatItemFallback(language, 'month', badiDate.month), 3);
  239. case 'W':
  240. return formatItemFallback(language, 'weekdayAbbr3', (badiDate.gregorianDate.weekday + 1) % 7 + 1);
  241. case 'y':
  242. return digitRewrite(badiDate.year, language);
  243. case 'v':
  244. return digitRewrite((Math.floor((badiDate.year - 1) / 19) % 19) + 1, language);
  245. case 'k':
  246. return digitRewrite(Math.floor((badiDate.year - 1) / 361) + 1, language);
  247. // Two character tokens
  248. case 'dd':
  249. return digitRewrite((`0${String(badiDate.day)}`).slice(-2), language);
  250. case 'DD':
  251. return postProcessLocaleItem(formatItemFallback(language, 'month', badiDate.day));
  252. case 'mm':
  253. return digitRewrite((`0${String(badiDate.month)}`).slice(-2), language);
  254. case 'MM':
  255. return postProcessLocaleItem(formatItemFallback(language, 'month', badiDate.month));
  256. case 'ww':
  257. return formatItemFallback(language, 'weekdayAbbr2', (badiDate.gregorianDate.weekday + 1) % 7 + 1);
  258. case 'WW':
  259. return formatItemFallback(language, 'weekday', (badiDate.gregorianDate.weekday + 1) % 7 + 1);
  260. case 'yy':
  261. return digitRewrite((`00${String(badiDate.year)}`).slice(-3), language);
  262. case 'yv':
  263. return digitRewrite((badiDate.year - 1) % 19 + 1, language);
  264. case 'YV':
  265. return formatItemFallback(language, 'yearInVahid', (badiDate.year - 1) % 19 + 1);
  266. case 'vv':
  267. return digitRewrite((`0${String((Math.floor((badiDate.year - 1) / 19) + 2) % 19 - 1)}`).slice(-2), language);
  268. case 'kk':
  269. return digitRewrite((`0${String(Math.floor((badiDate.year - 1) / 361) + 1)}`).slice(-2), language);
  270. case 'Va':
  271. return formatItemFallback(language, 'vahid');
  272. case 'BE':
  273. return formatItemFallback(language, 'BE');
  274. case 'BC':
  275. return formatItemFallback(language, 'badiCalendar');
  276. // Three character tokens
  277. case 'DDL':
  278. return formatItemFallback(language, 'monthL', badiDate.day);
  279. case 'DD+': {
  280. const day = postProcessLocaleItem(formatItemFallback(language, 'month', badiDate.day));
  281. const dayL = formatItemFallback(language, 'monthL', badiDate.day);
  282. if (day === dayL) {
  283. return day;
  284. }
  285. if (badiLocale[language] === badiLocale.fa) {
  286. return `<span dir="rtl">${day} (${dayL})</span>`;
  287. }
  288. return `${day} (${dayL})`;
  289. }
  290. case 'MML':
  291. return formatItemFallback(language, 'monthL', badiDate.month);
  292. case 'MM+': {
  293. const month = postProcessLocaleItem(formatItemFallback(language, 'month', badiDate.month));
  294. const monthL = formatItemFallback(language, 'monthL', badiDate.month);
  295. if (month === monthL) {
  296. return month;
  297. }
  298. if (badiLocale[language] === badiLocale.fa) {
  299. return `<span dir="rtl">${month} (${monthL})</span>`;
  300. }
  301. return `${month} (${monthL})`;
  302. }
  303. case 'WWL':
  304. return formatItemFallback(language, 'weekdayL', (badiDate.gregorianDate.weekday + 1) % 7 + 1);
  305. case 'yyv':
  306. return digitRewrite((`0${String((badiDate.year - 1) % 19 + 1)}`).slice(-2), language);
  307. case 'KiS':
  308. return postProcessLocaleItem(formatItemFallback(language, 'kulliShay'));
  309. // istanbul ignore next
  310. default:
  311. return '';
  312. }
  313. };
  314. const postProcessLocaleItem = (item, crop) => {
  315. if (crop && crop < item.length) {
  316. let char = 0;
  317. let counter = 0;
  318. while (counter < crop) {
  319. if (!'_’‘'.includes(item[char])) {
  320. counter++;
  321. }
  322. char++;
  323. }
  324. if ('_’‘'.includes(item[char])) {
  325. char++;
  326. }
  327. item = item.slice(0, char);
  328. if (item.split('_').length % 2 === 0) {
  329. item += '_';
  330. }
  331. }
  332. const stringComponents = item.split('_');
  333. for (let i = 1; i < stringComponents.length; i += 2) {
  334. stringComponents[i] = underlineString(stringComponents[i]);
  335. }
  336. return stringComponents.join('');
  337. };
  338. const underlineString = (str) => {
  339. switch (underlineFormat) {
  340. case 'css':
  341. return `<span style="text-decoration:underline">${str}</span>`;
  342. case 'diacritic':
  343. return str.split('').map(char => `${char}\u0332`).join('');
  344. case 'u':
  345. return `<u>${str}</u>`;
  346. case 'none':
  347. return str;
  348. // istanbul ignore next
  349. default:
  350. throw new TypeError('Unexpected underlineFormat');
  351. }
  352. };
  353. const digitRewrite = (number, language) => {
  354. number = String(number);
  355. const unicodeOffset = formatItemFallback(language, 'unicodeCharForZero').charCodeAt(0) - '0'.charCodeAt(0);
  356. if (unicodeOffset === 0) {
  357. return number;
  358. }
  359. const codePoints = [...number].map(num => num.charCodeAt(0) + unicodeOffset);
  360. return String.fromCharCode(...codePoints);
  361. };
  362. const formatItemFallback = (language, category, index) => {
  363. var _a;
  364. if (index === undefined) {
  365. while (badiLocale[language][category] === undefined) {
  366. language = languageFallback(language);
  367. }
  368. return badiLocale[language][category];
  369. }
  370. while (((_a = badiLocale[language][category]) === null || _a === void 0 ? void 0 : _a[index]) === undefined) {
  371. language = languageFallback(language);
  372. }
  373. return badiLocale[language][category][index];
  374. };
  375. const languageFallback = (languageCode) => {
  376. if (languageCode.includes('-')) {
  377. return languageCode.split('-')[0];
  378. // eslint-disable-next-line no-negated-condition
  379. }
  380. else if (languageCode !== 'default') {
  381. return 'default';
  382. }
  383. return 'en';
  384. };
  385. const badiYears = [
  386. 'l4da', 'k4ci', 'k5c7', 'l4d6', 'l4ce', 'k4c4', 'k5d4', 'l4cb', 'l4c1', 'k4cj', 'k5c8', 'l4d7', 'l4cf', 'k4c5',
  387. 'k4d5', 'k5ce', 'l4c2', 'k4d2', 'k4ca', 'k5da', 'l4ch', 'k4c6', 'k4d6', 'k5cf', 'l4c4', 'k4d4', 'k4cc', 'k5c1',
  388. 'l4cj', 'k4c8', 'k4d8', 'k5cg', 'l4c5', 'k4d5', 'k4ce', 'k5c3', 'l4d2', 'k4ca', 'k4d9', 'k5ci', 'l4c6', 'k4d6',
  389. 'k4cf', 'k4c4', 'k5d4', 'k4cb', 'k4bj', 'k4cj', 'k5c9', 'k4d8', 'k4cg', 'k4c6', 'k5d6', 'k4cd', 'k4c2', 'k4d2',
  390. 'k5ca', 'k4d9', 'k4ci', 'k4c7', 'k5d7', 'k4cf', 'k4c4', 'k4d4', 'k5cc', 'k4bj', 'k4cj', 'k4c9', 'k5d9', 'k4cg',
  391. 'k4c6', 'k4d5', 'k5cd', 'k4c2', 'k4d1', 'k4ca', 'k4da', 'j5cj', 'k4c7', 'k4d7', 'k4cf', 'j5c4', 'k4d3', 'k4cb',
  392. 'k4c1', 'k5d1', 'l4c9', 'l4d9', 'l4ch', 'k5c6', 'l4d5', 'l4cd', 'l4c2', 'k5d2', 'l4ca', 'l4da', 'l4cj', 'k5c8',
  393. 'l4d7', 'l4cf', 'l4c4', 'k5d4', 'l4cb', 'l4c1', 'l4d1', 'k5c9', 'l4d8', 'l4cg', 'l4c5', 'k4d5', 'k5ce', 'l4c2',
  394. 'l4d2', 'k4cb', 'k5db', 'l4ci', 'l4c7', 'k4d7', 'k5cf', 'l4c4', 'l4d4', 'k4cc', 'k5c2', 'l4d1', 'l4c9', 'k4d9',
  395. 'k5ch', 'l4c5', 'l4d5', 'k4ce', 'k5c3', 'l4d2', 'l4cb', 'k4da', 'k5ci', 'l4c6', 'l4d6', 'k4cf', 'k5c5', 'l4d4',
  396. 'l4cc', 'k4c1', 'k4d1', 'k5c9', 'l4d8', 'k4cg', 'k4c6', 'k5d6', 'l4ce', 'k4c3', 'k4d3', 'k5cb', 'l4da', 'k4ci',
  397. 'k4c7', 'k5d7', 'l4cf', 'k4c5', 'k4d5', 'k5cd', 'l4c1', 'k4cj', 'k4c9', 'k5d9', 'l4cg', 'k4c6', 'k4d6', 'k5ce',
  398. 'l4c3', 'k4d2', 'k4ca', 'k5bj', 'l4ci', 'k4c7', 'k4d7', 'k4cg', 'k5c5', 'k4d4', 'k4cc', 'k4c1', 'k5d1', 'k4c9',
  399. 'k4d9', 'k4ch', 'k5c7', 'l4d6', 'l4ce', 'l4c3', 'l5d3', 'l4ca', 'l4da', 'l4cj', 'l5c8', 'l4d7', 'l4cg', 'l4c5',
  400. 'l5d4', 'l4cb', 'l4c1', 'l4d1', 'l5ca', 'l4d9', 'l4ch', 'l4c6', 'l5d6', 'l4cd', 'l4c2', 'l4d2', 'l4cb', 'k5c1',
  401. 'l4cj', 'l4c8', 'l4d8', 'k5cg', 'l4c4', 'l4d4', 'l4cc', 'k5c2', 'l4d1', 'l4ca', 'l4da', 'k5ci', 'l4c6', 'l4d5',
  402. 'l4ce', 'k5c3', 'l4d2', 'l4cb', 'l4db', 'k5cj', 'l4c8', 'l4d7', 'l4cf', 'k5c5', 'l4d4', 'l4cc', 'l4c2', 'k5d2',
  403. 'l4c9', 'l4d9', 'l4ch', 'k4c6', 'k5d6', 'l4ce', 'l4c3', 'k4d3', 'k5cc', 'l4db', 'l4cj', 'k4c8', 'k5d8', 'l4cf',
  404. 'l4c4', 'k4d5', 'k5cd', 'l4c2', 'l4d2', 'k4ca', 'k5d9', 'l4cg', 'l4c6', 'k4d6', 'k5cf', 'l4c3', 'l4d3', 'k4cb',
  405. 'k5bj', 'l4ci', 'l4c7', 'k4d7', 'k5cg', 'l4c5', 'l4d5', 'k4cd', 'k4c2', 'k5d2', 'l4c9', 'k4d9', 'k4ch', 'k5c7',
  406. 'l4d6', 'k4cf', 'k4c4', 'k5d4', 'l4cb', 'l4bj', 'l4cj', 'l5c8', 'm4d7', 'l4cg', 'l4c5', 'l5d5', 'm4cc', 'l4c1',
  407. 'l4d1', 'l5ca', 'm4d9', 'l4ch', 'l4c7', 'l5d7', 'm4ce', 'l4c3', 'l4d3', 'l5cb', 'm4bi', 'l4ci', 'l4c8', 'l4d8',
  408. 'l5ch', 'l4c5', 'l4d5', 'l4cd', 'l5c2', 'l4d1', 'l4c9', 'l4da', 'l5ci', 'l4c7', 'l4d7', 'l4cf', 'l5c4', 'l4d2',
  409. 'l4cb', 'l4bj', 'l5d1', 'l4c8', 'l4d8', 'l4cg', 'l5c5', 'l4d4', 'l4cc', 'l4c2', 'l5d2', 'l4c9', 'l4da', 'l4ci',
  410. ];
  411. class BadiDate {
  412. constructor(date) {
  413. this._holyDay = undefined;
  414. this._valid = true;
  415. this._invalidReason = undefined;
  416. try {
  417. if (this._isDateObject(date)) {
  418. this._gregorianDate = luxon.DateTime.fromObject({ year: date.getFullYear(), month: date.getMonth() + 1, day: date.getDate(), zone: 'UTC' });
  419. }
  420. else if (luxon.DateTime.isDateTime(date)) {
  421. this._gregorianDate = luxon.DateTime.fromObject({ year: date.year, month: date.month, day: date.day, zone: 'UTC' });
  422. }
  423. else if (this._isYearMonthDay(date) || this._isYearHolyDayNumber(date)) {
  424. this._setFromBadiDate(date);
  425. }
  426. else {
  427. throw new TypeError('Unrecognized input format');
  428. }
  429. if (this._year === undefined) {
  430. // We haven't set the Badí' date yet
  431. this._setFromGregorianDate();
  432. }
  433. this._setHolyDay();
  434. }
  435. catch (err) {
  436. this._setInvalid(err);
  437. }
  438. Object.freeze(this);
  439. }
  440. format(formatString, language) {
  441. return formatBadiDate(this, formatString, language);
  442. }
  443. _isDateObject(arg) {
  444. return Object.prototype.toString.call(arg) === '[object Date]';
  445. }
  446. _isYearMonthDay(arg) {
  447. return typeof arg.year === 'number' && typeof arg.month === 'number' &&
  448. typeof arg.day === 'number';
  449. }
  450. _isYearHolyDayNumber(arg) {
  451. return typeof arg.year === 'number' && arg.month === undefined &&
  452. arg.day === undefined && typeof arg.holyDayNumber === 'number';
  453. }
  454. _notInValidGregorianDateRange(datetime) {
  455. const lowerBound = luxon.DateTime.fromObject({ year: 1844, month: 3, day: 21, zone: 'UTC' });
  456. const upperBound = luxon.DateTime.fromObject({ year: 2351, month: 3, day: 20, zone: 'UTC' });
  457. return datetime < lowerBound || datetime > upperBound;
  458. }
  459. _setFromGregorianDate() {
  460. if (this._notInValidGregorianDateRange(this._gregorianDate)) {
  461. throw new RangeError('Input date outside of valid range (1844-03-21 - 2351-03-20)');
  462. }
  463. const gregorianYear = this._gregorianDate.year;
  464. const oldImplementationCutoff = luxon.DateTime.fromObject({ year: 2015, month: 3, day: 21, zone: 'UTC' });
  465. if (this._gregorianDate < oldImplementationCutoff) {
  466. const { month, day } = this._gregorianDate;
  467. if (month < 3 || (month === 3 && day < 21)) {
  468. this._nawRuz = luxon.DateTime.fromObject({ year: gregorianYear - 1, month: 3, day: 21, zone: 'UTC' });
  469. this._year = gregorianYear - 1844;
  470. }
  471. else {
  472. this._nawRuz = luxon.DateTime.fromObject({ year: gregorianYear, month: 3, day: 21, zone: 'UTC' });
  473. this._year = gregorianYear - 1843;
  474. }
  475. this._setOldAyyamiHaLength();
  476. this._yearTwinBirthdays = [12, 5, 13, 9];
  477. }
  478. else {
  479. this._year = gregorianYear - 1843;
  480. this._setBadiYearInfo(true);
  481. }
  482. this._setBadiMonthAndDay();
  483. }
  484. /**
  485. * Set Badí' month and day from Gregorian date
  486. */
  487. _setBadiMonthAndDay() {
  488. const dayOfBadiYear = this._dayOfYear(this._gregorianDate);
  489. if (dayOfBadiYear < 343) {
  490. this._month = Math.floor((dayOfBadiYear - 1) / 19 + 1);
  491. this._day = (dayOfBadiYear - 1) % 19 + 1;
  492. }
  493. else if (dayOfBadiYear < 343 + this._ayyamiHaLength) {
  494. this._month = 20;
  495. this._day = dayOfBadiYear - 342;
  496. }
  497. else {
  498. this._month = 19;
  499. this._day = dayOfBadiYear - (342 + this._ayyamiHaLength);
  500. }
  501. }
  502. _setFromBadiDate(date) {
  503. this._year = date.year;
  504. if (this._year < 1 || this._year > 507) {
  505. throw new RangeError('Input date outside of valid range (1 - 507 B.E.)');
  506. }
  507. else if (this._year < 172) {
  508. this._nawRuz = luxon.DateTime.fromObject({ year: 1843 + this._year, month: 3, day: 21, zone: 'UTC' });
  509. this._setOldAyyamiHaLength();
  510. this._yearTwinBirthdays = [12, 5, 13, 9];
  511. }
  512. else {
  513. this._setBadiYearInfo();
  514. }
  515. if (this._isYearMonthDay(date)) {
  516. this._month = date.month;
  517. this._day = date.day;
  518. if (this._month === 20 && this._day > this._ayyamiHaLength) {
  519. // If only off by one day, we'll bubble up so that 5th Ayyám-i-Há in a year with only 4 days of
  520. // Ayyám-i-Há can be salvaged
  521. if (this._day - this._ayyamiHaLength === 1) {
  522. this._month = 19;
  523. this._day = 1;
  524. }
  525. else {
  526. throw new TypeError('Input numbers do not designate a valid date');
  527. }
  528. }
  529. if (this._month < 1 || this._month > 20 || this._day < 1 || this.day > 19) {
  530. throw new TypeError('Input numbers do not designate a valid date');
  531. }
  532. }
  533. else {
  534. if (date.holyDayNumber < 1 || date.holyDayNumber > 11) {
  535. throw new TypeError('Input numbers do not designate a valid Holy Day');
  536. }
  537. this._holyDay = date.holyDayNumber;
  538. [this._month, this._day] = this._holyDayMapping()[this._holyDay];
  539. }
  540. this._gregorianDate = this._nawRuz.plus(luxon.Duration.fromObject({ days: this._dayOfYear([this._year, this._month, this._day]) - 1 }));
  541. }
  542. _setOldAyyamiHaLength() {
  543. if (luxon.DateTime.fromObject({ year: this._nawRuz.year + 1 }).isInLeapYear) {
  544. this._ayyamiHaLength = 5;
  545. }
  546. else {
  547. this._ayyamiHaLength = 4;
  548. }
  549. }
  550. _setBadiYearInfo(fromGregorianDate = false) {
  551. let yearData = this._extractBadiYearInfo();
  552. if (fromGregorianDate && this._gregorianDate < yearData.nawRuz) {
  553. this._year -= 1;
  554. yearData = this._extractBadiYearInfo();
  555. }
  556. this._nawRuz = yearData.nawRuz;
  557. this._ayyamiHaLength = yearData.ayyamiHaLength;
  558. this._yearTwinBirthdays = yearData.twinBirthdays;
  559. }
  560. _extractBadiYearInfo() {
  561. let nawRuz, ayyamiHaLength, twinBirthdays;
  562. // Check whether data needs to be unpacked or exists in the verbose version
  563. // istanbul ignore else
  564. if (badiYears[0] === 'l4da') {
  565. const components = badiYears[this._year - 172].split('');
  566. nawRuz = luxon.DateTime.fromObject({ year: this._year - 172 + 2015, month: 3, day: parseInt(components[0], 36), zone: 'UTC' });
  567. ayyamiHaLength = parseInt(components[1], 36);
  568. const TB1 = [parseInt(components[2], 36), parseInt(components[3], 36)];
  569. const TB2 = TB1[1] < 19 ? [TB1[0], TB1[1] + 1] : [TB1[0] + 1, 1];
  570. twinBirthdays = [TB1[0], TB1[1], TB2[0], TB2[1]];
  571. }
  572. else {
  573. ({ nawRuz, ayyamiHaLength, twinBirthdays } = badiYears[this._year]);
  574. nawRuz = luxon.DateTime.fromISO(nawRuz, { zone: 'UTC' });
  575. }
  576. return { nawRuz, ayyamiHaLength, twinBirthdays };
  577. }
  578. _dayOfYear(date) {
  579. // Naw-Rúz is day 1
  580. if (Array.isArray(date)) {
  581. // We have a Badí' date
  582. if (date[1] < 19) {
  583. return 19 * (date[1] - 1) + date[2];
  584. }
  585. else if (date[1] === 20) {
  586. return 342 + date[2];
  587. }
  588. // date[1] === 19
  589. return 342 + this._ayyamiHaLength + date[2];
  590. }
  591. return date.diff(this._nawRuz).as('days') + 1;
  592. }
  593. _setInvalid(invalidReason) {
  594. this._gregorianDate = luxon.DateTime.invalid('Not a valid Badí‘ date');
  595. this._year = NaN;
  596. this._month = NaN;
  597. this._day = NaN;
  598. this._ayyamiHaLength = NaN;
  599. this._nawRuz = luxon.DateTime.invalid('Not a valid Badí‘ date');
  600. this._valid = false;
  601. this._invalidReason = invalidReason;
  602. }
  603. _setHolyDay() {
  604. const mapping = this._holyDayMapping();
  605. this._holyDay = parseInt(Object.keys(mapping)
  606. .find(key => mapping[key][0] === this._month && mapping[key][1] === this._day), 10);
  607. }
  608. _holyDayMapping() {
  609. return {
  610. [1 /* NawRuz */]: [1, 1],
  611. [2 /* FirstRidvan */]: [2, 13],
  612. [3 /* NinthRidvan */]: [3, 2],
  613. [4 /* TwelfthRidvan */]: [3, 5],
  614. [5 /* DeclarationOfTheBab */]: [4, this._year < 172 ? 7 : 8],
  615. [6 /* AscensionOfBahaullah */]: [4, 13],
  616. [7 /* MartyrdomOfTheBab */]: [6, this._year < 172 ? 16 : 17],
  617. [8 /* BirthOfTheBab */]: [this._yearTwinBirthdays[0], this._yearTwinBirthdays[1]],
  618. [9 /* BirthOfBahaullah */]: [this._yearTwinBirthdays[2], this._yearTwinBirthdays[3]],
  619. [10 /* DayOfTheCovenant */]: [14, 4],
  620. [11 /* AscensionOfAbdulBaha */]: [14, 6],
  621. };
  622. }
  623. _leapYearsBefore() {
  624. let leapYearsBefore = Math.floor(Math.min(this.year - 1, 171) / 4);
  625. if (this.year > 172) {
  626. // istanbul ignore else
  627. if (badiYears[0] === 'l4da') {
  628. leapYearsBefore += badiYears.slice(0, this.year - 172).filter(entry => entry[1] === '5').length;
  629. }
  630. else {
  631. leapYearsBefore += Object.entries(badiYears)
  632. .filter(([year, data]) => parseInt(year, 10) < this.year &&
  633. data.ayyamiHaLength === 5).length;
  634. }
  635. }
  636. return leapYearsBefore;
  637. }
  638. holyDay(language = undefined) {
  639. if (!this._holyDay) {
  640. return '';
  641. }
  642. if (language === undefined || badiLocale[language] === undefined) {
  643. language = 'default';
  644. }
  645. return formatItemFallback(language, 'holyDay', this._holyDay);
  646. }
  647. valueOf() {
  648. return this._dayOfYear([this.year, this.month, this.day]) + this._leapYearsBefore() + (this.year - 1) * 365;
  649. }
  650. equals(other) {
  651. return this.isValid && other.isValid && this.valueOf() === other.valueOf();
  652. }
  653. get isValid() {
  654. return this._valid;
  655. }
  656. get invalidReason() {
  657. return this._invalidReason;
  658. }
  659. get day() {
  660. return this._day;
  661. }
  662. get month() {
  663. return this._month;
  664. }
  665. get year() {
  666. return this._year;
  667. }
  668. // number of the Badí' weekday between 1 (Jalál ~> Saturday) and 7 (Istiqlál ~> Friday).
  669. get weekday() {
  670. return (this._gregorianDate.weekday + 1) % 7 + 1;
  671. }
  672. get yearInVahid() {
  673. return (this._year - 1) % 19 + 1;
  674. }
  675. get vahid() {
  676. return (Math.floor((this._year - 1) / 19) % 19) + 1;
  677. }
  678. get kullIShay() {
  679. return Math.floor((this._year - 1) / 361) + 1;
  680. }
  681. // Gregorian date on whose sunset the Badí' date ends.
  682. get gregorianDate() {
  683. return this._gregorianDate;
  684. }
  685. get ayyamiHaLength() {
  686. return this._ayyamiHaLength;
  687. }
  688. get holyDayNumber() {
  689. return this._holyDay ? this._holyDay : undefined;
  690. }
  691. get workSuspended() {
  692. return this._holyDay ? this.holyDayNumber < 10 : undefined;
  693. }
  694. get nextMonth() {
  695. let { year, month } = this;
  696. switch (month) {
  697. case 18:
  698. month = 20;
  699. break;
  700. case 19:
  701. month = 1;
  702. year += 1;
  703. break;
  704. case 20:
  705. month = 19;
  706. break;
  707. default:
  708. month += 1;
  709. }
  710. return new BadiDate({ year, month, day: 1 });
  711. }
  712. get previousMonth() {
  713. let { year, month } = this;
  714. switch (month) {
  715. case 1:
  716. month = 19;
  717. year -= 1;
  718. break;
  719. case 19:
  720. month = 20;
  721. break;
  722. case 20:
  723. month = 18;
  724. break;
  725. default:
  726. month -= 1;
  727. }
  728. return new BadiDate({ year, month, day: 1 });
  729. }
  730. get nextDay() {
  731. if (this._day === 19 || (this._month === 20 && this._day === this._ayyamiHaLength)) {
  732. return this.nextMonth;
  733. }
  734. return new BadiDate({ year: this._year, month: this._month, day: this._day + 1 });
  735. }
  736. get previousDay() {
  737. if (this._day === 1) {
  738. const { previousMonth } = this;
  739. let day = 19;
  740. if (this._month === 19) {
  741. day = this._ayyamiHaLength;
  742. }
  743. return new BadiDate({
  744. year: previousMonth.year,
  745. month: previousMonth.month,
  746. day,
  747. });
  748. }
  749. return new BadiDate({ year: this._year, month: this._month, day: this._day - 1 });
  750. }
  751. }
  752. const badiDateSettings = (settings) => {
  753. if (settings.defaultLanguage) {
  754. setDefaultLanguage(settings.defaultLanguage);
  755. }
  756. if (settings.underlineFormat) {
  757. setUnderlineFormat(settings.underlineFormat);
  758. }
  759. };
  760. exports.BadiDate = BadiDate;
  761. exports.badiDateSettings = badiDateSettings;
  762. Object.defineProperty(exports, '__esModule', { value: true });
  763. })));