UNPKG

nepali-date-converter-np

Version:

A lightweight TypeScript/JavaScript package for converting dates between Gregorian (AD) and Bikram Sambat (BS) calendar systems with Nepali and English formatting support.

242 lines (218 loc) 7.94 kB
// Nepali Date Converter - Core API // Provides conversion between Gregorian (AD) and Bikram Sambat (BS) calendars // and date formatting utilities. import { bsMonthData, bsEpoch } from './bs-data'; export type DateFormat = 'nepali-full' | 'nepali-short' | 'english-full' | 'english-short'; export interface BSDate { year: number; month: number; day: number; } export interface ADDate { year: number; month: number; day: number; } function daysBetweenAD(ad1: ADDate, ad2: ADDate): number { // Returns the number of days from ad1 to ad2 (ad2 - ad1) const d1 = new Date(ad1.year, ad1.month - 1, ad1.day); const d2 = new Date(ad2.year, ad2.month - 1, ad2.day); return Math.round((d2.getTime() - d1.getTime()) / (1000 * 60 * 60 * 24)); } function addDaysToBS(bs: BSDate, days: number): BSDate { let { year, month, day } = bs; day += days; while (true) { const daysInMonth = bsMonthData[year]?.[month - 1]; if (!daysInMonth) break; if (day > daysInMonth) { day -= daysInMonth; month++; if (month > 12) { month = 1; year++; } } else { break; } } return { year, month, day }; } function subtractDaysFromBS(bs: BSDate, days: number): BSDate { let { year, month, day } = bs; day -= days; while (day < 1) { month--; if (month < 1) { year--; month = 12; } const daysInMonth = bsMonthData[year]?.[month - 1]; if (!daysInMonth) break; day += daysInMonth; } return { year, month, day }; } function daysBetweenBS(bs1: BSDate, bs2: BSDate): number { // Returns the number of days from bs1 to bs2 (bs2 - bs1) let days = 0; let { year, month, day } = bs1; while (year < bs2.year || (year === bs2.year && month < bs2.month) || (year === bs2.year && month === bs2.month && day < bs2.day)) { day++; days++; const daysInMonth = bsMonthData[year]?.[month - 1]; if (!daysInMonth) { throw new Error(`Unsupported BS year: ${year}`); } if (day > daysInMonth) { day = 1; month++; if (month > 12) { month = 1; year++; } } } return days; } /** * Converts a Gregorian (AD) date to Bikram Sambat (BS). * @param adDate - The AD date to convert. * @returns The corresponding BS date. */ export function convertADToBS(adDate: ADDate): BSDate { // Validate input if (adDate.month < 1 || adDate.month > 12 || adDate.day < 1 || adDate.day > 31) { throw new Error('Invalid AD date'); } // Only supports AD dates between 1943-04-14 and 1945-04-13 (BS 2000-2002) const days = daysBetweenAD(bsEpoch.ad, adDate); if (days < 0 || days > 1095) { // ~3 years throw new Error('AD date outside supported range (1943-1945)'); } return addDaysToBS(bsEpoch.bs, days); } /** * Converts a Bikram Sambat (BS) date to Gregorian (AD). * @param bsDate - The BS date to convert. * @returns The corresponding AD date. */ export function convertBSToAD(bsDate: BSDate): ADDate { // Validate input if (bsDate.month < 1 || bsDate.month > 12 || bsDate.day < 1 || bsDate.day > 32) { throw new Error('Invalid BS date'); } // Only supports BS dates between 2000-01-01 and 2002-12-31 if (bsDate.year < 2000 || bsDate.year > 2002) { throw new Error('BS date outside supported range (2000-2002)'); } // Find days between bsEpoch.bs and bsDate const days = daysBetweenBS(bsEpoch.bs, bsDate); const d = new Date(bsEpoch.ad.year, bsEpoch.ad.month - 1, bsEpoch.ad.day); d.setDate(d.getDate() + days); return { year: d.getFullYear(), month: d.getMonth() + 1, day: d.getDate() }; } const nepaliMonths = ['बैशाख', 'जेठ', 'असार', 'श्रावण', 'भदौ', 'आश्विन', 'कार्तिक', 'मंसिर', 'पौष', 'माघ', 'फाल्गुण', 'चैत्र']; const englishMonths = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; const nepaliNumerals = ['०','१','२','३','४','५','६','७','८','९']; function toNepaliNumber(num: number): string { return num.toString().split('').map(d => nepaliNumerals[+d] ?? d).join(''); } export function formatDate(date: BSDate | ADDate, format: DateFormat): string { let isBS = false; let bs: BSDate; let ad: ADDate; // Check if it's a BS date in supported range if ((date as BSDate).year >= 2000 && (date as BSDate).year <= 2002) { isBS = true; bs = date as BSDate; ad = convertBSToAD(bs); } else if ((date as ADDate).year >= 1943 && (date as ADDate).year <= 1945) { // AD date in supported range ad = date as ADDate; bs = convertADToBS(ad); } else { // For dates outside supported range, treat as AD date for English formatting // and throw error for Nepali formatting since we can't convert ad = date as ADDate; if (format.startsWith('nepali')) { throw new Error('Cannot format date in Nepali: date outside supported range'); } // For English formatting, use the AD date as-is const year = ad.year; const month = englishMonths[ad.month - 1]; const day = ad.day; if (format === 'english-full') { return `${day} ${month} ${year}`; } else { return `${year}/${ad.month}/${day}`; } } if (format.startsWith('nepali')) { // Nepali format const year = toNepaliNumber(bs.year); const month = nepaliMonths[bs.month - 1]; const day = toNepaliNumber(bs.day); if (format === 'nepali-full') { return `${year} ${month} ${day}`; } else { return `${year}/${toNepaliNumber(bs.month)}/${day}`; } } else { // English format const year = ad.year; const month = englishMonths[ad.month - 1]; const day = ad.day; if (format === 'english-full') { return `${day} ${month} ${year}`; } else { return `${year}/${ad.month}/${day}`; } } } const nepaliWeekdays = ['आइतबार', 'सोमबार', 'मंगलबार', 'बुधबार', 'बिहीबार', 'शुक्रबार', 'शनिबार']; const englishWeekdays = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; function isBSDate(date: any): date is BSDate { // Only treat as BSDate if year is in the supported BS range return ( typeof date.year === 'number' && typeof date.month === 'number' && typeof date.day === 'number' && date.year >= 2000 && date.year <= 2002 && date.month >= 1 && date.month <= 12 && date.day >= 1 && date.day <= 32 ); } export function getDayOfWeek(date: BSDate | ADDate, locale: 'ne' | 'en' = 'en'): string { // Validate locale if (locale !== 'ne' && locale !== 'en') { throw new Error('Invalid locale. Use "ne" for Nepali or "en" for English'); } let ad: ADDate; if ('year' in date && 'month' in date && 'day' in date) { // Validate date structure if (date.month < 1 || date.month > 12 || date.day < 1 || date.day > 32) { throw new Error('Invalid date object'); } if (isBSDate(date)) { // BSDate logic if (date.year <= 2002) { try { ad = convertBSToAD(date); } catch (error) { throw new Error('BS date outside supported range'); } } else { throw new Error('BS date outside supported range'); } } else { // Treat as ADDate ad = date as ADDate; } } else { throw new Error('Invalid date object'); } const jsDate = new Date(ad.year, ad.month - 1, ad.day); const dayIdx = jsDate.getDay(); return locale === 'ne' ? nepaliWeekdays[dayIdx] : englishWeekdays[dayIdx]; }