indokit
Version:
Utility TypeScript untuk format & validasi data Indonesia (Rupiah, NIK, phone, slug, dsb.)
227 lines (219 loc) • 7.8 kB
JavaScript
;
function formatRupiah(angka) {
if (Number.isNaN(Number(angka)))
throw new TypeError('formatRupiah: angka must be a number');
return new Intl.NumberFormat('id-ID', {
style: 'currency',
currency: 'IDR'
}).format(angka);
}
function formatTanggalID(date) {
const d = new Date(date);
if (Number.isNaN(d.getTime()))
throw new TypeError('formatTanggalID: invalid date');
return new Intl.DateTimeFormat('id-ID', {
day: 'numeric',
month: 'long',
year: 'numeric'
}).format(d);
}
function isValidNIK(nik) {
if (typeof nik !== 'string')
return false;
return /^[0-9]{16}$/.test(nik);
}
function isValidPhone(phone) {
if (typeof phone !== 'string')
return false;
return /^(?:\+62|62|0)8[1-9][0-9]{6,9}$/.test(phone);
}
function terbilang(input) {
// Validasi dan normalisasi input
const normalizedInput = normalizeInput(input);
// Handle angka negatif
if (normalizedInput < 0) {
return 'minus ' + terbilangPositive(Math.abs(normalizedInput));
}
return terbilangPositive(normalizedInput);
}
function normalizeInput(input) {
// Handle null, undefined
if (input === null || input === undefined) {
throw new TypeError('terbilang: input tidak boleh null atau undefined');
}
// Handle boolean
if (typeof input === 'boolean') {
throw new TypeError('terbilang: input tidak boleh berupa boolean');
}
// Handle object, array, function
if (typeof input === 'object' || typeof input === 'function') {
throw new TypeError('terbilang: input harus berupa angka atau string yang valid');
}
let processedInput;
if (typeof input === 'string') {
// Trim whitespace
const trimmed = input.trim();
// Handle empty string
if (trimmed === '') {
throw new TypeError('terbilang: string tidak boleh kosong');
}
// Handle special string cases first
if (trimmed.toLowerCase() === 'infinity' || trimmed.toLowerCase() === 'inf') {
throw new TypeError('terbilang: input tidak boleh infinity');
}
if (trimmed.toLowerCase() === 'nan') {
throw new TypeError('terbilang: input tidak boleh NaN');
}
// Check for mixed alphanumeric (contains both letters and numbers)
const hasLetters = /[a-zA-Z]/.test(trimmed);
const hasNumbers = /[0-9]/.test(trimmed);
if (hasLetters && hasNumbers) {
throw new TypeError('terbilang: input tidak boleh mengandung campuran huruf dan angka');
}
// If contains only letters, reject
if (hasLetters && !hasNumbers) {
throw new TypeError('terbilang: input tidak boleh berupa huruf');
}
// Validate that the string is a proper number format
// This regex allows: optional +/-, digits, optional single decimal point with digits
const numberRegex = /^[+-]?(\d+\.?\d*|\.\d+)$/;
if (!numberRegex.test(trimmed)) {
throw new TypeError('terbilang: string tidak dapat dikonversi menjadi angka yang valid');
}
// Parse string to number
processedInput = parseFloat(trimmed);
// Check if parsing resulted in NaN (shouldn't happen with regex validation, but just in case)
if (isNaN(processedInput)) {
throw new TypeError('terbilang: string tidak dapat dikonversi menjadi angka yang valid');
}
}
else if (typeof input === 'number') {
processedInput = input;
}
else {
// Try to convert other types to number
processedInput = Number(input);
if (isNaN(processedInput)) {
throw new TypeError('terbilang: input tidak dapat dikonversi menjadi angka yang valid');
}
}
// Check for special number values
if (isNaN(processedInput)) {
throw new TypeError('terbilang: input tidak boleh NaN');
}
if (!isFinite(processedInput)) {
throw new TypeError('terbilang: input tidak boleh infinity atau -infinity');
}
// Check if it's an integer (allow decimal strings that represent integers)
if (!Number.isInteger(processedInput)) {
throw new TypeError('terbilang: input harus berupa bilangan bulat');
}
// Check maximum safe integer
if (Math.abs(processedInput) > Number.MAX_SAFE_INTEGER) {
throw new TypeError('terbilang: angka terlalu besar (melebihi batas maksimum JavaScript)');
}
// Check reasonable limits for Indonesian number system
if (Math.abs(processedInput) >= 1000000000000000) { // 1 kuadriliun
throw new TypeError('terbilang: angka terlalu besar (maksimum 999 triliun)');
}
return processedInput;
}
function terbilangPositive(angka) {
if (angka === 0)
return 'nol';
const satuan = ['', 'satu', 'dua', 'tiga', 'empat', 'lima', 'enam', 'tujuh', 'delapan', 'sembilan'];
const belasan = ['sepuluh', 'sebelas', 'dua belas', 'tiga belas', 'empat belas', 'lima belas', 'enam belas', 'tujuh belas', 'delapan belas', 'sembilan belas'];
function konversiRatusan(n) {
let hasil = '';
// Ratusan
if (n >= 100) {
const ratus = Math.floor(n / 100);
if (ratus === 1) {
hasil += 'seratus';
}
else {
hasil += satuan[ratus] + ' ratus';
}
n %= 100;
if (n > 0)
hasil += ' ';
}
// Puluhan dan satuan
if (n >= 20) {
const puluh = Math.floor(n / 10);
hasil += satuan[puluh] + ' puluh';
n %= 10;
if (n > 0)
hasil += ' ' + satuan[n];
}
else if (n >= 10) {
hasil += belasan[n - 10];
}
else if (n > 0) {
hasil += satuan[n];
}
return hasil;
}
if (angka < 1000) {
return konversiRatusan(angka);
}
let hasil = '';
// Triliun
if (angka >= 1000000000000) {
const triliun = Math.floor(angka / 1000000000000);
hasil += konversiRatusan(triliun) + ' triliun';
angka %= 1000000000000;
if (angka > 0)
hasil += ' ';
}
// Miliar
if (angka >= 1000000000) {
const miliar = Math.floor(angka / 1000000000);
hasil += konversiRatusan(miliar) + ' miliar';
angka %= 1000000000;
if (angka > 0)
hasil += ' ';
}
// Juta
if (angka >= 1000000) {
const juta = Math.floor(angka / 1000000);
hasil += konversiRatusan(juta) + ' juta';
angka %= 1000000;
if (angka > 0)
hasil += ' ';
}
// Ribu
if (angka >= 1000) {
const ribu = Math.floor(angka / 1000);
if (ribu === 1) {
hasil += 'seribu';
}
else {
hasil += konversiRatusan(ribu) + ' ribu';
}
angka %= 1000;
if (angka > 0)
hasil += ' ';
}
// Sisa (ratusan, puluhan, satuan)
if (angka > 0) {
hasil += konversiRatusan(angka);
}
return hasil;
}
function randomString(length) {
if (!Number.isInteger(length) || length < 0)
throw new TypeError('randomString: length must be a non-negative integer');
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let result = '';
for (let i = 0; i < length; i++) {
result += chars.charAt(Math.floor(Math.random() * chars.length));
}
return result;
}
exports.formatRupiah = formatRupiah;
exports.formatTanggalID = formatTanggalID;
exports.isValidNIK = isValidNIK;
exports.isValidPhone = isValidPhone;
exports.randomString = randomString;
exports.terbilang = terbilang;