@subrotosaha/bangla-date
Version:
A simple utility package for string manipulation in JavaScript.
793 lines (726 loc) • 25 kB
text/typescript
import {
formatBanglaDateToMatchTemplate,
numberToNumber,
} from "../utils/index.js";
type Language = "en" | "bn" | "hi";
class BanglaDate {
private banglaYear: number;
private banglaMonthIndex: number;
private banglaDay: number;
private gregorianDate: Date;
private language: Language;
private date: string;
constructor(gregorianDate: Date, language: Language = "en") {
this.language = language;
this.gregorianDate = new Date(gregorianDate.getTime());
const gYear = this.gregorianDate.getUTCFullYear();
const pohelaBoishakh = new Date(Date.UTC(gYear, 3, 14));
const isBeforePohelaBoishakh =
this.gregorianDate.getTime() < pohelaBoishakh.getTime();
this.banglaYear = (isBeforePohelaBoishakh ? gYear - 1 : gYear) - 593;
const refYear = isBeforePohelaBoishakh ? gYear - 1 : gYear;
const refDate = new Date(Date.UTC(refYear, 3, 14));
const dayDiff = Math.floor(
(this.gregorianDate.getTime() - refDate.getTime()) / (1000 * 60 * 60 * 24)
);
const monthLengths = [
31,
31,
31,
31,
31,
30,
30,
30,
30,
30,
BanglaDate.isLeapYear(refYear + 1) ? 30 : 29, // Falgun leap handling
30,
];
let remainingDays = dayDiff;
let monthIndex = 0;
while (monthIndex < 12 && remainingDays >= monthLengths[monthIndex]) {
remainingDays -= monthLengths[monthIndex];
monthIndex++;
}
this.banglaMonthIndex = monthIndex;
this.banglaDay = remainingDays + 1;
this.date = numberToNumber(
`${this.banglaYear}-${this.banglaMonthIndex + 1}-${
this.banglaDay
} ${this.gregorianDate.getHours()}:${this.gregorianDate.getMinutes()}:${this.gregorianDate.getSeconds()}.${this.gregorianDate.getMilliseconds()} ${
this.language === "bn"
? "সার্বজনীন সময়"
: this.language === "hi"
? "सार्वभौमिक समय"
: "UTC"
}`,
this.language
);
}
// **Static Methods**:
now(): string {
return this.date;
}
parse(dateString: string): string {
const [day, monthName, yearWithBS] = dateString.split(" ");
const banglaMonthNames = {
Boishakh: 0,
Jyoishtho: 1,
Asharh: 2,
Shrabon: 3,
Bhadro: 4,
Ashwin: 5,
Kartik: 6,
Ogrohayon: 7,
Poush: 8,
Magh: 9,
Falgun: 10,
Chaitra: 11,
};
const monthIndex =
banglaMonthNames[monthName as keyof typeof banglaMonthNames];
const dayOfMonth = parseInt(day);
const yearInBS = parseInt(yearWithBS);
const gregorianYear = yearInBS + 593;
const pohelaBoishakh = new Date(Date.UTC(gregorianYear, 3, 14));
const monthLengths = [
31,
31,
31,
31,
31,
30,
30,
30,
30,
30,
BanglaDate.isLeapYear(gregorianYear + 1) ? 30 : 29,
30,
];
let dayOffset = 0;
for (let i = 0; i < monthIndex; i++) {
dayOffset += monthLengths[i];
}
dayOffset += dayOfMonth - 1;
const gDate = new Date(
pohelaBoishakh.getTime() + dayOffset * (1000 * 60 * 60 * 24)
);
const banglaDate = new BanglaDate(gDate, this.language);
return banglaDate.date;
}
private static isLeapYear(year: number): boolean {
return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
}
// **Accessor Methods**:
getDate(): string {
switch (this.language) {
case "bn":
return numberToNumber(this.banglaDay, "bn"); // Gregorian date
case "hi":
return numberToNumber(this.banglaDay, "hi"); // Bengali date
default:
return this.banglaDay.toString(); // Default to Bengali date for unsupported languages
}
}
getDay(): string {
switch (this.language) {
case "bn":
return numberToNumber(this.gregorianDate.getDay(), "bn"); // Bengali day
case "hi":
return numberToNumber(this.gregorianDate.getDay(), "hi"); // Sanskrit day
default:
return this.gregorianDate.getDay().toString(); // Default to Gregorian day for unsupported languages
}
}
getFullYear(): string {
switch (this.language) {
case "bn":
return numberToNumber(this.banglaYear, "bn"); // Bengali year
case "hi":
return numberToNumber(this.banglaYear, "hi"); // Sanskrit year
default:
return this.banglaYear.toString(); // Default to Bengali year for unsupported languages
}
}
getHours(): string {
switch (this.language) {
case "bn":
return numberToNumber(this.gregorianDate.getHours(), "bn"); // Bengali hours
case "hi":
return numberToNumber(this.gregorianDate.getHours(), "hi"); // Sanskrit hours
default:
return this.gregorianDate.getHours().toString(); // Default to Gregorian hours for unsupported languages
}
}
getMilliseconds(): string {
switch (this.language) {
case "bn":
return numberToNumber(this.gregorianDate.getMilliseconds(), "bn"); // Bengali milliseconds
case "hi":
return numberToNumber(this.gregorianDate.getMilliseconds(), "hi"); // Sanskrit milliseconds
default:
return this.gregorianDate.getMilliseconds().toString(); // Default to Gregorian milliseconds for unsupported languages
}
}
getMinutes(): string {
switch (this.language) {
case "bn":
return numberToNumber(this.gregorianDate.getMinutes(), "bn"); // Bengali minutes
case "hi":
return numberToNumber(this.gregorianDate.getMinutes(), "hi"); // Sanskrit minutes
default:
return this.gregorianDate.getMinutes().toString(); // Default to Gregorian minutes for unsupported languages
}
}
getMonth(): string {
switch (this.language) {
case "bn":
return numberToNumber(this.banglaMonthIndex + 1, "bn"); // Bengali month
case "hi":
return numberToNumber(this.banglaMonthIndex + 1, "hi"); // Sanskrit month
default:
return (this.banglaMonthIndex + 1).toString(); // Default to Bengali month for unsupported languages
}
}
getSeconds(): string {
switch (this.language) {
case "bn":
return numberToNumber(this.gregorianDate.getSeconds(), "bn"); // Bengali seconds
case "hi":
return numberToNumber(this.gregorianDate.getSeconds(), "hi"); // Sanskrit seconds
default:
return this.gregorianDate.getSeconds().toString(); // Default to Gregorian seconds for unsupported languages
}
}
getTime(): string {
switch (this.language) {
case "bn":
return numberToNumber(this.gregorianDate.getTime(), "bn"); // Bengali time
case "hi":
return numberToNumber(this.gregorianDate.getTime(), "hi"); // Sanskrit time
default:
return this.gregorianDate.getTime().toString(); // Default to Gregorian time for unsupported languages
}
}
getTimezoneOffset(): string {
switch (this.language) {
case "bn":
return numberToNumber(this.gregorianDate.getTimezoneOffset(), "bn"); // Bengali timezone offset
case "hi":
return numberToNumber(this.gregorianDate.getTimezoneOffset(), "hi"); // Sanskrit timezone offset
default:
return this.gregorianDate.getTimezoneOffset().toString(); // Default to Gregorian timezone offset for unsupported languages
}
}
getUTCDate(): string {
switch (this.language) {
case "bn":
return numberToNumber(this.gregorianDate.getUTCDate(), "bn"); // Bengali UTC date
case "hi":
return numberToNumber(this.gregorianDate.getUTCDate(), "hi"); // Sanskrit UTC date
default:
return this.gregorianDate.getUTCDate().toString(); // Default to Gregorian UTC date for unsupported languages
}
}
getUTCDay(): string {
switch (this.language) {
case "bn":
return numberToNumber(this.gregorianDate.getUTCDay(), "bn"); // Bengali UTC day
case "hi":
return numberToNumber(this.gregorianDate.getUTCDay(), "hi"); // Sanskrit UTC day
default:
return this.gregorianDate.getUTCDay().toString(); // Default to Gregorian UTC day for unsupported languages
}
}
getUTCFullYear(): string {
switch (this.language) {
case "bn":
return numberToNumber(this.gregorianDate.getUTCFullYear(), "bn"); // Bengali UTC full year
case "hi":
return numberToNumber(this.gregorianDate.getUTCFullYear(), "hi"); // Sanskrit UTC full year
default:
return this.gregorianDate.getUTCFullYear().toString(); // Default to Gregorian UTC full year for unsupported languages
}
}
getUTCHours(): string {
switch (this.language) {
case "bn":
return numberToNumber(this.gregorianDate.getUTCHours(), "bn"); // Bengali UTC hours
case "hi":
return numberToNumber(this.gregorianDate.getUTCHours(), "hi"); // Sanskrit UTC hours
default:
return this.gregorianDate.getUTCHours().toString(); // Default to Gregorian UTC hours for unsupported languages
}
}
getUTCMilliseconds(): string {
switch (this.language) {
case "bn":
return numberToNumber(this.gregorianDate.getUTCMilliseconds(), "bn"); // Bengali UTC milliseconds
case "hi":
return numberToNumber(this.gregorianDate.getUTCMilliseconds(), "hi"); // Sanskrit UTC milliseconds
default:
return this.gregorianDate.getUTCMilliseconds().toString(); // Default to Gregorian UTC milliseconds for unsupported languages
}
}
getUTCMinutes(): string {
switch (this.language) {
case "bn":
return numberToNumber(this.gregorianDate.getUTCMinutes(), "bn"); // Bengali UTC minutes
case "hi":
return numberToNumber(this.gregorianDate.getUTCMinutes(), "hi"); // Sanskrit UTC minutes
default:
return this.gregorianDate.getUTCMinutes().toString(); // Default to Gregorian UTC minutes for unsupported languages
}
}
getUTCMonth(): string {
switch (this.language) {
case "bn":
return numberToNumber(this.gregorianDate.getUTCMonth(), "bn"); // Bengali UTC month
case "hi":
return numberToNumber(this.gregorianDate.getUTCMonth(), "hi"); // Sanskrit UTC month
default:
return this.gregorianDate.getUTCMonth().toString(); // Default to Gregorian UTC month for unsupported languages
}
}
getUTCSeconds(): string {
switch (this.language) {
case "bn":
return numberToNumber(this.gregorianDate.getUTCSeconds(), "bn"); // Bengali UTC seconds
case "hi":
return numberToNumber(this.gregorianDate.getUTCSeconds(), "hi"); // Sanskrit UTC seconds
default:
return this.gregorianDate.getUTCSeconds().toString(); // Default to Gregorian UTC seconds for unsupported languages
}
}
getYear(): string {
switch (this.language) {
case "bn":
return numberToNumber(this.banglaYear, "bn"); // Bengali year
case "hi":
return numberToNumber(this.banglaYear, "hi"); // Sanskrit year
default:
return (this.gregorianDate.getFullYear() - 1900).toString();
}
}
// **Formatted Output**:
private getMonthName(): string {
const monthNames = {
en: [
"Boishakh",
"Jyoishtho",
"Asharh",
"Shrabon",
"Bhadro",
"Ashwin",
"Kartik",
"Ogrohayon",
"Poush",
"Magh",
"Falgun",
"Chaitra",
],
bn: [
"বৈশাখ",
"জ্যৈষ্ঠ",
"আষাঢ়",
"শ্রাবণ",
"ভাদ্র",
"আশ্বিন",
"কার্তিক",
"অগ্রহায়ণ",
"পৌষ",
"মাঘ",
"ফাল্গুন",
"চৈত্র",
],
hi: [
"बैशाख",
"ज्येष्ठ",
"आषाढ",
"श्रावण",
"भाद्रपद",
"आश्विन",
"कार्तिक",
"अग्राहयण",
"पौष",
"माघ",
"फाल्गुन",
"चैत",
],
};
return monthNames[this.language][this.banglaMonthIndex];
}
private getWeekDay(): string {
const weekDays = {
en: [
"Sunday",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
],
bn: [
"রবিবার",
"সোমবার",
"মঙ্গলবার",
"বুধবার",
"বৃহস্পতিবার",
"শুক্রবার",
"শনিবার",
],
hi: [
"रविवार",
"सोमवार",
"मंगलवार",
"बुधवार",
"गुरुवार",
"शुक्रवार",
"शनिवार",
],
};
const gDay = this.gregorianDate.getDay();
return weekDays[this.language][gDay];
}
// **toLocaleString() with language formatting**:
toLocaleDateString(
locales?: string | string[],
options?: Intl.DateTimeFormatOptions
): string {
// If no locales are provided, fall back to the object's language
const currentLocale = locales ?? this.language;
// Supported locales prefixes: "en", "bn", "hi", and their variants (like "en-US", "bn-BD", "hi-IN")
const validLocales = ["en", "bn", "hi"];
// Check if the provided locale is a string (or array of strings)
const isValidLocale = Array.isArray(currentLocale)
? currentLocale.some((locale) =>
validLocales.some((valid) => locale.startsWith(valid))
)
: validLocales.some((valid) =>
(currentLocale as string).startsWith(valid)
);
if (!isValidLocale) {
throw new Error(
`Locale ${currentLocale} is not supported. Supported locales are: ${validLocales.join(
", "
)}, or any variant of them (e.g., en-US, bn-BD, hi-IN).`
);
}
// Define digit maps for supported locales
const digitsMap: Record<string, string[]> = {
en: ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"],
bn: ["০", "১", "২", "৩", "৪", "৫", "৬", "৭", "৮", "৯"],
hi: ["०", "१", "२", "३", "४", "५", "६", "७", "८", "९"],
};
const ampmMap: Record<string, string[]> = {
en: ["AM", "PM", "GMT", "UTC", "PST", "EST", "CET", "BST"],
bn: [
"পূর্বাহ্ণ",
"অপরাহ্ণ",
"জিএমটি",
"ইউটিসি",
"পিএসটি",
"ইএসটি",
"সিইটি",
"বিএসটি",
"জিএমটি",
],
hi: [
"पूर्वाह्न",
"अपराह्न",
"जीएमटी",
"यूटीसी",
"पीएसटी",
"ईएसटी",
"सीईटी",
"बीएसटी",
"जीएमटी",
],
};
// Helper function to localize digits based on the selected locale
const localeKey = currentLocale
.toString()
.split("-")[0] as keyof typeof digitsMap;
const localizeNumber = (num: number | string): string => {
return num
.toString()
.split("")
.map((d: string) => {
const digitIndex = parseInt(d, 10);
return isNaN(digitIndex) ? d : digitsMap[localeKey][digitIndex] ?? d;
})
.join("");
};
// Format the date using the provided options
const nDate = new BanglaDate(new Date(this.gregorianDate)).date;
let formattedDate = formatBanglaDateToMatchTemplate(
nDate,
new Intl.DateTimeFormat(
currentLocale.toString().includes("en") ? currentLocale : "en-US",
options
).format(this.gregorianDate)
);
// Localize the formatted date by replacing digits with the appropriate locale digits
formattedDate = formattedDate
.split("")
.map((char) => (/\d/.test(char) ? localizeNumber(char) : char))
.join("");
formattedDate = formattedDate
.split(" ")
.map((word) => {
// Normalize input by stripping periods and making uppercase
const normalized = word.replace(/\./g, "").toUpperCase();
const index =
ampmMap["en"].indexOf(normalized) > 0
? ampmMap["en"].indexOf(normalized)
: ampmMap["en"].indexOf(normalized.replace(/[^A-Za-z]/g, ""));
if (index !== -1) {
return (
ampmMap[localeKey][index] +
(localeKey !== "en" ? word.replace(/[A-Za-z]/g, "") : "")
);
}
return word; // Return unchanged if no match
})
.join(" ");
//return new BanglaDate(new Date(formattedDate.split(", ")[0])).date;
return formattedDate;
}
// Helper function to format numbers
formatNumber(num: number | string): string {
return num.toString().padStart(2, "0");
}
getAMPM(hour: number | string): string {
const h = parseInt(hour.toString(), 10);
return h >= 12
? this.language === "bn"
? "অপরাহ্ণ"
: this.language === "hi"
? "अपराह्न"
: "PM"
: this.language === "bn"
? "পূর্বাহ্ণ"
: this.language === "hi"
? "पूर्वाह्न"
: "AM";
}
// Main format function
format(formatString: string): string {
const year = this.banglaYear;
const month = this.banglaMonthIndex + 1; // Month index starts from 0
const day = this.banglaDay;
const hours = this.getHours();
const minutes = this.getMinutes();
const seconds = this.getSeconds();
const replacements: Record<string, string> = {
WWWW: this.getWeekDayFormat("long"),
WWW: this.getWeekDayFormat("short"),
WW: `0${this.gregorianDate.getDay().toString()}`,
W: this.gregorianDate.getDay().toString(),
era: this.language === "bn" ? "খ্রিস্টাব্দ" : "AD",
YYYY: this.formatNumber(year),
YY: this.formatNumber(year % 100),
MMMM: this.getFormatedMonthName("long"),
MMM: this.getFormatedMonthName("short"),
MM: this.formatNumber(month),
M: month.toString(),
DD: this.formatNumber(day),
D: day.toString(),
HH: this.formatNumber(hours),
H: hours.toString(),
mm: this.formatNumber(minutes),
m: minutes.toString(),
ss: this.formatNumber(seconds),
s: seconds.toString(),
"AM/PM": this.getAMPM(hours),
};
return numberToNumber(
formatString.replace(
/WWWW|WWW|WW|W|YYYY|YY|MMMM|MMM|MM|M|DD|D|HH|H|mm|m|ss|s|AM\/PM|era/g,
(match) => replacements[match] || match
),
this.language
);
}
// Function to get month name in Bangla
private getFormatedMonthName(format: "long" | "short" = "long"): string {
const monthNames: Record<string, string[]> = {
long: [
"বৈশাখ",
"জ্যৈষ্ঠ",
"আষাঢ়",
"শ্রাবণ",
"ভাদ্রপদ",
"আশ্বিন",
"কার্তিক",
"অগ্রাহায়ণ",
"পৌষ",
"মাঘ",
"ফাল্গুন",
"চৈত্র",
],
short: [
"বৈশা",
"জ্যৈ",
"আষা",
"শ্রা",
"ভাদ",
"আশ্বি",
"কার",
"অগ্র",
"পৌ",
"মা",
"ফাল",
"চৈ",
],
};
return monthNames[format][this.banglaMonthIndex];
}
toLocaleString(
locales?: string | string[],
options?: Intl.DateTimeFormatOptions
): string {
// If no locales are provided, fall back to the object's language
const currentLocale = locales ?? this.language;
// Supported locales prefixes: "en", "bn", "hi", and their variants (like "en-US", "bn-BD", "hi-IN")
const validLocales = ["en", "bn", "hi"];
// Check if the provided locale is a string (or array of strings)
const isValidLocale = Array.isArray(currentLocale)
? currentLocale.some((locale) =>
validLocales.some((valid) => locale.startsWith(valid))
)
: validLocales.some((valid) =>
(currentLocale as string).startsWith(valid)
);
if (!isValidLocale) {
throw new Error(
`Locale ${currentLocale} is not supported. Supported locales are: ${validLocales.join(
", "
)}, or any variant of them (e.g., en-US, bn-BD, hi-IN).`
);
}
// Define digit maps for supported locales
const digitsMap: Record<string, string[]> = {
en: ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"],
bn: ["০", "১", "২", "৩", "৪", "৫", "৬", "৭", "৮", "৯"],
hi: ["०", "१", "२", "३", "४", "५", "६", "७", "८", "९"],
};
const ampmMap: Record<string, string[]> = {
en: ["AM", "PM", "GMT", "UTC", "PST", "EST", "CET", "BST"],
bn: [
"পূর্বাহ্ণ",
"অপরাহ্ণ",
"জিএমটি",
"ইউটিসি",
"পিএসটি",
"ইএসটি",
"সিইটি",
"বিএসটি",
"জিএমটি",
],
hi: [
"पूर्वाह्न",
"अपराह्न",
"जीएमटी",
"यूटीसी",
"पीएसटी",
"ईएसटी",
"सीईटी",
"बीएसटी",
"जीएमटी",
],
};
// Helper function to localize digits based on the selected locale
const localeKey = currentLocale
.toString()
.split("-")[0] as keyof typeof digitsMap;
const localizeNumber = (num: number | string): string => {
return num
.toString()
.split("")
.map((d: string) => {
const digitIndex = parseInt(d, 10);
return isNaN(digitIndex) ? d : digitsMap[localeKey][digitIndex] ?? d;
})
.join("");
};
// Format the date using the provided options
const nDate = new BanglaDate(new Date(this.gregorianDate)).date;
let formattedDate = formatBanglaDateToMatchTemplate(
nDate,
new Intl.DateTimeFormat(
currentLocale.toString().includes("en") ? currentLocale : "en-US",
options
).format(this.gregorianDate)
);
// Localize the formatted date by replacing digits with the appropriate locale digits
formattedDate = formattedDate
.split("")
.map((char) => (/\d/.test(char) ? localizeNumber(char) : char))
.join("");
formattedDate = formattedDate
.split(" ")
.map((word) => {
// Normalize input by stripping periods and making uppercase
const normalized = word.replace(/\./g, "").toUpperCase();
const index =
ampmMap["en"].indexOf(normalized) > 0
? ampmMap["en"].indexOf(normalized)
: ampmMap["en"].indexOf(normalized.replace(/[^A-Za-z]/g, ""));
if (index !== -1) {
return (
ampmMap[localeKey][index] +
(localeKey !== "en" ? word.replace(/[A-Za-z]/g, "") : "")
);
}
return word; // Return unchanged if no match
})
.join(" ");
//return new BanglaDate(new Date(formattedDate.split(", ")[0])).date;
console.log(formattedDate);
return formattedDate;
}
private getWeekDayFormat(format: "long" | "short" = "long"): string {
const weekDays = {
long: {
en: [
"Sunday",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
],
bn: [
"রবিবার",
"সোমবার",
"মঙ্গলবার",
"বুধবার",
"বৃহস্পতিবার",
"শুক্রবার",
"শনিবার",
],
hi: [
"रविवार",
"सोमवार",
"मंगलवार",
"बुधवार",
"गुरुवार",
"शुक्रवार",
"शनिवार",
],
},
short: {
en: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
bn: ["রবি", "সোম", "মঙ্গল", "বুধ", "বৃহঃ", "শুক্র", "শনি"],
hi: ["रवि", "सोम", "मंगल", "बुध", "गुरु", "शुक्र", "शनि"],
},
};
const gDay = this.gregorianDate.getDay();
return weekDays[format][this.language][gDay];
}
}
export default BanglaDate;