UNPKG

indonesia-formatter-dev

Version:

A comprehensive TypeScript library for formatting Indonesian-specific data

404 lines (397 loc) 12.1 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var src_exports = {}; __export(src_exports, { CalendarFormatter: () => CalendarFormatter, DateFormatter: () => DateFormatter, NIKFormatter: () => NIKFormatter, NPWPFormatter: () => NPWPFormatter, PhoneFormatter: () => PhoneFormatter }); module.exports = __toCommonJS(src_exports); // src/formatters/date.ts var DateFormatter = class { /** * Format date to Indonesian format * @param date - Date to format * @param format - Format type */ static format(date, format = "long") { const day = date.getDate(); const month = this.months[date.getMonth()]; const year = date.getFullYear(); const dayName = this.days[date.getDay()]; switch (format) { case "long": return `${dayName}, ${day} ${month} ${year}`; case "medium": return `${day} ${month} ${year}`; case "short": return `${day.toString().padStart(2, "0")}/${(date.getMonth() + 1).toString().padStart(2, "0")}/${year}`; case "monthYear": return `${month} ${year}`; default: return `${day} ${month} ${year}`; } } /** * Format relative time in Indonesian * @param date - Date to compare * @param baseDate - Base date (default: now) */ static relative(date, baseDate = /* @__PURE__ */ new Date()) { const diff = baseDate.getTime() - date.getTime(); const seconds = Math.floor(diff / 1e3); const minutes = Math.floor(seconds / 60); const hours = Math.floor(minutes / 60); const days = Math.floor(hours / 24); const months = Math.floor(days / 30); const years = Math.floor(days / 365); if (seconds < 0) { const future = this.relative(baseDate, date); return `dalam ${future}`; } if (seconds < 60) return "baru saja"; if (minutes === 1) return "1 menit yang lalu"; if (minutes < 60) return `${minutes} menit yang lalu`; if (hours === 1) return "1 jam yang lalu"; if (hours < 24) return `${hours} jam yang lalu`; if (days === 1) return "kemarin"; if (days < 7) return `${days} hari yang lalu`; if (days < 14) return "1 minggu yang lalu"; if (days < 30) return `${Math.floor(days / 7)} minggu yang lalu`; if (months === 1) return "1 bulan yang lalu"; if (months < 12) return `${months} bulan yang lalu`; if (years === 1) return "1 tahun yang lalu"; return `${years} tahun yang lalu`; } }; DateFormatter.months = [ "Januari", "Februari", "Maret", "April", "Mei", "Juni", "Juli", "Agustus", "September", "Oktober", "November", "Desember" ]; DateFormatter.days = [ "Minggu", "Senin", "Selasa", "Rabu", "Kamis", "Jumat", "Sabtu" ]; // src/formatters/calendar.ts var CalendarFormatter = class { /** * Convert Gregorian date to Javanese calendar */ static toJavanese(date) { const baseDate = new Date(1633, 6, 8); const diff = Math.floor((date.getTime() - baseDate.getTime()) / (1e3 * 60 * 60 * 24)); let year = 1555; let days = diff; while (days > 354) { const yearLength = year % 8 === 2 || year % 8 === 5 || year % 8 === 0 ? 355 : 354; if (days >= yearLength) { days -= yearLength; year++; } else { break; } } const months = [ { name: "Sura", days: 30 }, { name: "Sapar", days: 29 }, { name: "Mulud", days: 30 }, { name: "Bakda Mulud", days: 29 }, { name: "Jumadil Awal", days: 30 }, { name: "Jumadil Akhir", days: 29 }, { name: "Rejeb", days: 30 }, { name: "Ruwah", days: 29 }, { name: "Pasa", days: 30 }, { name: "Sawal", days: 29 }, { name: "Sela", days: 30 }, { name: "Besar", days: 29 } ]; let month = 0; let dayOfMonth = days; for (let i = 0; i < months.length; i++) { if (dayOfMonth > months[i].days) { dayOfMonth -= months[i].days; month++; } else { break; } } const pasaranNames = ["Legi", "Pahing", "Pon", "Wage", "Kliwon"]; const pasaranIndex = diff % 5; return { year, month: months[month].name, day: dayOfMonth + 1, pasaran: pasaranNames[pasaranIndex] }; } /** * Convert Gregorian date to Hijri calendar */ static toHijri(date) { const gregorianYear = date.getFullYear(); const gregorianMonth = date.getMonth() + 1; const gregorianDay = date.getDate(); let jd = Math.floor(1461 * (gregorianYear + 4800 + Math.floor((gregorianMonth - 14) / 12)) / 4) + Math.floor(367 * (gregorianMonth - 2 - 12 * Math.floor((gregorianMonth - 14) / 12)) / 12) - Math.floor(3 * Math.floor((gregorianYear + 4900 + Math.floor((gregorianMonth - 14) / 12)) / 100) / 4) + gregorianDay - 32075; let l = jd - 1948440 + 10632; let n = Math.floor((l - 1) / 10631); l = l - 10631 * n + 354; let j = Math.floor((10985 - l) / 5316) * Math.floor(50 * l / 17719) + Math.floor(l / 5670) * Math.floor(43 * l / 15238); l = l - Math.floor((30 - j) / 15) * Math.floor(17719 * j / 50) - Math.floor(j / 16) * Math.floor(15238 * j / 43) + 29; let month = Math.floor(24 * l / 709); let day = l - Math.floor(709 * month / 24); let year = 30 * n + j - 30; const monthNames = [ "Muharram", "Safar", "Rabiul Awal", "Rabiul Akhir", "Jumadil Awal", "Jumadil Akhir", "Rajab", "Syaban", "Ramadhan", "Syawal", "Dzulqaidah", "Dzulhijjah" ]; return { year, month: monthNames[month - 1], day, monthNumber: month }; } /** * Format Javanese date to string */ static formatJavanese(date, includeGregorian = true) { const jav = this.toJavanese(date); const formatted = `${jav.pasaran}, ${jav.day} ${jav.month} ${jav.year}`; if (includeGregorian) { return `${formatted} (${DateFormatter.format(date, "medium")})`; } return formatted; } /** * Format Hijri date to string */ static formatHijri(date, includeGregorian = true) { const hijri = this.toHijri(date); const formatted = `${hijri.day} ${hijri.month} ${hijri.year} H`; if (includeGregorian) { return `${formatted} (${DateFormatter.format(date, "medium")})`; } return formatted; } }; // src/formatters/nik.ts var NIKFormatter = class { /** * Format NIK (Nomor Induk Kependudukan) * @param nik - NIK number * @returns Object with parsed NIK information */ static parse(nik) { const cleaned = nik.replace(/\D/g, ""); if (cleaned.length !== 16) { throw new Error("NIK must be 16 digits"); } const provinceCode = cleaned.slice(0, 2); const cityCode = cleaned.slice(2, 4); const districtCode = cleaned.slice(4, 6); const birthDate = cleaned.slice(6, 12); const uniqueCode = cleaned.slice(12, 16); let day = parseInt(birthDate.slice(0, 2)); const month = parseInt(birthDate.slice(2, 4)); const year = parseInt(birthDate.slice(4, 6)); const isFemale = day > 40; if (isFemale) { day -= 40; } const fullYear = year > 50 ? 1900 + year : 2e3 + year; return { nik: cleaned, provinceCode, cityCode, districtCode, birthDate: new Date(fullYear, month - 1, day), gender: isFemale ? "female" : "male", uniqueCode }; } /** * Validate NIK */ static validate(nik) { const cleaned = nik.replace(/\D/g, ""); if (cleaned.length !== 16 || !/^\d{16}$/.test(cleaned)) { return false; } try { const birthDateStr = cleaned.slice(6, 12); let day = parseInt(birthDateStr.slice(0, 2)); const month = parseInt(birthDateStr.slice(2, 4)); const year = parseInt(birthDateStr.slice(4, 6)); if (day > 40) { day -= 40; } if (month < 1 || month > 12) { return false; } if (day < 1 || day > 31) { return false; } const fullYear = year > 50 ? 1900 + year : 2e3 + year; const testDate = new Date(fullYear, month - 1, day); if (testDate.getFullYear() !== fullYear || testDate.getMonth() !== month - 1 || testDate.getDate() !== day) { return false; } return true; } catch { return false; } } /** * Format NIK with spaces for readability */ static format(nik) { const cleaned = nik.replace(/\D/g, ""); if (cleaned.length !== 16) { throw new Error("NIK must be 16 digits"); } return `${cleaned.slice(0, 4)} ${cleaned.slice(4, 8)} ${cleaned.slice(8, 12)} ${cleaned.slice(12, 16)}`; } }; // src/formatters/npwp.ts var NPWPFormatter = class { /** * Format NPWP (Nomor Pokok Wajib Pajak) * @param npwp - NPWP number * @returns Formatted NPWP (XX.XXX.XXX.X-XXX.XXX) */ static format(npwp) { const cleaned = npwp.replace(/\D/g, ""); if (cleaned.length !== 15) { throw new Error("NPWP must be 15 digits"); } return `${cleaned.slice(0, 2)}.${cleaned.slice(2, 5)}.${cleaned.slice(5, 8)}.${cleaned.slice(8, 9)}-${cleaned.slice(9, 12)}.${cleaned.slice(12, 15)}`; } /** * Validate NPWP */ static validate(npwp) { const cleaned = npwp.replace(/\D/g, ""); return cleaned.length === 15 && /^\d{15}$/.test(cleaned); } }; // src/formatters/phone.ts var PhoneFormatter = class { /** * Format nomor telepon Indonesia * @param phone - Nomor telepon * @param options - Opsi formatting * @returns Formatted phone number */ static format(phone, options) { const cleaned = phone.replace(/\D/g, ""); if (cleaned.startsWith("62")) { return this.formatInternational(cleaned, options); } else if (cleaned.startsWith("0")) { return this.formatDomestic(cleaned, options); } throw new Error("Invalid Indonesian phone number"); } static formatInternational(phone, options) { const format = options?.format || "standard"; switch (format) { case "standard": return `+${phone.slice(0, 2)} ${phone.slice(2, 5)}-${phone.slice(5, 9)}-${phone.slice(9)}`; case "dots": return `+${phone.slice(0, 2)}.${phone.slice(2, 5)}.${phone.slice(5, 9)}.${phone.slice(9)}`; case "spaces": return `+${phone.slice(0, 2)} ${phone.slice(2, 5)} ${phone.slice(5, 9)} ${phone.slice(9)}`; default: return `+${phone}`; } } static formatDomestic(phone, options) { const format = options?.format || "standard"; switch (format) { case "standard": return `${phone.slice(0, 4)}-${phone.slice(4, 8)}-${phone.slice(8)}`; case "dots": return `${phone.slice(0, 4)}.${phone.slice(4, 8)}.${phone.slice(8)}`; case "spaces": return `${phone.slice(0, 4)} ${phone.slice(4, 8)} ${phone.slice(8)}`; default: return phone; } } /** * Validate nomor telepon Indonesia */ static validate(phone) { const cleaned = phone.replace(/\D/g, ""); const patterns = [ /^62[2-9]\d{7,11}$/, // International format /^0[2-9]\d{7,11}$/ // Domestic format ]; return patterns.some((pattern) => pattern.test(cleaned)); } }; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { CalendarFormatter, DateFormatter, NIKFormatter, NPWPFormatter, PhoneFormatter });