nepalipatrojs
Version:
A JavaScript library for Nepali date conversion and calendar utilities
1,078 lines (1,065 loc) • 48.9 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.NepaliPatro = {}));
})(this, (function (exports) { 'use strict';
/**
* Month names in Nepali language (Devanagari script)
*/
exports.NepaliMonth = void 0;
(function (NepaliMonth) {
NepaliMonth[NepaliMonth["Baisakh"] = 1] = "Baisakh";
NepaliMonth[NepaliMonth["Jestha"] = 2] = "Jestha";
NepaliMonth[NepaliMonth["Ashadh"] = 3] = "Ashadh";
NepaliMonth[NepaliMonth["Shrawan"] = 4] = "Shrawan";
NepaliMonth[NepaliMonth["Bhadra"] = 5] = "Bhadra";
NepaliMonth[NepaliMonth["Ashwin"] = 6] = "Ashwin";
NepaliMonth[NepaliMonth["Kartik"] = 7] = "Kartik";
NepaliMonth[NepaliMonth["Mangsir"] = 8] = "Mangsir";
NepaliMonth[NepaliMonth["Poush"] = 9] = "Poush";
NepaliMonth[NepaliMonth["Magh"] = 10] = "Magh";
NepaliMonth[NepaliMonth["Falgun"] = 11] = "Falgun";
NepaliMonth[NepaliMonth["Chaitra"] = 12] = "Chaitra";
})(exports.NepaliMonth || (exports.NepaliMonth = {}));
/**
* Month names in Nepali language (Devanagari script)
*/
exports.NepaliMonthNepali = void 0;
(function (NepaliMonthNepali) {
NepaliMonthNepali[NepaliMonthNepali["\u092C\u0948\u0936\u093E\u0916"] = 1] = "\u092C\u0948\u0936\u093E\u0916";
NepaliMonthNepali[NepaliMonthNepali["\u091C\u0947\u0937\u094D\u0920"] = 2] = "\u091C\u0947\u0937\u094D\u0920";
NepaliMonthNepali[NepaliMonthNepali["\u0906\u0937\u093E\u0922"] = 3] = "\u0906\u0937\u093E\u0922";
NepaliMonthNepali[NepaliMonthNepali["\u0936\u094D\u0930\u093E\u0935\u0923"] = 4] = "\u0936\u094D\u0930\u093E\u0935\u0923";
NepaliMonthNepali[NepaliMonthNepali["\u092D\u093E\u0926\u094D\u0930"] = 5] = "\u092D\u093E\u0926\u094D\u0930";
NepaliMonthNepali[NepaliMonthNepali["\u0906\u0936\u094D\u0935\u093F\u0928"] = 6] = "\u0906\u0936\u094D\u0935\u093F\u0928";
NepaliMonthNepali[NepaliMonthNepali["\u0915\u093E\u0930\u094D\u0924\u093F\u0915"] = 7] = "\u0915\u093E\u0930\u094D\u0924\u093F\u0915";
NepaliMonthNepali[NepaliMonthNepali["\u092E\u0902\u0938\u093F\u0930"] = 8] = "\u092E\u0902\u0938\u093F\u0930";
NepaliMonthNepali[NepaliMonthNepali["\u092A\u094C\u0937"] = 9] = "\u092A\u094C\u0937";
NepaliMonthNepali[NepaliMonthNepali["\u092E\u093E\u0918"] = 10] = "\u092E\u093E\u0918";
NepaliMonthNepali[NepaliMonthNepali["\u092B\u093E\u0932\u094D\u0917\u0941\u0928"] = 11] = "\u092B\u093E\u0932\u094D\u0917\u0941\u0928";
NepaliMonthNepali[NepaliMonthNepali["\u091A\u0948\u0924\u094D\u0930"] = 12] = "\u091A\u0948\u0924\u094D\u0930";
})(exports.NepaliMonthNepali || (exports.NepaliMonthNepali = {}));
/**
* Days of the week in Nepali language (English transliteration)
*/
exports.NepaliWeekDay = void 0;
(function (NepaliWeekDay) {
NepaliWeekDay[NepaliWeekDay["Aitabar"] = 1] = "Aitabar";
NepaliWeekDay[NepaliWeekDay["Sombar"] = 2] = "Sombar";
NepaliWeekDay[NepaliWeekDay["Mangalbar"] = 3] = "Mangalbar";
NepaliWeekDay[NepaliWeekDay["Budhabar"] = 4] = "Budhabar";
NepaliWeekDay[NepaliWeekDay["Bihibar"] = 5] = "Bihibar";
NepaliWeekDay[NepaliWeekDay["Sukrabar"] = 6] = "Sukrabar";
NepaliWeekDay[NepaliWeekDay["Sanibar"] = 7] = "Sanibar";
})(exports.NepaliWeekDay || (exports.NepaliWeekDay = {}));
/**
* Days of the week in Nepali language (Devanagari script)
*/
exports.NepaliWeekDayNepali = void 0;
(function (NepaliWeekDayNepali) {
NepaliWeekDayNepali[NepaliWeekDayNepali["\u0906\u0907\u0924\u092C\u093E\u0930"] = 1] = "\u0906\u0907\u0924\u092C\u093E\u0930";
NepaliWeekDayNepali[NepaliWeekDayNepali["\u0938\u094B\u092E\u092C\u093E\u0930"] = 2] = "\u0938\u094B\u092E\u092C\u093E\u0930";
NepaliWeekDayNepali[NepaliWeekDayNepali["\u092E\u0902\u0917\u0932\u092C\u093E\u0930"] = 3] = "\u092E\u0902\u0917\u0932\u092C\u093E\u0930";
NepaliWeekDayNepali[NepaliWeekDayNepali["\u092C\u0941\u0927\u092C\u093E\u0930"] = 4] = "\u092C\u0941\u0927\u092C\u093E\u0930";
NepaliWeekDayNepali[NepaliWeekDayNepali["\u092C\u093F\u0939\u093F\u092C\u093E\u0930"] = 5] = "\u092C\u093F\u0939\u093F\u092C\u093E\u0930";
NepaliWeekDayNepali[NepaliWeekDayNepali["\u0936\u0941\u0915\u094D\u0930\u092C\u093E\u0930"] = 6] = "\u0936\u0941\u0915\u094D\u0930\u092C\u093E\u0930";
NepaliWeekDayNepali[NepaliWeekDayNepali["\u0936\u0928\u093F\u092C\u093E\u0930"] = 7] = "\u0936\u0928\u093F\u092C\u093E\u0930";
})(exports.NepaliWeekDayNepali || (exports.NepaliWeekDayNepali = {}));
const MIN_BS_YEAR = 2000;
const MAX_BS_YEAR = 2099;
const MIN_AD_YEAR = 1943;
/**
* Reference dates for calendar conversion
* 2000-01-01 BS == 1943-04-14 AD
*/
const REF_BS = { year: 2000, month: 1, day: 1 };
const REF_AD = new Date(1943, 3, 14);
/**
* Bikram Sambat calendar data with days in each month for years 2000-2099
* Each year entry contains:
* 1. An array with days in each month [Baisakh, Jestha, ..., Chaitra]
* 2. Total days in the year
*/
const BS_CALENDAR_DATA = {
2000: [[30, 32, 31, 32, 31, 30, 30, 30, 29, 30, 29, 31], 365],
2001: [[31, 31, 32, 31, 31, 31, 30, 29, 30, 29, 30, 30], 365],
2002: [[31, 31, 32, 32, 31, 30, 30, 29, 30, 29, 30, 30], 365],
2003: [[31, 32, 31, 32, 31, 30, 30, 30, 29, 29, 30, 31], 366],
2004: [[30, 32, 31, 32, 31, 30, 30, 30, 29, 30, 29, 31], 365],
2005: [[31, 31, 32, 31, 31, 31, 30, 29, 30, 29, 30, 30], 365],
2006: [[31, 31, 32, 32, 31, 30, 30, 29, 30, 29, 30, 30], 365],
2007: [[31, 32, 31, 32, 31, 30, 30, 30, 29, 29, 30, 31], 366],
2008: [[31, 31, 31, 32, 31, 31, 29, 30, 30, 29, 29, 31], 365],
2009: [[31, 31, 32, 31, 31, 31, 30, 29, 30, 29, 30, 30], 365],
2010: [[31, 31, 32, 32, 31, 30, 30, 29, 30, 29, 30, 30], 365],
2011: [[31, 32, 31, 32, 31, 30, 30, 30, 29, 29, 30, 31], 366],
2012: [[31, 31, 31, 32, 31, 31, 29, 30, 30, 29, 30, 30], 365],
2013: [[31, 31, 32, 31, 31, 31, 30, 29, 30, 29, 30, 30], 365],
2014: [[31, 31, 32, 32, 31, 30, 30, 29, 30, 29, 30, 30], 365],
2015: [[31, 32, 31, 32, 31, 30, 30, 30, 29, 29, 30, 31], 366],
2016: [[31, 31, 31, 32, 31, 31, 29, 30, 30, 29, 30, 30], 365],
2017: [[31, 31, 32, 31, 31, 31, 30, 29, 30, 29, 30, 30], 365],
2018: [[31, 32, 31, 32, 31, 30, 30, 29, 30, 29, 30, 30], 365],
2019: [[31, 32, 31, 32, 31, 30, 30, 30, 29, 30, 29, 31], 366],
2020: [[31, 31, 31, 32, 31, 31, 30, 29, 30, 29, 30, 30], 365],
2021: [[31, 31, 32, 31, 31, 31, 30, 29, 30, 29, 30, 30], 365],
2022: [[31, 32, 31, 32, 31, 30, 30, 30, 29, 29, 30, 30], 365],
2023: [[31, 32, 31, 32, 31, 30, 30, 30, 29, 30, 29, 31], 366],
2024: [[31, 31, 31, 32, 31, 31, 30, 29, 30, 29, 30, 30], 365],
2025: [[31, 31, 32, 31, 31, 31, 30, 29, 30, 29, 30, 30], 365],
2026: [[31, 32, 31, 32, 31, 30, 30, 30, 29, 29, 30, 31], 366],
2027: [[30, 32, 31, 32, 31, 30, 30, 30, 29, 30, 29, 31], 365],
2028: [[31, 31, 32, 31, 31, 31, 30, 29, 30, 29, 30, 30], 365],
2029: [[31, 31, 32, 31, 32, 30, 30, 29, 30, 29, 30, 30], 365],
2030: [[31, 32, 31, 32, 31, 30, 30, 30, 29, 29, 30, 31], 366],
2031: [[30, 32, 31, 32, 31, 30, 30, 30, 29, 30, 29, 31], 365],
2032: [[31, 31, 32, 31, 31, 31, 30, 29, 30, 29, 30, 30], 365],
2033: [[31, 31, 32, 32, 31, 30, 30, 29, 30, 29, 30, 30], 365],
2034: [[31, 32, 31, 32, 31, 30, 30, 30, 29, 29, 30, 31], 366],
2035: [[30, 32, 31, 32, 31, 31, 29, 30, 30, 29, 29, 31], 365],
2036: [[31, 31, 32, 31, 31, 31, 30, 29, 30, 29, 30, 30], 365],
2037: [[31, 31, 32, 32, 31, 30, 30, 29, 30, 29, 30, 30], 365],
2038: [[31, 32, 31, 32, 31, 30, 30, 30, 29, 29, 30, 31], 366],
2039: [[31, 31, 31, 32, 31, 31, 29, 30, 30, 29, 30, 30], 365],
2040: [[31, 31, 32, 31, 31, 31, 30, 29, 30, 29, 30, 30], 365],
2041: [[31, 31, 32, 32, 31, 30, 30, 29, 30, 29, 30, 30], 365],
2042: [[31, 32, 31, 32, 31, 30, 30, 30, 29, 29, 30, 31], 366],
2043: [[31, 31, 31, 32, 31, 31, 29, 30, 30, 29, 30, 30], 365],
2044: [[31, 31, 32, 31, 31, 31, 30, 29, 30, 29, 30, 30], 365],
2045: [[31, 32, 31, 32, 31, 30, 30, 29, 30, 29, 30, 30], 365],
2046: [[31, 32, 31, 32, 31, 30, 30, 30, 29, 29, 30, 31], 366],
2047: [[31, 31, 31, 32, 31, 31, 30, 29, 30, 29, 30, 30], 365],
2048: [[31, 31, 32, 31, 31, 31, 30, 29, 30, 29, 30, 30], 365],
2049: [[31, 32, 31, 32, 31, 30, 30, 30, 29, 29, 30, 30], 365],
2050: [[31, 32, 31, 32, 31, 30, 30, 30, 29, 30, 29, 31], 366],
2051: [[31, 31, 31, 32, 31, 31, 30, 29, 30, 29, 30, 30], 365],
2052: [[31, 31, 32, 31, 31, 31, 30, 29, 30, 29, 30, 30], 365],
2053: [[31, 32, 31, 32, 31, 30, 30, 30, 29, 29, 30, 30], 365],
2054: [[31, 32, 31, 32, 31, 30, 30, 30, 29, 30, 29, 31], 366],
2055: [[31, 31, 32, 31, 31, 31, 30, 29, 30, 29, 30, 30], 365],
2056: [[31, 31, 32, 31, 32, 30, 30, 29, 30, 29, 30, 30], 365],
2057: [[31, 32, 31, 32, 31, 30, 30, 30, 29, 29, 30, 31], 366],
2058: [[30, 32, 31, 32, 31, 30, 30, 30, 29, 30, 29, 31], 365],
2059: [[31, 31, 32, 31, 31, 31, 30, 29, 30, 29, 30, 30], 365],
2060: [[31, 31, 32, 32, 31, 30, 30, 29, 30, 29, 30, 30], 365],
2061: [[31, 32, 31, 32, 31, 30, 30, 30, 29, 29, 30, 31], 366],
2062: [[30, 32, 31, 32, 31, 31, 29, 30, 29, 30, 29, 31], 365],
2063: [[31, 31, 32, 31, 31, 31, 30, 29, 30, 29, 30, 30], 365],
2064: [[31, 31, 32, 32, 31, 30, 30, 29, 30, 29, 30, 30], 365],
2065: [[31, 32, 31, 32, 31, 30, 30, 30, 29, 29, 30, 31], 366],
2066: [[31, 31, 31, 32, 31, 31, 29, 30, 30, 29, 29, 31], 365],
2067: [[31, 31, 32, 31, 31, 31, 30, 29, 30, 29, 30, 30], 365],
2068: [[31, 31, 32, 32, 31, 30, 30, 29, 30, 29, 30, 30], 365],
2069: [[31, 32, 31, 32, 31, 30, 30, 30, 29, 29, 30, 31], 366],
2070: [[31, 31, 31, 32, 31, 31, 29, 30, 30, 29, 30, 30], 365],
2071: [[31, 31, 32, 31, 31, 31, 30, 29, 30, 29, 30, 30], 365],
2072: [[31, 32, 31, 32, 31, 30, 30, 29, 30, 29, 30, 30], 365],
2073: [[31, 32, 31, 32, 31, 30, 30, 30, 29, 29, 30, 31], 366],
2074: [[31, 31, 31, 32, 31, 31, 30, 29, 30, 29, 30, 30], 365],
2075: [[31, 31, 32, 31, 31, 31, 30, 29, 30, 29, 30, 30], 365],
2076: [[31, 32, 31, 32, 31, 30, 30, 30, 29, 29, 30, 30], 365],
2077: [[31, 32, 31, 32, 31, 30, 30, 30, 29, 30, 29, 31], 366],
2078: [[31, 31, 31, 32, 31, 31, 30, 29, 30, 29, 30, 30], 365],
2079: [[31, 31, 32, 31, 31, 31, 30, 29, 30, 29, 30, 30], 365],
2080: [[31, 32, 31, 32, 31, 30, 30, 30, 29, 29, 30, 30], 365],
2081: [[31, 32, 31, 32, 31, 30, 30, 30, 29, 30, 29, 31], 366],
2082: [[31, 31, 32, 31, 31, 31, 30, 29, 30, 29, 30, 30], 365],
2083: [[31, 31, 32, 31, 31, 31, 30, 29, 30, 29, 30, 30], 365],
2084: [[31, 32, 31, 32, 31, 30, 30, 30, 29, 29, 30, 31], 366],
2085: [[30, 32, 31, 32, 31, 30, 30, 30, 29, 30, 29, 31], 365],
2086: [[31, 31, 32, 31, 31, 31, 30, 29, 30, 29, 30, 30], 365],
2087: [[31, 31, 32, 32, 31, 30, 30, 29, 30, 29, 30, 30], 365],
2088: [[31, 32, 31, 32, 31, 30, 30, 30, 29, 29, 30, 31], 366],
2089: [[30, 32, 31, 32, 31, 30, 30, 30, 29, 30, 29, 31], 365],
2090: [[31, 31, 32, 31, 31, 31, 30, 29, 30, 29, 30, 30], 365],
2091: [[31, 31, 32, 32, 31, 30, 30, 29, 30, 29, 30, 30], 365],
2092: [[31, 32, 31, 32, 31, 30, 30, 30, 29, 29, 30, 31], 366],
2093: [[31, 31, 31, 32, 31, 31, 29, 30, 30, 29, 29, 31], 365],
2094: [[31, 31, 32, 31, 31, 31, 30, 29, 30, 29, 30, 30], 365],
2095: [[31, 31, 32, 32, 31, 30, 30, 29, 30, 29, 30, 30], 365],
2096: [[31, 32, 31, 32, 31, 30, 30, 30, 29, 29, 30, 31], 366],
2097: [[31, 31, 31, 32, 31, 31, 29, 30, 30, 29, 30, 30], 365],
2098: [[31, 31, 32, 31, 31, 31, 30, 29, 30, 29, 30, 30], 365],
2099: [[31, 31, 32, 32, 31, 30, 30, 29, 30, 29, 30, 30], 365],
};
const NEPALI_DIGITS = ['०', '१', '२', '३', '४', '५', '६', '७', '८', '९'];
/**
* Validates a Bikram Sambat (BS) date
* @param year The BS year to validate
* @param month The month to validate (1-12)
* @param day Optional day to validate
* @throws Error if the date is invalid or out of supported range
*/
function validateBsDate(year, month, day) {
if (year < MIN_BS_YEAR || year > MAX_BS_YEAR) {
throw new Error(`Year out of range. Supported BS years: ${MIN_BS_YEAR} to ${MAX_BS_YEAR}`);
}
if (month < 1 || month > 12) {
throw new Error('Month must be between 1 and 12');
}
if (day !== undefined) {
if (!BS_CALENDAR_DATA[year]) {
throw new Error(`Data not available for year ${year}`);
}
const daysInMonth = BS_CALENDAR_DATA[year][month - 1];
if (day < 1 || day > daysInMonth) {
throw new Error(`Day must be between 1 and ${daysInMonth} for month ${month} in year ${year}`);
}
}
}
/**
* Validates a Gregorian (AD) date
* @param year The AD year to validate
* @param month The month to validate (1-12)
* @param day Optional day to validate
* @throws Error if the date is invalid or out of supported range
*/
function validateAdDate(year, month, day) {
if (year < MIN_AD_YEAR) {
throw new Error(`Date out of range. Minimum supported AD year is ${MIN_AD_YEAR}`);
}
if (month < 1 || month > 12) {
throw new Error('Month must be between 1 and 12');
}
if (day !== undefined) {
const daysInMonth = new Date(year, month, 0).getDate();
if (day < 1 || day > daysInMonth) {
throw new Error(`Day must be between 1 and ${daysInMonth} for month ${month} in year ${year}`);
}
}
}
/**
* Gets the number of days in a Nepali month
* @param year The Nepali year in BS calendar
* @param month The month (1-12)
* @returns Number of days in the specified month
* @throws Error if data is not available for the given year
*/
function getDaysInMonth(year, month) {
if (month < 1 || month > 12) {
throw new Error('Month must be between 1 and 12');
}
if (!BS_CALENDAR_DATA[year]) {
throw new Error(`Year out of range. Supported BS years: ${MIN_BS_YEAR} to ${MAX_BS_YEAR}`);
}
return BS_CALENDAR_DATA[year][0][month - 1];
}
/**
* Parses different date input formats into a standardized object
* @param date Date input, either as a NepaliDate object or [year, month, day] array
* @returns Standardized date object with year, month, and day properties
* @throws Error if the date format is invalid
*/
function parseDateInput(date) {
if (typeof date === 'string') {
throw new Error('Invalid date format. Provide a NepaliDate object or [year, month, day] array.');
}
if ('year' in date && typeof date.year === 'number') {
return {
year: date.year,
month: date.month,
day: date.day
};
}
else if (Array.isArray(date) && date.length >= 3) {
return {
year: date[0],
month: date[1],
day: date[2]
};
}
else {
throw new Error('Invalid date format. Provide a NepaliDate object or [year, month, day] array.');
}
}
/**
* Localize number to Nepali digits based on language
* @param num The number to localize
* @param language The language code ('en' or 'np')
* @returns Localized string with Nepali digits if language is 'np', otherwise returns the original number as a string
*/
function localizeDigits(num, language) {
switch (language) {
case "np":
return String(num)
.split("")
.map((digit) => /\d/.test(digit) ? NEPALI_DIGITS[parseInt(digit, 10)] : digit)
.join("");
default:
return String(num);
}
}
/**
* Localizes the month name based on the language
* @param month The month number (1-12)
* @param language The language code ('en' or 'np')
* @returns Localized month name in either English or Nepali
*/
function localizeMonth(month, language) {
if (language === 'np') {
return exports.NepaliMonthNepali[month];
}
else {
return exports.NepaliMonth[month];
}
}
/**
* Converts Nepali digits to English digits
* @param text The text containing Nepali digits to convert
* @returns String with English digits
*/
function nepaliDigitsToEnglish(text) {
const nepaliToEnglish = {
'०': '0', '१': '1', '२': '2', '३': '3', '४': '4',
'५': '5', '६': '6', '७': '7', '८': '8', '९': '9'
};
return text.replace(/[०-९]/g, (match) => nepaliToEnglish[match] || match);
}
/**
* Gets common date format patterns for parsing various date formats
* @returns Array of date format patterns with regex and index mappings
*/
function getDateFormatPatterns() {
return [
// YYYY-MM-DD, YYYY/MM/DD, YYYY.MM.DD
{
regex: /(\d{4})[-/.]\s*(\d{1,2})[-/.]\s*(\d{1,2})/,
yearIndex: 1,
monthIndex: 2,
dayIndex: 3,
description: 'YYYY-MM-DD format'
},
// DD-MM-YYYY, DD/MM/YYYY, DD.MM.YYYY
{
regex: /(\d{1,2})[-/.]\s*(\d{1,2})[-/.]\s*(\d{4})/,
yearIndex: 3,
monthIndex: 2,
dayIndex: 1,
description: 'DD-MM-YYYY format'
},
// MM-DD-YYYY, MM/DD/YYYY, MM.DD.YYYY (less common but possible)
{
regex: /(\d{1,2})[-/.]\s*(\d{1,2})[-/.]\s*(\d{4})/,
yearIndex: 3,
monthIndex: 1,
dayIndex: 2,
description: 'MM-DD-YYYY format'
},
// YYYYMMDD
{
regex: /(\d{4})(\d{2})(\d{2})/,
yearIndex: 1,
monthIndex: 2,
dayIndex: 3,
description: 'YYYYMMDD format'
},
// DDMMYYYY
{
regex: /(\d{2})(\d{2})(\d{4})/,
yearIndex: 3,
monthIndex: 2,
dayIndex: 1,
description: 'DDMMYYYY format'
}
];
}
/**
* Calculates the number of days between two AD dates
* @param date1 The first date
* @param date2 The second date
* @returns Number of days between the two dates
*/
function daysBetweenAD(date1, date2) {
const msPerDay = 24 * 60 * 60 * 1000;
return Math.round((date2.getTime() - date1.getTime()) / msPerDay);
}
/**
* Converts a Gregorian (AD) date to Bikram Sambat (BS) date
* @param date Date to convert, can be a Date object or [year, month, day] array
* @returns NepaliDate object representing the equivalent BS date
* @throws Error if date is in invalid format or out of supported range
*/
function convertADToBS(date) {
let adYear;
let adMonth;
let adDay;
if (date instanceof Date) {
adYear = date.getFullYear();
adMonth = date.getMonth() + 1;
adDay = date.getDate();
}
else if (Array.isArray(date) && date.length >= 3) {
adYear = date[0];
adMonth = date[1];
adDay = date[2];
}
else {
throw new Error('Invalid date format. Provide a Date object or [year, month, day] array.');
}
validateAdDate(adYear, adMonth, adDay);
const inputAD = new Date(adYear, adMonth - 1, adDay);
const daysDiff = daysBetweenAD(REF_AD, inputAD);
let bsYear = REF_BS.year;
let bsMonth = REF_BS.month;
let bsDay = REF_BS.day;
let daysLeft = daysDiff;
while (daysLeft > 0) {
const daysInMonth = getDaysInMonth(bsYear, bsMonth);
if (bsDay < daysInMonth) {
bsDay++;
}
else {
bsDay = 1;
if (bsMonth < 12) {
bsMonth++;
}
else {
bsMonth = 1;
bsYear++;
if (!BS_CALENDAR_DATA[bsYear]) {
throw new Error(`Year out of range. Supported BS years: ${MIN_BS_YEAR} to ${MAX_BS_YEAR}`);
}
}
}
daysLeft--;
}
while (daysLeft < 0) {
if (bsDay > 1) {
bsDay--;
}
else {
if (bsMonth > 1) {
bsMonth--;
}
else {
bsYear--;
if (!BS_CALENDAR_DATA[bsYear]) {
throw new Error(`Year out of range. Supported BS years: ${MIN_BS_YEAR} to ${MAX_BS_YEAR}`);
}
bsMonth = 12;
}
bsDay = getDaysInMonth(bsYear, bsMonth);
}
daysLeft++;
}
return {
year: bsYear,
month: bsMonth,
day: bsDay
};
}
/**
* Gets the current date in Bikram Sambat calendar
* @returns Current date as NepaliDate object
*/
function getCurrentBSDate() {
return convertADToBS(new Date());
}
function daysBetweenBS(date1, date2) {
let days = 0;
if (date1.year > date2.year ||
(date1.year === date2.year && date1.month > date2.month) ||
(date1.year === date2.year && date1.month === date2.month && date1.day > date2.day)) {
return -daysBetweenBS(date2, date1);
}
if (date1.year === date2.year && date1.month === date2.month && date1.day === date2.day) {
return 0;
}
days += getDaysInMonth(date1.year, date1.month) - date1.day;
let currentYear = date1.year;
let currentMonth = date1.month + 1;
while (currentYear < date2.year || (currentYear === date2.year && currentMonth < date2.month)) {
if (currentMonth > 12) {
currentYear++;
currentMonth = 1;
}
days += getDaysInMonth(currentYear, currentMonth);
currentMonth++;
}
days += date2.day;
return days;
}
/**
* Converts a Bikram Sambat (BS) date to Gregorian (AD) date
* @param bsDate BS date to convert, can be a NepaliDate object or [year, month, day] array
* @returns JavaScript Date object representing the equivalent AD date
* @throws Error if date is invalid or out of supported range
*/
function convertBSToAD(bsDate) {
const { year, month, day } = parseDateInput(bsDate);
validateBsDate(year, month, day);
const daysDiff = daysBetweenBS(REF_BS, { year, month, day });
const adDate = new Date(REF_AD);
adDate.setDate(adDate.getDate() + daysDiff);
return adDate;
}
/**
* Converts English digits to Nepali digits
* @param num The number to convert
* @returns String with Nepali digits
*/
function toNepaliDigits(num) {
return num.toString().replace(/[0-9]/g, (match) => NEPALI_DIGITS[parseInt(match)]);
}
/**
* Formats a Nepali date according to the specified format string
* @param date The Nepali date to format
* @param format The format string
* @param options Formatting options
* @returns Formatted date string
*/
function formatBS(date, format = 'YYYY-MM-DD', options = {}) {
const { language = 'en' } = options;
const useNepaliDigits = language === 'np';
const adDate = convertBSToAD(date);
const weekDay = adDate.getDay() + 1;
let result = format;
// Year formats
result = result.replace(/YYYY/g, useNepaliDigits
? toNepaliDigits(date.year)
: date.year.toString());
result = result.replace(/YY/g, useNepaliDigits
? toNepaliDigits((date.year % 100).toString().padStart(2, '0'))
: (date.year % 100).toString().padStart(2, '0'));
// Month formats
// Full month name
if (language === 'en') {
result = result.replace(/MMMM/g, exports.NepaliMonth[date.month]);
result = result.replace(/MMM/g, exports.NepaliMonth[date.month].substring(0, 3));
}
else {
result = result.replace(/MMMM/g, exports.NepaliMonthNepali[date.month]);
result = result.replace(/MMM/g, exports.NepaliMonthNepali[date.month]);
}
// Two-digit month
result = result.replace(/MM/g, useNepaliDigits
? toNepaliDigits(date.month.toString().padStart(2, '0'))
: date.month.toString().padStart(2, '0'));
// Single-digit month
result = result.replace(/M/g, useNepaliDigits
? toNepaliDigits(date.month)
: date.month.toString());
// Day formats
// Two-digit day
result = result.replace(/DD/g, useNepaliDigits
? toNepaliDigits(date.day.toString().padStart(2, '0'))
: date.day.toString().padStart(2, '0'));
// Single-digit day
result = result.replace(/D/g, useNepaliDigits
? toNepaliDigits(date.day)
: date.day.toString());
// Weekday formats
// Full weekday name
if (language === 'en') {
result = result.replace(/dddd/g, exports.NepaliWeekDay[weekDay]);
result = result.replace(/ddd/g, exports.NepaliWeekDay[weekDay].substring(0, 3));
}
else {
result = result.replace(/dddd/g, exports.NepaliWeekDayNepali[weekDay]);
result = result.replace(/ddd/g, exports.NepaliWeekDayNepali[weekDay]);
}
// Numeric day of week (1-7)
result = result.replace(/d/g, useNepaliDigits
? toNepaliDigits(weekDay)
: weekDay.toString());
return result;
}
class NepaliDatePicker {
constructor(inputElement, options = {}) {
this.calendarVisible = false;
this.currentBSYear = getCurrentBSDate().year;
this.currentBSMonth = getCurrentBSDate().month - 1;
this.currentBSDay = getCurrentBSDate().day;
this.floatingCalendar = null;
this.handleClickOutside = (event) => {
const target = event.target;
if (this.floatingCalendar &&
target &&
!this.floatingCalendar.contains(target) &&
!this.inputElement.contains(target)) {
this.closeCalendar();
}
};
this.inputElement = inputElement;
this.format = options.format || "YYYY-MM-DD";
this.theme = options.theme || "light";
this.language = options.language || "en";
NepaliDatePicker.instances.add(this);
this.init();
}
init() {
this.inputElement.addEventListener("click", (e) => {
e.stopPropagation();
this.toggleCalendar();
});
document.addEventListener("click", this.handleClickOutside);
}
toggleCalendar() {
if (this.calendarVisible) {
this.closeCalendar();
}
else {
this.openCalendar();
}
}
parseAndSetDateFromInput() {
const inputValue = this.inputElement.value.trim();
if (!inputValue) {
// No value in input, keep current date
return;
}
try {
const parsedDate = this.parseDateString(inputValue);
if (parsedDate) {
this.currentBSYear = parsedDate.year;
this.currentBSMonth = parsedDate.month - 1; // Convert to 0-based index
this.currentBSDay = parsedDate.day;
}
}
catch (error) {
// If parsing fails, keep current date
console.warn("Unable to parse date from input:", inputValue, error);
}
}
parseDateString(dateString) {
if (!dateString)
return null;
const normalizedString = nepaliDigitsToEnglish(dateString);
const monthNameDate = this.parseWithMonthNames(normalizedString);
if (monthNameDate) {
return monthNameDate;
}
const formatPatterns = getDateFormatPatterns();
for (const pattern of formatPatterns) {
const match = normalizedString.match(pattern.regex);
if (match) {
try {
const year = parseInt(match[pattern.yearIndex], 10);
const month = parseInt(match[pattern.monthIndex], 10);
const day = parseInt(match[pattern.dayIndex], 10);
// Validate the parsed date
if (this.isValidBSDate(year, month, day)) {
return { year, month, day };
}
}
catch (e) {
continue;
}
}
}
return null;
}
parseWithMonthNames(dateString) {
const englishMonths = [];
const nepaliMonths = [];
for (let i = 1; i <= 12; i++) {
englishMonths.push(localizeMonth(i, "en"));
nepaliMonths.push(localizeMonth(i, "np"));
}
// Try English month names first
for (let i = 0; i < englishMonths.length; i++) {
const monthName = englishMonths[i];
// Escape special regex characters and create flexible patterns
const escapedMonth = monthName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const monthRegex = new RegExp(`${escapedMonth}\\s+(\\d{1,2}),?\\s+(\\d{4})|${escapedMonth}\\s+(\\d{1,2})\\s+(\\d{4})|(\\d{1,2})\\s+${escapedMonth}\\s+(\\d{4})`, 'i');
const match = dateString.match(monthRegex);
if (match) {
const day = parseInt(match[1] || match[3] || match[5], 10);
const year = parseInt(match[2] || match[4] || match[6], 10);
const month = i + 1;
if (this.isValidBSDate(year, month, day)) {
return { year, month, day };
}
}
}
// Try Nepali month names
for (let i = 0; i < nepaliMonths.length; i++) {
const monthName = nepaliMonths[i];
// Escape special regex characters for Nepali month names
const escapedMonth = monthName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const monthRegex = new RegExp(`${escapedMonth}\\s+(\\d{1,2}),?\\s+(\\d{4})|${escapedMonth}\\s+(\\d{1,2})\\s+(\\d{4})|(\\d{1,2})\\s+${escapedMonth}\\s+(\\d{4})`);
const match = dateString.match(monthRegex);
if (match) {
const day = parseInt(match[1] || match[3] || match[5], 10);
const year = parseInt(match[2] || match[4] || match[6], 10);
const month = i + 1;
if (this.isValidBSDate(year, month, day)) {
return { year, month, day };
}
}
}
return null;
}
isValidBSDate(year, month, day) {
if (!BS_CALENDAR_DATA[year]) {
return false;
}
if (month < 1 || month > 12) {
return false;
}
// Check if day is valid for the given month and year
const monthData = BS_CALENDAR_DATA[year][0][month - 1];
const daysInMonth = Array.isArray(monthData) ? monthData[0] : monthData;
return day >= 1 && day <= daysInMonth;
}
openCalendar() {
// Close all other calendars before opening this one
this.closeAllOtherCalendars();
// Parse existing value from input field if present
this.parseAndSetDateFromInput();
this.calendarVisible = true;
this.renderCalendar(this.currentBSYear, this.currentBSMonth);
}
renderCalendar(bsYear, bsMonth) {
if (this.floatingCalendar && this.floatingCalendar.parentNode) {
this.floatingCalendar.parentNode.removeChild(this.floatingCalendar);
}
const calendarDiv = document.createElement("div");
calendarDiv.className = `nepali-datepicker nepali-datepicker-${this.theme}`;
calendarDiv.innerHTML = this.generateCalendarHtml(bsYear, bsMonth);
this.setupCalendarEvents(calendarDiv);
document.body.appendChild(calendarDiv);
this.floatingCalendar = calendarDiv;
this.positionCalendarPopup();
}
generateCalendarHtml(bsYear, bsMonth) {
var _a, _b;
// Get available years from BS_CALENDAR_DATA
const bsYears = Object.keys(BS_CALENDAR_DATA)
.map(Number)
.sort((a, b) => a - b);
let yearOptions = "";
for (const y of bsYears) {
yearOptions += `<option value="${y}"${y === bsYear ? " selected" : ""}>${localizeDigits(y, this.language)}</option>`;
}
let monthOptions = "";
for (let i = 0; i < 12; i++) {
monthOptions += `<option value="${i}"${i === bsMonth ? " selected" : ""}>${localizeMonth(i + 1, this.language)}</option>`;
}
// Get days in this BS month
const rawDaysInMonth = (_a = BS_CALENDAR_DATA[bsYear]) === null || _a === void 0 ? void 0 : _a[0][bsMonth];
const daysInMonth = Array.isArray(rawDaysInMonth)
? rawDaysInMonth[0]
: typeof rawDaysInMonth === "number"
? rawDaysInMonth
: 30;
// Calculate the weekday of the 1st day of this BS month
const firstDayOfWeek = this.getBSFirstDayOfWeek(bsYear, bsMonth);
// Get days in previous month
let prevMonth = bsMonth - 1;
let prevYear = bsYear;
if (prevMonth < 0) {
prevMonth = 11;
prevYear--;
}
const rawLastDayOfPrevMonth = (_b = BS_CALENDAR_DATA[prevYear]) === null || _b === void 0 ? void 0 : _b[0][prevMonth];
const lastDayOfPrevMonth = Array.isArray(rawLastDayOfPrevMonth)
? rawLastDayOfPrevMonth[0]
: typeof rawLastDayOfPrevMonth === "number"
? rawLastDayOfPrevMonth
: 30;
const weekdays = this.language === "np"
? ["आ", "सो", "मं", "बु", "बि", "शु", "श"]
: ["S", "M", "T", "W", "T", "F", "S"];
return `
<div class="nepali-datepicker-header">
<button type="button" class="nepali-nav-btn nepali-prev-btn">◀</button>
<div class="nepali-header-content">
<select class="nepali-month-select">${monthOptions}</select>
<select class="nepali-year-select">${yearOptions}</select>
</div>
<button type="button" class="nepali-nav-btn nepali-next-btn">▶</button>
</div>
<div class="nepali-weekdays">
${weekdays.map(day => `<div class="nepali-weekday">${day}</div>`).join('')}
</div>
<div class="nepali-calendar-body">
${this.generateBSDaysFullGrid(bsYear, bsMonth, daysInMonth, firstDayOfWeek, lastDayOfPrevMonth)}
</div>
`;
}
getBSFirstDayOfWeek(bsYear, bsMonth) {
try {
const adDate = convertBSToAD({
year: bsYear,
month: bsMonth + 1,
day: 1,
});
if (adDate instanceof Date && !isNaN(adDate.getTime())) {
return adDate.getDay();
}
}
catch (e) {
console.log("Error converting BS to AD:", e);
}
return 0;
}
// Render the days grid for BS calendar
generateBSDaysFullGrid(bsYear, bsMonth, daysInMonth, firstDayOfWeek, lastDayOfPrevMonth) {
let daysHTML = "";
const totalCells = 42;
// Previous month days
for (let i = 0; i < firstDayOfWeek; i++) {
const prevDay = lastDayOfPrevMonth - firstDayOfWeek + i + 1;
daysHTML += `<div class="nepali-day nepali-other-month" data-day="${prevDay}" data-other="prev">${localizeDigits(prevDay, this.language)}</div>`;
}
// Current month days
for (let day = 1; day <= daysInMonth; day++) {
let classes = "nepali-day";
if (this.isBSSelectedDate(bsYear, bsMonth, day))
classes += " nepali-selected";
daysHTML += `<div class="${classes}" data-day="${day}">${localizeDigits(day, this.language)}</div>`;
}
// Next month days
const daysFilled = firstDayOfWeek + daysInMonth;
for (let i = 1; i <= totalCells - daysFilled; i++) {
daysHTML += `<div class="nepali-day nepali-other-month" data-day="${i}" data-other="next">${localizeDigits(i, this.language)}</div>`;
}
return daysHTML;
}
isBSSelectedDate(bsYear, bsMonth, bsDay) {
return (bsYear === this.currentBSYear &&
bsMonth === this.currentBSMonth &&
bsDay === this.currentBSDay);
}
setupCalendarEvents(calendarDiv) {
const prevButton = calendarDiv.querySelector(".nepali-prev-btn");
const nextButton = calendarDiv.querySelector(".nepali-next-btn");
const monthSelect = calendarDiv.querySelector(".nepali-month-select");
const yearSelect = calendarDiv.querySelector(".nepali-year-select");
prevButton === null || prevButton === void 0 ? void 0 : prevButton.addEventListener("click", (e) => {
e.stopPropagation();
this.previousBSMonth();
});
nextButton === null || nextButton === void 0 ? void 0 : nextButton.addEventListener("click", (e) => {
e.stopPropagation();
this.nextBSMonth();
});
monthSelect === null || monthSelect === void 0 ? void 0 : monthSelect.addEventListener("change", (e) => {
e.stopPropagation();
this.changeBSMonth(e);
});
yearSelect === null || yearSelect === void 0 ? void 0 : yearSelect.addEventListener("change", (e) => {
e.stopPropagation();
this.changeBSYear(e);
});
calendarDiv.addEventListener("click", (e) => this.handleBSDateClick(e));
}
handleBSDateClick(event) {
const target = event.target;
const dayEl = target.closest(".nepali-day");
if (!dayEl)
return;
event.stopPropagation();
this.selectBSDate(dayEl);
}
selectBSDate(dayElement) {
var _a;
let day = parseInt((_a = dayElement.dataset.day) !== null && _a !== void 0 ? _a : "0", 10);
let month = this.currentBSMonth;
let year = this.currentBSYear;
if (this.floatingCalendar) {
const monthSelect = this.floatingCalendar.querySelector(".nepali-month-select");
const yearSelect = this.floatingCalendar.querySelector(".nepali-year-select");
if (monthSelect && yearSelect) {
month = parseInt(monthSelect.value, 10);
year = parseInt(yearSelect.value, 10);
}
}
if (dayElement.dataset.other === "prev") {
month -= 1;
if (month < 0) {
month = 11;
year -= 1;
}
}
else if (dayElement.dataset.other === "next") {
month += 1;
if (month > 11) {
month = 0;
year += 1;
}
}
this.currentBSYear = year;
this.currentBSMonth = month;
this.currentBSDay = day;
const date = {
year: this.currentBSYear,
month: this.currentBSMonth + 1,
day: this.currentBSDay,
};
const formatted = formatBS(date, this.format, {
language: this.language,
});
this.inputElement.value = formatted;
this.inputElement.dispatchEvent(new Event("change", { bubbles: true }));
this.closeCalendar();
}
previousBSMonth() {
let month = this.currentBSMonth - 1;
let year = this.currentBSYear;
if (month < 0) {
month = 11;
year--;
}
this.currentBSMonth = month;
this.currentBSYear = year;
this.renderCalendar(year, month);
}
nextBSMonth() {
let month = this.currentBSMonth + 1;
let year = this.currentBSYear;
if (month > 11) {
month = 0;
year++;
}
this.currentBSMonth = month;
this.currentBSYear = year;
this.renderCalendar(year, month);
}
changeBSMonth(event) {
const target = event.target;
const newMonth = parseInt(target.value, 10);
this.currentBSMonth = newMonth;
this.renderCalendar(this.currentBSYear, newMonth);
}
changeBSYear(event) {
const target = event.target;
const newYear = parseInt(target.value, 10);
this.currentBSYear = newYear;
this.renderCalendar(newYear, this.currentBSMonth);
}
positionCalendarPopup() {
if (!this.floatingCalendar)
return;
this.floatingCalendar.style.position = "absolute";
this.floatingCalendar.style.zIndex = "9999";
const inputRect = this.inputElement.getBoundingClientRect();
const calendarRect = this.floatingCalendar.getBoundingClientRect();
let top = inputRect.bottom + window.scrollY + 4;
let left = inputRect.left + window.scrollX;
if (window.innerHeight - inputRect.bottom < calendarRect.height &&
inputRect.top > calendarRect.height) {
top = inputRect.top + window.scrollY - calendarRect.height - 4;
}
if (window.innerWidth - inputRect.left < calendarRect.width) {
left = inputRect.right + window.scrollX - calendarRect.width;
}
this.floatingCalendar.style.top = `${top}px`;
this.floatingCalendar.style.left = `${left}px`;
}
closeCalendar() {
this.calendarVisible = false;
if (this.floatingCalendar && this.floatingCalendar.parentNode) {
this.floatingCalendar.parentNode.removeChild(this.floatingCalendar);
this.floatingCalendar = null;
}
}
closeAllOtherCalendars() {
NepaliDatePicker.instances.forEach(instance => {
if (instance !== this && instance.calendarVisible) {
instance.closeCalendar();
}
});
}
destroy() {
this.closeCalendar();
document.removeEventListener("click", this.handleClickOutside);
NepaliDatePicker.instances.delete(this);
}
}
NepaliDatePicker.instances = new Set();
class NepaliPatro {
/**
* Converts a Gregorian (AD) date to Bikram Sambat (BS)
* @param date AD date to convert, can be a Date object or [year, month, day] array
* @returns NepaliDate object representing the equivalent BS date
*/
static convertADToBS(date) {
return convertADToBS(date);
}
/**
* Converts a Bikram Sambat (BS) date to Gregorian (AD)
* @param bsDate BS date to convert, can be a NepaliDate object or [year, month, day] array
* @returns JavaScript Date object representing the equivalent AD date
*/
static convertBSToAD(bsDate) {
return convertBSToAD(bsDate);
}
/**
* Gets the current date in Bikram Sambat calendar
* @returns Current date as NepaliDate object
*/
static getCurrentBSDate() {
return getCurrentBSDate();
}
/**
* Formats a Nepali date according to the specified format string
* @param date The Nepali date to format
* @param format The format string
* @param options : {} Formatting options
* @returns Formatted date string
*/
static formatBS(date, format = "YYYY-MM-DD", options = {}) {
return formatBS(date, format, options);
}
/**
* Gets the number of days in a Nepali month
* @param year The Nepali year in BS calendar
* @param month The month (1-12)
* @returns Number of days in the specified month
*/
static getDaysInMonth(year, month) {
return getDaysInMonth(year, month);
}
/**
* Converts English digits to Nepali digits
* @param num The number to convert
* @returns String with Nepali digits
*/
static toNepaliDigits(num) {
return toNepaliDigits(num);
}
/**
* Converts Nepali digits to English digits
* @param text The text containing Nepali digits to convert
* @returns String with English digits
*/
static nepaliDigitsToEnglish(text) {
return nepaliDigitsToEnglish(text);
}
/**
* Gets common date format patterns for parsing various date formats
* @returns Array of date format patterns with regex and index mappings
*/
static getDateFormatPatterns() {
return getDateFormatPatterns();
}
/**
* Gets the current date by using .now() method
* @returns Current date as NepaliDate object
*/
static now() {
return this.getCurrentBSDate();
}
}
NepaliPatro.MIN_YEAR = MIN_BS_YEAR;
NepaliPatro.MAX_YEAR = MAX_BS_YEAR;
NepaliPatro.MIN_AD_YEAR = MIN_AD_YEAR;
if (typeof window !== "undefined") {
window.NepaliDatePicker = NepaliDatePicker;
}
exports.NepaliDatePicker = NepaliDatePicker;
exports.NepaliPatro = NepaliPatro;
exports.convertADToBS = convertADToBS;
exports.convertBSToAD = convertBSToAD;
exports.default = NepaliPatro;
exports.formatBSDate = formatBS;
exports.getCurrentBSDate = getCurrentBSDate;
exports.getDateFormatPatterns = getDateFormatPatterns;
exports.getDaysInMonth = getDaysInMonth;
exports.nepaliDigitsToEnglish = nepaliDigitsToEnglish;
exports.toNepaliDigits = toNepaliDigits;
Object.defineProperty(exports, '__esModule', { value: true });
}));
//# sourceMappingURL=nepali-patro-js.umd.js.map