/** * Custom error classes for datetime module * * @module datetime/types * @packageDocumentation */ /** * Error thrown when an invalid date is provided to a function. * Extends native Error with a `code` property for programmatic error handling. * * @example * ```typescript * try { * formatDate('invalid'); * } catch (error) { * if (error instanceof InvalidDateError) { * console.log(error.code); // 'INVALID_DATE' * } * } * ``` */ declare class InvalidDateError extends Error { /** Error code for programmatic identification */ readonly code: "INVALID_DATE"; constructor(message?: string); } /** * Error thrown when an invalid date range is provided. * Extends native Error with a `code` property for programmatic error handling. * * @example * ```typescript * try { * formatDateRange(new Date('2026-01-05'), new Date('2026-01-01')); * } catch (error) { * if (error instanceof InvalidDateRangeError) { * console.log(error.code); // 'INVALID_DATE_RANGE' * } * } * ``` */ declare class InvalidDateRangeError extends Error { /** Error code for programmatic identification */ readonly code: "INVALID_DATE_RANGE"; constructor(message?: string); } /** * Date formatting style options */ type DateStyle = 'full' | 'long' | 'medium' | 'short' | 'weekday' | 'month'; /** * Options for getAge function */ interface AgeOptions { /** * Reference date to calculate age from. * Defaults to current date at function call time. * @defaultValue new Date() */ fromDate?: Date | string | number; /** * Return age as formatted string instead of object. * @defaultValue false */ asString?: boolean; } /** * Age calculation result object */ interface AgeResult { /** Full years */ years: number; /** Remaining months (0-11) */ months: number; /** Remaining days (0-30) */ days: number; } /** * The five-day Javanese market cycle (pancawara / pasaran). * * Listed in canonical order starting from the wetonan anchor (Legi). */ type Pasaran = 'Legi' | 'Pahing' | 'Pon' | 'Wage' | 'Kliwon'; /** * Indonesian weekday name (saptawara). * * Listed in canonical Monday-first order. For indexing against the * wetonan anchor (which starts on Jumat / Friday) use the * `WETON_WEEKDAY_ORDER` constant exported from `weton.ts`. */ type IndonesianWeekday = 'Senin' | 'Selasa' | 'Rabu' | 'Kamis' | 'Jumat' | 'Sabtu' | 'Minggu'; /** * Javanese market-day information for a given Gregorian date. * * The `neptu` value is the sum of the weekday neptu and the pasaran * neptu and always lies in the inclusive range `[7, 18]`. * * @see getWeton */ interface Weton { /** The five-day market cycle position */ pasaran: Pasaran; /** The Indonesian weekday name */ weekday: IndonesianWeekday; /** Combined neptu value, integer in [7, 18] */ neptu: number; } /** * Constants for Indonesian datetime formatting * * @module datetime/constants * @packageDocumentation */ /** Full Indonesian month names (1-indexed: index 0 = empty, 1 = Januari) */ declare const MONTH_NAMES: readonly string[]; /** Short Indonesian month names (3-letter abbreviation) */ declare const MONTH_NAMES_SHORT: readonly string[]; /** Full Indonesian day names */ declare const DAY_NAMES: readonly string[]; /** Short Indonesian day names (3-letter abbreviation) */ declare const DAY_NAMES_SHORT: readonly string[]; /** Mapping of IANA timezone names to Indonesian abbreviations */ declare const TIMEZONE_MAP: Readonly>; /** Valid UTC offset hours that map to Indonesian timezones */ declare const VALID_UTC_OFFSETS: readonly number[]; /** * Date calculation utilities * * @module datetime/calc * @packageDocumentation */ /** * Check if a year is a leap year. * * A year is a leap year if: * - Divisible by 4, but not by 100, OR * - Divisible by 400 * * @param year - The year to check * @returns `true` if leap year, `false` otherwise (including invalid inputs) * * @example * ```typescript * isLeapYear(2024); // true * isLeapYear(2023); // false * isLeapYear(1900); // false (divisible by 100 but not 400) * isLeapYear(2000); // true (divisible by 400) * isLeapYear(NaN); // false * isLeapYear(3.5); // false (non-integer) * ``` */ declare function isLeapYear(year: number): boolean; /** * Get the number of days in a month. * * Accounts for leap years in February. * * @param month - Month number (1-12, 1-indexed) * @param year - Full year (e.g., 2026) * @returns Number of days in the month, or 0 for invalid inputs * * @example * ```typescript * daysInMonth(1, 2026); // 31 (January) * daysInMonth(2, 2024); // 29 (February, leap year) * daysInMonth(2, 2023); // 28 (February, non-leap year) * daysInMonth(4, 2026); // 30 (April) * daysInMonth(13, 2026); // 0 (invalid month) * daysInMonth(2, NaN); // 0 (invalid year) * ``` */ declare function daysInMonth(month: number, year: number): number; /** * Type guard to check if a value is a valid Date object. * * Returns `true` only for Date instances that represent a valid date * (i.e., not `Invalid Date`). Returns `false` for null, undefined, * invalid dates, and non-Date values. * * @param date - Value to check * @returns `true` if valid Date object, `false` otherwise * * @example * ```typescript * isValidDate(new Date()); // true * isValidDate(new Date('invalid')); // false * isValidDate(null); // false * isValidDate(undefined); // false * isValidDate('2024-01-01'); // false (string, not Date) * isValidDate(1704067200000); // false (number, not Date) * ``` */ declare function isValidDate(date: unknown): date is Date; /** * Check if a date falls on a weekend (Saturday or Sunday). * * Note: This only checks Saturday/Sunday and does not account for * industry-specific Saturday work schedules. * * @param date - Date object to check * @returns `true` if Saturday or Sunday, `false` otherwise * * @example * ```typescript * isWeekend(new Date('2026-01-03')); // true (Saturday) * isWeekend(new Date('2026-01-04')); // true (Sunday) * isWeekend(new Date('2026-01-05')); // false (Monday) * ``` */ declare function isWeekend(date: Date): boolean; /** * Check if a date falls on a working day (Monday-Friday). * * Note: This only checks Monday-Friday and does not account for * national holidays (holiday lists require periodic updates and * are not included per project mandates). * * @param date - Date object to check * @returns `true` if Monday-Friday, `false` otherwise * * @example * ```typescript * isWorkingDay(new Date('2026-01-05')); // true (Monday) * isWorkingDay(new Date('2026-01-03')); // false (Saturday) * isWorkingDay(new Date('2026-01-04')); // false (Sunday) * ``` */ declare function isWorkingDay(date: Date): boolean; /** * Calculate age from a birth date. * * Accounts for leap years and month length variations. * Can return as an object { years, months, days } or as a formatted string. * * @param birthDate - Birth date (Date, string, or number timestamp) * @param options - Options for age calculation * @returns Age as object or formatted string (based on asString option) * @throws {InvalidDateError} If birthDate or fromDate is invalid * * @example * ```typescript * // Get age as object * getAge('1990-06-15'); // { years: 36, months: 9, days: 21 } * getAge(new Date('1990-06-15'), { fromDate: new Date('2024-06-15') }); * // { years: 34, months: 0, days: 0 } * * // Get age as string * getAge('1990-06-15', { asString: true }); * // '36 Tahun 9 Bulan 21 Hari' * * getAge(new Date('2020-01-01'), { fromDate: new Date('2020-01-15'), asString: true }); * // '15 Hari' * ``` */ declare function getAge(birthDate: Date | string | number, options?: { fromDate?: Date | string | number; asString?: boolean; }): { years: number; months: number; days: number; } | string; /** * Add (or subtract) a number of working days to a date, skipping * Saturdays and Sundays. * * National holidays are intentionally **not** considered. A holiday * calendar would require volatile government-decree data and is out * of scope (see `mandates.md`). * * The semantics are: advance the date by `count` calendar days, and * if the resulting day is a weekend, snap forward (positive `count`) * or backward (negative `count`) to the nearest weekday. This * matches the industry-standard `date-fns` behaviour. * * - `count === 0` returns a new Date equal to the input (no snap). * - For non-zero `count`, the result is always a Monday through * Friday. * * **Note on pre-weekend inputs**: a date that already lands on a * weekend is advanced by `count` calendar days from that weekend day * (not snapped first), then snapped to a weekday. So * `addBusinessDays(Saturday, 1) === next Monday`. * * @param date - Starting date. * @param count - Working days to move. Positive moves forward, negative * moves backward, `0` returns a new Date equal to the input. * @returns A new Date representing the resulting working day. * @throws {InvalidDateError} If `date` is not a valid Date instance. * * @example * ```typescript * // Friday + 1 working day = next Monday * addBusinessDays(new Date('2026-01-09'), 1); * // -> Date for 2026-01-12 (Monday) * * // Wednesday + 2 working days = Friday (weekend not crossed) * addBusinessDays(new Date('2026-01-07'), 2); * // -> Date for 2026-01-09 (Friday) * * // Monday - 1 working day = previous Friday * addBusinessDays(new Date('2026-01-12'), -1); * // -> Date for 2026-01-09 (Friday) * ``` */ declare function addBusinessDays(date: Date, count: number): Date; /** * Date parsing utilities for Indonesian formats * * @module datetime/parse * @packageDocumentation */ /** * Parse a date string in Indonesian format (DD-MM-YYYY) or ISO format (YYYY-MM-DD). * * Strict parsing rules: * - Accepts delimiters: `/`, `-`, `.` * - DD-MM-YYYY format: Day first (1-31), 4-digit year required * - ISO auto-detection: If first segment is 4 digits AND > 31, treated as YYYY-MM-DD * - Leap year validation: Feb 29 is only valid in leap years * - 2-digit years NOT supported * - Time components NOT supported * * @param dateStr - Date string to parse * @returns Date object if valid, `null` if invalid * * @example * ```typescript * // Indonesian format (DD-MM-YYYY) * parseDate('02-01-2026'); // Date(2026, 0, 2) - Jan 2, 2026 * parseDate('02/01/2026'); // Date(2026, 0, 2) * parseDate('02.01.2026'); // Date(2026, 0, 2) * * // ISO format auto-detected (YYYY-MM-DD) * parseDate('2026-01-02'); // Date(2026, 0, 2) * * // Invalid inputs return null * parseDate('29-02-2023'); // null (not a leap year) * parseDate('02-01-26'); // null (2-digit year) * parseDate('02-01-2026 14:30'); // null (time component) * parseDate('invalid'); // null * ``` */ declare function parseDate(dateStr: string): Date | null; /** * Date formatting utilities for Indonesian locale * * @module datetime/format * @packageDocumentation */ /** * Format a date with Indonesian locale. * * @param date - Date to format (Date, string, or number timestamp in milliseconds) * @param style - Formatting style (default: 'long') * @returns Formatted date string * @throws {InvalidDateError} If the date is invalid * * @example * ```typescript * formatDate(new Date('2026-01-02'), 'full'); // 'Jumat, 2 Januari 2026' * formatDate(new Date('2026-01-02'), 'long'); // '2 Januari 2026' * formatDate(new Date('2026-01-02'), 'medium'); // '2 Jan 2026' * formatDate(new Date('2026-01-02'), 'short'); // '02/01/2026' * formatDate(new Date('2026-01-02'), 'weekday'); // 'Jumat' * formatDate(new Date('2026-01-02'), 'month'); // 'Januari' * ``` */ declare function formatDate(date: Date | string | number, style?: DateStyle): string; /** * Format a date range with Indonesian locale and smart redundancy removal. * * Removes redundant month/year information when dates share them. * * @param start - Start date * @param end - End date * @param style - Formatting style (default: 'long') * @returns Formatted date range string * @throws {InvalidDateError} If either date is invalid * @throws {InvalidDateRangeError} If end date is before start date * * @example * ```typescript * // Same day * formatDateRange( * new Date('2026-01-02'), * new Date('2026-01-02') * ); // '2 Januari 2026' * * // Same month & year * formatDateRange( * new Date('2026-01-02'), * new Date('2026-01-05') * ); // '2 - 5 Januari 2026' * * // Same year * formatDateRange( * new Date('2026-01-30'), * new Date('2026-02-02') * ); // '30 Januari - 2 Februari 2026' * * // Different year * formatDateRange( * new Date('2025-12-30'), * new Date('2026-01-02') * ); // '30 Desember 2025 - 2 Januari 2026' * ``` */ declare function formatDateRange(start: Date, end: Date, style?: Exclude): string; /** * Relative time formatting for Indonesian locale * * @module datetime/relative * @packageDocumentation */ /** * Format a date as relative time in Indonesian. * * Returns human-readable relative time like "Baru saja", "X menit yang lalu", * "Kemarin", or falls back to formatted date for older dates. * * @param date - Date to format (Date, string, or number timestamp in milliseconds) * @param baseDate - Reference date for comparison (default: current date) * @returns Relative time string in Indonesian * @throws {InvalidDateError} If either date is invalid * * @example * ```typescript * // Assuming today is 2026-01-02 12:00:00 * toRelativeTime(new Date('2026-01-02 11:59:00')); // 'Baru saja' * toRelativeTime(new Date('2026-01-02 11:00:00')); // '1 jam yang lalu' * toRelativeTime(new Date('2026-01-01 12:00:00')); // 'Kemarin' * toRelativeTime(new Date('2025-12-30 12:00:00')); // '3 hari yang lalu' * toRelativeTime(new Date('2025-12-01 12:00:00')); // '1 Desember 2025' * ``` */ declare function toRelativeTime(date: Date | string | number, baseDate?: Date): string; /** * Timezone utilities for Indonesian locale * * @module datetime/timezone * @packageDocumentation */ /** * Map IANA timezone names or UTC offsets to Indonesian abbreviations (WIB/WITA/WIT). * * Supported mappings: * - UTC+7 / Asia/Jakarta / Asia/Pontianak → "WIB" * - UTC+8 / Asia/Makassar / Asia/Denpasar → "WITA" * - UTC+9 / Asia/Jayapura → "WIT" * * @param input - IANA timezone name (case-sensitive), offset in hours, or offset string * @returns Indonesian timezone abbreviation or null if not Indonesian * * @example * ```typescript * // IANA timezone names * getIndonesianTimezone('Asia/Jakarta'); // 'WIB' * getIndonesianTimezone('Asia/Makassar'); // 'WITA' * getIndonesianTimezone('Asia/Jayapura'); // 'WIT' * * // Offset as number (hours) * getIndonesianTimezone(7); // 'WIB' * getIndonesianTimezone(8); // 'WITA' * getIndonesianTimezone(9); // 'WIT' * * // Offset as string * getIndonesianTimezone('+07:00'); // 'WIB' * getIndonesianTimezone('+0700'); // 'WIB' * getIndonesianTimezone('+08:00'); // 'WITA' * * // Non-Indonesian returns null * getIndonesianTimezone('America/New_York'); // null * getIndonesianTimezone(-5); // null * ``` */ declare function getIndonesianTimezone(input: string | number): 'WIB' | 'WITA' | 'WIT' | null; /** * Javanese market-day (weton) utilities. * * @module datetime/weton * @packageDocumentation */ /** * Get the Javanese market-day information (weton) for a given date. * * The result combines the Indonesian weekday (saptawara), the five-day * market position (pancawara / pasaran), and the combined neptu value * (weekday neptu + pasaran neptu), which always lies in `[7, 18]`. * * The date is interpreted in UTC for deterministic, timezone- and * DST-independent behaviour. * * @param date - The date to look up. Interpreted in UTC. * @returns The weton for that date. * @throws {InvalidDateError} If `date` is not a valid Date instance, or * if the date falls before the Javanese calendar anchor (8 July 1633 * CE). Pre-anchor dates are outside the defined scope of the * Javanese kurup system; we refuse to return a plausible-looking but * possibly wrong result. * * @example * ```typescript * getWeton(new Date(Date.UTC(1633, 6, 8))); * // { weekday: 'Jumat', pasaran: 'Legi', neptu: 11 } * * getWeton(new Date(Date.UTC(1945, 7, 17))); * // { weekday: 'Jumat', pasaran: 'Pahing', neptu: 15 } * ``` */ declare function getWeton(date: Date): Weton; export { type AgeOptions, type AgeResult, DAY_NAMES, DAY_NAMES_SHORT, type DateStyle, type IndonesianWeekday, InvalidDateError, InvalidDateRangeError, MONTH_NAMES, MONTH_NAMES_SHORT, type Pasaran, TIMEZONE_MAP, VALID_UTC_OFFSETS, type Weton, addBusinessDays, daysInMonth, formatDate, formatDateRange, getAge, getIndonesianTimezone, getWeton, isLeapYear, isValidDate, isWeekend, isWorkingDay, parseDate, toRelativeTime };